Files
WeirdUtils/docs/outline-port-notes.md
T
MarcelineVQ 2aaa089c76 Add module control API, bigcursor with fractional scaling, shared mutex
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
2026-03-06 13:27:35 -08:00

4.6 KiB
Raw Blame History

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. The SUB ESP,0x80 is 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 Direct3DCreate9CreateDevice 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:

  1. Pass 1 - Mark body: Draw original geometry to stencil buffer (bit 0), no colour write.
  2. 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.
  3. 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

  1. Target (golden amber #FFC800) - current target, 2.25px outline
  2. Raid-marked (per-icon colour) - units with raid icons 1-8, 1.5px
  3. Dead player (cyan #00FFFF) - deceased friendly players, 2.5px, through walls

Per-frame Flow

  1. EndScene fires → tracker.scanObjects() rebuilds GUID tracking → stencil cleared
  2. Next frame: ManageRenderListNode classifies models → DrawBatchProj sets flags → DIP does three-pass rendering
  3. 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/hook inline hook library.
  • Object scanning is frame-based (EndScene), not event-driven (no separate Idris runtime thread).
  • Shader loading uses dynamic d3dx9_43.dll lookup; gracefully disabled if absent.