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
This commit is contained in:
MarcelineVQ
2026-03-06 13:27:35 -08:00
parent 0783f3b0dd
commit 2aaa089c76
44 changed files with 1904 additions and 1022 deletions
+13 -13
View File
@@ -14,9 +14,9 @@ Pure Zig reimplementation of the WoW 1.12.1 unit outline system, ported from the
**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.
- `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).
@@ -34,9 +34,9 @@ Vtable obtained by creating a temporary `IDirect3DDevice9` via `Direct3DCreate9`
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.
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
@@ -56,7 +56,7 @@ Pixel shader: `ps_3_0`, outputs solid colour from `c0`.
| `+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! |
| `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 |
@@ -64,9 +64,9 @@ Pixel shader: `ps_3_0`, outputs solid colour from `c0`.
## 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
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
@@ -76,8 +76,8 @@ Pixel shader: `ps_3_0`, outputs solid colour from `c0`.
## 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.
- 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.