-
Notifications
You must be signed in to change notification settings - Fork 2
Parser Architecture
How the C# port is structured. The wire-level behaviour it implements is on Binary Format; the client design it mirrors is on Asset System. This page is about the code.
The parser core is pure POCO (plain objects, no UI concerns). Observable bindings live in a separate editor adapter. The boundary is deliberate — the original C# conflated them, shipping INotifyPropertyChanged and ObservableCollection inside the parser's output, which forced every consumer (CLI, wiki, a server) to drag editor machinery along.
flowchart TB
subgraph L2 ["L2 — Editor (Avalonia, MVVM)"]
EN["EditorNode hierarchy<br/>INotifyPropertyChanged + ObservableCollection"]
ETB["EditorTreeBuilder.Build(AssetValue)"]
ETB --> EN
end
subgraph L1 ["L1 — Core (lean, immutable)"]
Dbpf["DbpfReader"]
Parser["AssetParser"]
Registry["TypeRegistry"]
Value["AssetValue tree (POCO)"]
Catalog["Catalog/ — fluent stubs"]
Dbpf --> Parser
Catalog -.->|registers| Registry
Parser -->|uses| Registry
Parser --> Value
end
Value -.->|consumed by| ETB
| Layer | Project | Carries |
|---|---|---|
| L1 — Core |
AssetData.Parser (library) |
DbpfReader, AssetParser, TypeRegistry, the AssetValue tree, the catalog stubs. Depends on nothing but the BCL. |
| L2 — Editor |
AssetData.Parser.Editor (Avalonia) |
EditorNode adapters, view-models, undo/redo, XML round-trip. |
The CLI and the wiki generator consume L1 directly — they never touch L2.
graph TD
subgraph libs["Libraries"]
Core["AssetData.Parser<br/>Core engine"]
CommonUI["ReCap.CommonUI<br/>Avalonia controls/theme"]
end
subgraph apps["Executables"]
CLI["AssetData.Parser.CLI<br/>batch parse / export"]
Editor["AssetData.Parser.Editor<br/>Avalonia desktop"]
Wiki["AssetData.Parser.Wiki<br/>doc generator (this wiki)"]
end
CLI --> Core
Wiki --> Core
Editor --> Core
Editor --> CommonUI
Core -.->|reads| Pkg[("AssetData_Binary.package")]
Core -.->|embedded| Reg["reg_type.txt / reg_file.txt"]
Dependency direction is clean: every consumer depends on Core; Core depends on nobody.
A fluent DSL describes each format. DataType is an enum whose values are the FNV-1a hashes of the client's canonical type names (sentinels + value types — see Binary Format). At AssetParser construction, every AssetCatalog subclass is discovered by reflection, its Build() runs, and its struct/enum definitions merge into the global TypeRegistry.
sequenceDiagram
participant P as AssetParser()
participant A as Assembly
participant C as AssetCatalog subclass
participant R as TypeRegistry
P->>A: find all AssetCatalog subclasses
loop each catalog
P->>C: instantiate -> ctor runs Build()
C-->>R: register structs / enums
end
P->>R: ResolveEnumReferences()
TypeRegistry.FindTypeByHash(uint) is the heart of dispatch — the direct analogue of the client's AssetTypeRegistry::FindTypeByHash. Element-type hashing is centralized in one place (WireHash); the original had three separate FNV implementations.
AssetParser.Parse(bytes, rootStruct, headerSize) walks the struct's fields in declaration order, reading scalars from the header and reserving variable data from the blob via a BlobReader cursor. Each field resolves through FindTypeByHash: registered struct -> recurse; sentinel -> handle the wire shape; value type -> read inline. This is the C# analogue of DeserializeObject (0x009cd2c0). Errors are wrapped with field + struct + offset context for diagnostics.
The parser produces an immutable POCO tree:
AssetValue
├── StructValue (TypeName, children)
├── ArrayValue (element values)
├── StringValue (Kind: Asset | Key | CharPtr | Char)
├── NumberValue (OriginalType, value)
├── BoolValue
├── EnumValue
├── VectorValue (Vector2/3/4, Orientation)
├── LocalizedStringValue
└── NullValue
No INotifyPropertyChanged, no ObservableCollection — just data. Walk it with a switch on the node type.
The editor wraps each L1 node in an EditorNode that adds INotifyPropertyChanged for two-way binding:
var value = await Task.Run(() => assetService.LoadFile(path)); // L1 (Core)
var editorRoot = EditorTreeBuilder.Build(value); // L2 (Editor)
ViewModel.SetRoot(editorRoot);XML export round-trips back through L1 (EditorToValue.Convert -> AssetSerializer.ToXml), so user edits are preserved. The editor uses Microsoft.Extensions.DependencyInjection and consumes ReCap.CommonUI for controls and theming.
sequenceDiagram
actor U as Consumer (Editor / CLI)
participant DR as DbpfReader
participant AP as AssetParser
participant T as AssetValue tree
U->>DR: GetAsset("ZelemBoss.phase")
DR->>DR: resolve name -> ResourceKey, read, RefPack decompress
DR-->>U: byte[] payload
U->>AP: Parse(bytes, "phase", headerSize)
loop each field
AP->>AP: read header / reserve blob
end
AP-->>U: root AssetValue
U->>T: navigate, export, or wrap in EditorNode
.NET 10, C# 14. Central package management via Directory.Packages.props; shared TFM/LangVersion/Nullable via Directory.Build.props. Avalonia 11.3 and CommunityToolkit.Mvvm power the editor; the Core library has zero third-party dependencies.
- Asset System — the client design this mirrors.
- Binary Format — the wire contract the parser implements.
- Adding a Format — extend the catalog.
Structures
- ability
- affix
- AffixTuning
- AIDefinition
- AudioTriggerDef
- cAffixDifficultyTuning
- cAgentBlackboard
- cAICondition
- cAIDirector
- cAINode
- cAnimatedData
- cAnimatorData
- cAssetProperty
- cAssetPropertyList
- cAssetQueryString
- Catalog
- CatalogEntry
- cAttributeData
- cAudioEventData
- cCameraComponent
- cCameraComponentData
- cCinematicView
- cCombatantData
- cControllerState
- cDecalData
- cDoorDef
- cEffectEventData
- cEliteAffix
- cGambitDefinition
- cGameObjectGfxStateData
- cGameObjectGfxStates
- cGfxComponentDef
- cGraphicsData
- cGrassData
- ChainLevel
- ChainLevels
- CharacterAnimation
- CharacterType
- cHardpointInfo
- Cinematic
- cInteractableData
- cKeyAsset
- cLabsMarker
- ClassAttributes
- cLayerPrefs
- cLineLightData
- cLobParams
- cLocomotionData
- cLongDescription
- cLootData
- cMapCameraData
- cNewGfxState
- cOccluderData
- CollisionVolumeDef
- CombatantDef
- CombatEvent
- Condition
- cParallelLightData
- cPointLightData
- cPressureSwitchDef
- cProjectileParams
- CrystalDef
- CrystalDropDef
- CrystalLevel
- CrystalTuning
- cSpaceshipCameraTuning
- cSPBoundingBox
- cSplineCameraData
- cSplineCameraNodeBaseData
- cSpotLightData
- cSwitchDef
- cThumbnailCaptureParameters
- cToolPos
- cVolumeDef
- cWaterData
- cWaterSimData
- DifficultyTuning
- DirectorBucket
- DirectorClass
- DirectorTuning
- EditorPrefs
- EliteNPCGlobals
- EventListenerData
- EventListenerDef
- ExtentsCategory
- GameObjectGfxStateTuning
- Gfx
- GravityForce
- InteractableDef
- labsCharacter
- labsCrystal
- labsPlayer
- Level
- LevelCameraSettings
- LevelConfig
- LevelKey
- LevelMarkerset
- LevelObjectives
- LocomotionTuning
- LootData
- LootPreferences
- LootPrefix
- LootRigblock
- LootSuffix
- MagicNumbers
- Markerset
- NavMeshLayer
- NavPowerTuning
- NonPlayerClass
- Noun
- NPCAffix
- ObjectExtents
- objective
- OrbitDef
- Phase
- PlayerClass
- PopupTip
- ProjectileDef
- SectionConfig
- ServerEvent
- ServerEventDef
- SharedComponentData
- SpaceshipSpawnPointDef
- SpaceshipTuning
- SpawnPointDef
- SpawnTriggerDef
- sporelabsObject
- TeleporterDef
- TestAsset
- TriggerVolumeComponentDef
- TriggerVolumeDef
- TriggerVolumeEvents
- UnlockDef
- UnlocksTuning
- WeaponDef
- WeaponTuning
Enums
- cGfxComponentDef.gfxType
- cHardpointInfo.bodyCap
- cHarpointInfo.type
- cLabsMarker.navMeshSetting
- cLabsMarker.shadowed
- cLabsMarker.type
- CollisionShape
- CrystalDef.rarity
- CrystalDef.type
- cVolumeDefShape
- EditorPrefs.cameraMode
- EditorPrefs.transformSpace
- gfxPickMethod
- LevelType
- LocomotionType
- Markerset.condition
- NonPlayerClass.creatureType
- NonPlayerClass.dropType
- NonPlayerClass.mNPCType
- NounType
- phaseType
- PhysicsType
- PlayerClass.creatureClass
- PlayerClass.creatureType
- PlayerClass.descriptionTag
- PlayerClass.homeworld
- PlayerClass.primaryAttribute
- presetExtents
- ProjectileDef.targetType
- SpawnPointDef.sectionType
- spawnTeamId
- targetType
- triggerActivationType
- TriggerShape
- type
- UnlockDef.unlockFunction
- UnlockDef.unlockType