Files
WeirdUtils/docs/outline-validation.md
T
MarcelineVQ 25ed0b3780 Add outline rendering system ported from C++ to Zig
Port the model outline hook system (CM2SceneRenderDraw, ManageRenderListNode,
DrawBatchProjected) from the C++ idris DLL to pure Zig. Includes D3D9 vtable
hooks for stencil-based outline rendering with per-category colors and thickness.

Fix GetObjectByGUID calling convention: was using fastcall (ECX/EDX) but Ghidra
confirms it's __stdcall with stack params and RET 8. Fix lua_pushcclosure address
from 0x6F3B80 (mid-body of another function) to 0x6F3920.

Guard resolveModelOwner in DrawBatchProj with hasTargets() check and cache unit
model status during ManageRenderListNode to avoid raw pointer chasing at render
time. Add IsBadReadPtr validation in resolveModelOwner to match C++ IsValidReadPtr
pattern for page-level memory safety.
2026-02-24 00:51:33 -08:00

3.8 KiB

Outline Validation Checklist

Build

  • zig build succeeds with no errors (x86 windows-msvc target)
  • Output DLL present at zig-out/lib/weirdutils.dll

Existing Feature Regression

  • DLL loads without crash (inject into WoW 1.12.1)
  • /wu version responds correctly
  • /wu test calls C function and returns string
  • Screenshot hook works (/wu ss, PNG output)
  • Interact/loot bindings work (InteractNearest, LootAllCorpses)
  • Addon loads on login (green "WeirdUtils loaded" message)

Outline Subsystem

Commands

  • /wu outline shows ON/OFF status
  • /wu outline on enables outlines
  • /wu outline off disables outlines
  • /wu help text includes outline commands

Dead Player Outlines

  • Dead friendly players in party/raid show cyan outline
  • Outline is visible through walls (no depth test)
  • Outline disappears when player is resurrected
  • Skeleton corpses are NOT outlined

Raid Mark Outlines

  • Units with raid icons (Star through Skull) show coloured outlines
  • Each icon has a distinct colour
  • Outlines update when raid marks change
  • Outlines respect terrain occlusion (depth test enabled)

Target Outline

  • Current target shows golden amber outline
  • Outline changes when target changes
  • Outline clears when target is deselected
  • Outline respects terrain occlusion

Rendering Quality

  • Outlines are clean (no jagged edges or flicker)
  • Outline thickness is consistent across distances
  • Normal model renders correctly on top of outline
  • No visible render state leakage to other models
  • Non-outlined units don't cover outline pixels (stencil test)

Performance

  • No noticeable FPS drop in normal gameplay
  • No FPS drop in 40-player raid with multiple outlines
  • Frame rate stable when toggling outlines on/off

Edge Cases

  • D3DX DLL not present → outlines gracefully disabled (no crash)
  • No targets tracked → zero overhead (fast path)
  • /wu outline off → DIP hook passes through immediately
  • Device Reset (resolution change) → shaders recreated properly

Hook Prologue Verification (Ghidra)

Verified 2026-02-23 using Ghidra MCP against WoW.exe 1.12.1 (build 5875, 4,907,008 bytes).

Method

Raw bytes read from each hook target address via get_bytes. Instruction boundaries decoded to confirm the prologue_size parameter passed to hook.prepare() lands on a clean instruction boundary. A mid-instruction cut would corrupt the trampoline — the copied bytes would decode as a different instruction when followed by the trampoline's JMP.

Results

Address Function Prologue bytes Decoded Boundary Safe Fixups
0x0070b360 CM2SceneRenderDraw 55 8B EC 81 EC 80 00 00 00 PUSH EBP; MOV EBP,ESP; SUB ESP,0x80 +1,+3,+9 9B none
0x00710b90 CM2Model_ManageRenderListNode 55 8B EC 8B 45 08 PUSH EBP; MOV EBP,ESP; MOV EAX,[EBP+8] +1,+3,+6 6B none
0x0070cb30 CM2Scene_DrawBatchProjected 55 8B EC 83 EC 10 PUSH EBP; MOV EBP,ESP; SUB ESP,0x10 +1,+3,+6 6B none

Fix applied

CM2SceneRenderDraw prologue changed from 6 to 9 bytes in src/outline/model_hook.zig. The original 6-byte value would have split the SUB ESP, 0x80 instruction (opcode 81 EC + 4-byte immediate, spanning offset +3 to +9). The trampoline would have executed 81 EC 80 E9 xx xx as SUB ESP, <corrupted>, leading to stack corruption and a crash.

No other changes needed

  • All three prologues contain only register/memory instructions (no E8 CALL or E9 JMP), so rel32_fixups remains &.{}.
  • The 4-byte NOP padding (bytes 5-8) at 0x0070b360 after the 5-byte E9 JMP is harmless — it is never executed (execution jumps to the detour thunk).