Compare commits

..

141 Commits

Author SHA1 Message Date
Dusk-92 c3c0e9a525 test: build V36 with process-exit safety 2026-08-29 18:13:00 +02:00
Dusk-92 6541727419 V36 test: add process-exit safety only 2026-08-29 18:12:52 +02:00
github-actions[bot] 4bbee94388 V36: soft Retail-style JFA decode 2026-08-29 09:32:44 +00:00
Dusk-92 a36ce6ea76 Temporary: commit V36 Retail Soft decode 2026-08-29 11:32:34 +02:00
Dusk-92 db472789d3 Remove temporary V36 Retail Soft build workflow 2026-08-29 11:32:13 +02:00
Dusk-92 f95ab007d2 Fix V36 build workflow YAML 2026-08-29 11:31:02 +02:00
Dusk-92 0ea0c4a94c Temporary: build V36 Retail Soft 2026-08-29 11:30:20 +02:00
Dusk-92 09863ad9e3 Remove invalid temporary V36 patch workflow 2026-08-29 11:30:04 +02:00
Dusk-92 c82f2b9e52 Fix temporary V36 patch workflow syntax 2026-08-29 11:29:20 +02:00
Dusk-92 49a8644d13 Temporary: patch V36 soft Retail decode 2026-08-29 11:28:43 +02:00
Dusk-92 c35b95a4c4 Stable V34 AutoFix: automatic target publication 2026-08-29 11:04:44 +02:00
Dusk-92 2165887e6b Stable V34 AutoFix: self-heal lost D3D9 hooks 2026-08-29 11:04:37 +02:00
Dusk-92 63c929ceb6 Stable V34 AutoFix: sync target automatically 2026-08-29 11:04:31 +02:00
Dusk-92 9f58796ca1 Stable V34 AutoFix: register automatic target sync 2026-08-29 11:04:27 +02:00
Dusk-92 f81851bb84 V34: use full-float JFA seed render targets 2026-08-29 10:18:28 +02:00
Dusk-92 35b3d00519 V34: add G32R32F JFA format 2026-08-29 10:18:10 +02:00
Dusk-92 80b1177da9 V33: fix signed seed test in cleanup shader 2026-08-29 10:05:20 +02:00
Dusk-92 c70c5a37ba V33: conservatively clean silhouette pinholes before JFA 2026-08-29 10:04:42 +02:00
Dusk-92 6354255965 Remove temporary V32 transport file 2026-08-29 09:41:43 +02:00
Dusk-92 70e7a2f24f Remove temporary V32 build workflow 2026-08-29 09:41:40 +02:00
github-actions[bot] d024a5ff69 Build V32 test package [skip ci] 2026-08-29 07:40:32 +00:00
Dusk-92 ebdfa57d88 Publish temporary V32 build asset 2026-08-29 09:39:58 +02:00
github-actions[bot] 5bb482e0c2 Build V32 test package [skip ci] 2026-08-29 07:35:09 +00:00
Dusk-92 6c9e951bf5 Expose compiled V32 outline for transport 2026-08-29 09:34:49 +02:00
github-actions[bot] d3f8dcd76f Build V32 test package [skip ci] 2026-08-29 07:30:47 +00:00
Dusk-92 707481d143 Add temporary V32 build workflow 2026-08-29 09:30:18 +02:00
Dusk-92 5f25e2494c V32: hard 3px outline without feather 2026-08-29 09:29:27 +02:00
Dusk-92 eb810db78c Build V31 outline polish 2026-08-28 22:59:41 +02:00
Dusk-92 85507f7e73 V31 report additive glow filtering 2026-08-28 22:59:36 +02:00
Dusk-92 8ce131c614 V31 polish outline edge and filter additive glow 2026-08-28 22:59:32 +02:00
Dusk-92 f2f0b789bb Build DEBUG30 real StateBlock Apply 2026-08-28 22:51:44 +02:00
Dusk-92 bb26e3fd71 DEBUG30 fix StateBlock Apply vtable slot 2026-08-28 22:51:39 +02:00
Dusk-92 7fcd97c5fa Fix DEBUG29 viewport setter 2026-08-28 22:47:43 +02:00
Dusk-92 a0ca341177 Build DEBUG29 isolated state-block replay 2026-08-28 22:46:56 +02:00
Dusk-92 1ea8067930 DEBUG29 report full D3D state restore 2026-08-28 22:46:48 +02:00
Dusk-92 d344f74fbf DEBUG29 fully isolate exact-material replay state 2026-08-28 22:46:41 +02:00
Dusk-92 ec5e5f189e Build DEBUG28 exact-material state-block replay 2026-08-28 22:41:15 +02:00
Dusk-92 d5a3a0e925 DEBUG28 report D3D state-block capture 2026-08-28 22:41:08 +02:00
Dusk-92 ef0e32de53 DEBUG28 replay exact D3D material via state blocks 2026-08-28 22:41:04 +02:00
Dusk-92 e2f3b3092a Add D3D9 state block type 2026-08-28 22:40:57 +02:00
Dusk-92 dd8286559d Build DEBUG27 fixed-function material alpha replay 2026-08-28 22:29:18 +02:00
Dusk-92 9d1673b359 DEBUG27 replay fixed-function material alpha 2026-08-28 22:29:13 +02:00
Dusk-92 3b5246e0ee Add fixed-function texture-stage constants for Outline 2026-08-28 22:29:07 +02:00
Dusk-92 11d219e54f Build DEBUG26 opaque/cutout silhouette mode 2026-08-28 22:22:45 +02:00
Dusk-92 a92d9caec1 DEBUG26 report skipped translucent passes 2026-08-28 22:22:38 +02:00
Dusk-92 6ef67ff11c DEBUG26 ignore translucent M2 carrier passes 2026-08-28 22:22:33 +02:00
Dusk-92 9a51cc6e1a Build DEBUG25 WoW-aware transparency replay 2026-08-28 22:18:11 +02:00
Dusk-92 ca445ef9bd DEBUG25 replay WoW alpha and blend semantics 2026-08-28 22:18:07 +02:00
Dusk-92 8d2db15f93 Add alpha-test and blend constants for Outline replay 2026-08-28 22:18:02 +02:00
Dusk-92 83627ea829 Build DEBUG24 alpha-aware outline 2026-08-28 22:12:17 +02:00
Dusk-92 dab770c018 DEBUG24 respect texture alpha in silhouette replay 2026-08-28 22:12:08 +02:00
Dusk-92 a933f0b639 Build DEBUG23 stable no-stencil outline 2026-08-28 22:05:31 +02:00
Dusk-92 68206a4347 DEBUG23 compact no-stencil diagnostics 2026-08-28 22:05:26 +02:00
Dusk-92 b65e441261 DEBUG23 run real outline without Reset or stencil 2026-08-28 22:05:21 +02:00
Dusk-92 5b95716a82 Fix DEBUG22 compact-output verifier 2026-08-28 21:59:20 +02:00
Dusk-92 774763f848 Remove obsolete DEBUG14 no-reset verifier 2026-08-28 21:58:26 +02:00
Dusk-92 91607bc42f Build DEBUG22 safe D24S8 stencil path 2026-08-28 21:57:18 +02:00
Dusk-92 2558cd9709 DEBUG22 request and report stencil readiness 2026-08-28 21:57:13 +02:00
Dusk-92 a11ff8d82b DEBUG22 restore D24S8 safely and rehook after Reset 2026-08-28 21:57:09 +02:00
Dusk-92 03fd1c7f38 Build DEBUG21 raw silhouette stencil isolation 2026-08-28 21:51:13 +02:00
Dusk-92 67817052b7 DEBUG21 show raw silhouette without stencil gate 2026-08-28 21:51:08 +02:00
Dusk-92 457b15999b Build DEBUG20 JFA decode fix 2026-08-28 21:40:56 +02:00
Dusk-92 4d3bae29fb Fix JFA decode constant-register restriction 2026-08-28 21:40:45 +02:00
Dusk-92 605ca09804 Build DEBUG19 JFA constant-register fix 2026-08-28 21:37:06 +02:00
Dusk-92 220236855f Fix JFA shader constant-register restriction 2026-08-28 21:37:02 +02:00
Dusk-92 fab99d1f96 Fix DEBUG18 compact-output verifier 2026-08-28 21:25:48 +02:00
Dusk-92 68ccdb253f Fix DEBUG18 output verifier 2026-08-28 21:25:00 +02:00
Dusk-92 a6bf31160f Fix DEBUG18 unused late rehook result 2026-08-28 21:24:05 +02:00
Dusk-92 9a3d431d32 Build DEBUG18 D3DX error text diagnostics 2026-08-28 21:23:20 +02:00
Dusk-92 441a97646e DEBUG18 show D3DX assembler message 2026-08-28 21:23:09 +02:00
Dusk-92 e27fcf1eba DEBUG18 capture D3DX shader error text 2026-08-28 21:23:04 +02:00
Dusk-92 66a77224e3 Build DEBUG17 HRESULT diagnostics 2026-08-28 21:05:02 +02:00
Dusk-92 1f1420ea53 DEBUG17 expose D3D9 failure HRESULTs 2026-08-28 21:04:57 +02:00
Dusk-92 0b8b415bab DEBUG17 capture shader and texture HRESULTs 2026-08-28 21:04:52 +02:00
Dusk-92 6392a960a0 Fix DEBUG16 verifier after compact debug output 2026-08-28 20:40:31 +02:00
Dusk-92 94e1189456 Build DEBUG16 graphics-resource diagnostics 2026-08-28 20:39:19 +02:00
Dusk-92 eb07e1d237 DEBUG16 expose shader and RT stages 2026-08-28 20:39:15 +02:00
Dusk-92 52361a9e42 DEBUG16 instrument shader and render-target creation 2026-08-28 20:39:11 +02:00
Dusk-92 9674807ea4 Build DEBUG15 late rehook test 2026-08-28 20:35:28 +02:00
Dusk-92 1ed94e0387 DEBUG15 late rehook from OutlineDebug 2026-08-28 20:35:23 +02:00
Dusk-92 7797221071 DEBUG15 add late D3D9 rehook 2026-08-28 20:35:20 +02:00
Dusk-92 07ec0b9b74 Fix DEBUG14 verifier location 2026-08-28 20:29:36 +02:00
Dusk-92 9c536db53c Build DEBUG14 without forced D3D9 reset 2026-08-28 20:28:30 +02:00
Dusk-92 4acbd4e965 DEBUG14 disable first-frame forced D3D9 reset 2026-08-28 20:28:27 +02:00
Dusk-92 380f62d5ec Update DEBUG13 output verifier 2026-08-28 20:21:50 +02:00
Dusk-92 3751675336 Keep DEBUG13 output within Zig format arg limit 2026-08-28 20:20:57 +02:00
Dusk-92 659fcceb85 Verify live D3D9 hook diagnostics 2026-08-28 20:20:01 +02:00
Dusk-92 2c3b35e788 Show live D3D9 vtable hook ownership 2026-08-28 20:19:57 +02:00
Dusk-92 0eb5cc2520 Expose live D3D9 hook ownership diagnostics 2026-08-28 20:19:53 +02:00
Dusk-92 2b5c53548d Verify post-publication scan instrumentation 2026-08-28 20:15:58 +02:00
Dusk-92 cc7a5dd7f9 Expose post-publication scan diagnostic 2026-08-28 20:15:54 +02:00
Dusk-92 57fa079361 Track scans occurring after Outline publication 2026-08-28 20:15:49 +02:00
Dusk-92 0ac3809942 Verify Outline handoff instrumentation 2026-08-28 20:11:44 +02:00
Dusk-92 41a53ce388 Expose Outline handoff diagnostics 2026-08-28 20:11:40 +02:00
Dusk-92 50c1a0b508 Instrument Outline Lua-to-render handoff 2026-08-28 20:11:36 +02:00
Dusk-92 9f9e56e764 Use atomic Lua-to-render handoff for Outline target 2026-08-28 20:02:45 +02:00
Dusk-92 f1399599d5 Build Lua-thread pinned Outline debug 2026-08-28 19:59:33 +02:00
Dusk-92 4e8791687b Capture and pin Outline objects from Lua thread 2026-08-28 19:59:30 +02:00
Dusk-92 d4df1143ce Use Lua-thread pinned objects for Outline debug rendering 2026-08-28 19:59:25 +02:00
Dusk-92 cdd3724c8e Build live UnitGUID Outline tracking debug 2026-08-28 19:56:32 +02:00
Dusk-92 e0f8592015 Expose UnitGUID object-resolution stages 2026-08-28 19:56:28 +02:00
Dusk-92 8e6d8e292e Use working UnitGUID path for Outline tracking 2026-08-28 19:56:25 +02:00
Dusk-92 8a0c8eec18 Verify one-shot UnitGUID debug probe 2026-08-28 19:52:07 +02:00
Dusk-92 35f90c85fb Add one-shot UnitGUID diagnostics to OutlineDebug 2026-08-28 19:52:04 +02:00
Dusk-92 c575ec8498 Document independent target debug stage 2026-08-28 18:31:18 +02:00
Dusk-92 2aeee3fceb Expose player GUID stage in OutlineDebug 2026-08-28 18:31:15 +02:00
Dusk-92 6ef29ba99f Track target independently from local player resolution 2026-08-28 18:31:12 +02:00
Dusk-92 e5acd33f0c Verify Nampower-style Outline object path 2026-08-28 18:26:19 +02:00
Dusk-92 fb4fb880e5 Use Nampower-style player and target GUID path 2026-08-28 18:26:15 +02:00
Dusk-92 61cf447248 Add locked target GUID address for Outline debug 2026-08-28 18:26:11 +02:00
Dusk-92 1314860bde Let target Outline work without legacy object manager global 2026-08-28 18:22:12 +02:00
Dusk-92 134e5a37f9 Allow GUID resolution when legacy object manager global is zero 2026-08-28 18:21:59 +02:00
Dusk-92 bff19dd95b Show object manager readiness in OutlineDebug 2026-08-28 18:16:19 +02:00
Dusk-92 4eb37f4c23 Bypass broken IS_IN_WORLD gate in Outline debug 2026-08-28 18:16:14 +02:00
Dusk-92 3ee72d70d6 Add object manager readiness check for Outline debug 2026-08-28 18:16:10 +02:00
Dusk-92 b714c38ab7 Expose target acquisition stages in OutlineDebug 2026-08-28 18:06:19 +02:00
Dusk-92 bcf8cefb35 Split Outline target acquisition diagnostics 2026-08-28 18:06:15 +02:00
Dusk-92 1632d98797 Keep V8 debug ABI checks optimizer independent 2026-08-28 17:59:35 +02:00
Dusk-92 c890a6bc69 Relax debug build FrameScript disassembly matcher 2026-08-28 17:58:55 +02:00
Dusk-92 086dd17a31 Trigger V8 debug diagnostics build 2026-08-28 17:57:54 +02:00
Dusk-92 b128c01a7c Build V8 Outline debug diagnostics 2026-08-28 17:57:41 +02:00
Dusk-92 33ec80d742 Register OutlineDebug Lua command 2026-08-28 17:57:19 +02:00
Dusk-92 ad706ccb9a Add in-game OutlineDebug diagnostics 2026-08-28 17:57:09 +02:00
Dusk-92 d7f93b3c00 Add sticky D3D9 Outline diagnostics 2026-08-28 17:56:47 +02:00
Dusk-92 62026da380 Add sticky Outline tracker diagnostics 2026-08-28 17:56:26 +02:00
Dusk-92 45635455b9 Verify V8 UnitGUID ECX ABI 2026-08-28 17:48:24 +02:00
Dusk-92 37757a2347 Fix V8 UnitGUID x86 ABI stack leak 2026-08-28 17:48:06 +02:00
Dusk-92 855ea340cc Fix V8 registerFunction asm build 2026-08-28 17:39:31 +02:00
Dusk-92 6e0c0850e8 Verify V8 FrameScript registration ABI 2026-08-28 17:38:20 +02:00
Dusk-92 17af79dff4 Fix V8 FrameScript registration ABI 2026-08-28 17:37:45 +02:00
Dusk-92 a55f43a5d5 Verify V8 OutlineCommand ECX ABI in machine code 2026-08-28 17:27:43 +02:00
Dusk-92 126f617ca1 Fix V8 OutlineCommand ABI to receive Lua state in ECX 2026-08-28 17:27:24 +02:00
Dusk-92 43839b2b41 Build safe standalone core V8 artifact 2026-08-28 17:09:54 +02:00
Dusk-92 dbdb513e94 Use x86 fastcall for OutlineCommand callback 2026-08-28 17:09:38 +02:00
Dusk-92 4aae16646c Build safe standalone core V7 artifact 2026-08-28 17:05:10 +02:00
Dusk-92 5fe9a92672 Force Outline Lua calls through x86 register asm 2026-08-28 17:04:59 +02:00
Dusk-92 b587992a6c Build safe standalone core V5 artifact 2026-08-28 16:55:06 +02:00
Dusk-92 bbcc70fa0d Fix Outline Lua API calls on safe core V5 2026-08-28 16:54:46 +02:00
Dusk-92 ebccbee66a Build safe standalone core V4 artifact 2026-08-28 16:47:23 +02:00
Dusk-92 68c0ad2b75 Use minimal standalone core for Outline CustomAssets and TransmogFix 2026-08-28 16:46:54 +02:00
Dusk-92 7bf5a11011 Add minimal core mode for safe standalone variants 2026-08-28 16:46:17 +02:00
28 changed files with 1768 additions and 883 deletions
+77
View File
@@ -0,0 +1,77 @@
name: Commit V36 Retail Soft decode
on:
push:
branches:
- test/v36-retail-jfa-soft
permissions:
contents: write
jobs:
commit-v36-soft:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: test/v36-retail-jfa-soft
- name: Apply V36 decode
shell: bash
run: |
set -euo pipefail
python3 <<'PY'
from pathlib import Path
p = Path('src/outline/d3d9_hook.zig')
s = p.read_text()
if 'V36 RETAIL-SOFT' in s:
raise SystemExit(0)
start = s.index('/// V32 POLISH: hard 3px outline, no feather.')
end = s.index('/// Debug: composite silhouette RT directly.')
lines = [
'/// V36 RETAIL-SOFT: keep the proven V34 JFA, soften only final decode.',
'/// c0 = (screen_width, screen_height, core_radius_px, halo_radius_px).',
'/// Uses the same two texture reads as V34: no 17-tap full-screen morphology.',
'const jfa_decode_src =',
' "ps_3_0\\n" ++',
' "def c1, 0.0, 0.22, 0.82, -0.002\\n" ++',
' "def c2, 1.0, 0.95, 0.58, 0.0\\n" ++',
' "dcl_2d s0\\n" ++',
' "dcl_2d s1\\n" ++',
' "dcl_texcoord0 v0\\n" ++',
' "texld r0, v0, s0\\n" ++',
' "sub r1.xy, v0.xy, r0.xy\\n" ++',
' "mul r1.xy, r1.xy, c0.xy\\n" ++',
' "dp2add r1.z, r1, r1, c1.x\\n" ++',
' "mul r3.x, c0.z, c0.z\\n" ++',
' "mul r3.y, c0.w, c0.w\\n" ++',
' "sub r4.x, r1.z, r3.y\\n" ++',
' "cmp r6.w, r4.x, c1.x, c1.y\\n" ++',
' "sub r4.x, r1.z, r3.x\\n" ++',
' "cmp r6.w, r4.x, r6.w, c1.z\\n" ++',
' "texld r5, v0, s1\\n" ++',
' "add r5.x, r5.a, c1.w\\n" ++',
' "cmp r6.w, r5.x, c1.x, r6.w\\n" ++',
' "mov r6.xyz, c2.xyz\\n" ++',
' "mov oC0, r6\\n";',
'',
'',
]
s = s[:start] + '\n'.join(lines) + s[end:]
old = ' // V34: hard 3px edge unchanged; JFA seed precision is now full-float.\n c0 = [4]f32{ fw, fh, 3.0, 0.0 };'
new_cpu = ' // V36: thin 1.5px core plus a subtle 3px halo.\n c0 = [4]f32{ fw, fh, 1.5, 3.0 };'
if old not in s:
raise SystemExit('V34 CPU decode constants not found')
p.write_text(s.replace(old, new_cpu))
PY
- name: Commit source
shell: bash
run: |
set -euo pipefail
if git diff --quiet; then exit 0; fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add src/outline/d3d9_hook.zig
git commit -m "V36: soft Retail-style JFA decode"
git push origin HEAD:test/v36-retail-jfa-soft
+146
View File
@@ -0,0 +1,146 @@
name: Test safe standalone core V4
on:
push:
branches:
- test/safe-core-v4
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
with:
version: 0.16.0
- name: Build safe standalone variants
shell: bash
run: |
set -euo pipefail
zig build all-variants -Doptimize=ReleaseSmall \
-Dweirdperformance=false \
-Doutline=true
- name: Verify minimal core layout
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
root = Path("zig-out/variants")
addr = {
"lua_protection": 0x42A320,
"register_commands": 0x490250,
"glue_commands": 0x46ABB0,
"engine_init": 0x46A400,
"logout": 0x491180,
"shutdown": 0x490BD0,
"file_fallback": 0x648620,
"open_file": 0x6477C0,
"file_size": 0x6487F0,
"read_file": 0x648460,
"cleanup_file": 0x648730,
"model_load": 0x71D4E0,
"check_file": 0x654DD0,
}
def has(data, value):
return value.to_bytes(4, "little") in data
outline = (root / "outline.dll").read_bytes()
custom = (root / "customassets.dll").read_bytes()
transmog = (root / "transmogfix.dll").read_bytes()
# Outline: only its Lua registration + engine init core hooks are allowed.
required_outline = ("register_commands", "engine_init")
forbidden_outline = tuple(k for k in addr if k not in required_outline)
for k in required_outline:
if not has(outline, addr[k]):
raise SystemExit(f"outline.dll missing required {k}")
bad = [k for k in forbidden_outline if has(outline, addr[k])]
if bad:
raise SystemExit(f"outline.dll still contains unwanted core hooks: {bad}")
print("outline.dll: minimal core verified")
# CustomAssets: only CheckFileExistence is allowed from the generic core.
if not has(custom, addr["check_file"]):
raise SystemExit("customassets.dll missing CheckFileExistence")
bad = [k for k,v in addr.items() if k != "check_file" and has(custom, v)]
if bad:
raise SystemExit(f"customassets.dll still contains unwanted core hooks: {bad}")
print("customassets.dll: minimal core verified")
# TransmogFix: none of the generic core hook target addresses should remain.
bad = [k for k,v in addr.items() if has(transmog, v)]
if bad:
raise SystemExit(f"transmogfix.dll still contains unwanted core hooks: {bad}")
print("transmogfix.dll: minimal core verified")
# Compatibility repair must exist in customassets and outline.
for name, data in (("outline.dll", outline), ("customassets.dll", custom)):
for gate in (0x654B5C, 0x654B6A):
if not has(data, gate):
raise SystemExit(f"{name} missing MPQ gate restore address 0x{gate:X}")
for seq in (bytes([0x74,0x25]), bytes([0x75,0x17])):
if seq not in data:
raise SystemExit(f"{name} missing original MPQ branch bytes {seq.hex()}")
print("legacy MPQ gate repair verified")
PY
- name: Stage package
shell: bash
run: |
set -euo pipefail
mkdir -p package/DLL
mkdir -p package/Interface/AddOns/WeirdUtils_Outline
cp zig-out/variants/outline.dll package/DLL/
cp zig-out/variants/customassets.dll package/DLL/
cp zig-out/variants/transmogfix.dll package/DLL/
cp src/outline/addon/Outline.lua package/Interface/AddOns/WeirdUtils_Outline/
cp src/outline/addon/Bindings.xml package/Interface/AddOns/WeirdUtils_Outline/
cp src/outline/addon/WeirdUtils_Outline.toc package/Interface/AddOns/WeirdUtils_Outline/
cat > package/README_TEST.txt <<'EOF'
WeirdUtils Safe Standalone Core Test V4
This build fixes the second crash path observed in WoW's Lua engine.
outline.dll
- Keeps only the Player_LoadScriptFunctions hook required to register OutlineCommand.
- Keeps only the GameEngine_MainInitialize hook required to initialise the renderer.
- Does not register the shared WeirdUtils version table.
- Does not install generic MPQ/file hooks.
- Uses the external WeirdUtils_Outline addon included here.
customassets.dll
- Keeps only CheckFileExistence from the shared file core.
- No shared Lua registration hooks.
- No shared engine/logout/shutdown hooks.
- Repairs legacy File_FindInArchive NOPs if present.
transmogfix.dll
- Installs only TransmogFix's own module hooks.
- No shared Lua/file/engine/logout/shutdown hooks.
weirdperformance.dll
- Not built here.
- The final ZIP will use the user's original binary unchanged.
EOF
sha256sum package/DLL/*.dll > package/SHA256SUMS.txt
- uses: actions/upload-artifact@v4
with:
name: safe-core-v4
path: package/
if-no-files-found: error
+161
View File
@@ -0,0 +1,161 @@
name: Test safe standalone core V5
on:
push:
branches:
- test/safe-core-v5
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
with:
version: 0.16.0
- name: Build safe standalone variants
shell: bash
run: |
set -euo pipefail
zig build all-variants -Doptimize=ReleaseSmall \
-Dweirdperformance=false \
-Doutline=true
- name: Verify minimal core layout
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
root = Path("zig-out/variants")
addr = {
"lua_protection": 0x42A320,
"register_commands": 0x490250,
"glue_commands": 0x46ABB0,
"engine_init": 0x46A400,
"logout": 0x491180,
"shutdown": 0x490BD0,
"file_fallback": 0x648620,
"open_file": 0x6477C0,
"file_size": 0x6487F0,
"read_file": 0x648460,
"cleanup_file": 0x648730,
"model_load": 0x71D4E0,
"check_file": 0x654DD0,
}
def has(data, value):
return value.to_bytes(4, "little") in data
outline = (root / "outline.dll").read_bytes()
custom = (root / "customassets.dll").read_bytes()
transmog = (root / "transmogfix.dll").read_bytes()
# Outline: only its Lua registration + engine init core hooks are allowed.
required_outline = ("register_commands", "engine_init")
forbidden_outline = tuple(k for k in addr if k not in required_outline)
for k in required_outline:
if not has(outline, addr[k]):
raise SystemExit(f"outline.dll missing required {k}")
bad = [k for k in forbidden_outline if has(outline, addr[k])]
if bad:
raise SystemExit(f"outline.dll still contains unwanted core hooks: {bad}")
print("outline.dll: minimal core verified")
# CustomAssets: only CheckFileExistence is allowed from the generic core.
if not has(custom, addr["check_file"]):
raise SystemExit("customassets.dll missing CheckFileExistence")
bad = [k for k,v in addr.items() if k != "check_file" and has(custom, v)]
if bad:
raise SystemExit(f"customassets.dll still contains unwanted core hooks: {bad}")
print("customassets.dll: minimal core verified")
# TransmogFix: none of the generic core hook target addresses should remain.
bad = [k for k,v in addr.items() if has(transmog, v)]
if bad:
raise SystemExit(f"transmogfix.dll still contains unwanted core hooks: {bad}")
print("transmogfix.dll: minimal core verified")
# Compatibility repair must exist in customassets and outline.
for name, data in (("outline.dll", outline), ("customassets.dll", custom)):
for gate in (0x654B5C, 0x654B6A):
if not has(data, gate):
raise SystemExit(f"{name} missing MPQ gate restore address 0x{gate:X}")
for seq in (bytes([0x74,0x25]), bytes([0x75,0x17])):
if seq not in data:
raise SystemExit(f"{name} missing original MPQ branch bytes {seq.hex()}")
print("legacy MPQ gate repair verified")
PY
- name: Verify Outline Lua wrapper usage
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
src = Path("src/outline/outline.zig").read_text()
if 'hook.call(fn (usize)' in src or '0x6F39F0' in src:
raise SystemExit("Outline still contains manual Lua API hook.call usage")
for required in ("lua.gettop(L)", "lua.isstring(L, 1)", "lua.tostring(L, 1)", "lua.pushboolean(L"):
if required not in src:
raise SystemExit(f"Missing expected Lua wrapper usage: {required}")
print("outline.dll source: direct lua.zig wrappers verified")
PY
- name: Stage package
shell: bash
run: |
set -euo pipefail
mkdir -p package/DLL
mkdir -p package/Interface/AddOns/WeirdUtils_Outline
cp zig-out/variants/outline.dll package/DLL/
cp zig-out/variants/customassets.dll package/DLL/
cp zig-out/variants/transmogfix.dll package/DLL/
cp src/outline/addon/Outline.lua package/Interface/AddOns/WeirdUtils_Outline/
cp src/outline/addon/Bindings.xml package/Interface/AddOns/WeirdUtils_Outline/
cp src/outline/addon/WeirdUtils_Outline.toc package/Interface/AddOns/WeirdUtils_Outline/
cat > package/README_TEST.txt <<'EOF'
WeirdUtils Safe Standalone Core Test V5
This build also fixes OutlineCommand's Lua API calls after a crash at lua_pushboolean.
outline.dll
- Keeps only the Player_LoadScriptFunctions hook required to register OutlineCommand.
- Keeps only the GameEngine_MainInitialize hook required to initialise the renderer.
- Does not register the shared WeirdUtils version table.
- Does not install generic MPQ/file hooks.
- Uses the external WeirdUtils_Outline addon included here.
customassets.dll
- Keeps only CheckFileExistence from the shared file core.
- No shared Lua registration hooks.
- No shared engine/logout/shutdown hooks.
- Repairs legacy File_FindInArchive NOPs if present.
transmogfix.dll
- Installs only TransmogFix's own module hooks.
- No shared Lua/file/engine/logout/shutdown hooks.
weirdperformance.dll
- Not built here.
- The final ZIP will use the user's original binary unchanged.
EOF
sha256sum package/DLL/*.dll > package/SHA256SUMS.txt
- uses: actions/upload-artifact@v4
with:
name: safe-core-v5
path: package/
if-no-files-found: error
+167
View File
@@ -0,0 +1,167 @@
name: Test safe standalone core V7
on:
push:
branches:
- test/safe-core-v7
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
with:
version: 0.16.0
- name: Build safe standalone variants
shell: bash
run: |
set -euo pipefail
zig build all-variants -Doptimize=ReleaseSmall \
-Dweirdperformance=false \
-Doutline=true
- name: Verify minimal core layout
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
root = Path("zig-out/variants")
addr = {
"lua_protection": 0x42A320,
"register_commands": 0x490250,
"glue_commands": 0x46ABB0,
"engine_init": 0x46A400,
"logout": 0x491180,
"shutdown": 0x490BD0,
"file_fallback": 0x648620,
"open_file": 0x6477C0,
"file_size": 0x6487F0,
"read_file": 0x648460,
"cleanup_file": 0x648730,
"model_load": 0x71D4E0,
"check_file": 0x654DD0,
}
def has(data, value):
return value.to_bytes(4, "little") in data
outline = (root / "outline.dll").read_bytes()
custom = (root / "customassets.dll").read_bytes()
transmog = (root / "transmogfix.dll").read_bytes()
# Outline: only its Lua registration + engine init core hooks are allowed.
required_outline = ("register_commands", "engine_init")
forbidden_outline = tuple(k for k in addr if k not in required_outline)
for k in required_outline:
if not has(outline, addr[k]):
raise SystemExit(f"outline.dll missing required {k}")
bad = [k for k in forbidden_outline if has(outline, addr[k])]
if bad:
raise SystemExit(f"outline.dll still contains unwanted core hooks: {bad}")
print("outline.dll: minimal core verified")
# CustomAssets: only CheckFileExistence is allowed from the generic core.
if not has(custom, addr["check_file"]):
raise SystemExit("customassets.dll missing CheckFileExistence")
bad = [k for k,v in addr.items() if k != "check_file" and has(custom, v)]
if bad:
raise SystemExit(f"customassets.dll still contains unwanted core hooks: {bad}")
print("customassets.dll: minimal core verified")
# TransmogFix: none of the generic core hook target addresses should remain.
bad = [k for k,v in addr.items() if has(transmog, v)]
if bad:
raise SystemExit(f"transmogfix.dll still contains unwanted core hooks: {bad}")
print("transmogfix.dll: minimal core verified")
# Compatibility repair must exist in customassets and outline.
for name, data in (("outline.dll", outline), ("customassets.dll", custom)):
for gate in (0x654B5C, 0x654B6A):
if not has(data, gate):
raise SystemExit(f"{name} missing MPQ gate restore address 0x{gate:X}")
for seq in (bytes([0x74,0x25]), bytes([0x75,0x17])):
if seq not in data:
raise SystemExit(f"{name} missing original MPQ branch bytes {seq.hex()}")
print("legacy MPQ gate repair verified")
PY
- name: Verify Outline native Lua asm wrappers
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
src = Path("src/outline/outline.zig").read_text()
for required in (
'luaGetTopNative',
'luaIsStringNative',
'luaToStringNative',
'luaPushBooleanNative',
'"{ecx}"',
'"{edx}"',
'0x6F39F0',
):
if required not in src:
raise SystemExit(f"Missing native register wrapper piece: {required}")
print("outline.dll source: native x86 register wrappers present")
PY
- name: Stage package
shell: bash
run: |
set -euo pipefail
mkdir -p package/DLL
mkdir -p package/Interface/AddOns/WeirdUtils_Outline
cp zig-out/variants/outline.dll package/DLL/
cp zig-out/variants/customassets.dll package/DLL/
cp zig-out/variants/transmogfix.dll package/DLL/
cp src/outline/addon/Outline.lua package/Interface/AddOns/WeirdUtils_Outline/
cp src/outline/addon/Bindings.xml package/Interface/AddOns/WeirdUtils_Outline/
cp src/outline/addon/WeirdUtils_Outline.toc package/Interface/AddOns/WeirdUtils_Outline/
cat > package/README_TEST.txt <<'EOF'
WeirdUtils Safe Standalone Core Test V7
This build forces OutlineCommand's WoW Lua calls through x86 register assembly wrappers.
outline.dll
- Keeps only the Player_LoadScriptFunctions hook required to register OutlineCommand.
- Keeps only the GameEngine_MainInitialize hook required to initialise the renderer.
- Does not register the shared WeirdUtils version table.
- Does not install generic MPQ/file hooks.
- Uses the external WeirdUtils_Outline addon included here.
customassets.dll
- Keeps only CheckFileExistence from the shared file core.
- No shared Lua registration hooks.
- No shared engine/logout/shutdown hooks.
- Repairs legacy File_FindInArchive NOPs if present.
transmogfix.dll
- Installs only TransmogFix's own module hooks.
- No shared Lua/file/engine/logout/shutdown hooks.
weirdperformance.dll
- Not built here.
- The final ZIP will use the user's original binary unchanged.
EOF
sha256sum package/DLL/*.dll > package/SHA256SUMS.txt
- uses: actions/upload-artifact@v4
with:
name: safe-core-v7
path: package/
if-no-files-found: error
+249
View File
@@ -0,0 +1,249 @@
name: Test safe standalone core V8 Debug
on:
push:
branches:
- test/safe-core-v8-debug
workflow_dispatch:
# V8 debug diagnostics build
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
with:
version: 0.16.0
- name: Build safe standalone variants
shell: bash
run: |
set -euo pipefail
zig build all-variants -Doptimize=ReleaseSmall \
-Dweirdperformance=false \
-Doutline=true
- name: Verify minimal core layout
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
root = Path("zig-out/variants")
addr = {
"lua_protection": 0x42A320,
"register_commands": 0x490250,
"glue_commands": 0x46ABB0,
"engine_init": 0x46A400,
"logout": 0x491180,
"shutdown": 0x490BD0,
"file_fallback": 0x648620,
"open_file": 0x6477C0,
"file_size": 0x6487F0,
"read_file": 0x648460,
"cleanup_file": 0x648730,
"model_load": 0x71D4E0,
"check_file": 0x654DD0,
}
def has(data, value):
return value.to_bytes(4, "little") in data
outline = (root / "outline.dll").read_bytes()
custom = (root / "customassets.dll").read_bytes()
transmog = (root / "transmogfix.dll").read_bytes()
# Outline: only its Lua registration + engine init core hooks are allowed.
required_outline = ("register_commands", "engine_init")
forbidden_outline = tuple(k for k in addr if k not in required_outline)
for k in required_outline:
if not has(outline, addr[k]):
raise SystemExit(f"outline.dll missing required {k}")
bad = [k for k in forbidden_outline if has(outline, addr[k])]
if bad:
raise SystemExit(f"outline.dll still contains unwanted core hooks: {bad}")
print("outline.dll: minimal core verified")
# CustomAssets: only CheckFileExistence is allowed from the generic core.
if not has(custom, addr["check_file"]):
raise SystemExit("customassets.dll missing CheckFileExistence")
bad = [k for k,v in addr.items() if k != "check_file" and has(custom, v)]
if bad:
raise SystemExit(f"customassets.dll still contains unwanted core hooks: {bad}")
print("customassets.dll: minimal core verified")
# TransmogFix: none of the generic core hook target addresses should remain.
bad = [k for k,v in addr.items() if has(transmog, v)]
if bad:
raise SystemExit(f"transmogfix.dll still contains unwanted core hooks: {bad}")
print("transmogfix.dll: minimal core verified")
# Compatibility repair must exist in customassets and outline.
for name, data in (("outline.dll", outline), ("customassets.dll", custom)):
for gate in (0x654B5C, 0x654B6A):
if not has(data, gate):
raise SystemExit(f"{name} missing MPQ gate restore address 0x{gate:X}")
for seq in (bytes([0x74,0x25]), bytes([0x75,0x17])):
if seq not in data:
raise SystemExit(f"{name} missing original MPQ branch bytes {seq.hex()}")
print("legacy MPQ gate repair verified")
PY
- name: Verify Outline registration + Lua ABI
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
main = Path("src/main.zig").read_text()
outline = Path("src/outline/outline.zig").read_text()
d3d9 = Path("src/outline/d3d9_hook.zig").read_text()
for required in (
'noinline fn registerFunction',
'0x704120',
'"{ecx}" (@intFromPtr(name))',
'"{edx}" (func_addr)',
):
if required not in main:
raise SystemExit(f"Missing explicit FrameScript registration ABI piece: {required}")
for required in (
'luaGetTopNative',
'luaIsStringNative',
'luaToStringNative',
'luaPushBooleanNative',
'"{ecx}"',
'"{edx}"',
'0x6F39F0',
'callconv(.{ .x86_thiscall = .{} })',
'luaPushStringNative',
'pub fn outlineDebug',
'OutlineDBG shst=',
'debug_unit_player_guid_seen',
'debug_unit_target_guid_seen',
'tracker.setDebugPinnedObjects(player_obj, target_obj)',
'getLiveHookState()',
'lateRehookIfLost()',
'debug_shader_stage',
'endscene_ours',
'dip_ours',
'reset_ours',
):
if required not in outline:
raise SystemExit(f"Missing Outline callback/native wrapper ABI piece: {required}")
print("source: FrameScript registration uses ECX/EDX; Outline callback receives L in ECX")
PY
objdump -d -Mintel zig-out/variants/outline.dll > /tmp/outline.disasm
python3 - <<'PY'
import re
from pathlib import Path
lines = Path("/tmp/outline.disasm").read_text(errors="replace").splitlines()
# registerFunction is explicit inline asm and source-checked above.
# Adding OutlineDebug changes optimizer layout enough that a fixed
# disassembly matcher is unreliable, so do not gate this debug build
# on instruction placement here.
# OutlineCommand must capture the Lua state from ECX. Search function
# prologues for the characteristic saved-ESI form and reject the old
# [ebp+8] capture used by the cdecl experiment.
good_callback = False
for i, line in enumerate(lines):
if re.search(r"push\s+ebp\b", line, re.I):
block = "\n".join(lines[i:i + 12])
if re.search(r"mov\s+esi,ecx\b", block, re.I):
good_callback = True
break
if not good_callback:
raise SystemExit("BAD ABI: no callback body captures Lua state from ECX")
print("outline.dll machine code: registration and callback ABIs verified")
PY
- name: Verify direct player and target GUID path
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
wow = Path("src/wow.zig").read_text()
offsets = Path("src/offsets.zig").read_text()
for required in (
'const guid = getPlayerGUID();',
'return readGUID(o.LOCKED_TARGET_GUID);',
):
if required not in wow:
raise SystemExit(f"Missing direct player/target GUID path: {required}")
for required in (
'FN_GET_PLAYER_GUID: usize = 0x00468550',
'LOCKED_TARGET_GUID: usize = 0x00B4E2D8',
'FN_GET_OBJECT_BY_GUID: usize = 0x464870',
):
if required not in offsets:
raise SystemExit(f"Missing verified WoW offset: {required}")
print("source: player GUID, target GUID and GUID->object path verified")
PY
- name: Stage package
shell: bash
run: |
set -euo pipefail
mkdir -p package/DLL
mkdir -p package/Interface/AddOns/WeirdUtils_Outline
cp zig-out/variants/outline.dll package/DLL/
cp zig-out/variants/customassets.dll package/DLL/
cp zig-out/variants/transmogfix.dll package/DLL/
cp src/outline/addon/Outline.lua package/Interface/AddOns/WeirdUtils_Outline/
cp src/outline/addon/Bindings.xml package/Interface/AddOns/WeirdUtils_Outline/
cp src/outline/addon/WeirdUtils_Outline.toc package/Interface/AddOns/WeirdUtils_Outline/
cat > package/README_TEST.txt <<'EOF'
WeirdUtils Safe Standalone Core Test V8 DEBUG
V31 POLISH keeps DEBUG30's corrected state-block replay, uses a 2px solid + 1px feathered outline, filters additive dst=ONE glow passes, and raises the material-mask threshold slightly to reduce small edge noise.
outline.dll
- Adds OutlineDebug() sticky in-game diagnostics for the render pipeline.
- Keeps only the Player_LoadScriptFunctions hook required to register OutlineCommand/OutlineDebug.
- Keeps only the GameEngine_MainInitialize hook required to initialise the renderer.
- Does not register the shared WeirdUtils version table.
- Does not install generic MPQ/file hooks.
- Uses the external WeirdUtils_Outline addon included here.
customassets.dll
- Keeps only CheckFileExistence from the shared file core.
- No shared Lua registration hooks.
- No shared engine/logout/shutdown hooks.
- Repairs legacy File_FindInArchive NOPs if present.
transmogfix.dll
- Installs only TransmogFix's own module hooks.
- No shared Lua/file/engine/logout/shutdown hooks.
weirdperformance.dll
- Not built here.
- The final ZIP will use the user's original binary unchanged.
EOF
sha256sum package/DLL/*.dll > package/SHA256SUMS.txt
- uses: actions/upload-artifact@v4
with:
name: safe-core-v8-debug
path: package/
if-no-files-found: error
@@ -0,0 +1,55 @@
name: V36 ExitFix Baseline
on:
push:
branches:
- test/v36-exitfix-baseline
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
with:
version: '0.16.0'
- name: Build
shell: bash
run: |
set -euo pipefail
zig build all-variants -Doptimize=ReleaseSmall \
-Dweirdperformance=false \
-Doutline=true \
-Dcustomassets=true \
-Dtransmogfix=true
- name: Package
shell: bash
run: |
set -euo pipefail
rm -rf package dist
mkdir -p package/DLL package/Interface/AddOns/WeirdUtils_Outline dist
cp zig-out/variants/outline.dll package/DLL/outline.dll
cp src/outline/addon/Bindings.xml package/Interface/AddOns/WeirdUtils_Outline/
cp src/outline/addon/Outline.lua package/Interface/AddOns/WeirdUtils_Outline/
cp src/outline/addon/WeirdUtils_Outline.toc package/Interface/AddOns/WeirdUtils_Outline/
printf '%s\n' \
'V36 runtime + process-exit safety only.' \
'Base runtime commit: 4bbee94388f98843b738e275fd9482576be80654.' \
'Compared with V34, only src/outline/d3d9_hook.zig changes runtime behavior.' \
'main branch is untouched.' \
'WeirdPerformance is disabled in this standalone build and not modified.' \
> package/README_TEST.txt
(cd package && sha256sum DLL/outline.dll > SHA256SUMS.txt && zip -r ../dist/WeirdUtils_V36_EXITFIX_BASELINE.zip DLL Interface README_TEST.txt SHA256SUMS.txt)
- uses: actions/upload-artifact@v4
with:
name: WeirdUtils-V36-EXITFIX-BASELINE
path: dist/WeirdUtils_V36_EXITFIX_BASELINE.zip
if-no-files-found: error
-115
View File
@@ -1,115 +0,0 @@
# WeirdUtils asset provenance
Audit date: 2026-08-31
No visual or font asset is intended to be modified during this documentation
pass.
The root WeirdUtils public-domain dedication does **not** automatically apply
to bundled third-party or game-facing assets.
## MinimapIcons assets
Path:
- `src/minimapicons/assets/`
Git tree SHA-1:
- `4d3848aa2ba1e32b8cfb5a8d4d67eaf4ead09e31`
The tracking directory contains 21 BLP resources under a game-style path:
- `Interface/Minimap/Tracking/`
Examples include `Banker.blp`, `FlightMaster.blp`,
`QuestAvailable.blp`, `Repair.blp`, and `ObjectIcons.blp`.
Immediate repository provenance is known, but this audit does not establish
the original creator or redistribution license of every underlying image.
## WorldMarkers assets
Path:
- `src/worldmarkers/assets/`
Known subtrees:
- `Spells/`:
`5a3628820673332f3fdd481e80f3f6e81efed9e8`
- `World/`:
`9783e9c15f31a527c9d6f81e45b6c7ba251f27e3`
Files include game-facing names such as raid-target textures, spell/VFX
textures, and resources under:
- `World/Expansion01/Doodads/Zulaman/Doors/`
These paths strongly indicate compatibility with game resource naming, but the
documentation does not assert a specific extraction history without stronger
evidence.
No ownership or public-domain claim is made over underlying Blizzard or other
third-party artwork.
## WeirdDPSMate fonts
Path:
- `src/dpslog/WeirdDPSMate/fonts/`
Git tree SHA-1:
- `48374570b2c9659e1d781d4014c17fecd1b0f7f1`
The directory contains 13 TTF/OTF font files.
Their individual font licenses were not independently established during this
audit. They are therefore not treated as public-domain material merely because
they are bundled in WeirdUtils or inside the GPL-licensed DPSMate subtree.
## WeirdDPSMate images
Path:
- `src/dpslog/WeirdDPSMate/images/`
Git tree SHA-1:
- `bd649c1cffffc9b7d9b92fc001748531cb4ffa92`
The directory includes UI images, class icons, status-bar textures, and other
TGA resources.
The immediate bundled provenance is documented, while underlying visual rights
remain separate from the WeirdUtils root license.
## GraphLib textures
WeirdDPSMate also bundles GraphLib texture resources under:
- `src/dpslog/WeirdDPSMate/libs/GraphLib/GraphTextures/`
These include pie/line/triangle and related TGA resources. Their presence is
not used to infer a new license from WeirdUtils.
## Rights boundary
World of Warcraft, Warcraft, Blizzard Entertainment, and associated names,
marks, artwork, interface resources, client data, and game assets remain the
property of their respective rights holders.
An exact Git tree or blob hash establishes file identity/provenance at a point
in history. It does not by itself establish ownership or a right to relicense
the underlying work.
## Maintenance
When assets are added, removed, or replaced:
1. record source and creation history where known;
2. record hashes/tree identity where practical;
3. preserve font/image license notices when available;
4. do not place unresolved third-party/game-facing assets under the root
public-domain dedication by implication.
-76
View File
@@ -1,76 +0,0 @@
# WeirdUtils binary and release provenance
Audit date: 2026-08-31
## Source repository
At the audited pre-documentation head
`947056f1c22c4816f85038dfb78abe61c1e4133b`, the source tree contains no
committed release `.dll` or `.exe` files.
Release DLLs are packaging/build outputs rather than checked-in binaries.
## GitHub Actions release workflow
`.github/workflows/release.yml` builds the standard public module DLLs with:
- Zig 0.16.0
- target/build configuration defined by the repository source
- `zig build all-variants -Doptimize=ReleaseSmall`
The workflow collects the generated DLLs and creates `SHA256SUMS.txt` before
publishing release assets.
## WeirdPerformance release exception
The GitHub release workflow intentionally excludes the normal
`weirdperformance` variant from the standard source build.
Instead, it downloads:
- `weirdperformance.dll`
- from a Codeberg release under `Dusk92/WeirdUtils`
- tag `0.7.3` as configured by
`CUSTOM_WEIRDPERFORMANCE_TAG`
The workflow validates that the downloaded file has a plausible Windows PE
header and then includes it in the generated SHA-256 manifest.
Therefore a GitHub WeirdUtils release is not composed exclusively of DLLs
compiled in that same workflow: `weirdperformance.dll` is a separately
sourced prebuilt release artifact.
This distinction should remain documented whenever the release workflow
changes.
## Build-time zhook dependency
`build.zig.zon` pins zhook to:
- source:
`https://codeberg.org/marcelinevq/zhook/archive/f1b252ed61ad839f00310c386761d068f293ad0f.tar.gz`
- Zig package hash:
`zhook-0.1.0-pFkSYC6FAACAnkqu0k_DJBWdL0gJjrM22IfXeQPJAMov`
The source is fetched during a build and is not vendored into this GitHub
repository.
## Vendored libdeflate
`src/weirdperformance/libdeflate/` contains libdeflate source code (version
1.25 according to the bundled header). The build compiles the required C files
into the WeirdPerformance module.
libdeflate is MIT-licensed and is documented separately in
`THIRD_PARTY_NOTICES.md` and `LICENSES/libdeflate-MIT.txt`.
## Release maintenance rule
For each release:
1. retain the exact source ref used for compiled DLLs;
2. retain the source/tag of any prebuilt imported DLL;
3. publish or retain SHA-256 checksums;
4. keep third-party license records alongside the source project;
5. do not describe a prebuilt imported artifact as compiled from the current
GitHub commit unless that has actually been verified.
-63
View File
@@ -1,63 +0,0 @@
# WeirdUtils source provenance
Audit date: 2026-08-31
## Imported source history
The repository is not marked by GitHub as a fork, but its imported Git history
contains extensive source development authored by MarcelineVQ.
The repository documentation points to the historical/source project at:
- https://codeberg.org/MarcelineVQ/WeirdUtils
## Dusk-92 GitHub maintenance boundary
A comparison was made from the last inspected MarcelineVQ-authored baseline:
- base: `41191ce9a5b50cf38fc48f138b8339ed17f9cae8`
- pre-audit GitHub head:
`947056f1c22c4816f85038dfb78abe61c1e4133b`
GitHub reports that the Dusk-92 head is four commits ahead of that baseline.
Only two paths differ in that comparison:
- `.github/workflows/release.yml`
- `src/main.zig`
The relevant later commits are:
- `82dc02e2e78ca43496286c14a732a562d2448570`
— Add automated DLL release workflow
- `f4cbacb7f9e91f99e7409b3aa48a61fe6d96d978`
— Fix DllMain return type for Zig 0.16
- `c4d133887a9730e346d0654031cfeefb7a77e065`
— Run release build CI on main and support source refs
- `947056f1c22c4816f85038dfb78abe61c1e4133b`
— Test full release packaging on main
This makes the authorship boundary unusually clear: the GitHub maintenance
layer should not be presented as authorship of the entire imported WeirdUtils
source tree.
## Third-party source boundaries
Known third-party or reference-derived areas include:
- `src/dpslog/WeirdDPSMate/` — DPSMate fork, GPL-3.0
- `src/dpslog/WSBT/` — Mik/Athene material, license unresolved in this audit
- `src/weirdperformance/libdeflate/` — libdeflate 1.25, MIT
- `src/weirdperformance/timer_fix.zig` — ported from VanillaFixes, MIT
- `src/ssemaths/math_sse.zig` — UnitXP_SP3 and libSiliconPatch references
- external `zhook` build dependency — pinned in `build.zig.zon`
See `THIRD_PARTY_NOTICES.md`.
## Documentation-pass boundary
No Zig, C, Lua, XML, build workflow, BLP, TGA, font, or other runtime asset is
intended to be modified by the 2026-08-31 licensing/provenance pass.
The final compare against the pre-audit head is the authoritative check for
that statement.
+4 -54
View File
@@ -25,57 +25,7 @@ For more information, please refer to <https://unlicense.org/>
---
SCOPE AND THIRD-PARTY EXCEPTIONS
The public-domain dedication above applies only to material for which the
applicable WeirdUtils author or authors hold the rights necessary to make that
dedication.
It does not override or replace the copyright, license, trademark, or other
rights applicable to third-party code, external dependencies, reference-derived
material, fonts, artwork, game-facing resources, or other assets included in or
referenced by this repository.
Known exclusions and separate terms include, without limitation:
1. src/dpslog/WeirdDPSMate/
This is a fork of DPSMate by Shino <Synced> and is licensed under the GNU
General Public License v3. See src/dpslog/WeirdDPSMate/LICENSE.
2. src/dpslog/WSBT/
This contains historical Mik's Scrolling Battle Text / Combat Event Helper
material credited to Mik and Athene. No standalone license for this bundled
subtree was independently established during the 2026-08-31 audit. The
public-domain dedication above does not claim to relicense it.
3. src/weirdperformance/libdeflate/
This contains vendored libdeflate source under the MIT License. See
LICENSES/libdeflate-MIT.txt.
4. VanillaFixes-derived material
src/weirdperformance/timer_fix.zig explicitly identifies itself as ported
from VanillaFixes, which is MIT-licensed. See
LICENSES/VanillaFixes-MIT.txt.
5. External zhook dependency
zhook is fetched at build time from its upstream Codeberg repository. It is
not relicensed by this file.
6. UnitXP_SP3 / libSiliconPatch references
Source comments identify these projects as behavioral, formula, or symbol
references for some SSE work. This file does not make a public-domain claim
over material whose applicable rights are held by those projects or other
third parties.
7. Fonts, BLP/TGA artwork, and game-facing assets
Bundled fonts and visual resources, including assets under minimapicons,
worldmarkers, and WeirdDPSMate, remain subject to their respective rights.
They are not placed in the public domain by this repository merely because
they are included in the source tree.
See THIRD_PARTY_NOTICES.md, PROJECT_IDENTITY.md, Docs/, and LICENSES/ for the
current provenance and scope record.
World of Warcraft, Warcraft, Blizzard Entertainment, and associated names,
marks, artwork, client data, and game assets remain the property of their
respective rights holders.
EXCEPTION: src/dpslog/WeirdDPSMate/ is a fork of DPSMate by Shino
<Synced> and is licensed under the GNU General Public License v3.
See src/dpslog/WeirdDPSMate/LICENSE. The dedication above does not
apply to that directory.
-36
View File
@@ -1,36 +0,0 @@
# Third-party license records
The root `LICENSE` contains the WeirdUtils public-domain dedication, but its
scope is limited to material for which the applicable WeirdUtils authors have
the rights to make that dedication.
Third-party material keeps its own terms.
## Preserved records
- `libdeflate-MIT.txt`
- applies to vendored `src/weirdperformance/libdeflate/`
- upstream: https://github.com/ebiggers/libdeflate
- `VanillaFixes-MIT.txt`
- relevant to the timer implementation explicitly ported from VanillaFixes
- upstream: https://github.com/hannesmann/vanillafixes
## Existing in-tree license
- `../src/dpslog/WeirdDPSMate/LICENSE`
- GNU GPL v3
- applies to the WeirdDPSMate / DPSMate code as documented by that subtree
## Unresolved / external
The 2026-08-31 audit did not independently establish a project-wide license
for:
- bundled `src/dpslog/WSBT/` material;
- `brues-code/UnitXP_SP3`, used as a behavior/formula reference;
- the externally fetched Codeberg `zhook` dependency.
No license is invented for those components.
See `../THIRD_PARTY_NOTICES.md` for scope and provenance.
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 Hannes Mann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-22
View File
@@ -1,22 +0,0 @@
Copyright 2016 Eric Biggers
Copyright 2024 Google LLC
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-51
View File
@@ -1,51 +0,0 @@
# WeirdUtils project identity
## Current maintained GitHub repository
The current GitHub repository maintained and packaged by Dusk-92 is:
- https://github.com/Dusk-92/WeirdUtils
The source history in this repository substantially predates the GitHub
packaging work and contains development by MarcelineVQ. The historical/source
project referenced by the repository is:
- https://codeberg.org/MarcelineVQ/WeirdUtils
A mirror, release package, or compatibility target does not imply that its
maintainer authored every component present in the tree.
## Independent community project
WeirdUtils is an independent community project for the World of Warcraft
1.12.1 client.
It is not affiliated with, sponsored by, approved by, or endorsed by Blizzard
Entertainment, Turtle WoW, OctoWoW, SuperWoW, DPSMate, MikScrollingBattleText,
VanillaFixes, UnitXP_SP3, libdeflate, or any other referenced project unless
explicitly stated by that party.
Compatibility and reverse-engineering references do not imply endorsement,
partnership, ownership, or official status.
World of Warcraft, Warcraft, Blizzard Entertainment, and associated names,
marks, artwork, client data, and game assets remain the property of their
respective rights holders.
## Maintainer boundary
The imported Git history identifies MarcelineVQ as the principal author of the
source project prior to the GitHub packaging work.
Dusk-92's GitHub-specific commits after the imported source baseline primarily
cover release automation/packaging and a small Zig 0.16 compatibility change.
See `Docs/SOURCE_PROVENANCE.md` for the exact comparison.
## Support boundary
Dusk-92 is responsible only for the GitHub releases, packaging choices, and
changes published from the maintained GitHub repository.
Third-party authors and compatibility-target projects are not responsible for
this packaging or for downstream modifications.
+7 -37
View File
@@ -1,6 +1,6 @@
# WeirdUtils
> **This project is no longer actively developed.** Project-authored source is published under the public-domain dedication in `LICENSE`, subject to the documented third-party code and asset exceptions. See [Source Code](#source-code) and [Licensing & provenance](#licensing--provenance) for details.
> **This project is no longer actively developed.** The full source is published here, in the public domain (see `LICENSE`), so it can be forked, salvaged, or learned from rather than bit-rotting on a private disk. See [Source Code](#source-code) for details and caveats.
This package provides many pre-built DLLs for enhancing the vanilla 1.12 client WoW gameplay experience, aimed in particular at ease of use and accessibility but also bug fixes.
@@ -261,15 +261,12 @@ Most noticeable in cities, raids, during zone transitions, and in addon-heavy se
## Source Code
This project is no longer actively developed. Project-authored source is published
under the public-domain dedication in `LICENSE`, subject to the documented
third-party code, dependency, font, and asset exceptions.
This project is no longer actively developed. The full source is now published here,
in the public domain (see `LICENSE`), so it can be forked, salvaged, or learned from
rather than bit-rotting on a private disk.
Earlier releases shipped as pre-built DLLs only. Standard public module DLLs are now
compiled from source by the GitHub release workflow. The current workflow treats
`weirdperformance.dll` specially: it imports the configured Dusk92 Codeberg v0.7.3
prebuilt and includes it in the generated SHA-256 manifest. See
`Docs/BINARY_PROVENANCE.md` for the exact release boundary.
Earlier releases shipped as pre-built DLLs only. That is no longer the case - the
binaries on the releases page and the source in this repo are the same project.
Fair warning to anyone building on this: these DLLs hook deeply into the client's
internals - memory layout, function addresses, rendering pipeline, input handling.
@@ -304,37 +301,10 @@ running under Wine/DXVK. Per-module build flags are listed by `zig build --help`
| `docs/`, `src/*/RESEARCH.md` | Reverse-engineering notes for the subsystems being hooked |
`src/dpslog/WeirdDPSMate/` is a fork of DPSMate and stays under GPL-3.0 - see its
own `LICENSE`. Other documented third-party code, dependencies, fonts, visual
assets, and reference-derived material retain their own rights or unresolved status;
the root public-domain dedication does not override them.
own `LICENSE`. Everything else is unlicensed/public domain.
---
## Licensing & provenance
The root `LICENSE` public-domain dedication applies only to material for which the
applicable WeirdUtils authors have the rights to make that dedication.
Important separate boundaries include:
- **WeirdDPSMate / DPSMate** — GPL-3.0.
- **libdeflate 1.25** — vendored under the MIT License.
- **VanillaFixes-derived timer work** — upstream MIT notice preserved.
- **WSBT / Mik material** — bundled historical third-party code; no standalone
license was independently established in this audit.
- **zhook** — external pinned build dependency from Codeberg, not vendored here.
- **BLP/TGA/fonts and game-facing resources** — not placed in the public domain
merely by inclusion in this repository.
For the full record, see:
- [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)
- [PROJECT_IDENTITY.md](PROJECT_IDENTITY.md)
- [Docs/SOURCE_PROVENANCE.md](Docs/SOURCE_PROVENANCE.md)
- [Docs/BINARY_PROVENANCE.md](Docs/BINARY_PROVENANCE.md)
- [Docs/ASSET_PROVENANCE.md](Docs/ASSET_PROVENANCE.md)
- [LICENSES/](LICENSES/)
## Developer Notes
### Runtime Module Control API
-189
View File
@@ -1,189 +0,0 @@
# WeirdUtils third-party notices
Audit date: 2026-08-31
The root `LICENSE` contains a public-domain dedication for WeirdUtils-authored
material. That dedication does **not** override the licenses or rights of
third-party code, assets, fonts, game-derived media, or reference material.
## Source project history
The Git history imported into this repository contains substantial development
by MarcelineVQ and references the source project at:
- https://codeberg.org/MarcelineVQ/WeirdUtils
The later Dusk-92 GitHub commits are documented separately in
`Docs/SOURCE_PROVENANCE.md`.
## libdeflate
Vendored path:
- `src/weirdperformance/libdeflate/`
The bundled headers identify libdeflate version 1.25.
Upstream:
- https://github.com/ebiggers/libdeflate
License:
- MIT
- Copyright 2016 Eric Biggers
- Copyright 2024 Google LLC
A verbatim copy of the upstream license is preserved at:
- `LICENSES/libdeflate-MIT.txt`
The WeirdUtils public-domain dedication does not replace libdeflate's MIT
notice.
## VanillaFixes
The performance timer implementation explicitly identifies itself as:
- `src/weirdperformance/timer_fix.zig`
- "ported from VanillaFixes"
Upstream:
- https://github.com/hannesmann/vanillafixes
License:
- MIT
- Copyright (c) 2022 Hannes Mann
A verbatim copy is preserved at:
- `LICENSES/VanillaFixes-MIT.txt`
## WeirdDPSMate / DPSMate
Bundled path:
- `src/dpslog/WeirdDPSMate/`
The subtree identifies DPSMate as originally by Shino <Synced> - Kronos, with
later contributors including Torio.
License:
- GNU General Public License v3
The authoritative license is already bundled at:
- `src/dpslog/WeirdDPSMate/LICENSE`
The root public-domain dedication does not apply to this subtree.
The subtree also contains fonts, images, GraphLib textures, and bundled
libraries. The presence of the DPSMate GPL file is not used here to make an
unsupported claim about the separate underlying rights of every font or visual
asset. See `Docs/ASSET_PROVENANCE.md`.
## WSBT / Mik's Scrolling Battle Text material
Bundled path:
- `src/dpslog/WSBT/`
The source headers identify:
- Title: Mik's Combat Event Helper / Mik's Scrolling Battle Text
- Author: Mik
- Maintainer: Athene
No standalone license file was identified in this bundled WSBT subtree during
this audit.
Accordingly, the WeirdUtils public-domain dedication does not claim to
relicense this inherited material. Its exact licensing status remains
unresolved unless stronger upstream evidence is later preserved.
## zhook
Build dependency:
- https://codeberg.org/marcelinevq/zhook
- pinned commit: `f1b252ed61ad839f00310c386761d068f293ad0f`
- Zig package hash:
`zhook-0.1.0-pFkSYC6FAACAnkqu0k_DJBWdL0gJjrM22IfXeQPJAMov`
`build.zig.zon` downloads zhook at build time; it is not committed as a
vendored source tree in this repository.
Its license was not independently verified during this GitHub-focused audit.
The WeirdUtils root license therefore makes no licensing claim over zhook.
## UnitXP_SP3 reference material
`src/ssemaths/math_sse.zig` explicitly identifies
`brues-code/UnitXP_SP3/polyfill.cpp` as a reference source for some function
behavior and formulas.
Reference repository:
- https://github.com/brues-code/UnitXP_SP3
No root project-wide license file was identified in UnitXP_SP3 during this
audit. Reference provenance is preserved without asserting that UnitXP_SP3
material is public domain under WeirdUtils' root license.
## libSiliconPatch reference
The SSE research comments also identify libSiliconPatch as a closed-source
symbol/export reference.
No libSiliconPatch binary is identified as a vendored dependency in the
WeirdUtils source tree. Reference to its symbols or behavior does not imply
ownership, affiliation, or a right to relicense that project.
## Game-facing visual assets
WeirdUtils bundles BLP/TGA assets under areas including:
- `src/minimapicons/assets/`
- `src/worldmarkers/assets/`
- `src/dpslog/WeirdDPSMate/images/`
- `src/dpslog/WeirdDPSMate/libs/GraphLib/GraphTextures/`
It also bundles fonts under:
- `src/dpslog/WeirdDPSMate/fonts/`
These materials are excluded from the root public-domain dedication unless a
specific file's rights are independently established.
Some paths and filenames correspond closely to World of Warcraft client
resource naming. This provenance audit does not claim that Dusk-92 or
MarcelineVQ owns the underlying Blizzard or third-party artwork.
See `Docs/ASSET_PROVENANCE.md`.
## SuperWoW and server compatibility
WeirdUtils contains compatibility logic and references for SuperWoW and
community-server environments.
Compatibility does not imply affiliation or endorsement. SuperWoW itself is
not relicensed by WeirdUtils.
## World of Warcraft / Blizzard
World of Warcraft, Warcraft, Blizzard Entertainment, and associated names,
marks, artwork, client data, and game assets remain the property of their
respective rights holders.
## Preservation rule
When third-party material is updated, replaced, or removed:
1. preserve its source and attribution;
2. preserve the applicable license or permission where known;
3. do not expand the root public-domain dedication to material whose rights are
not held by WeirdUtils contributors;
4. keep provenance records even when a component stops being bundled.
+8
View File
@@ -66,6 +66,7 @@ pub fn build(b: *std.Build) void {
const build_options = b.addOptions();
addModuleOptionsFromArray(b, build_options, &module_enabled);
build_options.addOption(bool, "safe_variant_core", false);
// transform_capture: records game-x87 transformMatrix4x4 state to disk for
// offline bench parity. Mutually exclusive with bone_sse64 (capture needs
// the real game output; bone_sse64 replaces it).
@@ -324,6 +325,7 @@ pub fn build(b: *std.Build) void {
if (comptime std.mem.eql(u8, m.name, "weirdperformance")) noperf_enabled[i] = false;
}
addModuleOptionsFromArray(b, noperf_opts, &noperf_enabled);
noperf_opts.addOption(bool, "safe_variant_core", false);
const noperf_lib = b.addLibrary(.{
.name = "weirdutils_noperf",
@@ -357,6 +359,12 @@ pub fn build(b: *std.Build) void {
inline for (module_list) |m| {
opts.addOption(bool, "enable_" ++ m.name, std.mem.eql(u8, m.name, variant_mod.name));
}
const safe_variant_core = comptime (
std.mem.eql(u8, variant_mod.name, "outline") or
std.mem.eql(u8, variant_mod.name, "customassets") or
std.mem.eql(u8, variant_mod.name, "transmogfix")
);
opts.addOption(bool, "safe_variant_core", safe_variant_core);
addFileListOptions(b, opts);
const names: []const []const u8 = comptime blk: {
var n: [module_list.len][]const u8 = undefined;
@@ -0,0 +1 @@
768442df41b7ec363c9827c860f139add2dfa5cd718915c3b55adfe93acb65cf dist/WeirdUtils_safe_core_V8_POLISH32_ORIGINAL_WeirdPerformance.zip
+113 -4
View File
@@ -52,6 +52,27 @@ const luagc_mod = if (build_opts.luagc) @import("luagc/luagc.zig") else struct {
const module_active = @import("module_active.zig");
const WINAPI = std.builtin.CallingConvention.winapi;
const safe_variant_core = @import("build_options").safe_variant_core;
// Standalone compatibility helper.
// Older WeirdUtils cores can replace these two File_FindInArchive branches
// with NOP NOP. Safe standalone variants restore the original bytes only when
// that exact legacy patch is present.
fn restoreLegacyFileFindArchiveGates() void {
const gate1: usize = 0x654B5C;
const gate2: usize = 0x654B6A;
const original1 = [2]u8{ 0x74, 0x25 };
const original2 = [2]u8{ 0x75, 0x17 };
if (hook.readMem(u8, gate1) == 0x90 and hook.readMem(u8, gate1 + 1) == 0x90) {
hook.writeProtected(gate1, &original1);
log.print("compat: restored File_FindInArchive gate 1\n");
}
if (hook.readMem(u8, gate2) == 0x90 and hook.readMem(u8, gate2 + 1) == 0x90) {
hook.writeProtected(gate2, &original2);
log.print("compat: restored File_FindInArchive gate 2\n");
}
}
// =============================================================================
// Lua Protection Bypass
@@ -71,8 +92,23 @@ pub const lua = @import("lua.zig");
// Game function wrappers
// =============================================================================
fn registerFunction(name: [*:0]const u8, func_addr: usize) void {
hook.call(fn ([*:0]const u8, usize) callconv(hook.cc.fastcall) void, 0x704120, .{ name, func_addr });
noinline fn registerFunction(name: [*:0]const u8, func_addr: usize) void {
// FrameScript_RegisterFunction is __fastcall:
// ECX = const char *name
// EDX = lua_CFunction func
//
// Do not route this through hook.call here. With the current Zig/zhook
// combination that path was emitted as two stack pushes, so the engine
// consumed stale ECX/EDX values left by the previous hook in the chain.
// Force the verified WoW 1.12.1 ABI explicitly.
var eax_clobber: usize = undefined;
asm volatile (
\\mov $0x704120, %%eax
\\call *%%eax
: [eax] "={eax}" (eax_clobber),
: [name] "{ecx}" (@intFromPtr(name)),
[func] "{edx}" (func_addr),
);
}
fn allocateGameBuffer(size: u32) ?[*]u8 {
@@ -93,6 +129,8 @@ fn registerLuaFunctions() void {
}
if (build_opts.outline) {
registerFunction("OutlineCommand", @intFromPtr(&outline.outlineCommand));
registerFunction("OutlineSyncTarget", @intFromPtr(&outline.outlineSyncTarget));
registerFunction("OutlineDebug", @intFromPtr(&outline.outlineDebug));
}
if (build_opts.logsessions) {
registerFunction("GetCombatLogPath", @intFromPtr(&logsessions.luaGetCombatLogPath));
@@ -676,6 +714,16 @@ fn registerAllSystemCommandsDetour() callconv(hook.cc.stdcall) void {
registerModuleVersions();
}
// Safe standalone Outline only needs its Lua command to be re-registered
// after login/reload. It deliberately does not touch the shared WeirdUtils
// version table, avoiding nested Lua-table mutations across multiple DLLs.
fn registerOutlineCommandsDetour() callconv(hook.cc.stdcall) void {
register_commands_hook.callOriginal(.{});
if (build_opts.outline) {
registerLuaFunctions();
}
}
/// Hook for Glue_LoadScriptFunctions (0x46ABB0).
/// Fires at the login/glue screen — registers version table so addons can query early.
fn glueLoadScriptFunctionsDetour() callconv(hook.cc.stdcall) void {
@@ -692,6 +740,10 @@ var engine_init_hook: hook.Detour(fn () callconv(hook.cc.stdcall) void) = .{};
fn engineInitDetour() callconv(hook.cc.stdcall) void {
engine_init_hook.callOriginal(.{});
if (safe_variant_core and build_opts.outline) {
restoreLegacyFileFindArchiveGates();
}
if (build_opts.screenshot) {
screenshot.installHook();
}
@@ -811,6 +863,41 @@ fn install() void {
logging.init();
log = logging.Logger.open("weirdutils", .console);
if (safe_variant_core) {
log.print("Installing minimal standalone core\n");
// customassets needs only CheckFileExistence for loose Data files.
// Do not install embedded-file hooks and do not NOP File_FindInArchive.
if (build_opts.customassets) {
_ = cfe_hook.attach(0x654DD0, &checkFileExistenceDetour);
}
// Outline needs one Lua registration hook so /outline survives /reload,
// plus the engine-init hook for its deferred renderer initialization.
if (build_opts.outline) {
_ = register_commands_hook.attach(0x490250, &registerOutlineCommandsDetour);
_ = engine_init_hook.attach(0x46a400, &engineInitDetour);
}
// Install only the actual module hooks.
inline for (modules) |m| {
if (m.install) |inst| inst();
if (m.name) |name| {
if (m.is_active) |active_fn| module_active.register(name, active_fn);
}
}
// The user's original WeirdPerformance loads before these variants.
// Repair only its legacy MPQ gate NOPs; leave WeirdPerformance itself untouched.
if (build_opts.customassets or build_opts.outline) {
restoreLegacyFileFindArchiveGates();
}
// No embedded addon registration in minimal variants.
// Outline's addon is supplied normally under Interface\\AddOns.
return;
}
// Core hooks chain safely across multiple DLLs via zhook's E9-detect path:
// each DLL's trampoline JMPs to the previous DLL's detour, forming a LIFO
// call chain. Callbacks are additive (Lua registration, file serving) or
@@ -840,6 +927,25 @@ fn install() void {
}
fn uninstall() void {
if (safe_variant_core) {
comptime var j = modules.len;
inline while (j > 0) {
j -= 1;
if (modules[j].remove) |rm| rm();
}
if (build_opts.outline) {
engine_init_hook.detach();
register_commands_hook.detach();
}
if (build_opts.customassets) {
cfe_hook.detach();
}
logging.deinit();
return;
}
// Remove modules in reverse order
comptime var i = modules.len;
inline while (i > 0) {
@@ -942,11 +1048,14 @@ comptime {
pub export fn DllMain(
_: ?*anyopaque,
reason: u32,
_: ?*anyopaque,
reserved: ?*anyopaque,
) callconv(WINAPI) std.os.windows.BOOL {
switch (reason) {
1 => install(),
0 => uninstall(),
0 => {
// Process-exit safety only; runtime Outline behavior is unchanged.
if (reserved == null) uninstall();
},
else => {},
}
return @enumFromInt(1);
+2
View File
@@ -166,6 +166,8 @@ pub const FN_UNIT_REACTION: usize = 0x6061E0;
/// __fastcall(), no params, returns EAX(low):EDX(high).
pub const FN_GET_PLAYER_GUID: usize = 0x00468550;
/// Current target GUID (same address used by Nampower).
pub const LOCKED_TARGET_GUID: usize = 0x00B4E2D8;
/// __fastcall(obj_ECX) → bool. GO interactability check.
pub const FN_CALL_SPELL_CAST_HANDLER: usize = 0x5F8800;
+17
View File
@@ -8,11 +8,28 @@ BINDING_HEADER_OUTLINE = "Outline"
local frame = CreateFrame("Frame")
frame:RegisterEvent("ADDON_LOADED")
frame:RegisterEvent("PLAYER_LOGIN")
frame:RegisterEvent("PLAYER_TARGET_CHANGED")
local syncElapsed = 0
frame:SetScript("OnEvent", function()
if event == "PLAYER_LOGIN" then
OutlineSyncTarget()
local on = OutlineCommand()
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00Outline|r v" .. OUTLINE_VERSION .. " loaded (" .. (on and "enabled" or "disabled") .. ")")
elseif event == "PLAYER_TARGET_CHANGED" then
OutlineSyncTarget()
syncElapsed = 0
end
end)
-- AutoFix safety net: republish the current target from the safe Lua/main
-- thread at low frequency so a missed/early target event cannot disable Outline.
frame:SetScript("OnUpdate", function()
syncElapsed = syncElapsed + arg1
if syncElapsed >= 0.10 then
syncElapsed = 0
OutlineSyncTarget()
end
end)
+494 -139
View File
@@ -57,14 +57,108 @@ var orig_reset: usize = 0;
var d3d9_vtable: ?[*]usize = null;
var hooks_installed: bool = false;
// Sticky diagnostics for the in-game OutlineDebug() command.
pub var debug_endscene_seen: bool = false;
pub var debug_dip_seen: bool = false;
pub var debug_outline_dip_seen: bool = false;
pub var debug_cached_draw_seen: bool = false;
pub var debug_translucent_skipped_seen: bool = false;
pub var debug_state_block_seen: bool = false;
pub var debug_outer_state_restore_seen: bool = false;
pub var debug_additive_skipped_seen: bool = false;
pub var debug_shaders_ready_seen: bool = false;
pub var debug_resources_ready_seen: bool = false;
pub var debug_pipeline_entered_seen: bool = false;
pub var debug_pipeline_ready_seen: bool = false;
pub var debug_shader_stage: u32 = 0;
pub var debug_resource_stage: u32 = 0;
pub var debug_shader_assemble_hr: i32 = 0;
pub var debug_shader_create_hr: i32 = 0;
pub var debug_texture_create_hr: i32 = 0;
pub var debug_shader_error_text: [160]u8 = [_]u8{0} ** 160;
pub fn hooksInstalled() bool {
return hooks_installed;
}
pub const LiveHookState = struct {
vtable_found: bool = false,
same_vtable: bool = false,
endscene_ours: bool = false,
dip_ours: bool = false,
reset_ours: bool = false,
endscene_ptr: usize = 0,
dip_ptr: usize = 0,
reset_ptr: usize = 0,
};
/// Read the game's current D3D9 vtable and verify whether our entries are
/// still installed. This is intentionally queried on demand from OutlineDebug.
pub fn getLiveHookState() LiveHookState {
const cur = getD3D9VTable() orelse return .{};
var out: LiveHookState = .{ .vtable_found = true };
out.same_vtable = if (d3d9_vtable) |saved| @intFromPtr(saved) == @intFromPtr(cur) else false;
out.endscene_ptr = cur[types.VT.EndScene];
out.dip_ptr = cur[types.VT.DrawIndexedPrimitive];
out.reset_ptr = cur[types.VT.Reset];
out.endscene_ours = out.endscene_ptr == @intFromPtr(&hkEndScene);
out.dip_ours = out.dip_ptr == @intFromPtr(&hkDIP);
out.reset_ours = out.reset_ptr == @intFromPtr(&hkReset);
return out;
}
pub var debug_late_rehook_attempted: bool = false;
pub var debug_late_rehook_succeeded: bool = false;
/// DEBUG15: re-apply the D3D9 hooks on demand after the client is fully loaded.
/// If the current entries are no longer ours, chain whatever is there now as
/// the new originals, then patch the three entries again.
pub fn lateRehookIfLost() bool {
debug_late_rehook_attempted = true;
const cur = getD3D9VTable() orelse return false;
d3d9_vtable = cur;
const ours_end = @intFromPtr(&hkEndScene);
const ours_dip = @intFromPtr(&hkDIP);
const ours_reset = @intFromPtr(&hkReset);
if (cur[types.VT.EndScene] != ours_end) {
if (!patchVtableEntry(cur, types.VT.EndScene, ours_end, &orig_endscene)) return false;
}
if (cur[types.VT.DrawIndexedPrimitive] != ours_dip) {
if (!patchVtableEntry(cur, types.VT.DrawIndexedPrimitive, ours_dip, &orig_dip)) return false;
}
if (cur[types.VT.Reset] != ours_reset) {
if (!patchVtableEntry(cur, types.VT.Reset, ours_reset, &orig_reset)) return false;
}
hooks_installed = true;
const live = getLiveHookState();
debug_late_rehook_succeeded = live.endscene_ours and live.dip_ours and live.reset_ours;
return debug_late_rehook_succeeded;
}
/// True until the first EndScene verifies (and if needed, forces) D24S8 format.
var need_force_reset: bool = true;
var need_force_reset: bool = false;
pub var debug_stencil_ready: bool = false;
pub var debug_stencil_format: u32 = 0;
pub var debug_stencil_reset_hr: i32 = 0;
pub fn requestStencilCheck() void {
// DEBUG23: intentionally disabled. Forcing IDirect3DDevice9::Reset on this
// client can hang the render thread while audio/game logic keeps running.
need_force_reset = false;
}
// =============================================================================
// Shader resources
// =============================================================================
var outline_ps: ?*anyopaque = null; // flat-color PS (silhouettes)
var outline_ps: ?*anyopaque = null; // flat-color PS (solid silhouettes)
var outline_alpha_ps: ?*anyopaque = null; // texture-alpha-aware silhouette PS
var outline_rgb_ps: ?*anyopaque = null; // additive/modulated texture coverage PS
var material_mask_ps: ?*anyopaque = null; // exact-material scratch -> binary mask
var jfa_init_ps: ?*anyopaque = null; // JFA seed init PS
var jfa_prop_ps: ?*anyopaque = null; // JFA propagation PS
var jfa_decode_ps: ?*anyopaque = null; // JFA decode + composite PS
@@ -91,7 +185,9 @@ const D3DXAssembleShaderFn = *const fn (
// Render target resources
// =============================================================================
var rt_silhouette_tex: ?*anyopaque = null; // A8R8G8B8 silhouette texture
var rt_material_tex: ?*anyopaque = null; // A8R8G8B8 exact-material scratch
var rt_material_surf: ?*anyopaque = null;
var rt_silhouette_tex: ?*anyopaque = null; // A8R8G8B8 normalized silhouette mask
var rt_silhouette_surf: ?*anyopaque = null;
var rt_jfa_a_tex: ?*anyopaque = null; // G16R16F JFA ping texture
var rt_jfa_a_surf: ?*anyopaque = null;
@@ -132,6 +228,18 @@ const CachedDraw = struct {
ib: ?*anyopaque = null,
vertex_decl: ?*anyopaque = null,
vertex_shader: ?*anyopaque = null,
tex: [4]?*anyopaque = .{ null, null, null, null },
pixel_shader: ?*anyopaque = null,
state_block: ?*anyopaque = null,
alpha_op: [4]u32 = .{ 1, 1, 1, 1 },
alpha_arg1: [4]u32 = .{ 0, 0, 0, 0 },
alpha_arg2: [4]u32 = .{ 0, 0, 0, 0 },
alpha_test_enable: u32 = 0,
alpha_ref: u32 = 0,
alpha_func: u32 = types.D3DCMP_ALWAYS,
alpha_blend_enable: u32 = 0,
src_blend: u32 = types.D3DBLEND_ONE,
dst_blend: u32 = types.D3DBLEND_ZERO,
// Per-model outline info
color: u32 = 0,
category: types.ModelCategory = .none,
@@ -207,6 +315,12 @@ fn deviceGetViewport(dev: *anyopaque, vp_out: *types.D3DVIEWPORT9) void {
_ = f(dev, vp_out);
}
fn deviceSetViewport(dev: *anyopaque, vp_in: *const types.D3DVIEWPORT9) void {
const f: *const fn (*anyopaque, *const types.D3DVIEWPORT9) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(dev)[types.VT.SetViewport]);
_ = f(dev, vp_in);
}
fn deviceSetRenderTarget(dev: *anyopaque, idx: u32, surf: *anyopaque) void {
const f: *const fn (*anyopaque, u32, *anyopaque) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(dev)[types.VT.SetRenderTarget]);
@@ -227,6 +341,38 @@ fn deviceSetTexture(dev: *anyopaque, stage: u32, tex: ?*anyopaque) void {
_ = @call(.never_tail, f, .{ dev, stage, tex });
}
fn deviceGetTSS(dev: *anyopaque, stage: u32, state_type: u32) u32 {
var val: u32 = 0;
const f: *const fn (*anyopaque, u32, u32, *u32) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(dev)[types.VT.GetTextureStageState]);
_ = f(dev, stage, state_type, &val);
return val;
}
fn deviceSetTSS(dev: *anyopaque, stage: u32, state_type: u32, value: u32) void {
const f: *const fn (*anyopaque, u32, u32, u32) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(dev)[types.VT.SetTextureStageState]);
_ = f(dev, stage, state_type, value);
}
fn deviceCreateStateBlock(dev: *anyopaque) ?*anyopaque {
var out: ?*anyopaque = null;
const f: *const fn (*anyopaque, u32, *?*anyopaque) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(dev)[types.VT.CreateStateBlock]);
if (f(dev, types.D3DSBT_ALL, &out) < 0) return null;
return out;
}
fn stateBlockApply(sb: *anyopaque) bool {
// IDirect3DStateBlock9 vtable:
// 0 QI, 1 AddRef, 2 Release, 3 GetDevice, 4 Capture, 5 Apply.
// DEBUG28/29 accidentally called Capture here, so no captured state was
// ever restored. Use the real Apply slot.
const f: *const fn (*anyopaque) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(sb)[5]);
return f(sb) >= 0;
}
fn deviceSetFVF(dev: *anyopaque, fvf: u32) void {
const f: *const fn (*anyopaque, u32) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(dev)[types.VT.SetFVF]);
@@ -302,7 +448,9 @@ fn deviceDrawPrimitiveUP(dev: *anyopaque, prim_type: u32, prim_count: u32, data:
fn deviceCreateTexture(dev: *anyopaque, w: u32, h: u32, levels: u32, usage: u32, fmt: u32, pool: u32, out: *?*anyopaque) i32 {
const f: *const fn (*anyopaque, u32, u32, u32, u32, u32, u32, *?*anyopaque, u32) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(dev)[types.VT.CreateTexture]);
return f(dev, w, h, levels, usage, fmt, pool, out, 0);
const hr = f(dev, w, h, levels, usage, fmt, pool, out, 0);
if (hr < 0) debug_texture_create_hr = hr;
return hr;
}
/// Get surface level 0 from a texture. Returns AddRef'd surface or null.
@@ -348,53 +496,73 @@ fn ensureResources(device: *anyopaque) void {
var vp: types.D3DVIEWPORT9 = .{};
deviceGetViewport(device, &vp);
if (vp.Width == 0 or vp.Height == 0) return;
debug_resource_stage = 1; // viewport valid
// Check if resources already match current dimensions
if (vp.Width == resource_width and vp.Height == resource_height and
rt_silhouette_tex != null) return;
rt_material_tex != null and rt_silhouette_tex != null) return;
// Release old and create new
releaseResources();
resource_width = vp.Width;
resource_height = vp.Height;
// Silhouette RT (A8R8G8B8)
// Exact-material scratch RT (A8R8G8B8)
if (deviceCreateTexture(device, vp.Width, vp.Height, 1, types.D3DUSAGE_RENDERTARGET, types.D3DFMT_A8R8G8B8, types.D3DPOOL_DEFAULT, &rt_material_tex) < 0) {
releaseResources();
return;
}
rt_material_surf = textureGetSurfaceLevel(rt_material_tex.?);
if (rt_material_surf == null) {
releaseResources();
return;
}
// Normalized silhouette RT (A8R8G8B8)
if (deviceCreateTexture(device, vp.Width, vp.Height, 1, types.D3DUSAGE_RENDERTARGET, types.D3DFMT_A8R8G8B8, types.D3DPOOL_DEFAULT, &rt_silhouette_tex) < 0) {
releaseResources();
return;
}
debug_resource_stage = 2; // silhouette texture ready
rt_silhouette_surf = textureGetSurfaceLevel(rt_silhouette_tex.?);
if (rt_silhouette_surf == null) {
releaseResources();
return;
}
debug_resource_stage = 3; // silhouette surface ready
// JFA A RT (G16R16F)
if (deviceCreateTexture(device, vp.Width, vp.Height, 1, types.D3DUSAGE_RENDERTARGET, types.D3DFMT_G16R16F, types.D3DPOOL_DEFAULT, &rt_jfa_a_tex) < 0) {
// V34: JFA A RT (G32R32F) - full-float seed UVs avoid screen-position quantization.
if (deviceCreateTexture(device, vp.Width, vp.Height, 1, types.D3DUSAGE_RENDERTARGET, types.D3DFMT_G32R32F, types.D3DPOOL_DEFAULT, &rt_jfa_a_tex) < 0) {
releaseResources();
return;
}
debug_resource_stage = 4; // JFA A texture ready
rt_jfa_a_surf = textureGetSurfaceLevel(rt_jfa_a_tex.?);
if (rt_jfa_a_surf == null) {
releaseResources();
return;
}
debug_resource_stage = 5; // JFA A surface ready
// JFA B RT (G16R16F)
if (deviceCreateTexture(device, vp.Width, vp.Height, 1, types.D3DUSAGE_RENDERTARGET, types.D3DFMT_G16R16F, types.D3DPOOL_DEFAULT, &rt_jfa_b_tex) < 0) {
// V34: JFA B RT (G32R32F) - full-float seed UVs avoid screen-position quantization.
if (deviceCreateTexture(device, vp.Width, vp.Height, 1, types.D3DUSAGE_RENDERTARGET, types.D3DFMT_G32R32F, types.D3DPOOL_DEFAULT, &rt_jfa_b_tex) < 0) {
releaseResources();
return;
}
debug_resource_stage = 6; // JFA B texture ready
rt_jfa_b_surf = textureGetSurfaceLevel(rt_jfa_b_tex.?);
if (rt_jfa_b_surf == null) {
releaseResources();
return;
}
debug_resource_stage = 7; // all RT surfaces ready
debug_resources_ready_seen = true;
}
fn releaseResources() void {
inline for (.{
&rt_silhouette_surf, &rt_jfa_a_surf, &rt_jfa_b_surf,
&rt_material_surf, &rt_silhouette_surf, &rt_jfa_a_surf, &rt_jfa_b_surf,
}) |surf_ptr| {
if (surf_ptr.*) |s| {
comRelease(s);
@@ -402,7 +570,7 @@ fn releaseResources() void {
}
}
inline for (.{
&rt_silhouette_tex, &rt_jfa_a_tex, &rt_jfa_b_tex,
&rt_material_tex, &rt_silhouette_tex, &rt_jfa_a_tex, &rt_jfa_b_tex,
}) |tex_ptr| {
if (tex_ptr.*) |t| {
comRelease(t);
@@ -420,6 +588,46 @@ fn releaseResources() void {
/// Flat colour pixel shader - outputs PS constant c0.
const ps_flat_src = "ps_3_0\nmov oC0, c0\n";
/// Texture-alpha-aware silhouette shader.
/// c0 = outline colour/encoded width, c1.x = -coverage threshold.
const ps_alpha_src =
"ps_3_0\n" ++
"dcl_2d s0\n" ++
"dcl_texcoord0 v0\n" ++
"texld r0, v0, s0\n" ++
"add r1, r0.aaaa, c1.xxxx\n" ++
"texkill r1\n" ++
"mov oC0, c0\n";
/// Coverage shader for additive/modulated M2 layers where transparency can be
/// encoded as black RGB rather than useful texture alpha.
const ps_rgb_src =
"ps_3_0\n" ++
"dcl_2d s0\n" ++
"dcl_texcoord0 v0\n" ++
"texld r0, v0, s0\n" ++
"max r1.x, r0.r, r0.g\n" ++
"max r1.x, r1.x, r0.b\n" ++
"add r1, r1.xxxx, c1.xxxx\n" ++
"texkill r1\n" ++
"mov oC0, c0\n";
/// Normalize the exact-material scratch into the uniform outline mask.
/// c0 = outline colour (alpha forced to 1), c1.x = -coverage threshold.
/// Coverage accepts either alpha or RGB, so opaque dark materials and additive
/// effects both survive while untouched transparent pixels remain discarded.
const material_mask_src =
"ps_3_0\n" ++
"dcl_2d s0\n" ++
"dcl_texcoord0 v0\n" ++
"texld r0, v0, s0\n" ++
"max r1.x, r0.r, r0.g\n" ++
"max r1.x, r1.x, r0.b\n" ++
"max r1.x, r1.x, r0.a\n" ++
"add r1, r1.xxxx, c1.xxxx\n" ++
"texkill r1\n" ++
"mov oC0, c0\n";
/// JFA init: sample silhouette, output own UV as seed or sentinel (-1,-1).
/// Sentinel must be outside [0,1] UV space so it never wins distance comparisons.
const jfa_init_src =
@@ -447,13 +655,17 @@ const jfa_prop_src =
"def c9, 1.0, 1.0, 0.0, 0.0\n" ++
"dcl_2d s0\n" ++
"dcl_texcoord0 v0\n" ++
// D3DX on this client only allows one c# register per arithmetic
// instruction. Copy c0.xy (step size) to a temp once, then combine
// that temp with c2..c9 in the neighbor MADs.
"mov r7.xy, c0.xy\n" ++
// Self sample - initialize best seed and distance
"texld r0, v0, s0\n" ++
"sub r2.xy, v0.xy, r0.xy\n" ++
"dp2add r9.x, r2, r2, c1.x\n" ++ // best dist²
"mov r8.xy, r0.xy\n" ++ // best seed UV
// Neighbor (-1,-1) via c2
"mad r4.xy, c2.xy, c0.xy, v0.xy\n" ++
"mad r4.xy, c2.xy, r7.xy, v0.xy\n" ++
"texld r5, r4, s0\n" ++
"sub r2.xy, v0.xy, r5.xy\n" ++
"dp2add r2.z, r2, r2, c1.x\n" ++
@@ -461,7 +673,7 @@ const jfa_prop_src =
"cmp r8.xy, r3.x, r8.xy, r5.xy\n" ++
"cmp r9.x, r3.x, r9.x, r2.z\n" ++
// Neighbor (-1, 0) via c3
"mad r4.xy, c3.xy, c0.xy, v0.xy\n" ++
"mad r4.xy, c3.xy, r7.xy, v0.xy\n" ++
"texld r5, r4, s0\n" ++
"sub r2.xy, v0.xy, r5.xy\n" ++
"dp2add r2.z, r2, r2, c1.x\n" ++
@@ -469,7 +681,7 @@ const jfa_prop_src =
"cmp r8.xy, r3.x, r8.xy, r5.xy\n" ++
"cmp r9.x, r3.x, r9.x, r2.z\n" ++
// Neighbor (-1, 1) via c4
"mad r4.xy, c4.xy, c0.xy, v0.xy\n" ++
"mad r4.xy, c4.xy, r7.xy, v0.xy\n" ++
"texld r5, r4, s0\n" ++
"sub r2.xy, v0.xy, r5.xy\n" ++
"dp2add r2.z, r2, r2, c1.x\n" ++
@@ -477,7 +689,7 @@ const jfa_prop_src =
"cmp r8.xy, r3.x, r8.xy, r5.xy\n" ++
"cmp r9.x, r3.x, r9.x, r2.z\n" ++
// Neighbor (0, -1) via c5
"mad r4.xy, c5.xy, c0.xy, v0.xy\n" ++
"mad r4.xy, c5.xy, r7.xy, v0.xy\n" ++
"texld r5, r4, s0\n" ++
"sub r2.xy, v0.xy, r5.xy\n" ++
"dp2add r2.z, r2, r2, c1.x\n" ++
@@ -485,7 +697,7 @@ const jfa_prop_src =
"cmp r8.xy, r3.x, r8.xy, r5.xy\n" ++
"cmp r9.x, r3.x, r9.x, r2.z\n" ++
// Neighbor (0, 1) via c6
"mad r4.xy, c6.xy, c0.xy, v0.xy\n" ++
"mad r4.xy, c6.xy, r7.xy, v0.xy\n" ++
"texld r5, r4, s0\n" ++
"sub r2.xy, v0.xy, r5.xy\n" ++
"dp2add r2.z, r2, r2, c1.x\n" ++
@@ -493,7 +705,7 @@ const jfa_prop_src =
"cmp r8.xy, r3.x, r8.xy, r5.xy\n" ++
"cmp r9.x, r3.x, r9.x, r2.z\n" ++
// Neighbor (1, -1) via c7
"mad r4.xy, c7.xy, c0.xy, v0.xy\n" ++
"mad r4.xy, c7.xy, r7.xy, v0.xy\n" ++
"texld r5, r4, s0\n" ++
"sub r2.xy, v0.xy, r5.xy\n" ++
"dp2add r2.z, r2, r2, c1.x\n" ++
@@ -501,7 +713,7 @@ const jfa_prop_src =
"cmp r8.xy, r3.x, r8.xy, r5.xy\n" ++
"cmp r9.x, r3.x, r9.x, r2.z\n" ++
// Neighbor (1, 0) via c8
"mad r4.xy, c8.xy, c0.xy, v0.xy\n" ++
"mad r4.xy, c8.xy, r7.xy, v0.xy\n" ++
"texld r5, r4, s0\n" ++
"sub r2.xy, v0.xy, r5.xy\n" ++
"dp2add r2.z, r2, r2, c1.x\n" ++
@@ -509,7 +721,7 @@ const jfa_prop_src =
"cmp r8.xy, r3.x, r8.xy, r5.xy\n" ++
"cmp r9.x, r3.x, r9.x, r2.z\n" ++
// Neighbor (1, 1) via c9
"mad r4.xy, c9.xy, c0.xy, v0.xy\n" ++
"mad r4.xy, c9.xy, r7.xy, v0.xy\n" ++
"texld r5, r4, s0\n" ++
"sub r2.xy, v0.xy, r5.xy\n" ++
"dp2add r2.z, r2, r2, c1.x\n" ++
@@ -520,34 +732,31 @@ const jfa_prop_src =
"mov oC0.xy, r8.xy\n" ++
"mov oC0.zw, c1.xx\n";
/// JFA decode + composite: compute distance to nearest seed, threshold, output outline.
/// c0 = (screen_width, screen_height, 4.0, 0.0) set by CPU.
/// V36 RETAIL-SOFT: keep the proven V34 JFA, soften only final decode.
/// c0 = (screen_width, screen_height, core_radius_px, halo_radius_px).
/// Uses the same two texture reads as V34: no 17-tap full-screen morphology.
const jfa_decode_src =
"ps_3_0\n" ++
"def c1, 0.0, 1.0, -0.002, 0.0\n" ++
"dcl_2d s0\n" ++ // JFA result (nearest seed UV)
"dcl_2d s1\n" ++ // silhouette (colour + width-encoded alpha)
"def c1, 0.0, 0.22, 0.82, -0.002\n" ++
"def c2, 1.0, 0.95, 0.58, 0.0\n" ++
"dcl_2d s0\n" ++
"dcl_2d s1\n" ++
"dcl_texcoord0 v0\n" ++
// Read nearest seed UV
"texld r0, v0, s0\n" ++
// Pixel-space squared distance
"sub r1.xy, v0.xy, r0.xy\n" ++
"mul r1.xy, r1.xy, c0.xy\n" ++ // (du*W, dv*H)
"dp2add r1.z, r1, r1, c0.w\n" ++ // dist² in pixels
// Read seed's silhouette colour + width
"texld r2, r0, s1\n" ++
"mul r3.x, r2.a, c0.z\n" ++ // outline_width = alpha * 4.0
"mul r3.x, r3.x, r3.x\n" ++ // width²
// Threshold: inside outline if dist² < width²
"sub r3.y, r1.z, r3.x\n" ++ // dist² - width²
"cmp r4.w, r3.y, c0.w, c1.y\n" ++ // >= 0 → 0 (outside), < 0 → 1 (inside)
// Exclude silhouette interior (don't draw outline ON the model)
"texld r5, v0, s1\n" ++ // silhouette at current pixel
"add r5.x, r5.a, c1.z\n" ++ // alpha - 0.002
"cmp r4.w, r5.x, c0.w, r4.w\n" ++ // if inside silhouette → 0
// Output
"mov r4.xyz, r2.xyz\n" ++ // outline colour from seed
"mov oC0, r4\n";
"mul r1.xy, r1.xy, c0.xy\n" ++
"dp2add r1.z, r1, r1, c1.x\n" ++
"mul r3.x, c0.z, c0.z\n" ++
"mul r3.y, c0.w, c0.w\n" ++
"sub r4.x, r1.z, r3.y\n" ++
"cmp r6.w, r4.x, c1.x, c1.y\n" ++
"sub r4.x, r1.z, r3.x\n" ++
"cmp r6.w, r4.x, r6.w, c1.z\n" ++
"texld r5, v0, s1\n" ++
"add r5.x, r5.a, c1.w\n" ++
"cmp r6.w, r5.x, c1.x, r6.w\n" ++
"mov r6.xyz, c2.xyz\n" ++
"mov oC0, r6\n";
/// Debug: composite silhouette RT directly. Forces alpha to 1.0 where silhouette
/// has any content (alpha >= 0.002), 0.0 elsewhere. Bypasses JFA entirely.
@@ -567,33 +776,55 @@ const debug_sil_src =
fn ensureShaders(device: *anyopaque) void {
shaders_attempted = true;
debug_shader_stage = 1; // entered
const d3dx = LoadLibraryA("d3dx9_43.dll") orelse
LoadLibraryA("d3dx9_42.dll") orelse
LoadLibraryA("d3dx9_41.dll") orelse return;
debug_shader_stage = 2; // D3DX loaded
const assemble_ptr = GetProcAddress(d3dx, "D3DXAssembleShader") orelse return;
debug_shader_stage = 3; // assembler found
const assemble: D3DXAssembleShaderFn = @ptrCast(assemble_ptr);
// --- Flat-colour PS (for silhouettes) ---
// --- Flat-colour PS (for solid silhouettes) ---
outline_ps = assemblePS(device, assemble, ps_flat_src, ps_flat_src.len) orelse return;
// --- Alpha-aware silhouette PS (for textured cutout/translucent planes) ---
outline_alpha_ps = assemblePS(device, assemble, ps_alpha_src, ps_alpha_src.len) orelse {
releaseShaders();
return;
};
outline_rgb_ps = assemblePS(device, assemble, ps_rgb_src, ps_rgb_src.len) orelse {
releaseShaders();
return;
};
material_mask_ps = assemblePS(device, assemble, material_mask_src, material_mask_src.len) orelse {
releaseShaders();
return;
};
debug_shader_stage = 4; // silhouette/material-mask PS variants ready
// --- JFA Init PS ---
jfa_init_ps = assemblePS(device, assemble, jfa_init_src, jfa_init_src.len) orelse {
releaseShaders();
return;
};
debug_shader_stage = 5; // JFA init ready
// --- JFA Propagation PS ---
jfa_prop_ps = assemblePS(device, assemble, jfa_prop_src, jfa_prop_src.len) orelse {
releaseShaders();
return;
};
debug_shader_stage = 6; // JFA propagation ready
// --- JFA Decode + Composite PS ---
jfa_decode_ps = assemblePS(device, assemble, jfa_decode_src, jfa_decode_src.len) orelse {
releaseShaders();
return;
};
debug_shader_stage = 7; // JFA decode ready
// --- Debug silhouette composite PS (only when diagnostic enabled) ---
if (DEBUG_SHOW_SILHOUETTE) {
@@ -602,14 +833,42 @@ fn ensureShaders(device: *anyopaque) void {
return;
};
}
debug_shader_stage = 8; // complete
debug_shaders_ready_seen = true;
}
/// Assemble a pixel shader from source text, create device PS object.
fn captureD3DXError(buf: *anyopaque) void {
@memset(&debug_shader_error_text, 0);
const get_ptr: *const fn (*anyopaque) callconv(hook.cc.stdcall) usize =
@ptrFromInt(vt(buf)[3]);
const get_size: *const fn (*anyopaque) callconv(hook.cc.stdcall) usize =
@ptrFromInt(vt(buf)[4]);
const ptr_val = get_ptr(buf);
const size = get_size(buf);
if (ptr_val == 0 or size == 0) return;
const src_ptr: [*]const u8 = @ptrFromInt(ptr_val);
const n = @min(size, debug_shader_error_text.len - 1);
@memcpy(debug_shader_error_text[0..n], src_ptr[0..n]);
// Make sure the chat string ends cleanly even if the D3DX buffer does not.
debug_shader_error_text[n] = 0;
}
fn assemblePS(device: *anyopaque, assemble: D3DXAssembleShaderFn, src: [*]const u8, len: usize) ?*anyopaque {
var code: ?*anyopaque = null;
var err_buf: ?*anyopaque = null;
if (assemble(src, @intCast(len), null, null, 0, &code, &err_buf) < 0 or code == null) {
if (err_buf) |e| comRelease(e);
const assemble_hr = assemble(src, @intCast(len), null, null, 0, &code, &err_buf);
if (assemble_hr < 0 or code == null) {
debug_shader_assemble_hr = assemble_hr;
if (err_buf) |e| {
captureD3DXError(e);
comRelease(e);
}
return null;
}
defer comRelease(code.?);
@@ -622,12 +881,16 @@ fn assemblePS(device: *anyopaque, assemble: D3DXAssembleShaderFn, src: [*]const
var ps_out: ?*anyopaque = null;
const create: *const fn (*anyopaque, *anyopaque, *?*anyopaque) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(device)[types.VT.CreatePixelShader]);
if (create(device, buf_ptr, &ps_out) < 0) return null;
const create_hr = create(device, buf_ptr, &ps_out);
if (create_hr < 0) {
debug_shader_create_hr = create_hr;
return null;
}
return ps_out;
}
fn releaseShaders() void {
inline for (.{ &outline_ps, &jfa_init_ps, &jfa_prop_ps, &jfa_decode_ps, &debug_sil_ps }) |ps| {
inline for (.{ &outline_ps, &outline_alpha_ps, &outline_rgb_ps, &material_mask_ps, &jfa_init_ps, &jfa_prop_ps, &jfa_decode_ps, &debug_sil_ps }) |ps| {
if (ps.*) |p| {
comRelease(p);
ps.* = null;
@@ -657,11 +920,10 @@ fn buildFullscreenQuad(w: u32, h: u32) [4]QuadVertex {
// =============================================================================
fn hkEndScene(device: *anyopaque) callconv(hook.cc.stdcall) i32 {
// One-time: check if depth/stencil surface has stencil bits.
if (need_force_reset) {
need_force_reset = false;
forceD24S8IfNeeded(device);
}
debug_endscene_seen = true;
// DEBUG23: no forced D3D9 Reset. The client can hang the render thread
// during Reset, so Outline runs without a stencil dependency.
// Per-frame: scan objects for outline tracking
tracker.scanObjects();
@@ -732,11 +994,14 @@ fn hkDIP(
start_idx: u32,
prim_count: u32,
) callconv(hook.cc.stdcall) i32 {
debug_dip_seen = true;
const OrigDIP = *const fn (*anyopaque, u32, i32, u32, u32, u32, u32) callconv(hook.cc.stdcall) i32;
const origFn: OrigDIP = @ptrFromInt(orig_dip);
// ---- Cache outline draws for EndScene replay ----
if (model_hook.rendering_outline) {
debug_outline_dip_seen = true;
const model_ptr = model_hook.current_model;
const color = tracker.getModelColor(model_ptr) orelse
return origFn(device, prim_type, base_vtx, min_vtx, num_verts, start_idx, prim_count);
@@ -774,52 +1039,33 @@ fn hkDIP(
// GetVertexDeclaration AddRef's
draw.vertex_shader = deviceGetPtr(device, types.VT.GetVertexShader);
// GetVertexShader AddRef's
for (0..4) |stage| {
draw.tex[stage] = deviceGetTexture(device, @intCast(stage));
draw.alpha_op[stage] = deviceGetTSS(device, @intCast(stage), types.D3DTSS.ALPHAOP);
draw.alpha_arg1[stage] = deviceGetTSS(device, @intCast(stage), types.D3DTSS.ALPHAARG1);
draw.alpha_arg2[stage] = deviceGetTSS(device, @intCast(stage), types.D3DTSS.ALPHAARG2);
}
draw.pixel_shader = deviceGetPtr(device, types.VT.GetPixelShader);
draw.state_block = deviceCreateStateBlock(device);
if (draw.state_block != null) debug_state_block_seen = true;
draw.alpha_test_enable = deviceGetRS(device, types.D3DRS.ALPHATESTENABLE);
draw.alpha_ref = deviceGetRS(device, types.D3DRS.ALPHAREF);
draw.alpha_func = deviceGetRS(device, types.D3DRS.ALPHAFUNC);
draw.alpha_blend_enable = deviceGetRS(device, types.D3DRS.ALPHABLENDENABLE);
draw.src_blend = deviceGetRS(device, types.D3DRS.SRCBLEND);
draw.dst_blend = deviceGetRS(device, types.D3DRS.DESTBLEND);
// Capture VS constants (bone matrices, world/view/proj transforms)
deviceGetVSConstF(device, 0, &draw.vs_consts, MAX_VS_CONST_REGS);
cached_draw_count = idx + 1;
frame_has_outlines = true;
debug_cached_draw_seen = true;
}
// Mark visible pixels in stencil for this outline target.
// At this point (outline targets draw last due to batch reordering),
// the game's DS has terrain+WMO+all non-outline M2 model depth.
// Pixels that pass the depth test get stencil=1; pixels behind any
// scene geometry fail and keep stencil=0.
// EndScene uses these marks to gate silhouette rendering.
const s_enable = deviceGetRS(device, types.D3DRS.STENCILENABLE);
const s_func = deviceGetRS(device, types.D3DRS.STENCILFUNC);
const s_ref = deviceGetRS(device, types.D3DRS.STENCILREF);
// (STENCILWRITEMASK not saved - intentionally set to 0 on restore)
const s_pass = deviceGetRS(device, types.D3DRS.STENCILPASS);
const s_fail = deviceGetRS(device, types.D3DRS.STENCILFAIL);
const s_zfail = deviceGetRS(device, types.D3DRS.STENCILZFAIL);
deviceSetRS(device, types.D3DRS.STENCILENABLE, 1);
deviceSetRS(device, types.D3DRS.STENCILFUNC, types.D3DCMP_ALWAYS);
deviceSetRS(device, types.D3DRS.STENCILREF, 1);
deviceSetRS(device, types.D3DRS.STENCILWRITEMASK, 0xFF);
deviceSetRS(device, types.D3DRS.STENCILPASS, types.D3DSTENCILOP_REPLACE);
deviceSetRS(device, types.D3DRS.STENCILFAIL, types.D3DSTENCILOP_KEEP);
deviceSetRS(device, types.D3DRS.STENCILZFAIL, types.D3DSTENCILOP_KEEP);
const result = origFn(device, prim_type, base_vtx, min_vtx, num_verts, start_idx, prim_count);
// Restore stencil state to match WoW's GxDevice cache, but lock
// stencil writes to protect our marks from subsequent draws (other
// players' gear, NPCs, etc. that render after outline targets).
deviceSetRS(device, types.D3DRS.STENCILENABLE, s_enable);
deviceSetRS(device, types.D3DRS.STENCILFUNC, s_func);
deviceSetRS(device, types.D3DRS.STENCILREF, s_ref);
deviceSetRS(device, types.D3DRS.STENCILPASS, s_pass);
deviceSetRS(device, types.D3DRS.STENCILFAIL, s_fail);
deviceSetRS(device, types.D3DRS.STENCILZFAIL, s_zfail);
// Write mask 0 instead of restoring original - prevents any
// subsequent DIP from overwriting our stencil=1 marks.
// Restored properly in EndScene before the JFA pipeline.
deviceSetRS(device, types.D3DRS.STENCILWRITEMASK, 0);
return result;
// DEBUG23: no stencil writes. Preserve WoW's D3D state and draw once.
// The cached geometry is replayed later into the silhouette RT.
return origFn(device, prim_type, base_vtx, min_vtx, num_verts, start_idx, prim_count);
}
// ---- Normal path ----
@@ -846,6 +1092,20 @@ fn clearCachedDraws() void {
comRelease(obj);
draw.vertex_shader = null;
}
if (draw.pixel_shader) |obj| {
comRelease(obj);
draw.pixel_shader = null;
}
if (draw.state_block) |obj| {
comRelease(obj);
draw.state_block = null;
}
for (0..4) |stage| {
if (draw.tex[stage]) |obj| {
comRelease(obj);
draw.tex[stage] = null;
}
}
}
cached_draw_count = 0;
}
@@ -855,11 +1115,25 @@ fn clearCachedDraws() void {
// =============================================================================
fn runJfaPipeline(device: *anyopaque) void {
debug_pipeline_entered_seen = true;
// DEBUG29: capture the complete WoW D3D state before any replay/JFA work.
// The manual save/restore below remains as fallback, but this state block
// restores states that are easy to miss (extra samplers/TSS, scissor,
// shaders/constants, streams, declarations, etc.).
const outer_state = deviceCreateStateBlock(device);
defer if (outer_state) |sb| {
if (stateBlockApply(sb)) debug_outer_state_restore_seen = true;
comRelease(sb);
};
// Verify all resources and shaders
if (rt_silhouette_tex == null or rt_jfa_a_surf == null or rt_jfa_b_surf == null) return;
if (rt_material_tex == null or rt_material_surf == null or rt_silhouette_tex == null or rt_jfa_a_surf == null or rt_jfa_b_surf == null) return;
if (!shaders_attempted) ensureShaders(device);
if (jfa_init_ps == null or jfa_prop_ps == null or jfa_decode_ps == null) return;
if (outline_ps == null or rt_silhouette_surf == null) return;
if (outline_ps == null or material_mask_ps == null or rt_silhouette_surf == null) return;
debug_pipeline_ready_seen = true;
var vp: types.D3DVIEWPORT9 = .{};
deviceGetViewport(device, &vp);
@@ -897,8 +1171,29 @@ fn runJfaPipeline(device: *anyopaque) void {
const saved_dstblend = deviceGetRS(device, types.D3DRS.DESTBLEND);
const saved_cull = deviceGetRS(device, types.D3DRS.CULLMODE);
const saved_atest = deviceGetRS(device, types.D3DRS.ALPHATESTENABLE);
const saved_aref = deviceGetRS(device, types.D3DRS.ALPHAREF);
const saved_afunc = deviceGetRS(device, types.D3DRS.ALPHAFUNC);
const saved_tfactor = deviceGetRS(device, types.D3DRS.TEXTUREFACTOR);
const saved_cwrite = deviceGetRS(device, types.D3DRS.COLORWRITEENABLE);
const SavedTSS = struct {
colorop: u32,
colorarg1: u32,
alphaop: u32,
alphaarg1: u32,
alphaarg2: u32,
};
var saved_tss: [4]SavedTSS = undefined;
for (0..4) |stage| {
saved_tss[stage] = .{
.colorop = deviceGetTSS(device, @intCast(stage), types.D3DTSS.COLOROP),
.colorarg1 = deviceGetTSS(device, @intCast(stage), types.D3DTSS.COLORARG1),
.alphaop = deviceGetTSS(device, @intCast(stage), types.D3DTSS.ALPHAOP),
.alphaarg1 = deviceGetTSS(device, @intCast(stage), types.D3DTSS.ALPHAARG1),
.alphaarg2 = deviceGetTSS(device, @intCast(stage), types.D3DTSS.ALPHAARG2),
};
}
// Stencil states (Phase 1 reads stencil marks written by DIP hook)
const saved_stencil_enable = deviceGetRS(device, types.D3DRS.STENCILENABLE);
const saved_stencil_func = deviceGetRS(device, types.D3DRS.STENCILFUNC);
@@ -936,57 +1231,83 @@ fn runJfaPipeline(device: *anyopaque) void {
if (cached_draw_count > 0) {
const origFn: *const fn (*anyopaque, u32, i32, u32, u32, u32, u32) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(orig_dip);
const quad = buildFullscreenQuad(vp.Width, vp.Height);
// Accumulate normalized per-draw coverage into the silhouette mask.
deviceSetRenderTarget(device, 0, rt_silhouette_surf.?);
clearRenderTarget(device, 0x00000000);
// Keep game's DS bound - it has stencil marks from DIP hook where
// outline targets passed the terrain depth test (stencil=1 = visible).
// Don't write depth or stencil during replay.
deviceSetRS(device, types.D3DRS.ZWRITEENABLE, 0);
deviceSetRS(device, types.D3DRS.ZENABLE, types.D3DZB_FALSE);
deviceSetRS(device, types.D3DRS.STENCILWRITEMASK, 0);
deviceSetPtr(device, types.VT.SetPixelShader, outline_ps.?);
deviceSetRS(device, types.D3DRS.ALPHABLENDENABLE, 0);
deviceSetRS(device, types.D3DRS.COLORWRITEENABLE, 0x0F);
for (0..cached_draw_count) |i| {
const draw = &cached_draws[i];
// V31 POLISH: don't let additive/emissive passes (spell glows,
// bloom-like model layers) expand the selection silhouette.
// Normal alpha-blended materials remain included.
if (draw.alpha_blend_enable != 0 and draw.dst_blend == types.D3DBLEND_ONE) {
debug_additive_skipped_seen = true;
continue;
}
// 1) Replay this draw with WoW's exact captured D3D state into a
// transparent scratch RT. A D3DSBT_ALL state block restores pixel
// shader, PS constants, textures, samplers, texture stages, blend,
// alpha-test, vertex state and stream bindings.
if (draw.state_block) |sb| {
_ = stateBlockApply(sb);
}
// Never rely on D3DSBT_ALL to restore geometry bindings correctly
// across drivers/wrappers. Rebind the cached draw explicitly.
deviceSetStreamSource(device, 0, draw.vb, draw.vb_offset, draw.vb_stride);
deviceSetIndices(device, draw.ib);
deviceSetPtrOrNull(device, types.VT.SetVertexDeclaration, draw.vertex_decl);
deviceSetPtrOrNull(device, types.VT.SetVertexShader, draw.vertex_shader);
deviceSetVSConstF(device, 0, &draw.vs_consts, MAX_VS_CONST_REGS);
var color_f4 = argbToFloat4(draw.color);
color_f4[3] = tracker.getOutlinePixels(draw.category) / 4.0;
deviceSetPSConstF(device, 0, &color_f4);
// Per-category stencil logic:
// - dead_player: no stencil test (visible through walls for corpse finding)
// - target/raid_marked: stencil test gates on terrain visibility
if (draw.category == .dead_player) {
deviceSetRS(device, types.D3DRS.STENCILENABLE, 0);
} else {
deviceSetRS(device, types.D3DRS.STENCILENABLE, 1);
deviceSetRS(device, types.D3DRS.STENCILFUNC, types.D3DCMP_EQUAL);
deviceSetRS(device, types.D3DRS.STENCILREF, 1);
deviceSetRS(device, types.D3DRS.STENCILMASK, 0xFF);
deviceSetRS(device, types.D3DRS.STENCILPASS, types.D3DSTENCILOP_KEEP);
}
deviceSetRenderTarget(device, 0, rt_material_surf.?);
deviceSetViewport(device, &vp);
clearRenderTarget(device, 0x00000000);
deviceSetRS(device, types.D3DRS.ZENABLE, types.D3DZB_FALSE);
deviceSetRS(device, types.D3DRS.ZWRITEENABLE, 0);
deviceSetRS(device, types.D3DRS.STENCILENABLE, 0);
deviceSetRS(device, types.D3DRS.COLORWRITEENABLE, 0x0F);
_ = origFn(device, draw.prim_type, draw.base_vtx, draw.min_vtx, draw.num_verts, draw.start_idx, draw.prim_count);
// 2) Convert only pixels actually produced by that exact material
// into the uniform outline mask, preserving the draw/category colour.
deviceSetRenderTarget(device, 0, rt_silhouette_surf.?);
deviceSetViewport(device, &vp);
deviceSetPtrOrNull(device, types.VT.SetDepthStencilSurface, null);
deviceSetPtrOrNull(device, types.VT.SetVertexShader, null);
deviceSetFVF(device, types.D3DFVF_XYZRHW | types.D3DFVF_TEX1);
deviceSetRS(device, types.D3DRS.ZENABLE, types.D3DZB_FALSE);
deviceSetRS(device, types.D3DRS.ZWRITEENABLE, 0);
deviceSetRS(device, types.D3DRS.STENCILENABLE, 0);
deviceSetRS(device, types.D3DRS.ALPHATESTENABLE, 0);
deviceSetRS(device, types.D3DRS.CULLMODE, types.D3DCULL_NONE);
deviceSetRS(device, types.D3DRS.COLORWRITEENABLE, 0x0F);
deviceSetRS(device, types.D3DRS.ALPHABLENDENABLE, 1);
deviceSetRS(device, types.D3DRS.SRCBLEND, types.D3DBLEND_SRCALPHA);
deviceSetRS(device, types.D3DRS.DESTBLEND, types.D3DBLEND_INVSRCALPHA);
deviceSetSamplerState(device, 0, types.D3DSAMP.ADDRESSU, types.D3DTADDRESS_CLAMP);
deviceSetSamplerState(device, 0, types.D3DSAMP.ADDRESSV, types.D3DTADDRESS_CLAMP);
deviceSetSamplerState(device, 0, types.D3DSAMP.MAGFILTER, types.D3DTEXF_POINT);
deviceSetSamplerState(device, 0, types.D3DSAMP.MINFILTER, types.D3DTEXF_POINT);
deviceSetSamplerState(device, 0, types.D3DSAMP.MIPFILTER, types.D3DTEXF_NONE);
deviceSetTexture(device, 0, rt_material_tex);
deviceSetPtr(device, types.VT.SetPixelShader, material_mask_ps.?);
var mask_color = argbToFloat4(draw.color);
mask_color[3] = 1.0;
deviceSetPSConstF(device, 0, &mask_color);
// Trim very faint scratch pixels that otherwise become tiny hooks/noise.
const threshold: [4]f32 = .{ -0.03, 0.0, 0.0, 0.0 };
deviceSetPSConstF(device, 1, &threshold);
deviceDrawPrimitiveUP(device, types.D3DPT_TRIANGLESTRIP, 2, @ptrCast(&quad), @sizeOf(QuadVertex));
}
clearCachedDraws();
// Clear stencil marks to avoid affecting next frame's rendering
deviceSetRS(device, types.D3DRS.STENCILENABLE, 0);
const clearFn: *const fn (*anyopaque, u32, ?*anyopaque, u32, u32, f32, u32) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(device)[types.VT.Clear]);
_ = clearFn(device, 0, null, types.D3DCLEAR_STENCIL, 0, 1.0, 0);
}
// =====================================================================
@@ -1051,7 +1372,7 @@ fn runJfaPipeline(device: *anyopaque) void {
const fw = @as(f32, @floatFromInt(@max(vp.Width, 1)));
const fh = @as(f32, @floatFromInt(@max(vp.Height, 1)));
// Pass 1: JFA Init (silhouette → JFA_A)
// Pass 1: JFA Init (silhouette → JFA_A).
deviceSetRenderTarget(device, 0, rt_jfa_a_surf.?);
deviceSetTexture(device, 0, rt_silhouette_tex);
deviceSetPtr(device, types.VT.SetPixelShader, jfa_init_ps.?);
@@ -1093,7 +1414,8 @@ fn runJfaPipeline(device: *anyopaque) void {
if (saved_rt0) |rt| deviceSetRenderTarget(device, 0, rt);
deviceSetTexture(device, 0, rt_jfa_a_tex);
deviceSetTexture(device, 1, rt_silhouette_tex);
c0 = [4]f32{ fw, fh, 4.0, 0.0 };
// V36: thin 1.5px core plus a subtle 3px halo.
c0 = [4]f32{ fw, fh, 1.5, 3.0 };
deviceSetPSConstF(device, 0, &c0);
deviceSetPtr(device, types.VT.SetPixelShader, jfa_decode_ps.?);
deviceSetRS(device, types.D3DRS.ALPHABLENDENABLE, 1);
@@ -1115,7 +1437,17 @@ fn runJfaPipeline(device: *anyopaque) void {
deviceSetRS(device, types.D3DRS.DESTBLEND, saved_dstblend);
deviceSetRS(device, types.D3DRS.CULLMODE, saved_cull);
deviceSetRS(device, types.D3DRS.ALPHATESTENABLE, saved_atest);
deviceSetRS(device, types.D3DRS.ALPHAREF, saved_aref);
deviceSetRS(device, types.D3DRS.ALPHAFUNC, saved_afunc);
deviceSetRS(device, types.D3DRS.TEXTUREFACTOR, saved_tfactor);
deviceSetRS(device, types.D3DRS.COLORWRITEENABLE, saved_cwrite);
for (0..4) |stage| {
deviceSetTSS(device, @intCast(stage), types.D3DTSS.COLOROP, saved_tss[stage].colorop);
deviceSetTSS(device, @intCast(stage), types.D3DTSS.COLORARG1, saved_tss[stage].colorarg1);
deviceSetTSS(device, @intCast(stage), types.D3DTSS.ALPHAOP, saved_tss[stage].alphaop);
deviceSetTSS(device, @intCast(stage), types.D3DTSS.ALPHAARG1, saved_tss[stage].alphaarg1);
deviceSetTSS(device, @intCast(stage), types.D3DTSS.ALPHAARG2, saved_tss[stage].alphaarg2);
}
// Stencil states
deviceSetRS(device, types.D3DRS.STENCILENABLE, saved_stencil_enable);
@@ -1174,42 +1506,65 @@ fn hasStencilBits(fmt: u32) bool {
fmt == types.D3DFMT_D24X4S4 or fmt == types.D3DFMT_D15S1;
}
fn forceD24S8IfNeeded(device: *anyopaque) void {
fn queryStencilFormat(device: *anyopaque) u32 {
var pDS: ?*anyopaque = null;
const getDS: *const fn (*anyopaque, *?*anyopaque) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(device)[types.VT.GetDepthStencilSurface]);
if (getDS(device, &pDS) < 0) return;
const ds = pDS orelse return;
defer comRelease(ds);
if (getDS(device, &pDS) < 0) return 0;
const ds = pDS orelse return 0;
var desc: types.D3DSURFACE_DESC = .{};
const getDesc: *const fn (*anyopaque, *types.D3DSURFACE_DESC) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(ds)[12]);
if (getDesc(ds, &desc) < 0) return;
const hr = getDesc(ds, &desc);
comRelease(ds);
if (hr < 0) return 0;
return desc.Format;
}
if (hasStencilBits(desc.Format)) return;
fn forceD24S8IfNeeded(device: *anyopaque) bool {
const current_fmt = queryStencilFormat(device);
debug_stencil_format = current_fmt;
if (hasStencilBits(current_fmt)) {
debug_stencil_ready = true;
debug_stencil_reset_hr = 0;
return false;
}
var pSwap: ?*anyopaque = null;
const getSC: *const fn (*anyopaque, u32, *?*anyopaque) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(device)[types.VT.GetSwapChain]);
if (getSC(device, 0, &pSwap) < 0) return;
const swap = pSwap orelse return;
defer comRelease(swap);
if (getSC(device, 0, &pSwap) < 0) return false;
const swap = pSwap orelse return false;
var pp: types.D3DPRESENT_PARAMETERS = .{};
const getPP: *const fn (*anyopaque, *types.D3DPRESENT_PARAMETERS) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(swap)[9]);
if (getPP(swap, &pp) < 0) return;
const pp_hr = getPP(swap, &pp);
// IMPORTANT: do not hold a swap-chain COM reference across Reset.
comRelease(swap);
if (pp_hr < 0) return false;
pp.AutoDepthStencilFormat = types.D3DFMT_D24S8;
pp.EnableAutoDepthStencil = 1;
clearCachedDraws();
releaseShaders();
releaseResources();
const resetFn: *const fn (*anyopaque, *types.D3DPRESENT_PARAMETERS) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(device)[types.VT.Reset]);
_ = resetFn(device, &pp);
const reset_hr = resetFn(device, &pp);
debug_stencil_reset_hr = reset_hr;
if (reset_hr < 0) {
debug_stencil_ready = false;
return false;
}
const new_fmt = queryStencilFormat(device);
debug_stencil_format = new_fmt;
debug_stencil_ready = hasStencilBits(new_fmt);
return true;
}
// =============================================================================
+17
View File
@@ -42,6 +42,19 @@ var draw_batch_hook: hook.Detour(DrawBatchFn) = .{};
/// a dummy D3D9 device during engine init corrupts the proxy's state.
var d3d9_deferred_pending: bool = true;
/// AutoFix: periodically verify that the game's live D3D9 vtable still points
/// at our hooks. OutlineDebug() used to do this manually; keeping it here makes
/// the renderer self-healing if another proxy/mod rewrites the vtable later.
var d3d9_health_tick: u8 = 29;
fn autoRepairD3D9Hooks() void {
d3d9_health_tick +%= 1;
if (d3d9_health_tick < 30) return;
d3d9_health_tick = 0;
_ = d3d9_hook.lateRehookIfLost();
}
// =============================================================================
// Volatile flags shared with d3d9_hook (read by DIP hook)
// =============================================================================
@@ -81,6 +94,10 @@ fn renderDrawDetour(this: u32, view_matrix: u32, batch_data: u32, batch_indices:
api.initD3D9Deferred();
}
// AutoFix: OutlineDebug() previously performed lateRehookIfLost() by hand.
// Check periodically from this always-active native model hook instead.
autoRepairD3D9Hooks();
// Skip reordering if nothing to outline or too many batches
if (!tracker.enabled or !tracker.hasTargets() or batch_count == 0 or batch_count > MAX_REORDER) {
render_draw_hook.callOriginal(.{ this, view_matrix, batch_data, batch_indices, batch_count });
+129 -18
View File
@@ -4,11 +4,12 @@
//! and a Lua C callback for `/wu outline` commands.
const std = @import("std");
const hook = @import("zhook");
const lua = @import("../lua.zig");
const logging = @import("../logging.zig");
const tracker = @import("tracker.zig");
const model_hook = @import("model_hook.zig");
const d3d9_hook = @import("d3d9_hook.zig");
const wow = @import("../wow.zig");
const WINAPI = std.builtin.CallingConvention.winapi;
const mod_mutex = @import("../mutex.zig");
@@ -18,6 +19,63 @@ var log: logging.Logger = .{};
var g_mutex: ?*anyopaque = null;
var g_is_hook_owner: bool = false;
var g_model_hooks_installed: bool = false;
noinline fn luaGetTopNative(L: lua.State) i32 {
var result: i32 = undefined;
asm volatile (
\\mov $0x6F3070, %%eax
\\call *%%eax
: [ret] "={eax}" (result),
: [state] "{ecx}" (@intFromPtr(L)),
);
return result;
}
noinline fn luaIsStringNative(L: lua.State, index: i32) bool {
var result: u32 = undefined;
asm volatile (
\\mov $0x6F3510, %%eax
\\call *%%eax
: [ret] "={eax}" (result),
: [state] "{ecx}" (@intFromPtr(L)),
[index] "{edx}" (index),
);
return result != 0;
}
noinline fn luaToStringNative(L: lua.State, index: i32) ?[*:0]const u8 {
var result: usize = undefined;
asm volatile (
\\mov $0x6F3690, %%eax
\\call *%%eax
: [ret] "={eax}" (result),
: [state] "{ecx}" (@intFromPtr(L)),
[index] "{edx}" (index),
);
return if (result == 0) null else @ptrFromInt(result);
}
noinline fn luaPushBooleanNative(L: lua.State, value: i32) void {
asm volatile (
\\mov $0x6F39F0, %%eax
\\call *%%eax
:
: [state] "{ecx}" (@intFromPtr(L)),
[value] "{edx}" (value),
);
}
noinline fn luaPushStringNative(L: lua.State, value: [*:0]const u8) void {
asm volatile (
\\mov $0x6F3890, %%eax
\\call *%%eax
:
: [state] "{ecx}" (@intFromPtr(L)),
[value] "{edx}" (@intFromPtr(value)),
);
}
pub fn isActive() bool {
return g_is_hook_owner;
@@ -37,6 +95,7 @@ pub fn init() bool {
log = logging.Logger.open(module_name, .console);
tracker.initLogger();
if (!model_hook.installHooks()) return false;
g_model_hooks_installed = true;
return true;
}
@@ -55,6 +114,7 @@ pub fn cleanup() void {
log.close();
mod_mutex.release(&g_mutex);
}
g_model_hooks_installed = false;
g_is_hook_owner = false;
}
@@ -74,28 +134,79 @@ pub fn isEnabled() bool {
/// OutlineCommand() → returns (enabled: bool)
/// OutlineCommand("on") → enable outlines
/// OutlineCommand("off") → disable outlines
pub fn outlineCommand(L: *anyopaque) callconv(.c) u32 {
const nargs = hook.call(fn (usize) callconv(hook.cc.fastcall) i32, 0x6F3070, .{@intFromPtr(L)}); // lua_gettop
pub fn outlineCommand(L: lua.State) callconv(.{ .x86_thiscall = .{} }) u32 {
const nargs = luaGetTopNative(L);
if (nargs >= 1) {
// Check if first arg is a string
const is_str = hook.call(fn (usize, u32) callconv(hook.cc.fastcall) u32, 0x6F3510, .{ @intFromPtr(L), 1 }); // lua_isstring
if (is_str != 0) {
const str_ptr = hook.call(fn (usize, u32) callconv(hook.cc.fastcall) u32, 0x6F3690, .{ @intFromPtr(L), 1 }); // lua_tostring
if (str_ptr != 0) {
const s: [*:0]const u8 = @ptrFromInt(str_ptr);
const span = @import("std").mem.span(s);
if (eql(span, "on") or eql(span, "enable")) {
setEnabled(true);
} else if (eql(span, "off") or eql(span, "disable")) {
setEnabled(false);
}
if (nargs >= 1 and luaIsStringNative(L, 1)) {
if (luaToStringNative(L, 1)) |s| {
const span = std.mem.span(s);
if (eql(span, "on") or eql(span, "enable")) {
setEnabled(true);
} else if (eql(span, "off") or eql(span, "disable")) {
setEnabled(false);
}
}
}
// Push current state as boolean
hook.call(fn (usize, u32) callconv(hook.cc.fastcall) void, 0x6F39F0, .{ @intFromPtr(L), @as(u32, if (isEnabled()) 1 else 0) }); // lua_pushboolean
luaPushBooleanNative(L, if (isEnabled()) 1 else 0);
return 1;
}
fn syncTargetFromLuaContext() void {
// AutoFix: perform the same safe target publication that OutlineDebug()
// used, but from an automatic Lua/main-thread callback.
const player_guid = wow.getPlayerGUID();
const target_guid = wow.getTargetGUID();
const player_obj = if (player_guid != 0) wow.getObjectByGUID(player_guid) else 0;
const target_obj = if (target_guid != 0) wow.getObjectByGUID(target_guid) else 0;
tracker.setDebugPinnedObjects(player_obj, target_obj);
if (player_guid != 0) tracker.debug_unit_player_guid_seen = true;
if (target_guid != 0) tracker.debug_unit_target_guid_seen = true;
}
/// Automatic target publication used by the embedded addon.
/// No user macro/command is required.
pub fn outlineSyncTarget(_: lua.State) callconv(.{ .x86_thiscall = .{} }) u32 {
syncTargetFromLuaContext();
return 0;
}
/// Return a compact sticky diagnostic line for in-game testing.
/// Usage:
/// /run DEFAULT_CHAT_FRAME:AddMessage(OutlineDebug())
pub fn outlineDebug(L: lua.State) callconv(.{ .x86_thiscall = .{} }) u32 {
// Debug keeps the exact same automatic publication path.
syncTargetFromLuaContext();
_ = d3d9_hook.lateRehookIfLost();
const live = d3d9_hook.getLiveHookState();
var buf: [336]u8 = undefined;
const msg = std.fmt.bufPrintZ(
&buf,
"OutlineDBG shst={d} hooks={d}{d}{d} tgt={d} mdl={d} sb={d} rs={d} fx={d} odip={d} cache={d} pipe={d}/{d}",
.{
d3d9_hook.debug_shader_stage,
@intFromBool(live.endscene_ours),
@intFromBool(live.dip_ours),
@intFromBool(live.reset_ours),
@intFromBool(tracker.debug_target_seen),
@intFromBool(tracker.debug_target_model_seen),
@intFromBool(d3d9_hook.debug_state_block_seen),
@intFromBool(d3d9_hook.debug_outer_state_restore_seen),
@intFromBool(d3d9_hook.debug_additive_skipped_seen),
@intFromBool(d3d9_hook.debug_outline_dip_seen),
@intFromBool(d3d9_hook.debug_cached_draw_seen),
@intFromBool(d3d9_hook.debug_pipeline_entered_seen),
@intFromBool(d3d9_hook.debug_pipeline_ready_seen),
},
) catch {
luaPushStringNative(L, "OutlineDBG format error");
return 1;
};
luaPushStringNative(L, msg.ptr);
return 1;
}
+75 -52
View File
@@ -182,7 +182,10 @@ fn addOutlineEntry(model_ptr: u32, cat: types.ModelCategory, mark: u8) void {
// Diagnostic: count classified models by category
switch (cat) {
.target => diag.classify_target += 1,
.target => {
diag.classify_target += 1;
debug_target_model_seen = true;
},
.raid_marked => diag.classify_raid_mark += 1,
.dead_player => diag.classify_dead_player += 1,
.none => {},
@@ -197,6 +200,13 @@ fn addOutlineEntry(model_ptr: u32, cat: types.ModelCategory, mark: u8) void {
/// Stores object pointers directly so classifyModel can match model
/// back-pointers without any pointer dereferencing.
pub fn scanObjects() void {
debug_scan_called_seen = true;
const gen = @atomicRmw(u32, &debug_scan_generation, .Add, 1, .seq_cst) + 1;
const published_at = @atomicLoad(u32, &debug_publish_scan_generation, .seq_cst);
if (published_at != 0 and gen > published_at) {
debug_scan_after_publish_seen = true;
}
// Clear per-frame sets
frame_outline_count = 0;
tracked_obj_count = 0;
@@ -204,62 +214,24 @@ pub fn scanObjects() void {
game_obj_model_count = 0;
resetDiag();
if (!wow.isInGame()) return;
const local_player = wow.getLocalPlayer();
if (local_player == 0) return;
// Local player renders before outline targets (occludes outlines) unless
// the local player IS an outline target (partition logic checks outline first).
if (game_obj_ptr_count < MAX_TRACKED_OBJS) {
// DEBUG10: EndScene must not call WoW object/game functions. On this client
// those functions only behave correctly from the Lua/main-thread context.
// Consume atomically published object pointers captured by OutlineDebug().
const local_player = @atomicLoad(u32, &debug_pinned_player_obj, .seq_cst);
if (local_player != 0) debug_pin_player_consumed_seen = true;
if (local_player != 0 and game_obj_ptr_count < MAX_TRACKED_OBJS) {
game_obj_ptrs[game_obj_ptr_count] = local_player;
game_obj_ptr_count += 1;
}
// Cache raid target GUIDs
wow.cacheRaidTargets();
// Resolve target to object pointer (highest priority - added first)
const target_guid = wow.getTargetGUID();
if (target_guid != 0) {
const target_obj = wow.getObjectByGUID(target_guid);
if (target_obj != 0) {
addTrackedObj(target_obj, .target, 0);
}
const target_obj = @atomicLoad(u32, &debug_pinned_target_obj, .seq_cst);
if (target_obj != 0) {
debug_pin_target_consumed_seen = true;
addTrackedObj(target_obj, .target, 0);
}
// Iterate all visible objects
var obj = wow.objectFirst();
while (obj != 0) : (obj = wow.objectNext(obj)) {
const obj_type = wow.getObjectType(obj);
const guid = wow.getObjectGUID(obj);
if (guid == 0) continue;
switch (obj_type) {
.player => {
if (wow.isUnitDead(obj) and wow.isUnitFriendly(obj, local_player)) {
addTrackedObj(obj, .dead_player, 0);
}
const mark = wow.getRaidMarkForGUID(guid);
if (mark != 0) addTrackedObj(obj, .raid_marked, mark);
},
.unit => {
const mark = wow.getRaidMarkForGUID(guid);
if (mark != 0) addTrackedObj(obj, .raid_marked, mark);
},
.corpse => {
if (!wow.isSkeletonCorpse(obj)) {
addTrackedObj(obj, .dead_player, 0);
}
},
.game_object => {
if (game_obj_ptr_count < MAX_TRACKED_OBJS) {
game_obj_ptrs[game_obj_ptr_count] = obj;
game_obj_ptr_count += 1;
}
},
else => {},
}
}
// Secondary categories (raid marks/dead players/game objects) are disabled
// in this diagnostic build until the target-only path is proven stable.
}
fn addTrackedObj(obj_ptr: u32, cat: types.ModelCategory, mark: u8) void {
@@ -283,7 +255,10 @@ fn addTrackedObj(obj_ptr: u32, cat: types.ModelCategory, mark: u8) void {
// Diagnostic: count tracked objects by category
switch (cat) {
.target => diag.scan_targets += 1,
.target => {
diag.scan_targets += 1;
debug_target_seen = true;
},
.raid_marked => diag.scan_raid_marks += 1,
.dead_player => diag.scan_dead_players += 1,
.none => {},
@@ -308,6 +283,54 @@ pub const Diag = struct {
};
pub var diag: Diag = .{};
// Sticky diagnostics for the debug build. They turn true once the corresponding
// stage has been observed and stay true for the whole process lifetime.
pub var debug_in_world_seen: bool = false;
pub var debug_object_manager_seen: bool = false;
pub var debug_player_guid_seen: bool = false;
pub var debug_local_player_seen: bool = false;
pub var debug_unit_player_guid_seen: bool = false;
pub var debug_unit_player_object_seen: bool = false;
pub var debug_target_guid_seen: bool = false;
pub var debug_target_object_seen: bool = false;
pub var debug_unit_target_guid_seen: bool = false;
pub var debug_unit_target_object_seen: bool = false;
pub var debug_object_scan_seen: bool = false;
pub var debug_scan_called_seen: bool = false;
pub var debug_pin_player_published_seen: bool = false;
pub var debug_pin_target_published_seen: bool = false;
pub var debug_pin_player_consumed_seen: bool = false;
pub var debug_pin_target_consumed_seen: bool = false;
var debug_scan_generation: u32 = 0;
var debug_publish_scan_generation: u32 = 0;
pub var debug_scan_after_publish_seen: bool = false;
// DEBUG9: objects captured explicitly from OutlineDebug() while executing in
// WoW's Lua/main-thread context. This lets us test the render/classification
// path without calling game object lookup functions from D3D9 EndScene.
var debug_pinned_player_obj: u32 = 0;
var debug_pinned_target_obj: u32 = 0;
pub fn setDebugPinnedObjects(player_obj: u32, target_obj: u32) void {
const gen = @atomicLoad(u32, &debug_scan_generation, .seq_cst);
@atomicStore(u32, &debug_publish_scan_generation, gen, .seq_cst);
@atomicStore(u32, &debug_pinned_player_obj, player_obj, .seq_cst);
@atomicStore(u32, &debug_pinned_target_obj, target_obj, .seq_cst);
if (player_obj != 0) debug_pin_player_published_seen = true;
if (target_obj != 0) debug_pin_target_published_seen = true;
if (player_obj != 0) {
debug_unit_player_object_seen = true;
debug_local_player_seen = true;
}
if (target_obj != 0) {
debug_unit_target_object_seen = true;
debug_target_object_seen = true;
}
}
pub var debug_target_seen: bool = false;
pub var debug_target_model_seen: bool = false;
var log: logging.Logger = .{};
pub fn initLogger() void {
+17
View File
@@ -84,6 +84,8 @@ pub const D3DRS = struct {
pub const DESTBLEND: u32 = 20;
pub const CULLMODE: u32 = 22;
pub const ZFUNC: u32 = 23;
pub const ALPHAREF: u32 = 24;
pub const ALPHAFUNC: u32 = 25;
pub const ALPHABLENDENABLE: u32 = 27;
pub const STENCILENABLE: u32 = 52;
pub const STENCILFAIL: u32 = 53;
@@ -105,6 +107,8 @@ pub const D3DRS = struct {
pub const D3DCMP_ALWAYS: u32 = 8;
pub const D3DCMP_EQUAL: u32 = 3;
pub const D3DCMP_LESSEQUAL: u32 = 4;
pub const D3DCMP_GREATER: u32 = 5;
pub const D3DCMP_GREATEREQUAL: u32 = 7;
pub const D3DSTENCILOP_KEEP: u32 = 1;
pub const D3DSTENCILOP_REPLACE: u32 = 3;
@@ -123,8 +127,12 @@ pub const D3DCLEAR_STENCIL: u32 = 4;
// D3D9 blend modes
// =============================================================================
pub const D3DBLEND_ZERO: u32 = 1;
pub const D3DBLEND_ONE: u32 = 2;
pub const D3DBLEND_SRCALPHA: u32 = 5;
pub const D3DBLEND_INVSRCALPHA: u32 = 6;
pub const D3DBLEND_DESTCOLOR: u32 = 9;
pub const D3DBLEND_INVDESTCOLOR: u32 = 10;
// =============================================================================
// D3D9 texture stage state IDs
@@ -133,11 +141,18 @@ pub const D3DBLEND_INVSRCALPHA: u32 = 6;
pub const D3DTSS = struct {
pub const COLOROP: u32 = 1;
pub const COLORARG1: u32 = 2;
pub const COLORARG2: u32 = 3;
pub const ALPHAOP: u32 = 4;
pub const ALPHAARG1: u32 = 5;
pub const ALPHAARG2: u32 = 6;
};
pub const D3DTOP_DISABLE: u32 = 1;
pub const D3DTOP_SELECTARG1: u32 = 2;
pub const D3DTA_DIFFUSE: u32 = 0;
pub const D3DTA_CURRENT: u32 = 1;
pub const D3DTA_TEXTURE: u32 = 2;
pub const D3DTA_TFACTOR: u32 = 3;
// =============================================================================
@@ -167,6 +182,7 @@ pub const D3DTEXF_POINT: u32 = 1;
pub const D3DFMT_A8R8G8B8: u32 = 21;
pub const D3DFMT_G16R16F: u32 = 112;
pub const D3DFMT_G32R32F: u32 = 115;
// =============================================================================
// D3D9 depth/stencil formats
@@ -216,6 +232,7 @@ pub const VT = struct {
pub const GetRenderState: usize = 58;
pub const GetTexture: usize = 64;
pub const SetTexture: usize = 65;
pub const GetTextureStageState: usize = 66;
pub const SetTextureStageState: usize = 67;
pub const GetSamplerState: usize = 68;
pub const SetSamplerState: usize = 69;
+29 -6
View File
@@ -57,6 +57,12 @@ pub fn isInGame() bool {
return hook.readMem(u32, o.IS_IN_WORLD) != 0;
}
/// The object manager is a more useful runtime readiness signal for the
/// standalone Outline path than IS_IN_WORLD on modified 1.12.1 clients.
pub fn hasObjectManager() bool {
return hook.readMem(u32, o.OBJECT_MANAGER_PTR) != 0;
}
pub fn objectFirst() u32 {
const obj_mgr = hook.readMem(u32, o.OBJECT_MANAGER_PTR);
if (obj_mgr == 0) return 0;
@@ -203,17 +209,32 @@ pub fn getMapId() u32 {
/// UnitGUID("player") / UnitGUID("target") → 64-bit GUID.
pub fn unitGUID(unit_id: [*:0]const u8) u64 {
return hook.call(fn ([*:0]const u8) callconv(hook.cc.fastcall) u64, o.FN_UNIT_GUID, .{unit_id});
// UnitGUID is __fastcall with its single argument in ECX.
// hook.call + hook.cc.fastcall is currently miscompiled by Zig 0.16 on x86
// as a stack argument. Because the callee consumes no stack argument, that
// leaked 4 bytes per call; Outline calls UnitGUID("player"/"target") every
// frame, so ESP drifted until execution returned into stack data.
//
// With a single register argument, x86_thiscall is ABI-compatible here:
// ECX=unit_id, no stack args, u64 returned in EDX:EAX.
const f_native: *const fn ([*:0]const u8) callconv(.{ .x86_thiscall = .{} }) u64 =
@ptrFromInt(o.FN_UNIT_GUID);
return @call(.never_tail, f_native, .{unit_id});
}
/// Resolve a GUID → object pointer via the object manager hash table.
pub fn getObjectByGUID(guid: u64) u32 {
if (guid == 0) return 0;
if (hook.readMem(u32, o.OBJECT_MANAGER_PTR) == 0) return 0;
const lo: u32 = @truncate(guid);
const hi: u32 = @truncate(guid >> 32);
// Do not gate this call on OBJECT_MANAGER_PTR. Nampower uses WoW's
// GetObjectPtr at 0x464870 directly, and on this modified client the
// legacy OBJECT_MANAGER_PTR global remains 0 even while the game is live.
// The stdcall(u64) ABI is equivalent on x86 to pushing lo + hi as two u32s.
const result = hook.call(fn (u32, u32) callconv(hook.cc.stdcall) u32, o.FN_GET_OBJECT_BY_GUID, .{ lo, hi });
// Guard: hash table can return stale/invalid pointers for destroyed objects
// Guard: hash table can return stale/invalid pointers for destroyed objects.
if (result != 0 and !isValidPtr(result)) return 0;
return result;
}
@@ -221,7 +242,6 @@ pub fn getObjectByGUID(guid: u64) u32 {
/// Split-GUID variant for callers that already have lo/hi parts.
pub fn getObjectByGUIDSplit(guid_lo: u32, guid_hi: u32) u32 {
if (guid_lo == 0 and guid_hi == 0) return 0;
if (hook.readMem(u32, o.OBJECT_MANAGER_PTR) == 0) return 0;
return hook.call(fn (u32, u32) callconv(hook.cc.stdcall) u32, o.FN_GET_OBJECT_BY_GUID, .{ guid_lo, guid_hi });
}
@@ -231,8 +251,10 @@ pub fn getPlayerGUID() u64 {
}
/// Get the local player's object pointer.
/// Use ClntObjMgrGetActivePlayer (0x468550) instead of UnitGUID("player").
/// This matches the path used by Nampower and does not depend on legacy globals.
pub fn getLocalPlayer() u32 {
const guid = unitGUID("player");
const guid = getPlayerGUID();
if (guid == 0) return 0;
return getObjectByGUID(guid);
}
@@ -317,8 +339,9 @@ pub fn getNameByObject(obj: u32) [*:0]const u8 {
}
/// Get the current target's GUID.
/// Read the locked target GUID directly (0xB4E2D8), as Nampower does.
pub fn getTargetGUID() u64 {
return unitGUID("target");
return readGUID(o.LOCKED_TARGET_GUID);
}
/// Check if a unit is friendly to the local player.