2cca8f4b53
Assetfix: hook CheckFileExistence to serve loose Data\ files. The original flags|1 approach failed because game paths contain backslashes, causing CheckFileExistence to skip BuildFilePath and check the raw path (no Data\ prefix). Fix: write the correct Data\-prefixed disk path to the output buffer directly and return 1, bypassing the original function for hash map hits. This preserves hook chaining (filename argument is never transformed). Also adds transmogfix (transmog update coalescing), minimapicons (stub), new build options for all three modules, mutex-based multi-DLL safety, embed .skin data into .m2 models, and various module improvements.
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 | ✓ |
|
||
| +0x94 | `BoneMatrix*` | boneArray | **Pointer to bone matrix array** | ✓ |
|
||
|
||
### 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 | ✓ |
|
||
| +0x06 | `uint16_t` | vertexCount | **Loop terminator in applyBoneTransforms** | ✓ |
|
||
| +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)
|