Compare commits
156 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa526303e1 | |||
| c3e494b2f7 | |||
| 635eb62ecc | |||
| 4e8466033e | |||
| e9d9858151 | |||
| 77ed24e68a | |||
| 26dea2e586 | |||
| 8f22c90401 | |||
| 6570829e27 | |||
| ef6c4d08c1 | |||
| d6c53ed634 | |||
| 2a51ab6030 | |||
| 1969bb83bd | |||
| d6b191a4c4 | |||
| 5c09056d7c | |||
| fa9dd1c730 | |||
| 6ddc2c5df9 | |||
| 4bbee94388 | |||
| a36ce6ea76 | |||
| db472789d3 | |||
| f95ab007d2 | |||
| 0ea0c4a94c | |||
| 09863ad9e3 | |||
| c82f2b9e52 | |||
| 49a8644d13 | |||
| c35b95a4c4 | |||
| 2165887e6b | |||
| 63c929ceb6 | |||
| 9f58796ca1 | |||
| f81851bb84 | |||
| 35b3d00519 | |||
| 80b1177da9 | |||
| c70c5a37ba | |||
| 6354255965 | |||
| 70e7a2f24f | |||
| d024a5ff69 | |||
| ebdfa57d88 | |||
| 5bb482e0c2 | |||
| 6c9e951bf5 | |||
| d3f8dcd76f | |||
| 707481d143 | |||
| 5f25e2494c | |||
| eb810db78c | |||
| 85507f7e73 | |||
| 8ce131c614 | |||
| f2f0b789bb | |||
| bb26e3fd71 | |||
| 7fcd97c5fa | |||
| a0ca341177 | |||
| 1ea8067930 | |||
| d344f74fbf | |||
| ec5e5f189e | |||
| d5a3a0e925 | |||
| ef0e32de53 | |||
| e2f3b3092a | |||
| dd8286559d | |||
| 9d1673b359 | |||
| 3b5246e0ee | |||
| 11d219e54f | |||
| a92d9caec1 | |||
| 6ef67ff11c | |||
| 9a51cc6e1a | |||
| ca445ef9bd | |||
| 8d2db15f93 | |||
| 83627ea829 | |||
| dab770c018 | |||
| a933f0b639 | |||
| 68206a4347 | |||
| b65e441261 | |||
| 5b95716a82 | |||
| 774763f848 | |||
| 91607bc42f | |||
| 2558cd9709 | |||
| a11ff8d82b | |||
| 03fd1c7f38 | |||
| 67817052b7 | |||
| 457b15999b | |||
| 4d3bae29fb | |||
| 605ca09804 | |||
| 220236855f | |||
| fab99d1f96 | |||
| 68ccdb253f | |||
| a6bf31160f | |||
| 9a3d431d32 | |||
| 441a97646e | |||
| e27fcf1eba | |||
| 66a77224e3 | |||
| 1f1420ea53 | |||
| 0b8b415bab | |||
| 6392a960a0 | |||
| 94e1189456 | |||
| eb07e1d237 | |||
| 52361a9e42 | |||
| 9674807ea4 | |||
| 1ed94e0387 | |||
| 7797221071 | |||
| 07ec0b9b74 | |||
| 9c536db53c | |||
| 4acbd4e965 | |||
| 380f62d5ec | |||
| 3751675336 | |||
| 659fcceb85 | |||
| 2c3b35e788 | |||
| 0eb5cc2520 | |||
| 2b5c53548d | |||
| cc7a5dd7f9 | |||
| 57fa079361 | |||
| 0ac3809942 | |||
| 41a53ce388 | |||
| 50c1a0b508 | |||
| 9f9e56e764 | |||
| f1399599d5 | |||
| 4e8791687b | |||
| d4df1143ce | |||
| cdd3724c8e | |||
| e0f8592015 | |||
| 8e6d8e292e | |||
| 8a0c8eec18 | |||
| 35f90c85fb | |||
| c575ec8498 | |||
| 2aeee3fceb | |||
| 6ef29ba99f | |||
| e5acd33f0c | |||
| fb4fb880e5 | |||
| 61cf447248 | |||
| 1314860bde | |||
| 134e5a37f9 | |||
| bff19dd95b | |||
| 4eb37f4c23 | |||
| 3ee72d70d6 | |||
| b714c38ab7 | |||
| bcf8cefb35 | |||
| 1632d98797 | |||
| c890a6bc69 | |||
| 086dd17a31 | |||
| b128c01a7c | |||
| 33ec80d742 | |||
| ad706ccb9a | |||
| d7f93b3c00 | |||
| 62026da380 | |||
| 45635455b9 | |||
| 37757a2347 | |||
| 855ea340cc | |||
| 6e0c0850e8 | |||
| 17af79dff4 | |||
| a55f43a5d5 | |||
| 126f617ca1 | |||
| 43839b2b41 | |||
| dbdb513e94 | |||
| 4aae16646c | |||
| 5fe9a92672 | |||
| b587992a6c | |||
| bbcc70fa0d | |||
| ebccbee66a | |||
| 68c0ad2b75 | |||
| 7bf5a11011 |
@@ -0,0 +1,146 @@
|
|||||||
|
name: Test safe standalone core V4
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- test/safe-core-v4
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: mlugg/setup-zig@v2
|
||||||
|
with:
|
||||||
|
version: 0.16.0
|
||||||
|
|
||||||
|
- name: Build safe standalone variants
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
zig build all-variants -Doptimize=ReleaseSmall \
|
||||||
|
-Dweirdperformance=false \
|
||||||
|
-Doutline=true
|
||||||
|
|
||||||
|
- name: Verify minimal core layout
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python3 - <<'PY'
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
root = Path("zig-out/variants")
|
||||||
|
|
||||||
|
addr = {
|
||||||
|
"lua_protection": 0x42A320,
|
||||||
|
"register_commands": 0x490250,
|
||||||
|
"glue_commands": 0x46ABB0,
|
||||||
|
"engine_init": 0x46A400,
|
||||||
|
"logout": 0x491180,
|
||||||
|
"shutdown": 0x490BD0,
|
||||||
|
"file_fallback": 0x648620,
|
||||||
|
"open_file": 0x6477C0,
|
||||||
|
"file_size": 0x6487F0,
|
||||||
|
"read_file": 0x648460,
|
||||||
|
"cleanup_file": 0x648730,
|
||||||
|
"model_load": 0x71D4E0,
|
||||||
|
"check_file": 0x654DD0,
|
||||||
|
}
|
||||||
|
|
||||||
|
def has(data, value):
|
||||||
|
return value.to_bytes(4, "little") in data
|
||||||
|
|
||||||
|
outline = (root / "outline.dll").read_bytes()
|
||||||
|
custom = (root / "customassets.dll").read_bytes()
|
||||||
|
transmog = (root / "transmogfix.dll").read_bytes()
|
||||||
|
|
||||||
|
# Outline: only its Lua registration + engine init core hooks are allowed.
|
||||||
|
required_outline = ("register_commands", "engine_init")
|
||||||
|
forbidden_outline = tuple(k for k in addr if k not in required_outline)
|
||||||
|
for k in required_outline:
|
||||||
|
if not has(outline, addr[k]):
|
||||||
|
raise SystemExit(f"outline.dll missing required {k}")
|
||||||
|
bad = [k for k in forbidden_outline if has(outline, addr[k])]
|
||||||
|
if bad:
|
||||||
|
raise SystemExit(f"outline.dll still contains unwanted core hooks: {bad}")
|
||||||
|
print("outline.dll: minimal core verified")
|
||||||
|
|
||||||
|
# CustomAssets: only CheckFileExistence is allowed from the generic core.
|
||||||
|
if not has(custom, addr["check_file"]):
|
||||||
|
raise SystemExit("customassets.dll missing CheckFileExistence")
|
||||||
|
bad = [k for k,v in addr.items() if k != "check_file" and has(custom, v)]
|
||||||
|
if bad:
|
||||||
|
raise SystemExit(f"customassets.dll still contains unwanted core hooks: {bad}")
|
||||||
|
print("customassets.dll: minimal core verified")
|
||||||
|
|
||||||
|
# TransmogFix: none of the generic core hook target addresses should remain.
|
||||||
|
bad = [k for k,v in addr.items() if has(transmog, v)]
|
||||||
|
if bad:
|
||||||
|
raise SystemExit(f"transmogfix.dll still contains unwanted core hooks: {bad}")
|
||||||
|
print("transmogfix.dll: minimal core verified")
|
||||||
|
|
||||||
|
# Compatibility repair must exist in customassets and outline.
|
||||||
|
for name, data in (("outline.dll", outline), ("customassets.dll", custom)):
|
||||||
|
for gate in (0x654B5C, 0x654B6A):
|
||||||
|
if not has(data, gate):
|
||||||
|
raise SystemExit(f"{name} missing MPQ gate restore address 0x{gate:X}")
|
||||||
|
for seq in (bytes([0x74,0x25]), bytes([0x75,0x17])):
|
||||||
|
if seq not in data:
|
||||||
|
raise SystemExit(f"{name} missing original MPQ branch bytes {seq.hex()}")
|
||||||
|
print("legacy MPQ gate repair verified")
|
||||||
|
PY
|
||||||
|
|
||||||
|
- name: Stage package
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p package/DLL
|
||||||
|
mkdir -p package/Interface/AddOns/WeirdUtils_Outline
|
||||||
|
|
||||||
|
cp zig-out/variants/outline.dll package/DLL/
|
||||||
|
cp zig-out/variants/customassets.dll package/DLL/
|
||||||
|
cp zig-out/variants/transmogfix.dll package/DLL/
|
||||||
|
|
||||||
|
cp src/outline/addon/Outline.lua package/Interface/AddOns/WeirdUtils_Outline/
|
||||||
|
cp src/outline/addon/Bindings.xml package/Interface/AddOns/WeirdUtils_Outline/
|
||||||
|
cp src/outline/addon/WeirdUtils_Outline.toc package/Interface/AddOns/WeirdUtils_Outline/
|
||||||
|
|
||||||
|
cat > package/README_TEST.txt <<'EOF'
|
||||||
|
WeirdUtils Safe Standalone Core Test V4
|
||||||
|
|
||||||
|
This build fixes the second crash path observed in WoW's Lua engine.
|
||||||
|
|
||||||
|
outline.dll
|
||||||
|
- Keeps only the Player_LoadScriptFunctions hook required to register OutlineCommand.
|
||||||
|
- Keeps only the GameEngine_MainInitialize hook required to initialise the renderer.
|
||||||
|
- Does not register the shared WeirdUtils version table.
|
||||||
|
- Does not install generic MPQ/file hooks.
|
||||||
|
- Uses the external WeirdUtils_Outline addon included here.
|
||||||
|
|
||||||
|
customassets.dll
|
||||||
|
- Keeps only CheckFileExistence from the shared file core.
|
||||||
|
- No shared Lua registration hooks.
|
||||||
|
- No shared engine/logout/shutdown hooks.
|
||||||
|
- Repairs legacy File_FindInArchive NOPs if present.
|
||||||
|
|
||||||
|
transmogfix.dll
|
||||||
|
- Installs only TransmogFix's own module hooks.
|
||||||
|
- No shared Lua/file/engine/logout/shutdown hooks.
|
||||||
|
|
||||||
|
weirdperformance.dll
|
||||||
|
- Not built here.
|
||||||
|
- The final ZIP will use the user's original binary unchanged.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sha256sum package/DLL/*.dll > package/SHA256SUMS.txt
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: safe-core-v4
|
||||||
|
path: package/
|
||||||
|
if-no-files-found: error
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
name: Test safe standalone core V5
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- test/safe-core-v5
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: mlugg/setup-zig@v2
|
||||||
|
with:
|
||||||
|
version: 0.16.0
|
||||||
|
|
||||||
|
- name: Build safe standalone variants
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
zig build all-variants -Doptimize=ReleaseSmall \
|
||||||
|
-Dweirdperformance=false \
|
||||||
|
-Doutline=true
|
||||||
|
|
||||||
|
- name: Verify minimal core layout
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python3 - <<'PY'
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
root = Path("zig-out/variants")
|
||||||
|
|
||||||
|
addr = {
|
||||||
|
"lua_protection": 0x42A320,
|
||||||
|
"register_commands": 0x490250,
|
||||||
|
"glue_commands": 0x46ABB0,
|
||||||
|
"engine_init": 0x46A400,
|
||||||
|
"logout": 0x491180,
|
||||||
|
"shutdown": 0x490BD0,
|
||||||
|
"file_fallback": 0x648620,
|
||||||
|
"open_file": 0x6477C0,
|
||||||
|
"file_size": 0x6487F0,
|
||||||
|
"read_file": 0x648460,
|
||||||
|
"cleanup_file": 0x648730,
|
||||||
|
"model_load": 0x71D4E0,
|
||||||
|
"check_file": 0x654DD0,
|
||||||
|
}
|
||||||
|
|
||||||
|
def has(data, value):
|
||||||
|
return value.to_bytes(4, "little") in data
|
||||||
|
|
||||||
|
outline = (root / "outline.dll").read_bytes()
|
||||||
|
custom = (root / "customassets.dll").read_bytes()
|
||||||
|
transmog = (root / "transmogfix.dll").read_bytes()
|
||||||
|
|
||||||
|
# Outline: only its Lua registration + engine init core hooks are allowed.
|
||||||
|
required_outline = ("register_commands", "engine_init")
|
||||||
|
forbidden_outline = tuple(k for k in addr if k not in required_outline)
|
||||||
|
for k in required_outline:
|
||||||
|
if not has(outline, addr[k]):
|
||||||
|
raise SystemExit(f"outline.dll missing required {k}")
|
||||||
|
bad = [k for k in forbidden_outline if has(outline, addr[k])]
|
||||||
|
if bad:
|
||||||
|
raise SystemExit(f"outline.dll still contains unwanted core hooks: {bad}")
|
||||||
|
print("outline.dll: minimal core verified")
|
||||||
|
|
||||||
|
# CustomAssets: only CheckFileExistence is allowed from the generic core.
|
||||||
|
if not has(custom, addr["check_file"]):
|
||||||
|
raise SystemExit("customassets.dll missing CheckFileExistence")
|
||||||
|
bad = [k for k,v in addr.items() if k != "check_file" and has(custom, v)]
|
||||||
|
if bad:
|
||||||
|
raise SystemExit(f"customassets.dll still contains unwanted core hooks: {bad}")
|
||||||
|
print("customassets.dll: minimal core verified")
|
||||||
|
|
||||||
|
# TransmogFix: none of the generic core hook target addresses should remain.
|
||||||
|
bad = [k for k,v in addr.items() if has(transmog, v)]
|
||||||
|
if bad:
|
||||||
|
raise SystemExit(f"transmogfix.dll still contains unwanted core hooks: {bad}")
|
||||||
|
print("transmogfix.dll: minimal core verified")
|
||||||
|
|
||||||
|
# Compatibility repair must exist in customassets and outline.
|
||||||
|
for name, data in (("outline.dll", outline), ("customassets.dll", custom)):
|
||||||
|
for gate in (0x654B5C, 0x654B6A):
|
||||||
|
if not has(data, gate):
|
||||||
|
raise SystemExit(f"{name} missing MPQ gate restore address 0x{gate:X}")
|
||||||
|
for seq in (bytes([0x74,0x25]), bytes([0x75,0x17])):
|
||||||
|
if seq not in data:
|
||||||
|
raise SystemExit(f"{name} missing original MPQ branch bytes {seq.hex()}")
|
||||||
|
print("legacy MPQ gate repair verified")
|
||||||
|
PY
|
||||||
|
|
||||||
|
- name: Verify Outline Lua wrapper usage
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python3 - <<'PY'
|
||||||
|
from pathlib import Path
|
||||||
|
src = Path("src/outline/outline.zig").read_text()
|
||||||
|
if 'hook.call(fn (usize)' in src or '0x6F39F0' in src:
|
||||||
|
raise SystemExit("Outline still contains manual Lua API hook.call usage")
|
||||||
|
for required in ("lua.gettop(L)", "lua.isstring(L, 1)", "lua.tostring(L, 1)", "lua.pushboolean(L"):
|
||||||
|
if required not in src:
|
||||||
|
raise SystemExit(f"Missing expected Lua wrapper usage: {required}")
|
||||||
|
print("outline.dll source: direct lua.zig wrappers verified")
|
||||||
|
PY
|
||||||
|
|
||||||
|
- name: Stage package
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p package/DLL
|
||||||
|
mkdir -p package/Interface/AddOns/WeirdUtils_Outline
|
||||||
|
|
||||||
|
cp zig-out/variants/outline.dll package/DLL/
|
||||||
|
cp zig-out/variants/customassets.dll package/DLL/
|
||||||
|
cp zig-out/variants/transmogfix.dll package/DLL/
|
||||||
|
|
||||||
|
cp src/outline/addon/Outline.lua package/Interface/AddOns/WeirdUtils_Outline/
|
||||||
|
cp src/outline/addon/Bindings.xml package/Interface/AddOns/WeirdUtils_Outline/
|
||||||
|
cp src/outline/addon/WeirdUtils_Outline.toc package/Interface/AddOns/WeirdUtils_Outline/
|
||||||
|
|
||||||
|
cat > package/README_TEST.txt <<'EOF'
|
||||||
|
WeirdUtils Safe Standalone Core Test V5
|
||||||
|
|
||||||
|
This build also fixes OutlineCommand's Lua API calls after a crash at lua_pushboolean.
|
||||||
|
|
||||||
|
outline.dll
|
||||||
|
- Keeps only the Player_LoadScriptFunctions hook required to register OutlineCommand.
|
||||||
|
- Keeps only the GameEngine_MainInitialize hook required to initialise the renderer.
|
||||||
|
- Does not register the shared WeirdUtils version table.
|
||||||
|
- Does not install generic MPQ/file hooks.
|
||||||
|
- Uses the external WeirdUtils_Outline addon included here.
|
||||||
|
|
||||||
|
customassets.dll
|
||||||
|
- Keeps only CheckFileExistence from the shared file core.
|
||||||
|
- No shared Lua registration hooks.
|
||||||
|
- No shared engine/logout/shutdown hooks.
|
||||||
|
- Repairs legacy File_FindInArchive NOPs if present.
|
||||||
|
|
||||||
|
transmogfix.dll
|
||||||
|
- Installs only TransmogFix's own module hooks.
|
||||||
|
- No shared Lua/file/engine/logout/shutdown hooks.
|
||||||
|
|
||||||
|
weirdperformance.dll
|
||||||
|
- Not built here.
|
||||||
|
- The final ZIP will use the user's original binary unchanged.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sha256sum package/DLL/*.dll > package/SHA256SUMS.txt
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: safe-core-v5
|
||||||
|
path: package/
|
||||||
|
if-no-files-found: error
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
name: Test safe standalone core V7
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- test/safe-core-v7
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: mlugg/setup-zig@v2
|
||||||
|
with:
|
||||||
|
version: 0.16.0
|
||||||
|
|
||||||
|
- name: Build safe standalone variants
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
zig build all-variants -Doptimize=ReleaseSmall \
|
||||||
|
-Dweirdperformance=false \
|
||||||
|
-Doutline=true
|
||||||
|
|
||||||
|
- name: Verify minimal core layout
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python3 - <<'PY'
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
root = Path("zig-out/variants")
|
||||||
|
|
||||||
|
addr = {
|
||||||
|
"lua_protection": 0x42A320,
|
||||||
|
"register_commands": 0x490250,
|
||||||
|
"glue_commands": 0x46ABB0,
|
||||||
|
"engine_init": 0x46A400,
|
||||||
|
"logout": 0x491180,
|
||||||
|
"shutdown": 0x490BD0,
|
||||||
|
"file_fallback": 0x648620,
|
||||||
|
"open_file": 0x6477C0,
|
||||||
|
"file_size": 0x6487F0,
|
||||||
|
"read_file": 0x648460,
|
||||||
|
"cleanup_file": 0x648730,
|
||||||
|
"model_load": 0x71D4E0,
|
||||||
|
"check_file": 0x654DD0,
|
||||||
|
}
|
||||||
|
|
||||||
|
def has(data, value):
|
||||||
|
return value.to_bytes(4, "little") in data
|
||||||
|
|
||||||
|
outline = (root / "outline.dll").read_bytes()
|
||||||
|
custom = (root / "customassets.dll").read_bytes()
|
||||||
|
transmog = (root / "transmogfix.dll").read_bytes()
|
||||||
|
|
||||||
|
# Outline: only its Lua registration + engine init core hooks are allowed.
|
||||||
|
required_outline = ("register_commands", "engine_init")
|
||||||
|
forbidden_outline = tuple(k for k in addr if k not in required_outline)
|
||||||
|
for k in required_outline:
|
||||||
|
if not has(outline, addr[k]):
|
||||||
|
raise SystemExit(f"outline.dll missing required {k}")
|
||||||
|
bad = [k for k in forbidden_outline if has(outline, addr[k])]
|
||||||
|
if bad:
|
||||||
|
raise SystemExit(f"outline.dll still contains unwanted core hooks: {bad}")
|
||||||
|
print("outline.dll: minimal core verified")
|
||||||
|
|
||||||
|
# CustomAssets: only CheckFileExistence is allowed from the generic core.
|
||||||
|
if not has(custom, addr["check_file"]):
|
||||||
|
raise SystemExit("customassets.dll missing CheckFileExistence")
|
||||||
|
bad = [k for k,v in addr.items() if k != "check_file" and has(custom, v)]
|
||||||
|
if bad:
|
||||||
|
raise SystemExit(f"customassets.dll still contains unwanted core hooks: {bad}")
|
||||||
|
print("customassets.dll: minimal core verified")
|
||||||
|
|
||||||
|
# TransmogFix: none of the generic core hook target addresses should remain.
|
||||||
|
bad = [k for k,v in addr.items() if has(transmog, v)]
|
||||||
|
if bad:
|
||||||
|
raise SystemExit(f"transmogfix.dll still contains unwanted core hooks: {bad}")
|
||||||
|
print("transmogfix.dll: minimal core verified")
|
||||||
|
|
||||||
|
# Compatibility repair must exist in customassets and outline.
|
||||||
|
for name, data in (("outline.dll", outline), ("customassets.dll", custom)):
|
||||||
|
for gate in (0x654B5C, 0x654B6A):
|
||||||
|
if not has(data, gate):
|
||||||
|
raise SystemExit(f"{name} missing MPQ gate restore address 0x{gate:X}")
|
||||||
|
for seq in (bytes([0x74,0x25]), bytes([0x75,0x17])):
|
||||||
|
if seq not in data:
|
||||||
|
raise SystemExit(f"{name} missing original MPQ branch bytes {seq.hex()}")
|
||||||
|
print("legacy MPQ gate repair verified")
|
||||||
|
PY
|
||||||
|
|
||||||
|
- name: Verify Outline native Lua asm wrappers
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python3 - <<'PY'
|
||||||
|
from pathlib import Path
|
||||||
|
src = Path("src/outline/outline.zig").read_text()
|
||||||
|
for required in (
|
||||||
|
'luaGetTopNative',
|
||||||
|
'luaIsStringNative',
|
||||||
|
'luaToStringNative',
|
||||||
|
'luaPushBooleanNative',
|
||||||
|
'"{ecx}"',
|
||||||
|
'"{edx}"',
|
||||||
|
'0x6F39F0',
|
||||||
|
):
|
||||||
|
if required not in src:
|
||||||
|
raise SystemExit(f"Missing native register wrapper piece: {required}")
|
||||||
|
print("outline.dll source: native x86 register wrappers present")
|
||||||
|
PY
|
||||||
|
|
||||||
|
- name: Stage package
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p package/DLL
|
||||||
|
mkdir -p package/Interface/AddOns/WeirdUtils_Outline
|
||||||
|
|
||||||
|
cp zig-out/variants/outline.dll package/DLL/
|
||||||
|
cp zig-out/variants/customassets.dll package/DLL/
|
||||||
|
cp zig-out/variants/transmogfix.dll package/DLL/
|
||||||
|
|
||||||
|
cp src/outline/addon/Outline.lua package/Interface/AddOns/WeirdUtils_Outline/
|
||||||
|
cp src/outline/addon/Bindings.xml package/Interface/AddOns/WeirdUtils_Outline/
|
||||||
|
cp src/outline/addon/WeirdUtils_Outline.toc package/Interface/AddOns/WeirdUtils_Outline/
|
||||||
|
|
||||||
|
cat > package/README_TEST.txt <<'EOF'
|
||||||
|
WeirdUtils Safe Standalone Core Test V7
|
||||||
|
|
||||||
|
This build forces OutlineCommand's WoW Lua calls through x86 register assembly wrappers.
|
||||||
|
|
||||||
|
outline.dll
|
||||||
|
- Keeps only the Player_LoadScriptFunctions hook required to register OutlineCommand.
|
||||||
|
- Keeps only the GameEngine_MainInitialize hook required to initialise the renderer.
|
||||||
|
- Does not register the shared WeirdUtils version table.
|
||||||
|
- Does not install generic MPQ/file hooks.
|
||||||
|
- Uses the external WeirdUtils_Outline addon included here.
|
||||||
|
|
||||||
|
customassets.dll
|
||||||
|
- Keeps only CheckFileExistence from the shared file core.
|
||||||
|
- No shared Lua registration hooks.
|
||||||
|
- No shared engine/logout/shutdown hooks.
|
||||||
|
- Repairs legacy File_FindInArchive NOPs if present.
|
||||||
|
|
||||||
|
transmogfix.dll
|
||||||
|
- Installs only TransmogFix's own module hooks.
|
||||||
|
- No shared Lua/file/engine/logout/shutdown hooks.
|
||||||
|
|
||||||
|
weirdperformance.dll
|
||||||
|
- Not built here.
|
||||||
|
- The final ZIP will use the user's original binary unchanged.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sha256sum package/DLL/*.dll > package/SHA256SUMS.txt
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: safe-core-v7
|
||||||
|
path: package/
|
||||||
|
if-no-files-found: error
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
name: Test safe standalone core V8 Debug
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- test/safe-core-v8-debug
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# V8 debug diagnostics build
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: mlugg/setup-zig@v2
|
||||||
|
with:
|
||||||
|
version: 0.16.0
|
||||||
|
|
||||||
|
- name: Build safe standalone variants
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
zig build all-variants -Doptimize=ReleaseSmall \
|
||||||
|
-Dweirdperformance=false \
|
||||||
|
-Doutline=true
|
||||||
|
|
||||||
|
- name: Verify minimal core layout
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python3 - <<'PY'
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
root = Path("zig-out/variants")
|
||||||
|
|
||||||
|
addr = {
|
||||||
|
"lua_protection": 0x42A320,
|
||||||
|
"register_commands": 0x490250,
|
||||||
|
"glue_commands": 0x46ABB0,
|
||||||
|
"engine_init": 0x46A400,
|
||||||
|
"logout": 0x491180,
|
||||||
|
"shutdown": 0x490BD0,
|
||||||
|
"file_fallback": 0x648620,
|
||||||
|
"open_file": 0x6477C0,
|
||||||
|
"file_size": 0x6487F0,
|
||||||
|
"read_file": 0x648460,
|
||||||
|
"cleanup_file": 0x648730,
|
||||||
|
"model_load": 0x71D4E0,
|
||||||
|
"check_file": 0x654DD0,
|
||||||
|
}
|
||||||
|
|
||||||
|
def has(data, value):
|
||||||
|
return value.to_bytes(4, "little") in data
|
||||||
|
|
||||||
|
outline = (root / "outline.dll").read_bytes()
|
||||||
|
custom = (root / "customassets.dll").read_bytes()
|
||||||
|
transmog = (root / "transmogfix.dll").read_bytes()
|
||||||
|
|
||||||
|
# Outline: only its Lua registration + engine init core hooks are allowed.
|
||||||
|
required_outline = ("register_commands", "engine_init")
|
||||||
|
forbidden_outline = tuple(k for k in addr if k not in required_outline)
|
||||||
|
for k in required_outline:
|
||||||
|
if not has(outline, addr[k]):
|
||||||
|
raise SystemExit(f"outline.dll missing required {k}")
|
||||||
|
bad = [k for k in forbidden_outline if has(outline, addr[k])]
|
||||||
|
if bad:
|
||||||
|
raise SystemExit(f"outline.dll still contains unwanted core hooks: {bad}")
|
||||||
|
print("outline.dll: minimal core verified")
|
||||||
|
|
||||||
|
# CustomAssets: only CheckFileExistence is allowed from the generic core.
|
||||||
|
if not has(custom, addr["check_file"]):
|
||||||
|
raise SystemExit("customassets.dll missing CheckFileExistence")
|
||||||
|
bad = [k for k,v in addr.items() if k != "check_file" and has(custom, v)]
|
||||||
|
if bad:
|
||||||
|
raise SystemExit(f"customassets.dll still contains unwanted core hooks: {bad}")
|
||||||
|
print("customassets.dll: minimal core verified")
|
||||||
|
|
||||||
|
# TransmogFix: none of the generic core hook target addresses should remain.
|
||||||
|
bad = [k for k,v in addr.items() if has(transmog, v)]
|
||||||
|
if bad:
|
||||||
|
raise SystemExit(f"transmogfix.dll still contains unwanted core hooks: {bad}")
|
||||||
|
print("transmogfix.dll: minimal core verified")
|
||||||
|
|
||||||
|
# Compatibility repair must exist in customassets and outline.
|
||||||
|
for name, data in (("outline.dll", outline), ("customassets.dll", custom)):
|
||||||
|
for gate in (0x654B5C, 0x654B6A):
|
||||||
|
if not has(data, gate):
|
||||||
|
raise SystemExit(f"{name} missing MPQ gate restore address 0x{gate:X}")
|
||||||
|
for seq in (bytes([0x74,0x25]), bytes([0x75,0x17])):
|
||||||
|
if seq not in data:
|
||||||
|
raise SystemExit(f"{name} missing original MPQ branch bytes {seq.hex()}")
|
||||||
|
print("legacy MPQ gate repair verified")
|
||||||
|
PY
|
||||||
|
|
||||||
|
- name: Verify Outline registration + Lua ABI
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python3 - <<'PY'
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
main = Path("src/main.zig").read_text()
|
||||||
|
outline = Path("src/outline/outline.zig").read_text()
|
||||||
|
d3d9 = Path("src/outline/d3d9_hook.zig").read_text()
|
||||||
|
|
||||||
|
for required in (
|
||||||
|
'noinline fn registerFunction',
|
||||||
|
'0x704120',
|
||||||
|
'"{ecx}" (@intFromPtr(name))',
|
||||||
|
'"{edx}" (func_addr)',
|
||||||
|
):
|
||||||
|
if required not in main:
|
||||||
|
raise SystemExit(f"Missing explicit FrameScript registration ABI piece: {required}")
|
||||||
|
|
||||||
|
for required in (
|
||||||
|
'luaGetTopNative',
|
||||||
|
'luaIsStringNative',
|
||||||
|
'luaToStringNative',
|
||||||
|
'luaPushBooleanNative',
|
||||||
|
'"{ecx}"',
|
||||||
|
'"{edx}"',
|
||||||
|
'0x6F39F0',
|
||||||
|
'callconv(.{ .x86_thiscall = .{} })',
|
||||||
|
'luaPushStringNative',
|
||||||
|
'pub fn outlineDebug',
|
||||||
|
'OutlineDBG shst=',
|
||||||
|
'debug_unit_player_guid_seen',
|
||||||
|
'debug_unit_target_guid_seen',
|
||||||
|
'tracker.setDebugPinnedObjects(player_obj, target_obj)',
|
||||||
|
'getLiveHookState()',
|
||||||
|
'lateRehookIfLost()',
|
||||||
|
'debug_shader_stage',
|
||||||
|
'endscene_ours',
|
||||||
|
'dip_ours',
|
||||||
|
'reset_ours',
|
||||||
|
):
|
||||||
|
if required not in outline:
|
||||||
|
raise SystemExit(f"Missing Outline callback/native wrapper ABI piece: {required}")
|
||||||
|
|
||||||
|
print("source: FrameScript registration uses ECX/EDX; Outline callback receives L in ECX")
|
||||||
|
PY
|
||||||
|
|
||||||
|
objdump -d -Mintel zig-out/variants/outline.dll > /tmp/outline.disasm
|
||||||
|
python3 - <<'PY'
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
lines = Path("/tmp/outline.disasm").read_text(errors="replace").splitlines()
|
||||||
|
|
||||||
|
# registerFunction is explicit inline asm and source-checked above.
|
||||||
|
# Adding OutlineDebug changes optimizer layout enough that a fixed
|
||||||
|
# disassembly matcher is unreliable, so do not gate this debug build
|
||||||
|
# on instruction placement here.
|
||||||
|
|
||||||
|
# OutlineCommand must capture the Lua state from ECX. Search function
|
||||||
|
# prologues for the characteristic saved-ESI form and reject the old
|
||||||
|
# [ebp+8] capture used by the cdecl experiment.
|
||||||
|
good_callback = False
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if re.search(r"push\s+ebp\b", line, re.I):
|
||||||
|
block = "\n".join(lines[i:i + 12])
|
||||||
|
if re.search(r"mov\s+esi,ecx\b", block, re.I):
|
||||||
|
good_callback = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not good_callback:
|
||||||
|
raise SystemExit("BAD ABI: no callback body captures Lua state from ECX")
|
||||||
|
|
||||||
|
print("outline.dll machine code: registration and callback ABIs verified")
|
||||||
|
PY
|
||||||
|
|
||||||
|
- name: Verify direct player and target GUID path
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python3 - <<'PY'
|
||||||
|
from pathlib import Path
|
||||||
|
wow = Path("src/wow.zig").read_text()
|
||||||
|
offsets = Path("src/offsets.zig").read_text()
|
||||||
|
for required in (
|
||||||
|
'const guid = getPlayerGUID();',
|
||||||
|
'return readGUID(o.LOCKED_TARGET_GUID);',
|
||||||
|
):
|
||||||
|
if required not in wow:
|
||||||
|
raise SystemExit(f"Missing direct player/target GUID path: {required}")
|
||||||
|
for required in (
|
||||||
|
'FN_GET_PLAYER_GUID: usize = 0x00468550',
|
||||||
|
'LOCKED_TARGET_GUID: usize = 0x00B4E2D8',
|
||||||
|
'FN_GET_OBJECT_BY_GUID: usize = 0x464870',
|
||||||
|
):
|
||||||
|
if required not in offsets:
|
||||||
|
raise SystemExit(f"Missing verified WoW offset: {required}")
|
||||||
|
print("source: player GUID, target GUID and GUID->object path verified")
|
||||||
|
PY
|
||||||
|
|
||||||
|
- name: Stage package
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p package/DLL
|
||||||
|
mkdir -p package/Interface/AddOns/WeirdUtils_Outline
|
||||||
|
|
||||||
|
cp zig-out/variants/outline.dll package/DLL/
|
||||||
|
cp zig-out/variants/customassets.dll package/DLL/
|
||||||
|
cp zig-out/variants/transmogfix.dll package/DLL/
|
||||||
|
|
||||||
|
cp src/outline/addon/Outline.lua package/Interface/AddOns/WeirdUtils_Outline/
|
||||||
|
cp src/outline/addon/Bindings.xml package/Interface/AddOns/WeirdUtils_Outline/
|
||||||
|
cp src/outline/addon/WeirdUtils_Outline.toc package/Interface/AddOns/WeirdUtils_Outline/
|
||||||
|
|
||||||
|
cat > package/README_TEST.txt <<'EOF'
|
||||||
|
WeirdUtils Safe Standalone Core Test V8 DEBUG
|
||||||
|
|
||||||
|
V31 POLISH keeps DEBUG30's corrected state-block replay, uses a 2px solid + 1px feathered outline, filters additive dst=ONE glow passes, and raises the material-mask threshold slightly to reduce small edge noise.
|
||||||
|
|
||||||
|
outline.dll
|
||||||
|
- Adds OutlineDebug() sticky in-game diagnostics for the render pipeline.
|
||||||
|
- Keeps only the Player_LoadScriptFunctions hook required to register OutlineCommand/OutlineDebug.
|
||||||
|
- Keeps only the GameEngine_MainInitialize hook required to initialise the renderer.
|
||||||
|
- Does not register the shared WeirdUtils version table.
|
||||||
|
- Does not install generic MPQ/file hooks.
|
||||||
|
- Uses the external WeirdUtils_Outline addon included here.
|
||||||
|
|
||||||
|
customassets.dll
|
||||||
|
- Keeps only CheckFileExistence from the shared file core.
|
||||||
|
- No shared Lua registration hooks.
|
||||||
|
- No shared engine/logout/shutdown hooks.
|
||||||
|
- Repairs legacy File_FindInArchive NOPs if present.
|
||||||
|
|
||||||
|
transmogfix.dll
|
||||||
|
- Installs only TransmogFix's own module hooks.
|
||||||
|
- No shared Lua/file/engine/logout/shutdown hooks.
|
||||||
|
|
||||||
|
weirdperformance.dll
|
||||||
|
- Not built here.
|
||||||
|
- The final ZIP will use the user's original binary unchanged.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sha256sum package/DLL/*.dll > package/SHA256SUMS.txt
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: safe-core-v8-debug
|
||||||
|
path: package/
|
||||||
|
if-no-files-found: error
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
name: V40 Attached Model Outline Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- test/v40-attached-model-outline
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-v40:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout V40 branch
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Zig
|
||||||
|
uses: mlugg/setup-zig@v2
|
||||||
|
with:
|
||||||
|
version: '0.16.0'
|
||||||
|
|
||||||
|
- name: Build standalone variants
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
zig build all-variants -Doptimize=ReleaseSmall \
|
||||||
|
-Dweirdperformance=false \
|
||||||
|
-Doutline=true \
|
||||||
|
-Dcustomassets=true \
|
||||||
|
-Dtransmogfix=true
|
||||||
|
|
||||||
|
- name: Assemble V40 outline-only test package
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
rm -rf package dist
|
||||||
|
mkdir -p package/DLL
|
||||||
|
mkdir -p package/Interface/AddOns/WeirdUtils_Outline
|
||||||
|
mkdir -p dist
|
||||||
|
|
||||||
|
cp zig-out/variants/outline.dll package/DLL/outline.dll
|
||||||
|
cp src/outline/addon/Bindings.xml package/Interface/AddOns/WeirdUtils_Outline/Bindings.xml
|
||||||
|
cp src/outline/addon/Outline.lua package/Interface/AddOns/WeirdUtils_Outline/Outline.lua
|
||||||
|
cp src/outline/addon/WeirdUtils_Outline.toc package/Interface/AddOns/WeirdUtils_Outline/WeirdUtils_Outline.toc
|
||||||
|
|
||||||
|
printf '%s\n' \
|
||||||
|
'WeirdUtils V40 Attached Model Outline' \
|
||||||
|
'Base: exact V38 Retail Polish + AutoTarget + AutoRehook + ExitFix.' \
|
||||||
|
'main is untouched.' \
|
||||||
|
'WeirdPerformance is disabled in this standalone build and not modified.' \
|
||||||
|
'Engine-proven WoW 1.12.1 attachment layout from 0x712F70:' \
|
||||||
|
'- child +0x1CC = parent/root CM2Model' \
|
||||||
|
'- child +0x1D0 = attachment id' \
|
||||||
|
'- root +0x1DC = child-list head' \
|
||||||
|
'- child +0x1E4 = next sibling' \
|
||||||
|
'V40 makes direct attached M2 models inherit the root target outline.' \
|
||||||
|
'No new hook, no linked-list traversal, no game-memory writes.' \
|
||||||
|
> package/README_TEST.txt
|
||||||
|
|
||||||
|
(
|
||||||
|
cd package
|
||||||
|
sha256sum DLL/outline.dll > SHA256SUMS.txt
|
||||||
|
zip -r ../dist/WeirdUtils_V40_ATTACHED_MODEL_OUTLINE.zip DLL Interface README_TEST.txt SHA256SUMS.txt
|
||||||
|
)
|
||||||
|
|
||||||
|
- name: Upload V40 package
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: WeirdUtils-V40-ATTACHED-MODEL-OUTLINE
|
||||||
|
path: dist/WeirdUtils_V40_ATTACHED_MODEL_OUTLINE.zip
|
||||||
|
if-no-files-found: error
|
||||||
@@ -66,6 +66,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
|
|
||||||
const build_options = b.addOptions();
|
const build_options = b.addOptions();
|
||||||
addModuleOptionsFromArray(b, build_options, &module_enabled);
|
addModuleOptionsFromArray(b, build_options, &module_enabled);
|
||||||
|
build_options.addOption(bool, "safe_variant_core", false);
|
||||||
// transform_capture: records game-x87 transformMatrix4x4 state to disk for
|
// transform_capture: records game-x87 transformMatrix4x4 state to disk for
|
||||||
// offline bench parity. Mutually exclusive with bone_sse64 (capture needs
|
// offline bench parity. Mutually exclusive with bone_sse64 (capture needs
|
||||||
// the real game output; bone_sse64 replaces it).
|
// the real game output; bone_sse64 replaces it).
|
||||||
@@ -324,6 +325,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
if (comptime std.mem.eql(u8, m.name, "weirdperformance")) noperf_enabled[i] = false;
|
if (comptime std.mem.eql(u8, m.name, "weirdperformance")) noperf_enabled[i] = false;
|
||||||
}
|
}
|
||||||
addModuleOptionsFromArray(b, noperf_opts, &noperf_enabled);
|
addModuleOptionsFromArray(b, noperf_opts, &noperf_enabled);
|
||||||
|
noperf_opts.addOption(bool, "safe_variant_core", false);
|
||||||
|
|
||||||
const noperf_lib = b.addLibrary(.{
|
const noperf_lib = b.addLibrary(.{
|
||||||
.name = "weirdutils_noperf",
|
.name = "weirdutils_noperf",
|
||||||
@@ -357,6 +359,12 @@ pub fn build(b: *std.Build) void {
|
|||||||
inline for (module_list) |m| {
|
inline for (module_list) |m| {
|
||||||
opts.addOption(bool, "enable_" ++ m.name, std.mem.eql(u8, m.name, variant_mod.name));
|
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);
|
addFileListOptions(b, opts);
|
||||||
const names: []const []const u8 = comptime blk: {
|
const names: []const []const u8 = comptime blk: {
|
||||||
var n: [module_list.len][]const u8 = undefined;
|
var n: [module_list.len][]const u8 = undefined;
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
768442df41b7ec363c9827c860f139add2dfa5cd718915c3b55adfe93acb65cf dist/WeirdUtils_safe_core_V8_POLISH32_ORIGINAL_WeirdPerformance.zip
|
||||||
+116
-4
@@ -52,6 +52,27 @@ const luagc_mod = if (build_opts.luagc) @import("luagc/luagc.zig") else struct {
|
|||||||
const module_active = @import("module_active.zig");
|
const module_active = @import("module_active.zig");
|
||||||
|
|
||||||
const WINAPI = std.builtin.CallingConvention.winapi;
|
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
|
// Lua Protection Bypass
|
||||||
@@ -71,8 +92,23 @@ pub const lua = @import("lua.zig");
|
|||||||
// Game function wrappers
|
// Game function wrappers
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
fn registerFunction(name: [*:0]const u8, func_addr: usize) void {
|
noinline 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 });
|
// FrameScript_RegisterFunction is __fastcall:
|
||||||
|
// ECX = const char *name
|
||||||
|
// EDX = lua_CFunction func
|
||||||
|
//
|
||||||
|
// Do not route this through hook.call here. With the current Zig/zhook
|
||||||
|
// combination that path was emitted as two stack pushes, so the engine
|
||||||
|
// consumed stale ECX/EDX values left by the previous hook in the chain.
|
||||||
|
// Force the verified WoW 1.12.1 ABI explicitly.
|
||||||
|
var eax_clobber: usize = undefined;
|
||||||
|
asm volatile (
|
||||||
|
\\mov $0x704120, %%eax
|
||||||
|
\\call *%%eax
|
||||||
|
: [eax] "={eax}" (eax_clobber),
|
||||||
|
: [name] "{ecx}" (@intFromPtr(name)),
|
||||||
|
[func] "{edx}" (func_addr),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn allocateGameBuffer(size: u32) ?[*]u8 {
|
fn allocateGameBuffer(size: u32) ?[*]u8 {
|
||||||
@@ -93,6 +129,8 @@ fn registerLuaFunctions() void {
|
|||||||
}
|
}
|
||||||
if (build_opts.outline) {
|
if (build_opts.outline) {
|
||||||
registerFunction("OutlineCommand", @intFromPtr(&outline.outlineCommand));
|
registerFunction("OutlineCommand", @intFromPtr(&outline.outlineCommand));
|
||||||
|
registerFunction("OutlineSyncTarget", @intFromPtr(&outline.outlineSyncTarget));
|
||||||
|
registerFunction("OutlineDebug", @intFromPtr(&outline.outlineDebug));
|
||||||
}
|
}
|
||||||
if (build_opts.logsessions) {
|
if (build_opts.logsessions) {
|
||||||
registerFunction("GetCombatLogPath", @intFromPtr(&logsessions.luaGetCombatLogPath));
|
registerFunction("GetCombatLogPath", @intFromPtr(&logsessions.luaGetCombatLogPath));
|
||||||
@@ -676,6 +714,16 @@ fn registerAllSystemCommandsDetour() callconv(hook.cc.stdcall) void {
|
|||||||
registerModuleVersions();
|
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).
|
/// Hook for Glue_LoadScriptFunctions (0x46ABB0).
|
||||||
/// Fires at the login/glue screen — registers version table so addons can query early.
|
/// Fires at the login/glue screen — registers version table so addons can query early.
|
||||||
fn glueLoadScriptFunctionsDetour() callconv(hook.cc.stdcall) void {
|
fn glueLoadScriptFunctionsDetour() callconv(hook.cc.stdcall) void {
|
||||||
@@ -692,6 +740,10 @@ var engine_init_hook: hook.Detour(fn () callconv(hook.cc.stdcall) void) = .{};
|
|||||||
fn engineInitDetour() callconv(hook.cc.stdcall) void {
|
fn engineInitDetour() callconv(hook.cc.stdcall) void {
|
||||||
engine_init_hook.callOriginal(.{});
|
engine_init_hook.callOriginal(.{});
|
||||||
|
|
||||||
|
if (safe_variant_core and build_opts.outline) {
|
||||||
|
restoreLegacyFileFindArchiveGates();
|
||||||
|
}
|
||||||
|
|
||||||
if (build_opts.screenshot) {
|
if (build_opts.screenshot) {
|
||||||
screenshot.installHook();
|
screenshot.installHook();
|
||||||
}
|
}
|
||||||
@@ -811,6 +863,41 @@ fn install() void {
|
|||||||
logging.init();
|
logging.init();
|
||||||
log = logging.Logger.open("weirdutils", .console);
|
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:
|
// 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
|
// each DLL's trampoline JMPs to the previous DLL's detour, forming a LIFO
|
||||||
// call chain. Callbacks are additive (Lua registration, file serving) or
|
// call chain. Callbacks are additive (Lua registration, file serving) or
|
||||||
@@ -840,6 +927,25 @@ fn install() void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn uninstall() 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
|
// Remove modules in reverse order
|
||||||
comptime var i = modules.len;
|
comptime var i = modules.len;
|
||||||
inline while (i > 0) {
|
inline while (i > 0) {
|
||||||
@@ -942,11 +1048,17 @@ comptime {
|
|||||||
pub export fn DllMain(
|
pub export fn DllMain(
|
||||||
_: ?*anyopaque,
|
_: ?*anyopaque,
|
||||||
reason: u32,
|
reason: u32,
|
||||||
_: ?*anyopaque,
|
reserved: ?*anyopaque,
|
||||||
) callconv(WINAPI) std.os.windows.BOOL {
|
) callconv(WINAPI) std.os.windows.BOOL {
|
||||||
switch (reason) {
|
switch (reason) {
|
||||||
1 => install(),
|
1 => install(),
|
||||||
0 => uninstall(),
|
0 => {
|
||||||
|
// ExitFix: when lpReserved is non-null, Windows is terminating the
|
||||||
|
// whole process. Do not call into D3D9/COM or other DLLs from
|
||||||
|
// DLL_PROCESS_DETACH during global teardown; the OS will reclaim
|
||||||
|
// those resources. Keep full uninstall for explicit FreeLibrary.
|
||||||
|
if (reserved == null) uninstall();
|
||||||
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
return @enumFromInt(1);
|
return @enumFromInt(1);
|
||||||
|
|||||||
@@ -166,6 +166,8 @@ pub const FN_UNIT_REACTION: usize = 0x6061E0;
|
|||||||
|
|
||||||
/// __fastcall(), no params, returns EAX(low):EDX(high).
|
/// __fastcall(), no params, returns EAX(low):EDX(high).
|
||||||
pub const FN_GET_PLAYER_GUID: usize = 0x00468550;
|
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.
|
/// __fastcall(obj_ECX) → bool. GO interactability check.
|
||||||
pub const FN_CALL_SPELL_CAST_HANDLER: usize = 0x5F8800;
|
pub const FN_CALL_SPELL_CAST_HANDLER: usize = 0x5F8800;
|
||||||
|
|||||||
@@ -1,18 +1,35 @@
|
|||||||
-- Outline addon (embedded in DLL, loaded from memory)
|
-- Outline addon (embedded in DLL, loaded from memory)
|
||||||
-- Part of WeirdUtils - only loaded when outline module is compiled
|
-- Part of WeirdUtils - only loaded when outline module is compiled
|
||||||
|
|
||||||
OUTLINE_VERSION = 1
|
OUTLINE_VERSION = 40
|
||||||
|
|
||||||
BINDING_HEADER_OUTLINE = "Outline"
|
BINDING_HEADER_OUTLINE = "Outline"
|
||||||
|
|
||||||
local frame = CreateFrame("Frame")
|
local frame = CreateFrame("Frame")
|
||||||
frame:RegisterEvent("ADDON_LOADED")
|
frame:RegisterEvent("ADDON_LOADED")
|
||||||
frame:RegisterEvent("PLAYER_LOGIN")
|
frame:RegisterEvent("PLAYER_LOGIN")
|
||||||
|
frame:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||||||
|
|
||||||
|
local syncElapsed = 0
|
||||||
|
|
||||||
frame:SetScript("OnEvent", function()
|
frame:SetScript("OnEvent", function()
|
||||||
if event == "PLAYER_LOGIN" then
|
if event == "PLAYER_LOGIN" then
|
||||||
|
OutlineSyncTarget()
|
||||||
local on = OutlineCommand()
|
local on = OutlineCommand()
|
||||||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00Outline|r v" .. OUTLINE_VERSION .. " loaded (" .. (on and "enabled" or "disabled") .. ")")
|
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
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|||||||
+518
-139
File diff suppressed because it is too large
Load Diff
@@ -42,6 +42,19 @@ var draw_batch_hook: hook.Detour(DrawBatchFn) = .{};
|
|||||||
/// a dummy D3D9 device during engine init corrupts the proxy's state.
|
/// a dummy D3D9 device during engine init corrupts the proxy's state.
|
||||||
var d3d9_deferred_pending: bool = true;
|
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)
|
// Volatile flags shared with d3d9_hook (read by DIP hook)
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -81,6 +94,10 @@ fn renderDrawDetour(this: u32, view_matrix: u32, batch_data: u32, batch_indices:
|
|||||||
api.initD3D9Deferred();
|
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
|
// 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) {
|
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 });
|
render_draw_hook.callOriginal(.{ this, view_matrix, batch_data, batch_indices, batch_count });
|
||||||
@@ -96,7 +113,7 @@ fn renderDrawDetour(this: u32, view_matrix: u32, batch_data: u32, batch_indices:
|
|||||||
const idx_u: u32 = @bitCast(indices[i]);
|
const idx_u: u32 = @bitCast(indices[i]);
|
||||||
const model_ptr = hook.readMem(u32, batch_data +% idx_u *% 0x40 +% 4);
|
const model_ptr = hook.readMem(u32, batch_data +% idx_u *% 0x40 +% 4);
|
||||||
if (model_ptr != 0) {
|
if (model_ptr != 0) {
|
||||||
if (tracker.findOutlineEntry(model_ptr) != null) {
|
if (tracker.findOutlineEntryIncludingAttachment(model_ptr) != null) {
|
||||||
outline_count += 1;
|
outline_count += 1;
|
||||||
} else if (tracker.isGameObjectModel(model_ptr)) {
|
} else if (tracker.isGameObjectModel(model_ptr)) {
|
||||||
game_obj_count += 1;
|
game_obj_count += 1;
|
||||||
@@ -120,7 +137,7 @@ fn renderDrawDetour(this: u32, view_matrix: u32, batch_data: u32, batch_indices:
|
|||||||
const batch_idx = indices[i];
|
const batch_idx = indices[i];
|
||||||
const idx_u: u32 = @bitCast(batch_idx);
|
const idx_u: u32 = @bitCast(batch_idx);
|
||||||
const model_ptr = hook.readMem(u32, batch_data +% idx_u *% 0x40 +% 4);
|
const model_ptr = hook.readMem(u32, batch_data +% idx_u *% 0x40 +% 4);
|
||||||
if (model_ptr != 0 and tracker.findOutlineEntry(model_ptr) != null) {
|
if (model_ptr != 0 and tracker.findOutlineEntryIncludingAttachment(model_ptr) != null) {
|
||||||
reordered_indices[outline_pos] = batch_idx;
|
reordered_indices[outline_pos] = batch_idx;
|
||||||
outline_pos += 1;
|
outline_pos += 1;
|
||||||
} else if (model_ptr != 0 and tracker.isGameObjectModel(model_ptr)) {
|
} else if (model_ptr != 0 and tracker.isGameObjectModel(model_ptr)) {
|
||||||
@@ -176,7 +193,7 @@ fn drawBatchProjDetour(ctx: u32) callconv(hook.cc.thiscall) void {
|
|||||||
else
|
else
|
||||||
0;
|
0;
|
||||||
|
|
||||||
const entry = if (model_ptr != 0) tracker.findOutlineEntry(model_ptr) else null;
|
const entry = if (model_ptr != 0) tracker.findOutlineEntryIncludingAttachment(model_ptr) else null;
|
||||||
|
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
// This batch is an outline target - signal the DIP hook
|
// This batch is an outline target - signal the DIP hook
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ pub const MODEL_OWNER_DIRECT: usize = 0x28;
|
|||||||
/// model + this → render callback owner pointer (set by SetRenderCallbacks).
|
/// model + this → render callback owner pointer (set by SetRenderCallbacks).
|
||||||
pub const MODEL_OWNER_CALLBACK: usize = 0x3C0;
|
pub const MODEL_OWNER_CALLBACK: usize = 0x3C0;
|
||||||
|
|
||||||
|
/// V40: parent/root CM2Model for an attached model.
|
||||||
|
/// Proven from WoW 1.12.1 build 5875 function 0x712F70:
|
||||||
|
/// child+0x1CC = root, child+0x1D0 = attachment id,
|
||||||
|
/// root+0x1DC = child-list head, child+0x1E4 = next sibling.
|
||||||
|
pub const MODEL_ATTACHMENT_PARENT: usize = 0x1CC;
|
||||||
|
pub const MODEL_ATTACHMENT_ID: usize = 0x1D0;
|
||||||
|
pub const MODEL_CHILD_HEAD: usize = 0x1DC;
|
||||||
|
pub const MODEL_NEXT_SIBLING: usize = 0x1E4;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Render context
|
// Render context
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
+133
-19
@@ -4,11 +4,12 @@
|
|||||||
//! and a Lua C callback for `/wu outline` commands.
|
//! and a Lua C callback for `/wu outline` commands.
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const hook = @import("zhook");
|
const lua = @import("../lua.zig");
|
||||||
const logging = @import("../logging.zig");
|
const logging = @import("../logging.zig");
|
||||||
const tracker = @import("tracker.zig");
|
const tracker = @import("tracker.zig");
|
||||||
const model_hook = @import("model_hook.zig");
|
const model_hook = @import("model_hook.zig");
|
||||||
const d3d9_hook = @import("d3d9_hook.zig");
|
const d3d9_hook = @import("d3d9_hook.zig");
|
||||||
|
const wow = @import("../wow.zig");
|
||||||
|
|
||||||
const WINAPI = std.builtin.CallingConvention.winapi;
|
const WINAPI = std.builtin.CallingConvention.winapi;
|
||||||
const mod_mutex = @import("../mutex.zig");
|
const mod_mutex = @import("../mutex.zig");
|
||||||
@@ -18,6 +19,63 @@ var log: logging.Logger = .{};
|
|||||||
|
|
||||||
var g_mutex: ?*anyopaque = null;
|
var g_mutex: ?*anyopaque = null;
|
||||||
var g_is_hook_owner: bool = false;
|
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 {
|
pub fn isActive() bool {
|
||||||
return g_is_hook_owner;
|
return g_is_hook_owner;
|
||||||
@@ -37,6 +95,7 @@ pub fn init() bool {
|
|||||||
log = logging.Logger.open(module_name, .console);
|
log = logging.Logger.open(module_name, .console);
|
||||||
tracker.initLogger();
|
tracker.initLogger();
|
||||||
if (!model_hook.installHooks()) return false;
|
if (!model_hook.installHooks()) return false;
|
||||||
|
g_model_hooks_installed = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,11 +109,15 @@ pub fn initD3D9Deferred() void {
|
|||||||
/// Remove all outline hooks. Called during DLL_PROCESS_DETACH.
|
/// Remove all outline hooks. Called during DLL_PROCESS_DETACH.
|
||||||
pub fn cleanup() void {
|
pub fn cleanup() void {
|
||||||
if (g_is_hook_owner) {
|
if (g_is_hook_owner) {
|
||||||
d3d9_hook.removeHooks();
|
// ExitFix: stop any future self-heal first, then detach the model hooks
|
||||||
|
// that can call lateRehookIfLost(), and only then tear down D3D9.
|
||||||
|
d3d9_hook.beginShutdown();
|
||||||
model_hook.removeHooks();
|
model_hook.removeHooks();
|
||||||
|
d3d9_hook.removeHooks();
|
||||||
log.close();
|
log.close();
|
||||||
mod_mutex.release(&g_mutex);
|
mod_mutex.release(&g_mutex);
|
||||||
}
|
}
|
||||||
|
g_model_hooks_installed = false;
|
||||||
g_is_hook_owner = false;
|
g_is_hook_owner = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,28 +137,79 @@ pub fn isEnabled() bool {
|
|||||||
/// OutlineCommand() → returns (enabled: bool)
|
/// OutlineCommand() → returns (enabled: bool)
|
||||||
/// OutlineCommand("on") → enable outlines
|
/// OutlineCommand("on") → enable outlines
|
||||||
/// OutlineCommand("off") → disable outlines
|
/// OutlineCommand("off") → disable outlines
|
||||||
pub fn outlineCommand(L: *anyopaque) callconv(.c) u32 {
|
pub fn outlineCommand(L: lua.State) callconv(.{ .x86_thiscall = .{} }) u32 {
|
||||||
const nargs = hook.call(fn (usize) callconv(hook.cc.fastcall) i32, 0x6F3070, .{@intFromPtr(L)}); // lua_gettop
|
const nargs = luaGetTopNative(L);
|
||||||
|
|
||||||
if (nargs >= 1) {
|
if (nargs >= 1 and luaIsStringNative(L, 1)) {
|
||||||
// Check if first arg is a string
|
if (luaToStringNative(L, 1)) |s| {
|
||||||
const is_str = hook.call(fn (usize, u32) callconv(hook.cc.fastcall) u32, 0x6F3510, .{ @intFromPtr(L), 1 }); // lua_isstring
|
const span = std.mem.span(s);
|
||||||
if (is_str != 0) {
|
if (eql(span, "on") or eql(span, "enable")) {
|
||||||
const str_ptr = hook.call(fn (usize, u32) callconv(hook.cc.fastcall) u32, 0x6F3690, .{ @intFromPtr(L), 1 }); // lua_tostring
|
setEnabled(true);
|
||||||
if (str_ptr != 0) {
|
} else if (eql(span, "off") or eql(span, "disable")) {
|
||||||
const s: [*:0]const u8 = @ptrFromInt(str_ptr);
|
setEnabled(false);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push current state as boolean
|
luaPushBooleanNative(L, if (isEnabled()) 1 else 0);
|
||||||
hook.call(fn (usize, u32) callconv(hook.cc.fastcall) void, 0x6F39F0, .{ @intFromPtr(L), @as(u32, if (isEnabled()) 1 else 0) }); // lua_pushboolean
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syncTargetFromLuaContext() void {
|
||||||
|
// AutoFix: perform the same safe target publication that OutlineDebug()
|
||||||
|
// used, but from an automatic Lua/main-thread callback.
|
||||||
|
const player_guid = wow.getPlayerGUID();
|
||||||
|
const target_guid = wow.getTargetGUID();
|
||||||
|
const player_obj = if (player_guid != 0) wow.getObjectByGUID(player_guid) else 0;
|
||||||
|
const target_obj = if (target_guid != 0) wow.getObjectByGUID(target_guid) else 0;
|
||||||
|
tracker.setDebugPinnedObjects(player_obj, target_obj);
|
||||||
|
|
||||||
|
if (player_guid != 0) tracker.debug_unit_player_guid_seen = true;
|
||||||
|
if (target_guid != 0) tracker.debug_unit_target_guid_seen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Automatic target publication used by the embedded addon.
|
||||||
|
/// No user macro/command is required.
|
||||||
|
pub fn outlineSyncTarget(_: lua.State) callconv(.{ .x86_thiscall = .{} }) u32 {
|
||||||
|
syncTargetFromLuaContext();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a compact sticky diagnostic line for in-game testing.
|
||||||
|
/// Usage:
|
||||||
|
/// /run DEFAULT_CHAT_FRAME:AddMessage(OutlineDebug())
|
||||||
|
pub fn outlineDebug(L: lua.State) callconv(.{ .x86_thiscall = .{} }) u32 {
|
||||||
|
// Debug keeps the exact same automatic publication path.
|
||||||
|
syncTargetFromLuaContext();
|
||||||
|
|
||||||
|
_ = d3d9_hook.lateRehookIfLost();
|
||||||
|
const live = d3d9_hook.getLiveHookState();
|
||||||
|
|
||||||
|
var buf: [336]u8 = undefined;
|
||||||
|
const msg = std.fmt.bufPrintZ(
|
||||||
|
&buf,
|
||||||
|
"OutlineDBG shst={d} hooks={d}{d}{d} tgt={d} mdl={d} sb={d} rs={d} fx={d} odip={d} cache={d} pipe={d}/{d}",
|
||||||
|
.{
|
||||||
|
d3d9_hook.debug_shader_stage,
|
||||||
|
@intFromBool(live.endscene_ours),
|
||||||
|
@intFromBool(live.dip_ours),
|
||||||
|
@intFromBool(live.reset_ours),
|
||||||
|
@intFromBool(tracker.debug_target_seen),
|
||||||
|
@intFromBool(tracker.debug_target_model_seen),
|
||||||
|
@intFromBool(d3d9_hook.debug_state_block_seen),
|
||||||
|
@intFromBool(d3d9_hook.debug_outer_state_restore_seen),
|
||||||
|
@intFromBool(d3d9_hook.debug_additive_skipped_seen),
|
||||||
|
@intFromBool(d3d9_hook.debug_outline_dip_seen),
|
||||||
|
@intFromBool(d3d9_hook.debug_cached_draw_seen),
|
||||||
|
@intFromBool(d3d9_hook.debug_pipeline_entered_seen),
|
||||||
|
@intFromBool(d3d9_hook.debug_pipeline_ready_seen),
|
||||||
|
},
|
||||||
|
) catch {
|
||||||
|
luaPushStringNative(L, "OutlineDBG format error");
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
luaPushStringNative(L, msg.ptr);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+101
-54
@@ -79,6 +79,19 @@ pub fn findOutlineEntry(model_ptr: u32) ?*const types.OutlineEntry {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// V40: resolve a live attached M2 to the already-tracked root model.
|
||||||
|
/// 0x712F70 writes the root CM2Model pointer directly to child+0x1CC.
|
||||||
|
/// We only read that fixed field and compare the value; the parent pointer is
|
||||||
|
/// never dereferenced here.
|
||||||
|
pub fn findOutlineEntryIncludingAttachment(model_ptr: u32) ?*const types.OutlineEntry {
|
||||||
|
if (findOutlineEntry(model_ptr)) |entry| return entry;
|
||||||
|
if (model_ptr == 0) return null;
|
||||||
|
|
||||||
|
const parent = hook.readMem(u32, model_ptr + o.MODEL_ATTACHMENT_PARENT);
|
||||||
|
if (parent == 0 or parent == model_ptr) return null;
|
||||||
|
return findOutlineEntry(parent);
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if any outline targets are tracked this frame.
|
/// Check if any outline targets are tracked this frame.
|
||||||
pub fn hasTargets() bool {
|
pub fn hasTargets() bool {
|
||||||
return tracked_obj_count > 0;
|
return tracked_obj_count > 0;
|
||||||
@@ -86,7 +99,7 @@ pub fn hasTargets() bool {
|
|||||||
|
|
||||||
/// Get the outline colour for a model, or null if not tracked.
|
/// Get the outline colour for a model, or null if not tracked.
|
||||||
pub fn getModelColor(model_ptr: u32) ?u32 {
|
pub fn getModelColor(model_ptr: u32) ?u32 {
|
||||||
const entry = findOutlineEntry(model_ptr) orelse return null;
|
const entry = findOutlineEntryIncludingAttachment(model_ptr) orelse return null;
|
||||||
return switch (entry.category) {
|
return switch (entry.category) {
|
||||||
.target => types.COLOR_TARGET,
|
.target => types.COLOR_TARGET,
|
||||||
.raid_marked => if (entry.raid_mark > 0 and entry.raid_mark <= 8)
|
.raid_marked => if (entry.raid_mark > 0 and entry.raid_mark <= 8)
|
||||||
@@ -100,7 +113,7 @@ pub fn getModelColor(model_ptr: u32) ?u32 {
|
|||||||
|
|
||||||
/// Get the outline category for a model.
|
/// Get the outline category for a model.
|
||||||
pub fn getModelCategory(model_ptr: u32) types.ModelCategory {
|
pub fn getModelCategory(model_ptr: u32) types.ModelCategory {
|
||||||
const entry = findOutlineEntry(model_ptr) orelse return .none;
|
const entry = findOutlineEntryIncludingAttachment(model_ptr) orelse return .none;
|
||||||
return entry.category;
|
return entry.category;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +149,7 @@ pub fn classifyModel(model_ptr: u32) void {
|
|||||||
// Read the model's back-pointers to its owning game object.
|
// Read the model's back-pointers to its owning game object.
|
||||||
const owner_direct = hook.readMem(u32, model_ptr + o.MODEL_OWNER_DIRECT);
|
const owner_direct = hook.readMem(u32, model_ptr + o.MODEL_OWNER_DIRECT);
|
||||||
const owner_callback = hook.readMem(u32, model_ptr + o.MODEL_OWNER_CALLBACK);
|
const owner_callback = hook.readMem(u32, model_ptr + o.MODEL_OWNER_CALLBACK);
|
||||||
|
const attachment_parent = hook.readMem(u32, model_ptr + o.MODEL_ATTACHMENT_PARENT);
|
||||||
|
|
||||||
// Match against tracked outline objects (target, raid marks, dead players).
|
// Match against tracked outline objects (target, raid marks, dead players).
|
||||||
if (tracked_obj_count > 0 and frame_outline_count < MAX_OUTLINE_MODELS) {
|
if (tracked_obj_count > 0 and frame_outline_count < MAX_OUTLINE_MODELS) {
|
||||||
@@ -149,6 +163,16 @@ pub fn classifyModel(model_ptr: u32) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// V40: attached weapons, shields and other M2 children inherit the
|
||||||
|
// selected root's outline. This is only a value comparison; no traversal.
|
||||||
|
if (attachment_parent != 0 and attachment_parent != model_ptr and
|
||||||
|
findOutlineEntry(model_ptr) == null and frame_outline_count < MAX_OUTLINE_MODELS)
|
||||||
|
{
|
||||||
|
if (findOutlineEntry(attachment_parent)) |parent_entry| {
|
||||||
|
addOutlineEntry(model_ptr, parent_entry.category, parent_entry.raid_mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Match against game object pointers (for render ordering).
|
// Match against game object pointers (for render ordering).
|
||||||
if (game_obj_ptr_count > 0 and game_obj_model_count < MAX_GAME_OBJ_MODELS) {
|
if (game_obj_ptr_count > 0 and game_obj_model_count < MAX_GAME_OBJ_MODELS) {
|
||||||
for (game_obj_ptrs[0..game_obj_ptr_count]) |go_ptr| {
|
for (game_obj_ptrs[0..game_obj_ptr_count]) |go_ptr| {
|
||||||
@@ -182,7 +206,10 @@ fn addOutlineEntry(model_ptr: u32, cat: types.ModelCategory, mark: u8) void {
|
|||||||
|
|
||||||
// Diagnostic: count classified models by category
|
// Diagnostic: count classified models by category
|
||||||
switch (cat) {
|
switch (cat) {
|
||||||
.target => diag.classify_target += 1,
|
.target => {
|
||||||
|
diag.classify_target += 1;
|
||||||
|
debug_target_model_seen = true;
|
||||||
|
},
|
||||||
.raid_marked => diag.classify_raid_mark += 1,
|
.raid_marked => diag.classify_raid_mark += 1,
|
||||||
.dead_player => diag.classify_dead_player += 1,
|
.dead_player => diag.classify_dead_player += 1,
|
||||||
.none => {},
|
.none => {},
|
||||||
@@ -197,6 +224,13 @@ fn addOutlineEntry(model_ptr: u32, cat: types.ModelCategory, mark: u8) void {
|
|||||||
/// Stores object pointers directly so classifyModel can match model
|
/// Stores object pointers directly so classifyModel can match model
|
||||||
/// back-pointers without any pointer dereferencing.
|
/// back-pointers without any pointer dereferencing.
|
||||||
pub fn scanObjects() void {
|
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
|
// Clear per-frame sets
|
||||||
frame_outline_count = 0;
|
frame_outline_count = 0;
|
||||||
tracked_obj_count = 0;
|
tracked_obj_count = 0;
|
||||||
@@ -204,62 +238,24 @@ pub fn scanObjects() void {
|
|||||||
game_obj_model_count = 0;
|
game_obj_model_count = 0;
|
||||||
resetDiag();
|
resetDiag();
|
||||||
|
|
||||||
if (!wow.isInGame()) return;
|
// DEBUG10: EndScene must not call WoW object/game functions. On this client
|
||||||
const local_player = wow.getLocalPlayer();
|
// those functions only behave correctly from the Lua/main-thread context.
|
||||||
if (local_player == 0) return;
|
// Consume atomically published object pointers captured by OutlineDebug().
|
||||||
|
const local_player = @atomicLoad(u32, &debug_pinned_player_obj, .seq_cst);
|
||||||
// Local player renders before outline targets (occludes outlines) unless
|
if (local_player != 0) debug_pin_player_consumed_seen = true;
|
||||||
// the local player IS an outline target (partition logic checks outline first).
|
if (local_player != 0 and game_obj_ptr_count < MAX_TRACKED_OBJS) {
|
||||||
if (game_obj_ptr_count < MAX_TRACKED_OBJS) {
|
|
||||||
game_obj_ptrs[game_obj_ptr_count] = local_player;
|
game_obj_ptrs[game_obj_ptr_count] = local_player;
|
||||||
game_obj_ptr_count += 1;
|
game_obj_ptr_count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache raid target GUIDs
|
const target_obj = @atomicLoad(u32, &debug_pinned_target_obj, .seq_cst);
|
||||||
wow.cacheRaidTargets();
|
if (target_obj != 0) {
|
||||||
|
debug_pin_target_consumed_seen = true;
|
||||||
// Resolve target to object pointer (highest priority - added first)
|
addTrackedObj(target_obj, .target, 0);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate all visible objects
|
// Secondary categories (raid marks/dead players/game objects) are disabled
|
||||||
var obj = wow.objectFirst();
|
// in this diagnostic build until the target-only path is proven stable.
|
||||||
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 {
|
fn addTrackedObj(obj_ptr: u32, cat: types.ModelCategory, mark: u8) void {
|
||||||
@@ -283,7 +279,10 @@ fn addTrackedObj(obj_ptr: u32, cat: types.ModelCategory, mark: u8) void {
|
|||||||
|
|
||||||
// Diagnostic: count tracked objects by category
|
// Diagnostic: count tracked objects by category
|
||||||
switch (cat) {
|
switch (cat) {
|
||||||
.target => diag.scan_targets += 1,
|
.target => {
|
||||||
|
diag.scan_targets += 1;
|
||||||
|
debug_target_seen = true;
|
||||||
|
},
|
||||||
.raid_marked => diag.scan_raid_marks += 1,
|
.raid_marked => diag.scan_raid_marks += 1,
|
||||||
.dead_player => diag.scan_dead_players += 1,
|
.dead_player => diag.scan_dead_players += 1,
|
||||||
.none => {},
|
.none => {},
|
||||||
@@ -308,6 +307,54 @@ pub const Diag = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub var diag: Diag = .{};
|
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 = .{};
|
var log: logging.Logger = .{};
|
||||||
|
|
||||||
pub fn initLogger() void {
|
pub fn initLogger() void {
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ pub const D3DRS = struct {
|
|||||||
pub const DESTBLEND: u32 = 20;
|
pub const DESTBLEND: u32 = 20;
|
||||||
pub const CULLMODE: u32 = 22;
|
pub const CULLMODE: u32 = 22;
|
||||||
pub const ZFUNC: u32 = 23;
|
pub const ZFUNC: u32 = 23;
|
||||||
|
pub const ALPHAREF: u32 = 24;
|
||||||
|
pub const ALPHAFUNC: u32 = 25;
|
||||||
pub const ALPHABLENDENABLE: u32 = 27;
|
pub const ALPHABLENDENABLE: u32 = 27;
|
||||||
pub const STENCILENABLE: u32 = 52;
|
pub const STENCILENABLE: u32 = 52;
|
||||||
pub const STENCILFAIL: u32 = 53;
|
pub const STENCILFAIL: u32 = 53;
|
||||||
@@ -105,6 +107,8 @@ pub const D3DRS = struct {
|
|||||||
pub const D3DCMP_ALWAYS: u32 = 8;
|
pub const D3DCMP_ALWAYS: u32 = 8;
|
||||||
pub const D3DCMP_EQUAL: u32 = 3;
|
pub const D3DCMP_EQUAL: u32 = 3;
|
||||||
pub const D3DCMP_LESSEQUAL: u32 = 4;
|
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_KEEP: u32 = 1;
|
||||||
pub const D3DSTENCILOP_REPLACE: u32 = 3;
|
pub const D3DSTENCILOP_REPLACE: u32 = 3;
|
||||||
@@ -123,8 +127,12 @@ pub const D3DCLEAR_STENCIL: u32 = 4;
|
|||||||
// D3D9 blend modes
|
// D3D9 blend modes
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
|
pub const D3DBLEND_ZERO: u32 = 1;
|
||||||
|
pub const D3DBLEND_ONE: u32 = 2;
|
||||||
pub const D3DBLEND_SRCALPHA: u32 = 5;
|
pub const D3DBLEND_SRCALPHA: u32 = 5;
|
||||||
pub const D3DBLEND_INVSRCALPHA: u32 = 6;
|
pub const D3DBLEND_INVSRCALPHA: u32 = 6;
|
||||||
|
pub const D3DBLEND_DESTCOLOR: u32 = 9;
|
||||||
|
pub const D3DBLEND_INVDESTCOLOR: u32 = 10;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// D3D9 texture stage state IDs
|
// D3D9 texture stage state IDs
|
||||||
@@ -133,11 +141,18 @@ pub const D3DBLEND_INVSRCALPHA: u32 = 6;
|
|||||||
pub const D3DTSS = struct {
|
pub const D3DTSS = struct {
|
||||||
pub const COLOROP: u32 = 1;
|
pub const COLOROP: u32 = 1;
|
||||||
pub const COLORARG1: u32 = 2;
|
pub const COLORARG1: u32 = 2;
|
||||||
|
pub const COLORARG2: u32 = 3;
|
||||||
pub const ALPHAOP: u32 = 4;
|
pub const ALPHAOP: u32 = 4;
|
||||||
pub const ALPHAARG1: u32 = 5;
|
pub const ALPHAARG1: u32 = 5;
|
||||||
|
pub const ALPHAARG2: u32 = 6;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const D3DTOP_DISABLE: u32 = 1;
|
||||||
pub const D3DTOP_SELECTARG1: u32 = 2;
|
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;
|
pub const D3DTA_TFACTOR: u32 = 3;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -167,6 +182,7 @@ pub const D3DTEXF_POINT: u32 = 1;
|
|||||||
|
|
||||||
pub const D3DFMT_A8R8G8B8: u32 = 21;
|
pub const D3DFMT_A8R8G8B8: u32 = 21;
|
||||||
pub const D3DFMT_G16R16F: u32 = 112;
|
pub const D3DFMT_G16R16F: u32 = 112;
|
||||||
|
pub const D3DFMT_G32R32F: u32 = 115;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// D3D9 depth/stencil formats
|
// D3D9 depth/stencil formats
|
||||||
@@ -216,6 +232,7 @@ pub const VT = struct {
|
|||||||
pub const GetRenderState: usize = 58;
|
pub const GetRenderState: usize = 58;
|
||||||
pub const GetTexture: usize = 64;
|
pub const GetTexture: usize = 64;
|
||||||
pub const SetTexture: usize = 65;
|
pub const SetTexture: usize = 65;
|
||||||
|
pub const GetTextureStageState: usize = 66;
|
||||||
pub const SetTextureStageState: usize = 67;
|
pub const SetTextureStageState: usize = 67;
|
||||||
pub const GetSamplerState: usize = 68;
|
pub const GetSamplerState: usize = 68;
|
||||||
pub const SetSamplerState: usize = 69;
|
pub const SetSamplerState: usize = 69;
|
||||||
|
|||||||
+29
-6
@@ -57,6 +57,12 @@ pub fn isInGame() bool {
|
|||||||
return hook.readMem(u32, o.IS_IN_WORLD) != 0;
|
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 {
|
pub fn objectFirst() u32 {
|
||||||
const obj_mgr = hook.readMem(u32, o.OBJECT_MANAGER_PTR);
|
const obj_mgr = hook.readMem(u32, o.OBJECT_MANAGER_PTR);
|
||||||
if (obj_mgr == 0) return 0;
|
if (obj_mgr == 0) return 0;
|
||||||
@@ -203,17 +209,32 @@ pub fn getMapId() u32 {
|
|||||||
|
|
||||||
/// UnitGUID("player") / UnitGUID("target") → 64-bit GUID.
|
/// UnitGUID("player") / UnitGUID("target") → 64-bit GUID.
|
||||||
pub fn unitGUID(unit_id: [*:0]const u8) u64 {
|
pub fn unitGUID(unit_id: [*:0]const u8) u64 {
|
||||||
return hook.call(fn ([*:0]const u8) callconv(hook.cc.fastcall) u64, o.FN_UNIT_GUID, .{unit_id});
|
// UnitGUID is __fastcall with its single argument in ECX.
|
||||||
|
// hook.call + hook.cc.fastcall is currently miscompiled by Zig 0.16 on x86
|
||||||
|
// as a stack argument. Because the callee consumes no stack argument, that
|
||||||
|
// leaked 4 bytes per call; Outline calls UnitGUID("player"/"target") every
|
||||||
|
// frame, so ESP drifted until execution returned into stack data.
|
||||||
|
//
|
||||||
|
// With a single register argument, x86_thiscall is ABI-compatible here:
|
||||||
|
// ECX=unit_id, no stack args, u64 returned in EDX:EAX.
|
||||||
|
const f_native: *const fn ([*:0]const u8) callconv(.{ .x86_thiscall = .{} }) u64 =
|
||||||
|
@ptrFromInt(o.FN_UNIT_GUID);
|
||||||
|
return @call(.never_tail, f_native, .{unit_id});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a GUID → object pointer via the object manager hash table.
|
/// Resolve a GUID → object pointer via the object manager hash table.
|
||||||
pub fn getObjectByGUID(guid: u64) u32 {
|
pub fn getObjectByGUID(guid: u64) u32 {
|
||||||
if (guid == 0) return 0;
|
if (guid == 0) return 0;
|
||||||
if (hook.readMem(u32, o.OBJECT_MANAGER_PTR) == 0) return 0;
|
|
||||||
const lo: u32 = @truncate(guid);
|
const lo: u32 = @truncate(guid);
|
||||||
const hi: u32 = @truncate(guid >> 32);
|
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 });
|
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;
|
if (result != 0 and !isValidPtr(result)) return 0;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -221,7 +242,6 @@ pub fn getObjectByGUID(guid: u64) u32 {
|
|||||||
/// Split-GUID variant for callers that already have lo/hi parts.
|
/// Split-GUID variant for callers that already have lo/hi parts.
|
||||||
pub fn getObjectByGUIDSplit(guid_lo: u32, guid_hi: u32) u32 {
|
pub fn getObjectByGUIDSplit(guid_lo: u32, guid_hi: u32) u32 {
|
||||||
if (guid_lo == 0 and guid_hi == 0) return 0;
|
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 });
|
return hook.call(fn (u32, u32) callconv(hook.cc.stdcall) u32, o.FN_GET_OBJECT_BY_GUID, .{ guid_lo, guid_hi });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,8 +251,10 @@ pub fn getPlayerGUID() u64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the local player's object pointer.
|
/// 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 {
|
pub fn getLocalPlayer() u32 {
|
||||||
const guid = unitGUID("player");
|
const guid = getPlayerGUID();
|
||||||
if (guid == 0) return 0;
|
if (guid == 0) return 0;
|
||||||
return getObjectByGUID(guid);
|
return getObjectByGUID(guid);
|
||||||
}
|
}
|
||||||
@@ -317,8 +339,9 @@ pub fn getNameByObject(obj: u32) [*:0]const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current target's GUID.
|
/// Get the current target's GUID.
|
||||||
|
/// Read the locked target GUID directly (0xB4E2D8), as Nampower does.
|
||||||
pub fn getTargetGUID() u64 {
|
pub fn getTargetGUID() u64 {
|
||||||
return unitGUID("target");
|
return readGUID(o.LOCKED_TARGET_GUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a unit is friendly to the local player.
|
/// Check if a unit is friendly to the local player.
|
||||||
|
|||||||
Reference in New Issue
Block a user