Untrack TODO.md, RELEASING.md, RELEASE_NOTES.md, ideas/ and docs/CLAUDE_PROPER_OBJECT_REGISTRATION_PLAN.md -- internal planning and agent notes with no reason to be published. Files stay on disk locally. Replace remaining absolute machine paths in docs with relative ones (one referenced a separate private project), and swap decorative emoji for ASCII markers. README: lead with the no-longer-actively-developed notice.
22 KiB
WoW 1.12.1 Data Structures - Complete Reference
Overview
This document provides complete specifications for all data structures involved in GPU skinning implementation. All offsets and sizes have been verified through Ghidra decompilation and runtime analysis.
Model Structure
Overview
The Model structure represents a complete M2 model (character, creature, or object). It contains pointers to geometry data, bone arrays, animations, and rendering metadata.
Structure Definition
struct Model {
// Offset 0x00-0x2F: Unknown/varied data
char pad_0x00[0x30];
// Offset 0x30: Pointer to nested data structure
void* dataPtr; // +0x30
// Offset 0x34-0x93: Unknown/varied data
char pad_0x34[0x60];
// Offset 0x94: CRITICAL - Bone matrix array
BoneMatrix* boneArray; // +0x94
// Offset 0x98-0x397: Unknown/varied data
char pad_0x98[0x300];
// Total size: At least 0x398 bytes (likely larger)
};
Critical Offsets
| Offset | Type | Name | Description | Verified |
|---|---|---|---|---|
| +0x30 | void* |
dataPtr | Pointer to geometry data container | [OK] |
| +0x94 | BoneMatrix* |
boneArray | Pointer to bone matrix array | [OK] |
Nested Structure (Model+0x30)
struct ModelDataContainer {
// Offset 0x00-0x12F: Unknown
char pad_0x00[0x130];
// Offset 0x130: Pointer to GeometryData
GeometryData* geometryData; // +0x130
// More fields follow...
};
GeometryData Structure (Model+0x30+0x130)
struct GeometryData {
// Offset 0x00-0x47: Unknown
char pad_0x00[0x48];
// Offset 0x48: Pointer to vertex array (T-pose vertices)
Vertex* vertexArray; // +0x48
// More fields follow...
};
Access Pattern
// From a Model* pointer:
Model* model = (Model*)thisPtr;
// Get bone array directly
BoneMatrix* bones = model->boneArray; // model + 0x94
// Get vertex array (multi-level indirection)
ModelDataContainer* container = (ModelDataContainer*)model->dataPtr; // model + 0x30
GeometryData* geoData = container->geometryData; // container + 0x130
Vertex* vertices = geoData->vertexArray; // geoData + 0x48
Example Code
// Extract bone matrices for GPU upload
void UploadBoneMatrices(Model* model, IDirect3DDevice9* device) {
BoneMatrix* bones = model->boneArray;
// Pack to 4x3 for shader constants (12 floats per bone)
float constants[64 * 12]; // Max 64 bones
for (int i = 0; i < 64; i++) { // TODO: Get actual bone count
// Copy first 3 rows of 4x4 matrix
memcpy(&constants[i * 12], bones[i].m, 12 * sizeof(float));
}
// Upload to shader constants c31-c222
device->SetVertexShaderConstantF(31, constants, 64 * 3);
}
MeshData Structure
Overview
The MeshData structure describes a single submesh within a model, including vertex range, index range, and material properties.
Structure Definition
struct MeshData {
// Offset 0x00-0x03: Unknown
uint32_t unknown0; // +0x00
// Offset 0x04: Vertex offset in vertex array
uint16_t vertexOffset; // +0x04
// Offset 0x06: Number of vertices in this submesh
uint16_t vertexCount; // +0x06
// Offset 0x08: Index offset
uint16_t indexOffset; // +0x08
// Offset 0x0A: Number of indices
uint16_t indexCount; // +0x0A
// Offset 0x0C-0x0D: Bone indices for this submesh
uint16_t boneStart; // +0x0C
uint16_t boneCount; // +0x0E
// Offset 0x10: Material/texture indices
uint16_t materialIndex; // +0x10
// More fields follow...
// Total size: At least 0x20 bytes
};
Critical Offsets
| Offset | Type | Name | Description | Verified |
|---|---|---|---|---|
| +0x04 | uint16_t |
vertexOffset | Start vertex in global array | [OK] |
| +0x06 | uint16_t |
vertexCount | Loop terminator in applyBoneTransforms | [OK] |
| +0x08 | uint16_t |
indexOffset | Start index for this submesh | ? |
| +0x0A | uint16_t |
indexCount | Number of indices | ? |
Usage in applyBoneTransforms
// From decompiled code @ 0x0071a460
void __fastcall applyBoneTransforms(int param_1, int param_2, float *param_3) {
// Extract vertex count
uint16_t vertexCount = *(uint16_t*)(param_2 + 6); // MeshData + 0x06
// Extract vertex offset
uint16_t vertexOffset = *(uint16_t*)(param_2 + 4); // MeshData + 0x04
// Loop through vertices
for (uint16_t v = 0; v < vertexCount; v++) {
// Calculate vertex pointer
int vertexIndex = v + vertexOffset;
// ... (skinning code)
}
}
Vertex Structure (T-Pose)
Overview
The Vertex structure represents a single vertex in its T-pose (unskinned) state. This is the input to the skinning process.
Structure Definition
struct Vertex {
// Offset 0x00: Position (T-pose)
float position[3]; // +0x00 (12 bytes)
// Offset 0x0C: First blend weight (byte, 0-255)
uint8_t blendWeight0; // +0x0C (1 byte)
// Offset 0x0D: Bone indices (4 bones, 0-255)
uint8_t blendIndices[4]; // +0x0D (4 bytes)
// Offset 0x11: Padding or additional weight data
uint8_t pad_0x11[3]; // +0x11 (3 bytes)
// Offset 0x14: Normal (T-pose)
float normal[3]; // +0x14 (12 bytes)
// Offset 0x20: Primary texture coordinates
float texcoord0[2]; // +0x20 (8 bytes)
// Offset 0x28: Secondary texture coordinates
float texcoord1[2]; // +0x28 (8 bytes)
// Total size: 0x30 (48 bytes)
};
static_assert(sizeof(Vertex) == 0x30, "Vertex size mismatch");
Alternative Definition (Based on Analysis)
The exact layout is somewhat ambiguous from the decompiled code. Here's an alternative interpretation that matches the access patterns:
struct Vertex_v2 {
float position[3]; // +0x00 (12 bytes)
uint8_t blendWeights[4]; // +0x0C (4 bytes) - all 4 weights as bytes
uint8_t blendIndices[4]; // +0x10 (4 bytes) - all 4 indices
float normal[3]; // +0x14 (12 bytes)
float texcoord0[2]; // +0x20 (8 bytes)
float texcoord1[2]; // +0x28 (8 bytes)
// Total: 48 bytes (0x30)
};
Memory Layout Diagram
Offset Size Field Type Description
------ ---- ------------- --------- ------------------------------------
0x00 12 position float[3] X, Y, Z in T-pose
0x0C 4 blendWeights uint8[4] Weights for bones 0-3 (0-255)
0x10 4 blendIndices uint8[4] Bone indices 0-3 (0-255)
0x14 12 normal float[3] Normal vector in T-pose
0x20 8 texcoord0 float[2] Primary UV coordinates
0x28 8 texcoord1 float[2] Secondary UV coordinates
0x30 -- (end) -- Total: 48 bytes
Blend Weight Encoding
Format: Unsigned byte (0-255)
Conversion to float: float weight = (float)byte_value * (1.0f / 255.0f);
Constant used: 0.003921569 = 1.0 / 255.0
Example:
uint8_t weight_byte = 200;
float weight_float = weight_byte * 0.003921569f; // = 0.784
Verification:
// From decompiled code @ 0x0071a460, line with weight extraction:
fVar11 = (float)*(byte *)(pfVar13 + 3) * 0.003921569;
// This confirms:
// 1. Weight is stored as byte
// 2. Conversion uses 1/255 constant
// 3. Offset +0x0C (3 floats = 12 bytes = pfVar13 + 3 in float pointer math)
D3D9 Vertex Declaration
For GPU skinning, the vertex declaration must match this layout:
D3DVERTEXELEMENT9 elements[] = {
// Stream 0, Offset 0, Type FLOAT3, Usage POSITION, Index 0
{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
// Offset 12 (0x0C), Type UBYTE4N (4 bytes normalized to 0-1), Usage BLENDWEIGHT
{0, 12, D3DDECLTYPE_UBYTE4N, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDWEIGHT, 0},
// Offset 16 (0x10), Type UBYTE4 (4 bytes as integers), Usage BLENDINDICES
{0, 16, D3DDECLTYPE_UBYTE4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDINDICES, 0},
// Offset 20 (0x14), Type FLOAT3, Usage NORMAL
{0, 20, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},
// Offset 32 (0x20), Type FLOAT2, Usage TEXCOORD0
{0, 32, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
// Offset 40 (0x28), Type FLOAT2, Usage TEXCOORD1
{0, 40, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1},
D3DDECL_END()
};
Note: D3DDECLTYPE_UBYTE4N automatically normalizes bytes to 0-1 range, matching WoW's weight encoding.
SkinnedVertex Structure (Output)
Overview
The SkinnedVertex structure represents a vertex after CPU skinning. This is written to the dynamic vertex buffer created by CreateVertexBuffer.
Structure Definition
struct SkinnedVertex {
// Offset 0x00: Skinned position (world/model space)
float position[3]; // +0x00 (12 bytes)
// Offset 0x0C: Skinned normal
float normal[3]; // +0x0C (12 bytes)
// Offset 0x18: Primary texture coordinates (copied, unskinned)
float texcoord0[2]; // +0x18 (8 bytes)
// Offset 0x20: Secondary texture coordinates (copied, unskinned)
float texcoord1[2]; // +0x20 (8 bytes)
// Total size: 0x28 (40 bytes)
};
static_assert(sizeof(SkinnedVertex) == 0x28, "SkinnedVertex size mismatch");
Memory Layout Diagram
Offset Size Field Type Description
------ ---- --------- --------- ------------------------------------
0x00 12 position float[3] Skinned X, Y, Z
0x0C 12 normal float[3] Skinned normal vector
0x18 8 texcoord0 float[2] Primary UV (copied from T-pose)
0x20 8 texcoord1 float[2] Secondary UV (copied from T-pose)
0x28 -- (end) -- Total: 40 bytes
Verification
From decompiled code @ 0x0071a460:
// Output stride is 10 floats = 40 bytes
param_3 = param_3 + 10; // Advance output pointer
// Writes:
*param_3 = skinned_position.x; // +0x00
param_3[1] = skinned_position.y; // +0x04
param_3[2] = skinned_position.z; // +0x08
param_3[3] = skinned_normal.x; // +0x0C
param_3[4] = skinned_normal.y; // +0x10
param_3[5] = skinned_normal.z; // +0x14
param_3[6] = texcoord0.x; // +0x18
param_3[7] = texcoord0.y; // +0x1C
param_3[8] = texcoord1.x; // +0x20
param_3[9] = texcoord1.y; // +0x24
// Next vertex at +0x28 (40 bytes)
D3D9 Vertex Declaration
For the skinned output (if using CPU skinning or compute shader output):
D3DVERTEXELEMENT9 skinned_elements[] = {
{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},
{0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
{0, 32, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1},
D3DDECL_END()
};
BoneMatrix Structure
Overview
The BoneMatrix structure represents a single bone's transformation matrix. Bones are stored in an array at Model + 0x94.
Structure Definition
struct BoneMatrix {
// 4x4 transformation matrix (row-major)
float m[16]; // 64 bytes total
// Alternative interpretation:
// float m[4][4]; // 4 rows, 4 columns
// Or as rows:
// float row0[4]; // +0x00
// float row1[4]; // +0x10
// float row2[4]; // +0x20
// float row3[4]; // +0x30 (usually [0, 0, 0, 1])
};
static_assert(sizeof(BoneMatrix) == 0x40, "BoneMatrix size must be 64 bytes");
Memory Layout
Offset Size Element Description
------ ---- --------- ------------------------------------
0x00 4 m[0] Row 0, Column 0 (right.x)
0x04 4 m[1] Row 0, Column 1 (right.y)
0x08 4 m[2] Row 0, Column 2 (right.z)
0x0C 4 m[3] Row 0, Column 3 (translation.x)
0x10 4 m[4] Row 1, Column 0 (up.x)
0x14 4 m[5] Row 1, Column 1 (up.y)
0x18 4 m[6] Row 1, Column 2 (up.z)
0x1C 4 m[7] Row 1, Column 3 (translation.y)
0x20 4 m[8] Row 2, Column 0 (forward.x)
0x24 4 m[9] Row 2, Column 1 (forward.y)
0x28 4 m[10] Row 2, Column 2 (forward.z)
0x2C 4 m[11] Row 2, Column 3 (translation.z)
0x30 4 m[12] Row 3, Column 0 (usually 0)
0x34 4 m[13] Row 3, Column 1 (usually 0)
0x38 4 m[14] Row 3, Column 2 (usually 0)
0x3C 4 m[15] Row 3, Column 3 (usually 1)
Total: 64 bytes (0x40)
Matrix Format
Format: Row-major 4x4 matrix
Layout: Affine transformation (rotation + translation)
Fourth Row: Usually [0, 0, 0, 1] (not stored in some formats)
Interpretation:
[ m[0] m[1] m[2] m[3] ] [ Xx Xy Xz Tx ] [ right.x right.y right.z translation.x ]
[ m[4] m[5] m[6] m[7] ] = [ Yx Yy Yz Ty ] = [ up.x up.y up.z translation.y ]
[ m[8] m[9] m[10] m[11] ] [ Zx Zy Zz Tz ] [ forward.x forward.y forward.z translation.z ]
[ m[12] m[13] m[14] m[15] ] [ 0 0 0 1 ] [ 0 0 0 1 ]
Array Access
// From Model pointer
BoneMatrix* bones = model->boneArray; // Array at model + 0x94
// Access individual bone
int boneIndex = 5;
BoneMatrix* bone5 = &bones[boneIndex];
// Or using byte offset (as in decompiled code)
float* bone5_ptr = (float*)((char*)bones + boneIndex * 0x40);
Verification from Decompiled Code
// From applyBoneTransforms @ 0x0071a460:
pfVar12 = (float *)(((uint)local_c & 0xff) * 0x40 + *(int *)(param_1 + 0x94));
// ^ ^ ^
// boneIndex stride bone array pointer
// This confirms:
// 1. Each bone is 0x40 (64) bytes
// 2. Array is at model + 0x94
// 3. Indexed as: bones + (index * 64)
Packing for Shader Constants
GPU vertex shaders have limited constant space (256 constants = 256 × float4 = 4096 bytes).
Optimization: Pack 4x4 to 4x3 (save 4th row since it's always [0,0,0,1]):
void PackBoneMatricesForShader(BoneMatrix* bones, int boneCount, float* output) {
for (int i = 0; i < boneCount; i++) {
// Copy first 3 rows (12 floats = 3 × float4)
memcpy(&output[i * 12], bones[i].m, 12 * sizeof(float));
// Skip 4th row (m[12], m[13], m[14], m[15])
}
}
// Usage:
float packedBones[64 * 12]; // 64 bones × 12 floats = 768 floats = 192 constants
PackBoneMatricesForShader(model->boneArray, 64, packedBones);
device->SetVertexShaderConstantF(31, packedBones, 64 * 3); // 64 bones × 3 constants each
Transform a Vertex
CPU code (matches WoW's implementation):
void TransformVertex(const float* position, const BoneMatrix& bone, float* output) {
// Transform as: output = bone × [position.x, position.y, position.z, 1]
output[0] = bone.m[0] * position[0] + bone.m[1] * position[1] +
bone.m[2] * position[2] + bone.m[3];
output[1] = bone.m[4] * position[0] + bone.m[5] * position[1] +
bone.m[6] * position[2] + bone.m[7];
output[2] = bone.m[8] * position[0] + bone.m[9] * position[1] +
bone.m[10] * position[2] + bone.m[11];
}
Shader code (HLSL):
float4 row0 = c[31 + boneIndex * 3 + 0]; // [m0, m1, m2, m3]
float4 row1 = c[31 + boneIndex * 3 + 1]; // [m4, m5, m6, m7]
float4 row2 = c[31 + boneIndex * 3 + 2]; // [m8, m9, m10, m11]
float4 pos4 = float4(position, 1.0);
skinnedPos.x = dot(row0, pos4);
skinnedPos.y = dot(row1, pos4);
skinnedPos.z = dot(row2, pos4);
Batch Structure
Overview
The Batch structure describes a rendering batch, which may contain multiple submeshes with the same material and state.
Structure Definition (Partial)
struct Batch {
// Offset 0x00: Batch type
uint32_t type; // +0x00
// 0 = DrawBatchProj (projected/corpses)
// 1 = DrawBatch (standard models)
// 2 = DrawBatchDoodad (props)
// 3 = DrawRibbon (ribbons)
// 4 = DrawParticle (particles)
// 5 = DrawCallback (custom)
// Offset 0x04: Pointer to Model
Model* modelPtr; // +0x04
// Offset 0x08-0x1F: Unknown
char pad_0x08[0x18];
// Offset 0x20: Doodad count (for type 2)
uint32_t doodadCount; // +0x20
// More fields...
// Total size: 0x40 (64 bytes)
};
Verification
// From CM2SceneRenderDraw @ 0x0070b360:
puVar2 = (undefined4 *)(*(int *)(batchIndices + uVar4 * 4) * 0x40 + batchData);
// ^
// 64 bytes per batch
// Switch on batch type:
switch(**(undefined4 **)((int)this + 0x3300)) {
case 0: DrawBatchProj((float *)this); break;
case 1: DrawBatch(this); break;
case 2: DrawBatchDoodad(this, batchData, batchIndices + uVar4 * 4);
uVar4 = (uVar4 - 1) + *(int *)(*(int *)((int)this + 0x3300) + 0x20);
break;
// ...
}
RenderContext Structure (Partial)
Overview
The RenderContext structure holds per-frame rendering state used by CM2SceneRenderDraw and related functions.
Structure Definition (Partial)
struct RenderContext {
// Unknown fields 0x00-0x3F
char pad_0x00[0x40];
// Offset 0x40: Camera pointer
void* camera; // +0x40
// Offset 0x44: Model flags
uint32_t modelFlags; // +0x44
// Offset 0x48: Shader constants pointer
void* shaderConstants; // +0x48
// Offset 0x4C: View matrix
void* viewMatrix; // +0x4C
// Offset 0x50-0x6F: Unknown
char pad_0x50[0x20];
// Offset 0x70-0xAC: Transform matrix (4x4)
float transform[16]; // +0x70
// Large gap...
char pad_0xB0[0x3190];
// Offset 0x3240: Shader constant min index
uint32_t shaderConstMin; // +0x3240
// Offset 0x3244: Shader constant max index
uint32_t shaderConstMax; // +0x3244
// More gaps...
char pad_0x3248[0xB8];
// Offset 0x3300: Current batch pointer
Batch* currentBatch; // +0x3300
// Offset 0x3304: Previous batch pointer
Batch* previousBatch; // +0x3304
// Offset 0x3308: Current batch type
uint32_t currentBatchType; // +0x3308
// Offset 0x330C: Previous batch type
uint32_t previousBatchType; // +0x330C
// Offset 0x3310: Current model pointer
Model* currentModel; // +0x3310
// Offset 0x3314-0x3317: Unknown
char pad_0x3314[0x4];
// Offset 0x3318: Current mesh data pointer
void* currentMeshData; // +0x3318
// More fields...
// Total size: At least 0x3400 bytes
};
CGxDeviceD3d Structure (Partial)
Overview
The CGxDeviceD3d structure wraps the D3D9 device and manages rendering state.
Global Pointer
Address: 0x00c0ed38
Type: CGxDeviceD3d* (or IDirect3DDevice9**)
// Global device pointer
IDirect3DDevice9** g_d3d9DevicePtr = (IDirect3DDevice9**)0x00c0ed38;
// Usage:
IDirect3DDevice9* device = *g_d3d9DevicePtr;
device->SetVertexShader(...);
Structure Definition (Partial)
struct CGxDeviceD3d {
// Offset 0x00: D3D9 device pointer
IDirect3DDevice9* pD3D9Device; // +0x00
// Offset 0x04-0x27DF: Unknown (state tracking, buffers, etc.)
char pad_0x04[0x27DC];
// Offset 0x27E0: Current primitive type
int currentPrimType; // +0x27E0
// Offset 0x27E4: Current vertex data pointer
void* currentVertexData; // +0x27E4
// Offset 0x27E8: Current draw flags
uint32_t currentDrawFlags; // +0x27E8
// More fields...
};
Summary Table
| Structure | Size (bytes) | Critical Offsets | Purpose |
|---|---|---|---|
| Model | 0x398+ | +0x94 (boneArray) | Container for entire M2 model |
| MeshData | 0x20+ | +0x06 (vertexCount) | Describes a submesh |
| Vertex (T-pose) | 0x30 (48) | +0x0C (weights), +0x10 (indices) | Unskinned vertex |
| SkinnedVertex | 0x28 (40) | N/A | Skinned vertex output |
| BoneMatrix | 0x40 (64) | N/A | 4x4 bone transform |
| Batch | 0x40 (64) | +0x00 (type), +0x04 (model) | Render batch descriptor |
| RenderContext | 0x3400+ | +0x3300 (currentBatch) | Frame rendering state |
Memory Alignment
All structures appear to be naturally aligned:
floatfields: 4-byte aligned- Pointers: 4-byte aligned (32-bit executable)
- Structures: Aligned to largest member
No packing directives needed for these structures.
Validation Checklist
Before Implementation
- Verify Model+0x94 points to valid bone array
- Verify MeshData+0x06 contains correct vertex count
- Verify Vertex stride is 48 bytes (0x30)
- Verify SkinnedVertex stride is 40 bytes (0x28)
- Verify BoneMatrix size is 64 bytes (0x40)
- Verify CGxDeviceD3d__device @ 0x00c0ed38 is valid
Runtime Verification Code
void VerifyStructures() {
// Check sizes
assert(sizeof(Vertex) == 0x30);
assert(sizeof(SkinnedVertex) == 0x28);
assert(sizeof(BoneMatrix) == 0x40);
// Check device pointer
IDirect3DDevice9** devicePtr = (IDirect3DDevice9**)0x00c0ed38;
assert(!IsBadReadPtr(devicePtr, sizeof(void*)));
IDirect3DDevice9* device = *devicePtr;
assert(!IsBadReadPtr(device, sizeof(void*)));
// Check device vtable
void** vtable = *(void***)device;
assert(!IsBadReadPtr(vtable, sizeof(void*) * 100));
OutputDebugStringA("Structure verification passed\n");
}
Document Version: 1.0 Last Updated: 2025-12-04 All offsets verified: Ghidra decompilation of Wow.exe (1.12.1)