Compare commits

..

10 Commits

Author SHA1 Message Date
Dusk-92 dde316a184 Clarify licensing and release provenance 2026-08-31 17:28:20 +02:00
Dusk-92 43df6fc452 Clarify public-domain scope and third-party exceptions 2026-08-31 17:27:56 +02:00
Dusk-92 eff857398d Document third-party licensing and provenance 2026-08-31 17:27:23 +02:00
Dusk-92 091bd36858 Document third-party licensing and provenance 2026-08-31 17:27:17 +02:00
Dusk-92 8a8ea1e6e1 Document third-party licensing and provenance 2026-08-31 17:27:10 +02:00
Dusk-92 ace2046ae8 Document third-party licensing and provenance 2026-08-31 17:27:04 +02:00
Dusk-92 1462cabed5 Document third-party licensing and provenance 2026-08-31 17:26:58 +02:00
Dusk-92 b500f46d00 Document third-party licensing and provenance 2026-08-31 17:26:51 +02:00
Dusk-92 ca209ae383 Document third-party licensing and provenance 2026-08-31 17:26:45 +02:00
Dusk-92 ce7f57f412 Document third-party licensing and provenance 2026-08-31 17:26:40 +02:00
28 changed files with 883 additions and 1768 deletions
-77
View File
@@ -1,77 +0,0 @@
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
@@ -1,146 +0,0 @@
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
@@ -1,161 +0,0 @@
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
@@ -1,167 +0,0 @@
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
@@ -1,249 +0,0 @@
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
@@ -1,55 +0,0 @@
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
@@ -0,0 +1,115 @@
# 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
@@ -0,0 +1,76 @@
# 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
@@ -0,0 +1,63 @@
# 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.
+54 -4
View File
@@ -25,7 +25,57 @@ For more information, please refer to <https://unlicense.org/>
---
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.
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.
+36
View File
@@ -0,0 +1,36 @@
# 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
@@ -0,0 +1,21 @@
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
@@ -0,0 +1,22 @@
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
@@ -0,0 +1,51 @@
# 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.
+37 -7
View File
@@ -1,6 +1,6 @@
# WeirdUtils
> **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 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 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,12 +261,15 @@ Most noticeable in cities, raids, during zone transitions, and in addon-heavy se
## Source Code
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.
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.
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.
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.
Fair warning to anyone building on this: these DLLs hook deeply into the client's
internals - memory layout, function addresses, rendering pipeline, input handling.
@@ -301,10 +304,37 @@ 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`. Everything else is unlicensed/public domain.
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.
---
## 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
@@ -0,0 +1,189 @@
# 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,7 +66,6 @@ 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).
@@ -325,7 +324,6 @@ 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",
@@ -359,12 +357,6 @@ 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;
@@ -1 +0,0 @@
768442df41b7ec363c9827c860f139add2dfa5cd718915c3b55adfe93acb65cf dist/WeirdUtils_safe_core_V8_POLISH32_ORIGINAL_WeirdPerformance.zip
+4 -113
View File
@@ -52,27 +52,6 @@ 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
@@ -92,23 +71,8 @@ pub const lua = @import("lua.zig");
// Game function wrappers
// =============================================================================
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 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 });
}
fn allocateGameBuffer(size: u32) ?[*]u8 {
@@ -129,8 +93,6 @@ 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));
@@ -714,16 +676,6 @@ 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 {
@@ -740,10 +692,6 @@ 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();
}
@@ -863,41 +811,6 @@ 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
@@ -927,25 +840,6 @@ 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) {
@@ -1048,14 +942,11 @@ comptime {
pub export fn DllMain(
_: ?*anyopaque,
reason: u32,
reserved: ?*anyopaque,
_: ?*anyopaque,
) callconv(WINAPI) std.os.windows.BOOL {
switch (reason) {
1 => install(),
0 => {
// Process-exit safety only; runtime Outline behavior is unchanged.
if (reserved == null) uninstall();
},
0 => uninstall(),
else => {},
}
return @enumFromInt(1);
-2
View File
@@ -166,8 +166,6 @@ 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,28 +8,11 @@ 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)
+139 -494
View File
@@ -57,108 +57,14 @@ 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 = 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;
}
var need_force_reset: bool = true;
// =============================================================================
// Shader resources
// =============================================================================
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 outline_ps: ?*anyopaque = null; // flat-color PS (silhouettes)
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
@@ -185,9 +91,7 @@ const D3DXAssembleShaderFn = *const fn (
// Render target resources
// =============================================================================
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_tex: ?*anyopaque = null; // A8R8G8B8 silhouette texture
var rt_silhouette_surf: ?*anyopaque = null;
var rt_jfa_a_tex: ?*anyopaque = null; // G16R16F JFA ping texture
var rt_jfa_a_surf: ?*anyopaque = null;
@@ -228,18 +132,6 @@ 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,
@@ -315,12 +207,6 @@ 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]);
@@ -341,38 +227,6 @@ 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]);
@@ -448,9 +302,7 @@ 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]);
const hr = f(dev, w, h, levels, usage, fmt, pool, out, 0);
if (hr < 0) debug_texture_create_hr = hr;
return hr;
return f(dev, w, h, levels, usage, fmt, pool, out, 0);
}
/// Get surface level 0 from a texture. Returns AddRef'd surface or null.
@@ -496,73 +348,53 @@ 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_material_tex != null and rt_silhouette_tex != null) return;
rt_silhouette_tex != null) return;
// Release old and create new
releaseResources();
resource_width = vp.Width;
resource_height = vp.Height;
// 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)
// 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
// 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) {
// 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) {
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
// 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) {
// 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) {
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_material_surf, &rt_silhouette_surf, &rt_jfa_a_surf, &rt_jfa_b_surf,
&rt_silhouette_surf, &rt_jfa_a_surf, &rt_jfa_b_surf,
}) |surf_ptr| {
if (surf_ptr.*) |s| {
comRelease(s);
@@ -570,7 +402,7 @@ fn releaseResources() void {
}
}
inline for (.{
&rt_material_tex, &rt_silhouette_tex, &rt_jfa_a_tex, &rt_jfa_b_tex,
&rt_silhouette_tex, &rt_jfa_a_tex, &rt_jfa_b_tex,
}) |tex_ptr| {
if (tex_ptr.*) |t| {
comRelease(t);
@@ -588,46 +420,6 @@ 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 =
@@ -655,17 +447,13 @@ 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, r7.xy, v0.xy\n" ++
"mad r4.xy, c2.xy, c0.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" ++
@@ -673,7 +461,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, r7.xy, v0.xy\n" ++
"mad r4.xy, c3.xy, c0.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" ++
@@ -681,7 +469,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, r7.xy, v0.xy\n" ++
"mad r4.xy, c4.xy, c0.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" ++
@@ -689,7 +477,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, r7.xy, v0.xy\n" ++
"mad r4.xy, c5.xy, c0.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" ++
@@ -697,7 +485,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, r7.xy, v0.xy\n" ++
"mad r4.xy, c6.xy, c0.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" ++
@@ -705,7 +493,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, r7.xy, v0.xy\n" ++
"mad r4.xy, c7.xy, c0.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" ++
@@ -713,7 +501,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, r7.xy, v0.xy\n" ++
"mad r4.xy, c8.xy, c0.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" ++
@@ -721,7 +509,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, r7.xy, v0.xy\n" ++
"mad r4.xy, c9.xy, c0.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" ++
@@ -732,31 +520,34 @@ const jfa_prop_src =
"mov oC0.xy, r8.xy\n" ++
"mov oC0.zw, c1.xx\n";
/// 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.
/// JFA decode + composite: compute distance to nearest seed, threshold, output outline.
/// c0 = (screen_width, screen_height, 4.0, 0.0) set by CPU.
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" ++
"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)
"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" ++
"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";
"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";
/// Debug: composite silhouette RT directly. Forces alpha to 1.0 where silhouette
/// has any content (alpha >= 0.002), 0.0 elsewhere. Bypasses JFA entirely.
@@ -776,55 +567,33 @@ 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 solid silhouettes) ---
// --- Flat-colour PS (for 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) {
@@ -833,42 +602,14 @@ 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;
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);
}
if (assemble(src, @intCast(len), null, null, 0, &code, &err_buf) < 0 or code == null) {
if (err_buf) |e| comRelease(e);
return null;
}
defer comRelease(code.?);
@@ -881,16 +622,12 @@ 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]);
const create_hr = create(device, buf_ptr, &ps_out);
if (create_hr < 0) {
debug_shader_create_hr = create_hr;
return null;
}
if (create(device, buf_ptr, &ps_out) < 0) return null;
return ps_out;
}
fn releaseShaders() void {
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| {
inline for (.{ &outline_ps, &jfa_init_ps, &jfa_prop_ps, &jfa_decode_ps, &debug_sil_ps }) |ps| {
if (ps.*) |p| {
comRelease(p);
ps.* = null;
@@ -920,10 +657,11 @@ fn buildFullscreenQuad(w: u32, h: u32) [4]QuadVertex {
// =============================================================================
fn hkEndScene(device: *anyopaque) callconv(hook.cc.stdcall) i32 {
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.
// One-time: check if depth/stencil surface has stencil bits.
if (need_force_reset) {
need_force_reset = false;
forceD24S8IfNeeded(device);
}
// Per-frame: scan objects for outline tracking
tracker.scanObjects();
@@ -994,14 +732,11 @@ 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);
@@ -1039,33 +774,52 @@ 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;
}
// 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);
// 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;
}
// ---- Normal path ----
@@ -1092,20 +846,6 @@ 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;
}
@@ -1115,25 +855,11 @@ 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_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 (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 material_mask_ps == null or rt_silhouette_surf == null) return;
debug_pipeline_ready_seen = true;
if (outline_ps == null or rt_silhouette_surf == null) return;
var vp: types.D3DVIEWPORT9 = .{};
deviceGetViewport(device, &vp);
@@ -1171,29 +897,8 @@ 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);
@@ -1231,83 +936,57 @@ 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);
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);
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);
}
_ = 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);
}
// =====================================================================
@@ -1372,7 +1051,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.?);
@@ -1414,8 +1093,7 @@ 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);
// V36: thin 1.5px core plus a subtle 3px halo.
c0 = [4]f32{ fw, fh, 1.5, 3.0 };
c0 = [4]f32{ fw, fh, 4.0, 0.0 };
deviceSetPSConstF(device, 0, &c0);
deviceSetPtr(device, types.VT.SetPixelShader, jfa_decode_ps.?);
deviceSetRS(device, types.D3DRS.ALPHABLENDENABLE, 1);
@@ -1437,17 +1115,7 @@ 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);
@@ -1506,65 +1174,42 @@ fn hasStencilBits(fmt: u32) bool {
fmt == types.D3DFMT_D24X4S4 or fmt == types.D3DFMT_D15S1;
}
fn queryStencilFormat(device: *anyopaque) u32 {
fn forceD24S8IfNeeded(device: *anyopaque) void {
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 0;
const ds = pDS orelse return 0;
if (getDS(device, &pDS) < 0) return;
const ds = pDS orelse return;
defer comRelease(ds);
var desc: types.D3DSURFACE_DESC = .{};
const getDesc: *const fn (*anyopaque, *types.D3DSURFACE_DESC) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(ds)[12]);
const hr = getDesc(ds, &desc);
comRelease(ds);
if (hr < 0) return 0;
return desc.Format;
}
if (getDesc(ds, &desc) < 0) 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;
}
if (hasStencilBits(desc.Format)) return;
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 false;
const swap = pSwap orelse return false;
if (getSC(device, 0, &pSwap) < 0) return;
const swap = pSwap orelse return;
defer comRelease(swap);
var pp: types.D3DPRESENT_PARAMETERS = .{};
const getPP: *const fn (*anyopaque, *types.D3DPRESENT_PARAMETERS) callconv(hook.cc.stdcall) i32 =
@ptrFromInt(vt(swap)[9]);
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;
if (getPP(swap, &pp) < 0) return;
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]);
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;
_ = resetFn(device, &pp);
}
// =============================================================================
-17
View File
@@ -42,19 +42,6 @@ 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)
// =============================================================================
@@ -94,10 +81,6 @@ 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 });
+18 -129
View File
@@ -4,12 +4,11 @@
//! and a Lua C callback for `/wu outline` commands.
const std = @import("std");
const lua = @import("../lua.zig");
const hook = @import("zhook");
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");
@@ -19,63 +18,6 @@ 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;
@@ -95,7 +37,6 @@ 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;
}
@@ -114,7 +55,6 @@ pub fn cleanup() void {
log.close();
mod_mutex.release(&g_mutex);
}
g_model_hooks_installed = false;
g_is_hook_owner = false;
}
@@ -134,79 +74,28 @@ pub fn isEnabled() bool {
/// OutlineCommand() → returns (enabled: bool)
/// OutlineCommand("on") → enable outlines
/// OutlineCommand("off") → disable outlines
pub fn outlineCommand(L: lua.State) callconv(.{ .x86_thiscall = .{} }) u32 {
const nargs = luaGetTopNative(L);
pub fn outlineCommand(L: *anyopaque) callconv(.c) u32 {
const nargs = hook.call(fn (usize) callconv(hook.cc.fastcall) i32, 0x6F3070, .{@intFromPtr(L)}); // lua_gettop
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);
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);
}
}
}
}
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);
// 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
return 1;
}
+52 -75
View File
@@ -182,10 +182,7 @@ fn addOutlineEntry(model_ptr: u32, cat: types.ModelCategory, mark: u8) void {
// Diagnostic: count classified models by category
switch (cat) {
.target => {
diag.classify_target += 1;
debug_target_model_seen = true;
},
.target => diag.classify_target += 1,
.raid_marked => diag.classify_raid_mark += 1,
.dead_player => diag.classify_dead_player += 1,
.none => {},
@@ -200,13 +197,6 @@ 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;
@@ -214,24 +204,62 @@ pub fn scanObjects() void {
game_obj_model_count = 0;
resetDiag();
// 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) {
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) {
game_obj_ptrs[game_obj_ptr_count] = local_player;
game_obj_ptr_count += 1;
}
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);
// 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);
}
}
// Secondary categories (raid marks/dead players/game objects) are disabled
// in this diagnostic build until the target-only path is proven stable.
// 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 => {},
}
}
}
fn addTrackedObj(obj_ptr: u32, cat: types.ModelCategory, mark: u8) void {
@@ -255,10 +283,7 @@ fn addTrackedObj(obj_ptr: u32, cat: types.ModelCategory, mark: u8) void {
// Diagnostic: count tracked objects by category
switch (cat) {
.target => {
diag.scan_targets += 1;
debug_target_seen = true;
},
.target => diag.scan_targets += 1,
.raid_marked => diag.scan_raid_marks += 1,
.dead_player => diag.scan_dead_players += 1,
.none => {},
@@ -283,54 +308,6 @@ 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,8 +84,6 @@ 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;
@@ -107,8 +105,6 @@ 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;
@@ -127,12 +123,8 @@ 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
@@ -141,18 +133,11 @@ pub const D3DBLEND_INVDESTCOLOR: u32 = 10;
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;
// =============================================================================
@@ -182,7 +167,6 @@ 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
@@ -232,7 +216,6 @@ 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;
+6 -29
View File
@@ -57,12 +57,6 @@ 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;
@@ -209,32 +203,17 @@ pub fn getMapId() u32 {
/// UnitGUID("player") / UnitGUID("target") → 64-bit GUID.
pub fn unitGUID(unit_id: [*:0]const u8) u64 {
// 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});
return hook.call(fn ([*:0]const u8) callconv(hook.cc.fastcall) u64, o.FN_UNIT_GUID, .{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;
}
@@ -242,6 +221,7 @@ 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 });
}
@@ -251,10 +231,8 @@ 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 = getPlayerGUID();
const guid = unitGUID("player");
if (guid == 0) return 0;
return getObjectByGUID(guid);
}
@@ -339,9 +317,8 @@ 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 readGUID(o.LOCKED_TARGET_GUID);
return unitGUID("target");
}
/// Check if a unit is friendly to the local player.