Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dde316a184 | |||
| 43df6fc452 | |||
| eff857398d | |||
| 091bd36858 | |||
| 8a8ea1e6e1 | |||
| ace2046ae8 | |||
| 1462cabed5 | |||
| b500f46d00 | |||
| ca209ae383 | |||
| ce7f57f412 |
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,54 +0,0 @@
|
||||
name: V34 ExitFix Baseline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- test/v34-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' \
|
||||
'V34 stable + process-exit safety only.' \
|
||||
'Runtime Outline code is otherwise identical to stable/outline-v34-autofix.' \
|
||||
'main 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_V34_EXITFIX_BASELINE.zip DLL Interface README_TEST.txt SHA256SUMS.txt)
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WeirdUtils-V34-EXITFIX-BASELINE
|
||||
path: dist/WeirdUtils_V34_EXITFIX_BASELINE.zip
|
||||
if-no-files-found: error
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
768442df41b7ec363c9827c860f139add2dfa5cd718915c3b55adfe93acb65cf dist/WeirdUtils_safe_core_V8_POLISH32_ORIGINAL_WeirdPerformance.zip
|
||||
+4
-116
@@ -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, ®isterOutlineCommandsDetour);
|
||||
_ = 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,17 +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: when Windows is terminating the whole
|
||||
// process, do not call heavy cleanup from DLL_PROCESS_DETACH.
|
||||
// Resources are reclaimed by the OS. Explicit FreeLibrary still
|
||||
// performs the normal uninstall path.
|
||||
if (reserved == null) uninstall();
|
||||
},
|
||||
0 => uninstall(),
|
||||
else => {},
|
||||
}
|
||||
return @enumFromInt(1);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
+136
-494
@@ -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,33 +520,33 @@ const jfa_prop_src =
|
||||
"mov oC0.xy, r8.xy\n" ++
|
||||
"mov oC0.zw, c1.xx\n";
|
||||
|
||||
/// V32 POLISH: hard 3px outline, no feather.
|
||||
/// c0 = (screen_width, screen_height, radius_px=3, 0).
|
||||
/// The shader squares radius_px and emits a binary 0/1 edge alpha.
|
||||
/// 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, 1.0, -0.002, 0.0\n" ++
|
||||
"dcl_2d s0\n" ++
|
||||
"dcl_2d s1\n" ++
|
||||
"dcl_2d s0\n" ++ // JFA result (nearest seed UV)
|
||||
"dcl_2d s1\n" ++ // silhouette (colour + width-encoded alpha)
|
||||
"dcl_texcoord0 v0\n" ++
|
||||
// Nearest seed and pixel-space squared distance.
|
||||
// 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, c0.w\n" ++
|
||||
// Seed colour.
|
||||
"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" ++
|
||||
// Hard radius test: dist² < radius² => alpha 1, otherwise 0.
|
||||
"mov r3.x, c0.z\n" ++
|
||||
"mul r3.x, r3.x, r3.x\n" ++
|
||||
"sub r3.y, r1.z, r3.x\n" ++
|
||||
"mov r6.w, c1.y\n" ++
|
||||
"cmp r4.w, r3.y, c0.w, r6.w\n" ++
|
||||
// Do not paint over the model interior.
|
||||
"texld r5, v0, s1\n" ++
|
||||
"add r5.x, r5.a, c1.z\n" ++
|
||||
"cmp r4.w, r5.x, c0.w, r4.w\n" ++
|
||||
"mov r4.xyz, r2.xyz\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
|
||||
@@ -779,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) {
|
||||
@@ -836,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.?);
|
||||
@@ -884,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;
|
||||
@@ -923,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();
|
||||
@@ -997,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);
|
||||
@@ -1042,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 ----
|
||||
@@ -1095,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;
|
||||
}
|
||||
@@ -1118,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);
|
||||
@@ -1174,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);
|
||||
@@ -1234,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);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
@@ -1375,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.?);
|
||||
@@ -1417,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);
|
||||
// V34: hard 3px edge unchanged; JFA seed precision is now full-float.
|
||||
c0 = [4]f32{ fw, fh, 3.0, 0.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);
|
||||
@@ -1440,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);
|
||||
@@ -1509,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);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user