diff --git a/src/dpslog/SUPERWOW_EVENTS.md b/src/dpslog/SUPERWOW_EVENTS.md new file mode 100644 index 0000000..8dfa98f --- /dev/null +++ b/src/dpslog/SUPERWOW_EVENTS.md @@ -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. diff --git a/src/transform44/RESEARCH.md b/src/transform44/RESEARCH.md new file mode 100644 index 0000000..01b3e1a --- /dev/null +++ b/src/transform44/RESEARCH.md @@ -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