69051fd486
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.
758 lines
22 KiB
Markdown
758 lines
22 KiB
Markdown
# 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
|
||
|
||
```cpp
|
||
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)
|
||
|
||
```cpp
|
||
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)
|
||
|
||
```cpp
|
||
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
|
||
|
||
```cpp
|
||
// 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
|
||
|
||
```cpp
|
||
// 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
|
||
|
||
```cpp
|
||
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
|
||
|
||
```c
|
||
// 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
|
||
|
||
```cpp
|
||
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:
|
||
|
||
```cpp
|
||
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**:
|
||
```cpp
|
||
uint8_t weight_byte = 200;
|
||
float weight_float = weight_byte * 0.003921569f; // = 0.784
|
||
```
|
||
|
||
**Verification**:
|
||
```c
|
||
// 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:
|
||
|
||
```cpp
|
||
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
|
||
|
||
```cpp
|
||
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:
|
||
```c
|
||
// 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):
|
||
|
||
```cpp
|
||
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
|
||
|
||
```cpp
|
||
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
|
||
|
||
```cpp
|
||
// 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
|
||
|
||
```c
|
||
// 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]):
|
||
|
||
```cpp
|
||
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)**:
|
||
```cpp
|
||
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)**:
|
||
```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)
|
||
|
||
```cpp
|
||
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
|
||
|
||
```c
|
||
// 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)
|
||
|
||
```cpp
|
||
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**`)
|
||
|
||
```cpp
|
||
// Global device pointer
|
||
IDirect3DDevice9** g_d3d9DevicePtr = (IDirect3DDevice9**)0x00c0ed38;
|
||
|
||
// Usage:
|
||
IDirect3DDevice9* device = *g_d3d9DevicePtr;
|
||
device->SetVertexShader(...);
|
||
```
|
||
|
||
### Structure Definition (Partial)
|
||
|
||
```cpp
|
||
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:
|
||
- `float` fields: 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
|
||
|
||
```cpp
|
||
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)
|