WD1/WD2 HKX Injection Internals
Cross-reference: Havok packfile structure, fixup tables, virtual fixups → XeNTaX Havok knowledge §1–2, 13–14
WD2 Injection Pipeline (Disrupt format)
Format Structure
TAG0 header → DATA chunk → ITEM table → PTCH table → TCRF compendium
ITEM Table (12 bytes each)
struct HkxItem {
uint32 typeIdAndFlags; // typeId & 0xFFFFFF, flags (typeId >> 28) & 0xF
uint32 dataOffset; // offset within DATA section
uint32 count; // element count
};
Key Type IDs (WD2 static collisions)
- 0x21: shape root (quantized)
- 0x49: vertex header
- 0x25: vertex/plane data
- 0x4b: face indices
- 0x2D: shape data
- 0x93: shape root (full precision)
- 0x57: vertices (full precision)
- 0x4E: planes (full precision)
- 0x0D: face indices (full precision)
Injection Flow
- Parse ITEM table → find shape item by index
- Follow fixups from shape item → find shape_data (type 0x2D)
- Follow fixups from shape_data → find vertex array (type 0x25) and header (type 0x49)
- Update AABB in shape_data (+0x20 min, +0x30 max)
- Update AABB in vertex header (+0x10 min, +0x20 max)
- Re-encode vertices as 11/11/10-bit quantized (scale = extent/2047 for x/y, /1023 for z)
- Write quantized u32s back to DATA section
PTCH Table (fixups)
struct HkxFixup {
uint32 pointerType; // type of pointer
uint32 pointerLocation; // offset within DATA
uint32 targetItemIndex; // index into ITEM table
};
Fixups resolve inter-item references (e.g., shape → shape_data → vertex_array).
WD1 Injection Pipeline (Old packfile format)
Format Structure
Havok header → __classnames__ → __types__ → __data__
Fixup Tables (within data)
- Virtual fixups: data offset → class name (12-byte entries)
- Local fixups: data offset → data offset (8-byte entries)
- Global fixups: data offset → object offset (12-byte entries)
Injection Flow
- Parse data section → resolve all fixups
- Find shape objects by class name (from virtual fixups)
- Resolve data pointers via local fixups
- Patch float data at resolved addresses
- Write modified data back (byte-identical)
Key Differences
| Feature | WD1 (Old packfile) | WD2 (Disrupt TAG0) |
|---|---|---|
| Object lookup | Class name strings | Type IDs (0x21, 0x49, etc.) |
| Fixup storage | Inline in data | Separate PTCH table |
| Data location | data section | DATA chunk |
| Compendium | N/A | TCRF (24 bytes) |
| Injection target | Direct byte patching | ITEM table + DATA offset |
Implications for Compressed Mesh
The compressed mesh data is stored within the packfile as hkpBvCompressedMeshShape objects. To decode the bitstream:
- Old packfile (WD1): Find the compressed mesh object via virtual fixups, resolve the tree pointer via local fixups, read the inline tree data
- TAG0 (Legion): Find the compressed mesh item via ITEM table, follow fixups to the tree data in DATA section
The tree data contains:
- 160 bytes inline: BVH nodes, domain AABB, array pointers
- Array pointers (local fixups) → sections, packedVertices, primitives, dataRuns, connectivity
The bitstream is encoded within the dataRuns array (PrimitiveDataRun entries). Each run specifies an offset, type, and count for reading primitives from a compact representation.
What's Still Unknown
- Exact mapping of section header fields (+0x4c, +0x50, +0x54) to HKLib equivalents
- How dataRuns decode primitives — the encoding scheme (strip, fan, individual edges, etc.)
- How the connectivity structure maps sections to triangles
- Whether the old packfile and TAG0 formats use the same compressed mesh class layouts (likely yes, but need validation)