Add research notes for SuperWoW events and transformMatrix4x4 analysis
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
# SuperWoW Event Registration — Ghidra Analysis
|
||||
|
||||
Analysis of SuperWoWhook.dll
|
||||
|
||||
## CreateEvents Hook (0x100056c0)
|
||||
|
||||
1. Calls the original `FrameScript_CreateEvents` via its trampoline — this lets the
|
||||
engine (and any earlier hooks like nampower) build the internal event table normally.
|
||||
2. After the original returns, checks if the count that was passed in was > 200 (0xC8).
|
||||
This distinguishes the main event table call (549 events) from the GlueXML call
|
||||
(~26 events at `0xB41E70`). If ≤ 200, it does nothing.
|
||||
3. Reads the internal array pointer from `PTR_00ceef68` (the struct at `0xceef60` has
|
||||
`{count, capacity, array_ptr}` at offsets +0, +4, +8).
|
||||
4. Calls `DuplicateStringWithAllocation` (0x64a620, stdcall) twice — once for
|
||||
`"UNIT_CASTEVENT"`, once for `"RAW_COMBATLOG"`. This allocates via `SMemAlloc` and
|
||||
copies the string, giving engine-owned memory that won't be freed unexpectedly.
|
||||
5. Writes each allocated name pointer into the internal table at hardcoded offsets:
|
||||
`array + 0x2580` (slot 600 × 16 bytes) and `array + 0x2590` (slot 601 × 16 bytes).
|
||||
Each internal table entry is 16 bytes: `{name_ptr, 0, self_ptr, self_ptr|1}` — it
|
||||
only writes the name at +0.
|
||||
|
||||
## resize_lua_event_array Hook (0x10005710)
|
||||
|
||||
1. This is the function that reallocates the internal event table array. It's
|
||||
`__thiscall(ECX=0xceef60, stack=new_capacity)`.
|
||||
2. SuperWoW intercepts every call. If the requested count is > 200 (again, skipping
|
||||
GlueXML), it overwrites the count argument with 700 (0x2BC) before calling the
|
||||
original.
|
||||
3. This ensures the internal array is always large enough for slots 600-601, regardless
|
||||
of how many base events the engine requests.
|
||||
4. `RET 0x4` — thiscall cleans the one stack parameter.
|
||||
|
||||
## Ordering
|
||||
|
||||
`resize_lua_event_array` is called internally by `FrameScript_CreateEvents` as it
|
||||
processes entries and needs to grow the array. So the resize hook fires during the
|
||||
original CreateEvents call, expanding capacity to 700 before the CreateEvents hook's
|
||||
post-processing writes to slots 600/601. By the time SuperWoW writes its events, the
|
||||
array is already large enough.
|
||||
|
||||
## What It Doesn't Do
|
||||
|
||||
It doesn't touch the input name array at all, doesn't modify `maxEventId`, and doesn't
|
||||
fill unused entry fields beyond the name pointer. The engine only needs the name at +0
|
||||
for `RegisterEvent`/`SignalEvent` lookups.
|
||||
|
||||
## Contrast with Nampower
|
||||
|
||||
Nampower overwrites entries in the **input name array** (the `ECX` parameter to
|
||||
`FrameScript_CreateEvents`). The input array at `0xBE1198` has 549 string pointers.
|
||||
Nampower hooks CreateEvents, bumps `maxEventId` from 549 to 551, and writes its event
|
||||
names (`SPELL_DAMAGE_EVENT_SELF`, `SPELL_DAMAGE_EVENT_OTHER`) directly into the input
|
||||
array at slots 549 and 550 (addresses `0xBE1A2C` and `0xBE1A30`). The function then
|
||||
processes these as normal entries when building the internal table.
|
||||
|
||||
Problem: slot 550+ overlap with float globals in the `.data` section — `0xBE1A30` is
|
||||
actually `float 0.25` (0x3E800000).
|
||||
|
||||
## Our Approach
|
||||
|
||||
Follows SuperWoW's pattern: post-CreateEvents write into the internal table at slot 650,
|
||||
with a resize hook to ensure capacity ≥ 700. Compatible with both SuperWoW and nampower
|
||||
in the hook chain.
|
||||
@@ -0,0 +1,146 @@
|
||||
# transformMatrix4x4 (0x714260) -- Research Notes
|
||||
|
||||
## Function Overview
|
||||
|
||||
- **Address**: 0x714260
|
||||
- **Size**: 17703 bytes (0x4527)
|
||||
- **Convention**: `__thiscall(ECX=SceneObject*, stack: Matrix4x4*, Matrix4x4*, Matrix4x4*, Matrix4x4*)`
|
||||
- **Returns**: void
|
||||
- **Epilogue**: `RET 0x10` (4 stack params, normal exit) and `RET 0x4` (early exit path)
|
||||
- **Recursive**: calls itself at 0x0071875c for child scene objects
|
||||
|
||||
## Calling Convention Evidence
|
||||
|
||||
```
|
||||
PROLOGUE:
|
||||
0x00714260 PUSH EBP
|
||||
0x00714261 MOV EBP,ESP
|
||||
0x00714263 SUB ESP,0x19c ; 412 bytes of locals
|
||||
0x00714269 PUSH EBX
|
||||
0x0071426a MOV EBX,ECX ; this = ECX (thiscall)
|
||||
```
|
||||
|
||||
Stack frame: 0x19c (412) bytes of locals. Massive function.
|
||||
|
||||
## Callers
|
||||
|
||||
| Address | Function | Notes |
|
||||
|---------|----------|-------|
|
||||
| 0x707662 | processLinkedObjectList (0x707600) | |
|
||||
| 0x7077b6 | renderFrame (0x707680) | |
|
||||
| 0x707824 | renderFrame (0x707680) | Second call in same function |
|
||||
| 0x714069 | updateAnimationTransform (0x714000) | |
|
||||
| 0x714158 | updateAnimationTransform (0x714000) | |
|
||||
| 0x71417e | updateAnimationTransform (0x714000) | |
|
||||
| 0x7191b2 | renderSceneNode (0x718960) | |
|
||||
| 0x71875c | transformMatrix4x4 (0x714260) | Recursive self-call |
|
||||
|
||||
## Internal Calls
|
||||
|
||||
| Address | Function | Count | Purpose |
|
||||
|---------|----------|-------|---------|
|
||||
| 0x713d50 | findInterpolationIndices | 58 | Core animation interpolation index lookup |
|
||||
| 0x713ea0 | interpolateAnimationKeyframes | 2 | Full keyframe interpolation |
|
||||
| 0x71af20 | getInterpolatedFloat | 4 | Single float interpolation |
|
||||
| 0x71aff0 | getIndexOffset | 12 | Animation index calculation |
|
||||
| 0x71b010 | setShortValue | 12 | Write short values |
|
||||
| 0x74a7c0 | initParticlePixelShaderGeneration | 3 | Particle system setup |
|
||||
| 0x74b6b5 | initPixelShaderDispatcher5 | 1 | Pixel shader setup |
|
||||
| 0x7b5e60 | TransformParticleVelocities | 1 | Particle velocity transforms |
|
||||
| 0x7b5f60 | IsParticleBufferEmpty | 1 | Check particle buffer state |
|
||||
| 0x7b7bc0 | TransformParticleVectors | 1 | Particle vector transforms |
|
||||
| 0x7bd820 | calculateScaledInverseMatrix | 1 | Inverse matrix for billboarding? |
|
||||
| 0x7bdca0 | scaleMatrix3x3ByVector | 2 | Scale 3x3 portion of matrix |
|
||||
| 0x7bdc40 | ApplyTranslationMatrix | 5 | Apply translation to matrix |
|
||||
| 0x7bddb0 | rotateMatrixByQuaternion | 1 | Quaternion rotation |
|
||||
| 0x4549f0 | emptyFunction | 10 | No-op (likely stripped debug/assert) |
|
||||
| 0x409aef | validateMemoryOperation | 1 | Memory validation |
|
||||
| 0x40a2b0 | __ftol | 4 | Float-to-long conversion |
|
||||
|
||||
## High-Level Structure
|
||||
|
||||
### Entry Checks (lines 110-111)
|
||||
```c
|
||||
if (this->model_data_ptr != NULL &&
|
||||
this->transform_sync_value != *(this->animation_context_ptr + 0x10))
|
||||
```
|
||||
Bails immediately if no model data or transform is already up to date (sync value matches).
|
||||
|
||||
### Global Sequence Processing (lines 137-149)
|
||||
Iterates global sequence array at `model+0x130`, computes per-sequence time offsets using
|
||||
`animation_context_ptr+0xC` (current timestamp) modulo sequence duration.
|
||||
|
||||
### Identity Matrix Init (lines 163-194)
|
||||
Sets up two identity matrices: `local_74` (4x4) and a second 3x4 matrix in `local_e8..local_ac`.
|
||||
|
||||
### Main Bone Loop (lines 203-2204)
|
||||
```c
|
||||
do {
|
||||
pMVar23 = param_3 * 0x6c + *(local_18 + 0x38); // bone def from model
|
||||
puVar20 = param_3 * 0x118 + this->unknown_0x80; // bone runtime state
|
||||
...
|
||||
param_3++;
|
||||
} while (param_3 < *(local_18 + 0x34)); // bone count
|
||||
```
|
||||
|
||||
Each bone is 0x6c (108) bytes in the model definition and 0x118 (280) bytes in runtime state.
|
||||
|
||||
Per-bone processing:
|
||||
1. **Parent bone inheritance** (lines 210-268): Copy transform from parent bone if parent index != -1
|
||||
2. **Animation time computation** (lines 230-267): Handle looping vs clamped animations, compute current keyframe position
|
||||
3. **Blend weight (crossfade)** (lines 334-367): Hermite interpolation for animation blending
|
||||
4. **Bone flags processing** (lines 368-478): Billboard types (flags & 7):
|
||||
- 0x2: Cylindrical billboard (normalize rotation columns)
|
||||
- 0x4: Spherical billboard (inherit parent rotation)
|
||||
- 0x6: Full billboard (copy parent rotation directly)
|
||||
- Flag 0x1: Fixed translation vs pivot-relative
|
||||
5. **Scale interpolation** (lines 522-572): `scaleMatrix3x3ByVector` with interpolated scale
|
||||
6. **Translation interpolation** (lines 583-628): Add interpolated translation to pivot
|
||||
7. **Rotation interpolation** (quaternion, lines 630+): `rotateMatrixByQuaternion`
|
||||
8. **Matrix composition** (lines 1050+): `ApplyTranslationMatrix` to build final bone matrix
|
||||
9. **Write to output** (lines 480-492): Copy final matrix to bone transform array at `this->transform_vec2_x`
|
||||
|
||||
### Attachment Processing (lines 2206-2257)
|
||||
After all bones, iterates attached child objects:
|
||||
- Extracts parent bone matrix
|
||||
- Applies attachment offset translation
|
||||
- **Recursive call** to transformMatrix4x4 for each child SceneObject
|
||||
|
||||
### Sync Value Update (line 2259)
|
||||
```c
|
||||
this->transform_sync_value = *(this->animation_context_ptr + 0x10);
|
||||
```
|
||||
Marks transform as up to date.
|
||||
|
||||
## Key Data Structures
|
||||
|
||||
### SceneObject (this pointer)
|
||||
| Offset | Field | Type | Notes |
|
||||
|--------|-------|------|-------|
|
||||
| +0x10 | model_data_ptr | void* | NULL check for early bail |
|
||||
| +0x2C | ptr_at_2c | void* | -> model header? |
|
||||
| +0x30 | animation_context_ptr | void* | +0x0C=timestamp, +0x10=sync_value |
|
||||
| +0x40 | transform_sync_value | int | Compared with anim_ctx+0x10 |
|
||||
| +0x80 | unknown_0x80 | uint | Bone runtime state array base |
|
||||
| +0x1CC | field_0x1cc | int* | Emitter/particle data? |
|
||||
|
||||
### Bone Definition (0x6c = 108 bytes per bone in model)
|
||||
From `model+0x38` array. Contains:
|
||||
- Flags, parent bone index, billboard type
|
||||
- Keyframe data pointers for translation, rotation, scale
|
||||
- Pivot point (Vec3)
|
||||
|
||||
### Bone Runtime State (0x118 = 280 bytes per bone)
|
||||
From `this->unknown_0x80` array. Contains:
|
||||
- Current interpolation indices and weights
|
||||
- Interpolated translation, rotation, scale values
|
||||
- Blend state for animation crossfading
|
||||
- Final composed 4x4 transform matrix
|
||||
|
||||
## Key Observations
|
||||
|
||||
1. **Performance critical**: Called per-frame for every visible M2 model with animated bones
|
||||
2. **58 calls to findInterpolationIndices**: This is the hot inner function
|
||||
3. **Recursive for attachments**: Child objects (weapons, shoulders, etc.) recurse through this same function
|
||||
4. **Two animation blend sources**: Primary animation + blend target with crossfade weight at puVar20[0x43]
|
||||
5. **Billboard support**: Flags-based billboard types for UI/particle-facing bones
|
||||
Reference in New Issue
Block a user