Module control API: - Three cdecl exports: WeirdUtils_IsModuleActive, WeirdUtils_DisableModule, WeirdUtils_DisableAll - C header include/weirdutils_api.h for runtime DLL discovery - All modules gain pub module_name, isActive(), shared mutex via src/mutex.zig - build.zig refactored to module_list array of ModuleDesc Bigcursor: - D3D9 vtable hooks on SetCursorProperties/ShowCursor - Scale2x/Scale3x pixel-art upscalers in pure Zig (replaces 12K-line hqx C) - Bilinear resampler for fractional scales (1.0-4.0, default 1.2) - Win32 cursor via CreateIconIndirect bypasses D3D9 32x32 limit - FNV-1a hash cache for ~16 cursor bitmaps - Lua API: SetCursorScale(n) / GetCursorScale() - CVar cursorScale for Config.wtf persistence (tenths) Other: - Add dpslog module stub - Docs and README updates
4.6 KiB
Outline Port Notes
Pure Zig reimplementation of the WoW 1.12.1 unit outline system, ported from the Idris C/C++ reference at /media/storage/projects/idris/dlls.
Hooked Functions
Model pipeline (inline hooks via libs/hook)
| Address | Function | Convention | Prologue | Fixups | Notes |
|---|---|---|---|---|---|
0x0070b360 |
CM2SceneRenderDraw |
__thiscall |
9 bytes | none | Batch reordering for outline priority |
0x00710b90 |
CM2Model_ManageRenderListNode |
__thiscall |
6 bytes | none | Classifies models on render-list add |
0x0070cb30 |
CM2Scene_DrawBatchProjected |
__fastcall |
6 bytes | none | Flags DIP hook for outline rendering |
Prologue verification (Ghidra, WoW.exe 1.12.1 build 5875):
0x0070b360:55 8B EC 81 EC 80 00 00 00-PUSH EBP; MOV EBP,ESP; SUB ESP,0x80. Boundaries at +1, +3, +9. TheSUB ESP,0x80is a 6-byte instruction (81 EC + imm32) spanning offset +3..+9, so 6-byte overwrite is unsafe - changed to 9.0x00710b90:55 8B EC 8B 45 08-PUSH EBP; MOV EBP,ESP; MOV EAX,[EBP+8]. Boundaries at +1, +3, +6. Clean 6-byte boundary.0x0070cb30:55 8B EC 83 EC 10-PUSH EBP; MOV EBP,ESP; SUB ESP,0x10. Boundaries at +1, +3, +6. Clean 6-byte boundary.
All three use buildFastcallToCdeclThunk to bridge to callconv(.c) detour functions (since __thiscall is __fastcall with unused EDX).
D3D9 (vtable patching via dummy device)
| VTable Index | Method | Purpose |
|---|---|---|
| 42 | EndScene |
Per-frame object scan, stencil clear |
| 82 | DrawIndexedPrimitive |
Three-pass stencil outline rendering |
| 16 | Reset |
Force D24S8 depth/stencil format |
Vtable obtained by creating a temporary IDirect3DDevice9 via Direct3DCreate9 → CreateDevice with a hidden window. All D3D9 devices share the same vtable, so patching affects the game's device.
Outline Rendering (DIP hook)
Three-pass stencil approach per outline model:
- Pass 1 - Mark body: Draw original geometry to stencil buffer (bit 0), no colour write.
- Pass 2 - Draw outline: Screen-space vertex shader expands vertices along normals. Stencil test rejects body pixels. Write outline bit 1. Dead players disable depth test (through-wall); targets/raid marks respect depth.
- Pass 3 - Normal draw: Restore all state, draw model normally on top.
Vertex Shader
Compiled at runtime via D3DXAssembleShader from d3dx9_43.dll (loaded dynamically). Falls back to no outlines if the DLL isn't present.
Format: vs_2_0, uses WoW's bone matrix constants (c[idx+31..33]), view-projection at c2-c5, custom constants at c251 (bone scale) and c252 (pixel thickness).
Pixel shader: ps_3_0, outputs solid colour from c0.
Key Offsets
| Address/Offset | Purpose |
|---|---|
0x00B41414 |
Object Manager pointer |
+0xAC |
First object in linked list |
+0xA4 |
Base for next-object traversal |
+0xC0 |
Local player GUID (from ObjMgr) |
0x00B71368 |
Raid target GUID array (8 × 8 bytes) |
0x515970 |
UnitGUID(__fastcall, string_ECX→EAX:EDX) |
0x464870 |
GetObjectByGUID(__stdcall, lo_stack, hi_stack→EAX) - NOT fastcall! |
0x6061E0 |
UnitReaction(__thiscall, player_ECX, unit_stack→int) |
model+0x28 |
Direct owner object pointer |
model+0x3C0 |
Callback owner object pointer |
ctx+0x3310 |
Model pointer in render context |
Category Priority
- Target (golden amber
#FFC800) - current target, 2.25px outline - Raid-marked (per-icon colour) - units with raid icons 1-8, 1.5px
- Dead player (cyan
#00FFFF) - deceased friendly players, 2.5px, through walls
Per-frame Flow
EndScenefires →tracker.scanObjects()rebuilds GUID tracking → stencil cleared- Next frame:
ManageRenderListNodeclassifies models →DrawBatchProjsets flags →DIPdoes three-pass rendering - One-frame latency for tracking updates (imperceptible)
Differences from Reference
- No
std::unordered_set/std::unordered_map- fixed arrays with linear search (max 64 dead GUIDs, 8 raid marks, 256 outline models). - No
CriticalSection- all hooks run on the main WoW thread; no synchronisation needed. - No MinHook - uses the project's existing
libs/hookinline hook library. - Object scanning is frame-based (EndScene), not event-driven (no separate Idris runtime thread).
- Shader loading uses dynamic
d3dx9_43.dlllookup; gracefully disabled if absent.