Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2b8d2a7e0 | ||
|
|
5dde1f641e | ||
|
|
d6262135a8 | ||
|
|
83d85f5d5c | ||
|
|
5c61928260 | ||
|
|
722406fef2 | ||
|
|
c9ba29f60b | ||
|
|
650825d793 | ||
|
|
ce63965502 | ||
|
|
dd04a4f480 | ||
|
|
b5662b64ec | ||
|
|
3d821c41c3 | ||
|
|
39b0097ed2 | ||
|
|
232f37d762 | ||
|
|
762034301a | ||
|
|
9d5d7bd0d6 | ||
|
|
f72686f27a | ||
|
|
3e5628f198 | ||
|
|
1cc0ca7e29 | ||
|
|
cfc584ebfa | ||
|
|
1a0aad3223 | ||
|
|
368ae01c64 | ||
|
|
b4f9cca516 | ||
|
|
bb6e6b9236 | ||
|
|
dde316a184 | ||
|
|
43df6fc452 | ||
|
|
eff857398d | ||
|
|
091bd36858 | ||
|
|
8a8ea1e6e1 | ||
|
|
ace2046ae8 | ||
|
|
1462cabed5 | ||
|
|
b500f46d00 | ||
|
|
ca209ae383 | ||
|
|
ce7f57f412 |
@@ -1,77 +0,0 @@
|
||||
name: Commit V36 Retail Soft decode
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- test/v36-retail-jfa-soft
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
commit-v36-soft:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: test/v36-retail-jfa-soft
|
||||
|
||||
- name: Apply V36 decode
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 <<'PY'
|
||||
from pathlib import Path
|
||||
p = Path('src/outline/d3d9_hook.zig')
|
||||
s = p.read_text()
|
||||
if 'V36 RETAIL-SOFT' in s:
|
||||
raise SystemExit(0)
|
||||
start = s.index('/// V32 POLISH: hard 3px outline, no feather.')
|
||||
end = s.index('/// Debug: composite silhouette RT directly.')
|
||||
lines = [
|
||||
'/// V36 RETAIL-SOFT: keep the proven V34 JFA, soften only final decode.',
|
||||
'/// c0 = (screen_width, screen_height, core_radius_px, halo_radius_px).',
|
||||
'/// Uses the same two texture reads as V34: no 17-tap full-screen morphology.',
|
||||
'const jfa_decode_src =',
|
||||
' "ps_3_0\\n" ++',
|
||||
' "def c1, 0.0, 0.22, 0.82, -0.002\\n" ++',
|
||||
' "def c2, 1.0, 0.95, 0.58, 0.0\\n" ++',
|
||||
' "dcl_2d s0\\n" ++',
|
||||
' "dcl_2d s1\\n" ++',
|
||||
' "dcl_texcoord0 v0\\n" ++',
|
||||
' "texld r0, v0, s0\\n" ++',
|
||||
' "sub r1.xy, v0.xy, r0.xy\\n" ++',
|
||||
' "mul r1.xy, r1.xy, c0.xy\\n" ++',
|
||||
' "dp2add r1.z, r1, r1, c1.x\\n" ++',
|
||||
' "mul r3.x, c0.z, c0.z\\n" ++',
|
||||
' "mul r3.y, c0.w, c0.w\\n" ++',
|
||||
' "sub r4.x, r1.z, r3.y\\n" ++',
|
||||
' "cmp r6.w, r4.x, c1.x, c1.y\\n" ++',
|
||||
' "sub r4.x, r1.z, r3.x\\n" ++',
|
||||
' "cmp r6.w, r4.x, r6.w, c1.z\\n" ++',
|
||||
' "texld r5, v0, s1\\n" ++',
|
||||
' "add r5.x, r5.a, c1.w\\n" ++',
|
||||
' "cmp r6.w, r5.x, c1.x, r6.w\\n" ++',
|
||||
' "mov r6.xyz, c2.xyz\\n" ++',
|
||||
' "mov oC0, r6\\n";',
|
||||
'',
|
||||
'',
|
||||
]
|
||||
s = s[:start] + '\n'.join(lines) + s[end:]
|
||||
old = ' // V34: hard 3px edge unchanged; JFA seed precision is now full-float.\n c0 = [4]f32{ fw, fh, 3.0, 0.0 };'
|
||||
new_cpu = ' // V36: thin 1.5px core plus a subtle 3px halo.\n c0 = [4]f32{ fw, fh, 1.5, 3.0 };'
|
||||
if old not in s:
|
||||
raise SystemExit('V34 CPU decode constants not found')
|
||||
p.write_text(s.replace(old, new_cpu))
|
||||
PY
|
||||
|
||||
- name: Commit source
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if git diff --quiet; then exit 0; fi
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add src/outline/d3d9_hook.zig
|
||||
git commit -m "V36: soft Retail-style JFA decode"
|
||||
git push origin HEAD:test/v36-retail-jfa-soft
|
||||
@@ -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,55 +0,0 @@
|
||||
name: V36 ExitFix Baseline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- test/v36-exitfix-baseline
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: mlugg/setup-zig@v2
|
||||
with:
|
||||
version: '0.16.0'
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
zig build all-variants -Doptimize=ReleaseSmall \
|
||||
-Dweirdperformance=false \
|
||||
-Doutline=true \
|
||||
-Dcustomassets=true \
|
||||
-Dtransmogfix=true
|
||||
|
||||
- name: Package
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
rm -rf package dist
|
||||
mkdir -p package/DLL package/Interface/AddOns/WeirdUtils_Outline dist
|
||||
cp zig-out/variants/outline.dll package/DLL/outline.dll
|
||||
cp src/outline/addon/Bindings.xml package/Interface/AddOns/WeirdUtils_Outline/
|
||||
cp src/outline/addon/Outline.lua package/Interface/AddOns/WeirdUtils_Outline/
|
||||
cp src/outline/addon/WeirdUtils_Outline.toc package/Interface/AddOns/WeirdUtils_Outline/
|
||||
printf '%s\n' \
|
||||
'V36 runtime + process-exit safety only.' \
|
||||
'Base runtime commit: 4bbee94388f98843b738e275fd9482576be80654.' \
|
||||
'Compared with V34, only src/outline/d3d9_hook.zig changes runtime behavior.' \
|
||||
'main branch is untouched.' \
|
||||
'WeirdPerformance is disabled in this standalone build and not modified.' \
|
||||
> package/README_TEST.txt
|
||||
(cd package && sha256sum DLL/outline.dll > SHA256SUMS.txt && zip -r ../dist/WeirdUtils_V36_EXITFIX_BASELINE.zip DLL Interface README_TEST.txt SHA256SUMS.txt)
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WeirdUtils-V36-EXITFIX-BASELINE
|
||||
path: dist/WeirdUtils_V36_EXITFIX_BASELINE.zip
|
||||
if-no-files-found: error
|
||||
@@ -1,55 +0,0 @@
|
||||
name: V37A DIP Passthrough
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- test/v37a-dip-passthrough
|
||||
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' \
|
||||
'V37A DIP passthrough isolation test.' \
|
||||
'EndScene and DrawIndexedPrimitive vtable hooks are installed.' \
|
||||
'DIP detour performs no Outline capture/state/resource work and immediately calls original DIP.' \
|
||||
'Reset remains untouched.' \
|
||||
'Visible outline is intentionally absent in this diagnostic build.' \
|
||||
'main branch untouched; WeirdPerformance not modified.' \
|
||||
> package/README_TEST.txt
|
||||
(cd package && sha256sum DLL/outline.dll > SHA256SUMS.txt && zip -r ../dist/WeirdUtils_V37A_DIP_PASSTHROUGH.zip DLL Interface README_TEST.txt SHA256SUMS.txt)
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WeirdUtils-V37A-DIP-PASSTHROUGH
|
||||
path: dist/WeirdUtils_V37A_DIP_PASSTHROUGH.zip
|
||||
if-no-files-found: error
|
||||
@@ -1,55 +0,0 @@
|
||||
name: V37A EndScene Only
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- test/v37a-endscene-only
|
||||
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' \
|
||||
'V37A EndScene-only isolation test.' \
|
||||
'Base: V37A no-Reset-hook + ExitFix.' \
|
||||
'Only IDirect3DDevice9::EndScene is hooked.' \
|
||||
'DrawIndexedPrimitive and Reset are untouched.' \
|
||||
'Visible outline is intentionally absent in this diagnostic build.' \
|
||||
'main branch untouched; WeirdPerformance not modified.' \
|
||||
> package/README_TEST.txt
|
||||
(cd package && sha256sum DLL/outline.dll > SHA256SUMS.txt && zip -r ../dist/WeirdUtils_V37A_ENDSCENE_ONLY.zip DLL Interface README_TEST.txt SHA256SUMS.txt)
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WeirdUtils-V37A-ENDSCENE-ONLY
|
||||
path: dist/WeirdUtils_V37A_ENDSCENE_ONLY.zip
|
||||
if-no-files-found: error
|
||||
@@ -1,54 +0,0 @@
|
||||
name: V37A No AutoRehook
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- test/v37a-no-autorehook
|
||||
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' \
|
||||
'V37A no-auto-rehook isolation test.' \
|
||||
'Base: V37A radii-only + ExitFix.' \
|
||||
'Only runtime change: automatic lateRehookIfLost() call removed from RenderDraw.' \
|
||||
'Initial deferred D3D9 hook installation remains unchanged.' \
|
||||
'main branch untouched; WeirdPerformance not modified.' \
|
||||
> package/README_TEST.txt
|
||||
(cd package && sha256sum DLL/outline.dll > SHA256SUMS.txt && zip -r ../dist/WeirdUtils_V37A_NO_AUTOREHOOK.zip DLL Interface README_TEST.txt SHA256SUMS.txt)
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WeirdUtils-V37A-NO-AUTOREHOOK
|
||||
path: dist/WeirdUtils_V37A_NO_AUTOREHOOK.zip
|
||||
if-no-files-found: error
|
||||
@@ -1,55 +0,0 @@
|
||||
name: V37A No Reset Hook
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- test/v37a-no-reset-hook
|
||||
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' \
|
||||
'V37A no-Reset-hook isolation test.' \
|
||||
'Base: V37A no-auto-rehook + ExitFix.' \
|
||||
'EndScene and DrawIndexedPrimitive hooks remain active.' \
|
||||
'Reset hook is NOT installed.' \
|
||||
'Visible outline rendering should still work.' \
|
||||
'main branch untouched; WeirdPerformance not modified.' \
|
||||
> package/README_TEST.txt
|
||||
(cd package && sha256sum DLL/outline.dll > SHA256SUMS.txt && zip -r ../dist/WeirdUtils_V37A_NO_RESET_HOOK.zip DLL Interface README_TEST.txt SHA256SUMS.txt)
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WeirdUtils-V37A-NO-RESET-HOOK
|
||||
path: dist/WeirdUtils_V37A_NO_RESET_HOOK.zip
|
||||
if-no-files-found: error
|
||||
@@ -1,51 +0,0 @@
|
||||
name: V37A Radii Only
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- test/v37a-radii-only
|
||||
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' \
|
||||
'V37A isolation test.' \
|
||||
'Base: V36 + ExitFix (stable in user test).' \
|
||||
'Only runtime change: radii 1.5/3.0 -> 1.35/3.20.' \
|
||||
'V36 decode shader remains unchanged.' \
|
||||
'main branch untouched; WeirdPerformance not modified.' \
|
||||
> package/README_TEST.txt
|
||||
(cd package && sha256sum DLL/outline.dll > SHA256SUMS.txt && zip -r ../dist/WeirdUtils_V37A_RADII_ONLY.zip DLL Interface README_TEST.txt SHA256SUMS.txt)
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WeirdUtils-V37A-RADII-ONLY
|
||||
path: dist/WeirdUtils_V37A_RADII_ONLY.zip
|
||||
if-no-files-found: error
|
||||
@@ -0,0 +1,121 @@
|
||||
name: Build WeirdPerformance 2.4-B1 GC test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- test/wp24b-gc-safe-sweep
|
||||
paths:
|
||||
- 'experiments/wp24b-gc/**'
|
||||
- '.github/workflows/wp24b-gc.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: mlugg/setup-zig@v2
|
||||
with:
|
||||
version: '0.17.0-dev.1970+67f39b551'
|
||||
- name: Build and ABI-validate x86 DLL
|
||||
working-directory: experiments/wp24b-gc
|
||||
run: |
|
||||
set -euo pipefail
|
||||
zig build --fetch
|
||||
python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
matches = list(Path("zig-pkg").glob("zhook-*/src/zhook.zig"))
|
||||
if len(matches) != 1:
|
||||
raise SystemExit(f"expected one pinned zhook source, found {len(matches)}")
|
||||
p = matches[0]
|
||||
text = p.read_text(encoding="utf-8")
|
||||
old = "var patch: [MAX_STOLEN]u8 = .{0x90} ** MAX_STOLEN;"
|
||||
new = "var patch: [MAX_STOLEN]u8 = @splat(0x90);"
|
||||
if old not in text:
|
||||
raise SystemExit("expected pinned zhook repeat initializer not found")
|
||||
p.write_text(text.replace(old, new), encoding="utf-8")
|
||||
PY
|
||||
|
||||
zig build -Doptimize=small
|
||||
DLL=zig-out/bin/weirdperformance_gc24b1.dll
|
||||
test -s "$DLL"
|
||||
file "$DLL" | tee BINARY_INFO.txt
|
||||
file "$DLL" | grep -Eq 'PE32.*Intel (80386|i386)'
|
||||
|
||||
if command -v llvm-objdump >/dev/null 2>&1; then
|
||||
llvm-objdump -d --x86-asm-syntax=intel "$DLL" > ABI_DISASM.txt
|
||||
else
|
||||
objdump -d -M intel "$DLL" > ABI_DISASM.txt
|
||||
fi
|
||||
|
||||
python3 - <<'PY'
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
asm = Path("ABI_DISASM.txt").read_text(encoding="utf-8", errors="replace").lower()
|
||||
|
||||
def require(pattern, label):
|
||||
if not re.search(pattern, asm, re.S):
|
||||
raise SystemExit(f"ABI validation failed: {label}")
|
||||
|
||||
# zhook must jump to callbacks compiled with L arriving in ECX.
|
||||
m = re.search(r"mov\s+edx,\s*0x6f7340.{0,240}?push\s+0x([0-9a-f]+)", asm, re.S)
|
||||
if not m:
|
||||
raise SystemExit("ABI validation failed: collector callback address not found")
|
||||
cb = m.group(1).lstrip("0") or "0"
|
||||
pos = asm.find(cb + ":")
|
||||
if pos < 0 or not re.search(r"mov\s+esi,\s*ecx", asm[pos:pos+500]):
|
||||
raise SystemExit("ABI validation failed: collector callback does not consume L from ECX")
|
||||
|
||||
m = re.search(r"mov\s+edx,\s*0x6f6ef0.{0,240}?push\s+0x([0-9a-f]+)", asm, re.S)
|
||||
if not m:
|
||||
raise SystemExit("ABI validation failed: lua_close callback address not found")
|
||||
cb = m.group(1).lstrip("0") or "0"
|
||||
pos = asm.find(cb + ":")
|
||||
if pos < 0 or not re.search(r"mov\s+esi,\s*ecx", asm[pos:pos+400]):
|
||||
raise SystemExit("ABI validation failed: lua_close callback does not consume L from ECX")
|
||||
|
||||
# Native helper ABI observed in WoW 5875:
|
||||
# ECX=L, EDX=&list, stack arg=all for 0x6F7210.
|
||||
require(
|
||||
r"lea\s+edx,\s*\[edi\s*\+\s*0x14\].{0,160}?"
|
||||
r"mov\s+eax,\s*0x6f7210.{0,120}?"
|
||||
r"mov\s+ecx,\s*esi.{0,120}?"
|
||||
r"push\s+0x0.{0,80}?call\s+eax",
|
||||
"lua_gc_remove_objects register/stack ABI",
|
||||
)
|
||||
|
||||
require(
|
||||
r"mov\s+eax,\s*0x6f72f0.{0,120}?"
|
||||
r"mov\s+ecx,\s*esi.{0,100}?"
|
||||
r"xor\s+edx,\s*edx.{0,80}?call\s+eax",
|
||||
"lua_gc_sweep_all_lists fastcall ABI",
|
||||
)
|
||||
|
||||
for addr, label in (
|
||||
("6f73e0", "lua_gc_full_collection"),
|
||||
("6f7370", "lua_gc_shrink_memory"),
|
||||
("6f7080", "luaCallUserDataGC"),
|
||||
):
|
||||
require(
|
||||
rf"mov\s+eax,\s*0x{addr}.{{0,120}}?mov\s+ecx,\s*esi.{{0,80}}?call\s+eax",
|
||||
f"{label} ECX ABI",
|
||||
)
|
||||
|
||||
print("ABI validation: PASS")
|
||||
PY
|
||||
|
||||
sha256sum "$DLL" | tee SHA256SUMS.txt
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WeirdPerformance-2.4-B1-GC-Safe-Sweep
|
||||
path: |
|
||||
experiments/wp24b-gc/zig-out/bin/weirdperformance_gc24b1.dll
|
||||
experiments/wp24b-gc/SHA256SUMS.txt
|
||||
experiments/wp24b-gc/BINARY_INFO.txt
|
||||
experiments/wp24b-gc/ABI_DISASM.txt
|
||||
experiments/wp24b-gc/README.md
|
||||
if-no-files-found: error
|
||||
@@ -0,0 +1,131 @@
|
||||
name: Build WeirdPerformance 2.4-B2 GC reload-guard test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- test/wp24b2-gc-reload-guard
|
||||
paths:
|
||||
- 'experiments/wp24b-gc/**'
|
||||
- '.github/workflows/wp24b2-gc.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: mlugg/setup-zig@v2
|
||||
with:
|
||||
version: '0.17.0-dev.1970+67f39b551'
|
||||
- name: Build and ABI-validate x86 DLL
|
||||
working-directory: experiments/wp24b-gc
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
python3 b2_patch.py
|
||||
grep -q 'NATIVE_GRACE_COLLECTIONS: u32 = 3' main.zig
|
||||
grep -q '2.4-B2-gc-safe-sweep-reload-guard' main.zig
|
||||
grep -q 'weirdperformance_gc24b2' build.zig
|
||||
|
||||
zig build --fetch
|
||||
python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
matches = list(Path("zig-pkg").glob("zhook-*/src/zhook.zig"))
|
||||
if len(matches) != 1:
|
||||
raise SystemExit(f"expected one pinned zhook source, found {len(matches)}")
|
||||
p = matches[0]
|
||||
text = p.read_text(encoding="utf-8")
|
||||
old = "var patch: [MAX_STOLEN]u8 = .{0x90} ** MAX_STOLEN;"
|
||||
new = "var patch: [MAX_STOLEN]u8 = @splat(0x90);"
|
||||
if old not in text:
|
||||
raise SystemExit("expected pinned zhook repeat initializer not found")
|
||||
p.write_text(text.replace(old, new), encoding="utf-8")
|
||||
PY
|
||||
|
||||
zig build -Doptimize=small
|
||||
DLL=zig-out/bin/weirdperformance_gc24b2.dll
|
||||
test -s "$DLL"
|
||||
file "$DLL" | tee BINARY_INFO.txt
|
||||
file "$DLL" | grep -Eq 'PE32.*Intel (80386|i386)'
|
||||
strings "$DLL" | grep -q '2.4-B2-gc-safe-sweep-reload-guard'
|
||||
|
||||
if command -v llvm-objdump >/dev/null 2>&1; then
|
||||
llvm-objdump -d --x86-asm-syntax=intel "$DLL" > ABI_DISASM.txt
|
||||
else
|
||||
objdump -d -M intel "$DLL" > ABI_DISASM.txt
|
||||
fi
|
||||
|
||||
python3 - <<'PY'
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
asm = Path("ABI_DISASM.txt").read_text(encoding="utf-8", errors="replace").lower()
|
||||
|
||||
def require(pattern, label):
|
||||
if not re.search(pattern, asm, re.S):
|
||||
raise SystemExit(f"ABI validation failed: {label}")
|
||||
|
||||
# zhook must jump to callbacks compiled with L arriving in ECX.
|
||||
m = re.search(r"mov\s+edx,\s*0x6f7340.{0,240}?push\s+0x([0-9a-f]+)", asm, re.S)
|
||||
if not m:
|
||||
raise SystemExit("ABI validation failed: collector callback address not found")
|
||||
cb = m.group(1).lstrip("0") or "0"
|
||||
pos = asm.find(cb + ":")
|
||||
if pos < 0 or not re.search(r"mov\s+esi,\s*ecx", asm[pos:pos+700]):
|
||||
raise SystemExit("ABI validation failed: collector callback does not consume L from ECX")
|
||||
|
||||
m = re.search(r"mov\s+edx,\s*0x6f6ef0.{0,240}?push\s+0x([0-9a-f]+)", asm, re.S)
|
||||
if not m:
|
||||
raise SystemExit("ABI validation failed: lua_close callback address not found")
|
||||
cb = m.group(1).lstrip("0") or "0"
|
||||
pos = asm.find(cb + ":")
|
||||
if pos < 0 or not re.search(r"mov\s+esi,\s*ecx", asm[pos:pos+500]):
|
||||
raise SystemExit("ABI validation failed: lua_close callback does not consume L from ECX")
|
||||
|
||||
# Native helper ABI observed in WoW 5875:
|
||||
# ECX=L, EDX=&list, stack arg=all for 0x6F7210.
|
||||
require(
|
||||
r"lea\s+edx,\s*\[edi\s*\+\s*0x14\].{0,160}?"
|
||||
r"mov\s+eax,\s*0x6f7210.{0,120}?"
|
||||
r"mov\s+ecx,\s*esi.{0,120}?"
|
||||
r"push\s+0x0.{0,80}?call\s+eax",
|
||||
"lua_gc_remove_objects register/stack ABI",
|
||||
)
|
||||
|
||||
require(
|
||||
r"mov\s+eax,\s*0x6f72f0.{0,120}?"
|
||||
r"mov\s+ecx,\s*esi.{0,100}?"
|
||||
r"xor\s+edx,\s*edx.{0,80}?call\s+eax",
|
||||
"lua_gc_sweep_all_lists fastcall ABI",
|
||||
)
|
||||
|
||||
for addr, label in (
|
||||
("6f73e0", "lua_gc_full_collection"),
|
||||
("6f7370", "lua_gc_shrink_memory"),
|
||||
("6f7080", "luaCallUserDataGC"),
|
||||
):
|
||||
require(
|
||||
rf"mov\s+eax,\s*0x{addr}.{{0,120}}?mov\s+ecx,\s*esi.{{0,80}}?call\s+eax",
|
||||
f"{label} ECX ABI",
|
||||
)
|
||||
|
||||
print("ABI validation: PASS")
|
||||
PY
|
||||
|
||||
sha256sum "$DLL" | tee SHA256SUMS.txt
|
||||
sha256sum main.zig build.zig b2_patch.py > PATCHED_SOURCE_SHA256.txt
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WeirdPerformance-2.4-B2-GC-Reload-Guard
|
||||
path: |
|
||||
experiments/wp24b-gc/zig-out/bin/weirdperformance_gc24b2.dll
|
||||
experiments/wp24b-gc/SHA256SUMS.txt
|
||||
experiments/wp24b-gc/PATCHED_SOURCE_SHA256.txt
|
||||
experiments/wp24b-gc/BINARY_INFO.txt
|
||||
experiments/wp24b-gc/ABI_DISASM.txt
|
||||
experiments/wp24b-gc/README-B2.md
|
||||
experiments/wp24b-gc/b2_patch.py
|
||||
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
|
||||
@@ -0,0 +1,53 @@
|
||||
# WeirdPerformance 2.4-B2 — GC Safe Sweep Reload Guard
|
||||
|
||||
Strict A/B experiment for WoW 1.12.1 build 5875.
|
||||
|
||||
## Baseline
|
||||
|
||||
Keep the validated 2.4-A binary unchanged:
|
||||
|
||||
- `weirdperformance.dll`
|
||||
- SHA-256: `d35168ae06c19087ef9b7c68918a054640396dfbfcef0664e18e9372284b5eef`
|
||||
|
||||
## B2 variant
|
||||
|
||||
Add:
|
||||
|
||||
- `weirdperformance_gc24b2.dll`
|
||||
|
||||
B2 is generated from the ABI-fixed B1 source with one isolated behavioral change:
|
||||
|
||||
- whenever a new Lua `global_State` is observed (initial world entry, `/reload`, logout/relog), the first **3 GC collections stay fully native**;
|
||||
- after those 3 native collections, the exact B1 incremental rootgc safe-sweep behavior resumes.
|
||||
|
||||
Nothing else is changed: same 50,000-object chunk size, same native mark/userdata/string work, same birth-mark ownership checks, same `lua_close` reconnect safety, same `IS_IN_WORLD` guard, no allocator changes, no generational age bitmap, no write barriers, no profiling/RDTSC.
|
||||
|
||||
The B2 delta is applied reproducibly by `b2_patch.py`; the build fails if any expected B1 source anchor no longer matches.
|
||||
|
||||
## Why this test exists
|
||||
|
||||
A delayed crash can be caused by corruption that happened earlier, so the crashing stack does not have to contain the GC companion. B2 specifically reduces risk during Lua state recreation without changing the core GC experiment.
|
||||
|
||||
## Installation
|
||||
|
||||
Keep both DLLs next to WoW and list both in `dlls.txt`:
|
||||
|
||||
```text
|
||||
weirdperformance.dll
|
||||
weirdperformance_gc24b2.dll
|
||||
```
|
||||
|
||||
Remove `weirdperformance_gc24b1.dll` while testing B2. Never load B1 and B2 together.
|
||||
|
||||
Removing `weirdperformance_gc24b2.dll` returns exactly to the validated 2.4-A baseline.
|
||||
|
||||
## First test
|
||||
|
||||
1. Launch and enter the world.
|
||||
2. Play normally for 10–15 minutes.
|
||||
3. Do one `/reload`, then wait and play for several minutes.
|
||||
4. If clean, do 10 `/reload` total.
|
||||
5. Then test logout/relog and character changes.
|
||||
6. Keep all other DLLs/addons/settings unchanged for the A/B comparison.
|
||||
|
||||
If an ERROR #132 occurs, keep both `Crash.txt` and `Crash.dmp` and note what happened shortly before the crash.
|
||||
@@ -0,0 +1,46 @@
|
||||
# WeirdPerformance 2.4-B1 — GC Safe Sweep ABI-fixed test
|
||||
|
||||
Strict A/B experiment for WoW 1.12.1 build 5875.
|
||||
|
||||
## Baseline
|
||||
|
||||
Keep the validated 2.4-A binary **unchanged**:
|
||||
|
||||
- `weirdperformance.dll`
|
||||
- SHA-256: `d35168ae06c19087ef9b7c68918a054640396dfbfcef0664e18e9372284b5eef`
|
||||
|
||||
## B1 variant
|
||||
|
||||
Add:
|
||||
|
||||
- `weirdperformance_gc24b1.dll`
|
||||
|
||||
The companion changes **only Lua GC behavior**. It does not replace or rebuild the validated 2.4-A DLL.
|
||||
|
||||
The first 2.4-B build is invalid and must not be used. It was built with an i386 Windows fastcall ABI mismatch and crashed at startup in WoW's native `lua_gc_remove_objects`. B1 is built with a pinned Zig 0.17 development toolchain and is binary-disassembly checked for the expected register ABI before distribution.
|
||||
|
||||
The implementation is based on the historical pre-generational incremental sweep: WoW keeps its native mark, userdata/string sweep, memory shrink and finalizers. Only the main `rootgc` sweep is split into 50,000-object chunks.
|
||||
|
||||
## Safety scope
|
||||
|
||||
- hooks only `luaC_collectgarbage` and `lua_close`;
|
||||
- no overlap with the lifecycle hooks referenced by the validated 2.4-A binary;
|
||||
- any fragmented sweep is reconnected before native `lua_close` destroys the Lua state;
|
||||
- GC calls made from inside `lua_close` stay native;
|
||||
- the birth-mark byte is ownership checked and restored only while still owned;
|
||||
- a changed Lua `global_State` forces native fallback rather than reconnecting stale pointers;
|
||||
- hook installation is transactional;
|
||||
- no generational age bitmap, no write barriers, no GC tuning, no profiling/RDTSC.
|
||||
|
||||
## Installation for the test
|
||||
|
||||
Keep both DLLs next to WoW and list both in `dlls.txt`:
|
||||
|
||||
```text
|
||||
weirdperformance.dll
|
||||
weirdperformance_gc24b1.dll
|
||||
```
|
||||
|
||||
Delete the old `weirdperformance_gc24b.dll` if it is still present.
|
||||
|
||||
Removing `weirdperformance_gc24b1.dll` returns you exactly to the validated 2.4-A baseline.
|
||||
@@ -0,0 +1,79 @@
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(__file__).resolve().parent
|
||||
main_path = root / "main.zig"
|
||||
build_path = root / "build.zig"
|
||||
|
||||
main = main_path.read_text(encoding="utf-8")
|
||||
build = build_path.read_text(encoding="utf-8")
|
||||
|
||||
replacements = [
|
||||
(
|
||||
"const BATCH_HEADROOM: u32 = 128 * 1024;\n",
|
||||
"const BATCH_HEADROOM: u32 = 128 * 1024;\n"
|
||||
"const NATIVE_GRACE_COLLECTIONS: u32 = 3;\n",
|
||||
),
|
||||
(
|
||||
"var installed = false;\nvar in_gc = false;\nvar closing_lua = false;\n",
|
||||
"var installed = false;\nvar in_gc = false;\nvar closing_lua = false;\n"
|
||||
"var active_g: u32 = 0;\n"
|
||||
"var native_grace_collections: u32 = 0;\n",
|
||||
),
|
||||
(
|
||||
" // Never carry private list state into another Lua global_State.\n"
|
||||
" if (sweeping and g != saved_g) {\n"
|
||||
" resetSweepState();\n"
|
||||
" collect_hook.callOriginal(.{L});\n"
|
||||
" return;\n"
|
||||
" }\n\n"
|
||||
" // If another module changed the birth byte during our split cycle,\n",
|
||||
" // Never carry private list state into another Lua global_State.\n"
|
||||
" if (sweeping and g != saved_g) {\n"
|
||||
" resetSweepState();\n"
|
||||
" collect_hook.callOriginal(.{L});\n"
|
||||
" return;\n"
|
||||
" }\n\n"
|
||||
" // B2: when a fresh Lua global_State appears (initial world entry,\n"
|
||||
" // /reload, logout/relog), keep the first three collections fully\n"
|
||||
" // native. This gives WoW ownership of the fragile state-rebuild\n"
|
||||
" // window before incremental rootgc sweeping resumes.\n"
|
||||
" if (active_g != g) {\n"
|
||||
" active_g = g;\n"
|
||||
" native_grace_collections = NATIVE_GRACE_COLLECTIONS;\n"
|
||||
" }\n"
|
||||
" if (native_grace_collections != 0) {\n"
|
||||
" native_grace_collections -= 1;\n"
|
||||
" collect_hook.callOriginal(.{L});\n"
|
||||
" return;\n"
|
||||
" }\n\n"
|
||||
" // If another module changed the birth byte during our split cycle,\n",
|
||||
),
|
||||
(
|
||||
" reconnectSweep();\n in_gc = false;\n\n lua_close_hook.callOriginal(.{L});\n",
|
||||
" reconnectSweep();\n in_gc = false;\n"
|
||||
" active_g = 0;\n"
|
||||
" native_grace_collections = 0;\n\n"
|
||||
" lua_close_hook.callOriginal(.{L});\n",
|
||||
),
|
||||
(
|
||||
'const version: [*:0]const u8 = "2.4-B1-gc-safe-sweep-abi";\n',
|
||||
'const version: [*:0]const u8 = "2.4-B2-gc-safe-sweep-reload-guard";\n',
|
||||
),
|
||||
]
|
||||
|
||||
for old, new in replacements:
|
||||
count = main.count(old)
|
||||
if count != 1:
|
||||
raise SystemExit(f"main.zig patch anchor mismatch: expected 1, found {count}: {old[:80]!r}")
|
||||
main = main.replace(old, new, 1)
|
||||
|
||||
old_name = '.name = "weirdperformance_gc24b1",'
|
||||
new_name = '.name = "weirdperformance_gc24b2",'
|
||||
if build.count(old_name) != 1:
|
||||
raise SystemExit("build.zig output-name anchor mismatch")
|
||||
build = build.replace(old_name, new_name, 1)
|
||||
|
||||
main_path.write_text(main, encoding="utf-8")
|
||||
build_path.write_text(build, encoding="utf-8")
|
||||
|
||||
print("B2 patch applied: new-global_State native grace = 3 GC cycles")
|
||||
@@ -0,0 +1,30 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.resolveTargetQuery(.{
|
||||
.cpu_arch = .x86,
|
||||
.os_tag = .windows,
|
||||
.abi = .msvc,
|
||||
.cpu_features_add = std.Target.x86.featureSet(&.{ .sse, .sse2 }),
|
||||
});
|
||||
const optimize = b.option(std.builtin.OptimizeMode, "optimize", "Optimization mode") orelse .small;
|
||||
|
||||
const zhook_dep = b.dependency("zhook", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const lib = b.addLibrary(.{
|
||||
.name = "weirdperformance_gc24b1",
|
||||
.linkage = .dynamic,
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.imports = &.{
|
||||
.{ .name = "zhook", .module = zhook_dep.module("zhook") },
|
||||
},
|
||||
}),
|
||||
});
|
||||
b.installArtifact(lib);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
.{
|
||||
.name = .weirdperformance_gc24b,
|
||||
.version = "2.4.0",
|
||||
.fingerprint = 0x95e8be635406d50f,
|
||||
.dependencies = .{
|
||||
.zhook = .{
|
||||
.url = "https://codeberg.org/marcelinevq/zhook/archive/f1b252ed61ad839f00310c386761d068f293ad0f.tar.gz",
|
||||
.hash = "zhook-0.1.0-pFkSYC6FAACAnkqu0k_DJBWdL0gJjrM22IfXeQPJAMov",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"main.zig",
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
//! WeirdPerformance 2.4-B GC safe-sweep companion.
|
||||
//!
|
||||
//! A/B contract:
|
||||
//! A = validated WeirdPerformance 2.4-A binary, unchanged.
|
||||
//! B = the exact same 2.4-A binary + this companion DLL.
|
||||
//!
|
||||
//! This companion only changes Lua GC behavior: WoW's native mark/udata/string
|
||||
//! work is kept, while the rootgc sweep is split into bounded chunks.
|
||||
//!
|
||||
//! Target: WoW 1.12.1 build 5875, x86 only.
|
||||
|
||||
const std = @import("std");
|
||||
const hook = @import("zhook");
|
||||
const WINAPI = std.builtin.CallingConvention.winapi;
|
||||
const X86_FASTCALL: std.builtin.CallingConvention = .{ .x86_fastcall = .{} };
|
||||
|
||||
const LUA_COLLECT_GARBAGE_ADDR: usize = 0x6F7340;
|
||||
const LUA_CLOSE_ADDR: usize = 0x6F6EF0;
|
||||
const BIRTH_MARK_ADDR: usize = 0x6F7B37;
|
||||
const IS_IN_WORLD_ADDR: u32 = 0x00B4B424;
|
||||
|
||||
const GS_ROOTGC: u32 = 0x10;
|
||||
const GS_ROOTUDATA: u32 = 0x14;
|
||||
const GS_GCTHRESHOLD: u32 = 0x24;
|
||||
const GS_TOTALBYTES: u32 = 0x28;
|
||||
const OBJ_NEXT: u32 = 0;
|
||||
|
||||
const CHUNK_SIZE: u32 = 50_000;
|
||||
const BATCH_HEADROOM: u32 = 128 * 1024;
|
||||
|
||||
// Crash logs for the supported client show 0x00400000..0x00D2B000.
|
||||
const EXPECTED_IMAGE_BASE: usize = 0x00400000;
|
||||
const EXPECTED_IMAGE_SIZE: u32 = 0x0092B000;
|
||||
|
||||
const CollectFn = fn (u32) callconv(X86_FASTCALL) void;
|
||||
const LuaCloseFn = fn (u32) callconv(X86_FASTCALL) void;
|
||||
const SweepAllFn = fn (u32, u32) callconv(X86_FASTCALL) void;
|
||||
const RemoveObjectsFn = fn (u32, u32, u32) callconv(X86_FASTCALL) u32;
|
||||
|
||||
const lua_gc_full_collection: *const CollectFn = @ptrFromInt(0x6F73E0);
|
||||
const lua_gc_shrink_memory: *const CollectFn = @ptrFromInt(0x6F7370);
|
||||
const luaCallUserDataGC: *const CollectFn = @ptrFromInt(0x6F7080);
|
||||
const lua_gc_sweep_all_lists: *const SweepAllFn = @ptrFromInt(0x6F72F0);
|
||||
const lua_gc_remove_objects: *const RemoveObjectsFn = @ptrFromInt(0x6F7210);
|
||||
|
||||
var collect_hook: hook.Detour(CollectFn) = .{};
|
||||
var lua_close_hook: hook.Detour(LuaCloseFn) = .{};
|
||||
|
||||
var installed = false;
|
||||
var in_gc = false;
|
||||
var closing_lua = false;
|
||||
|
||||
var sweeping = false;
|
||||
var swept_head: u32 = 0;
|
||||
var swept_tail: u32 = 0;
|
||||
var unswept_rest: u32 = 0;
|
||||
var saved_g: u32 = 0;
|
||||
|
||||
var birth_mark_owned = false;
|
||||
var birth_mark_original: u8 = 0;
|
||||
|
||||
inline fn readU8(addr: usize) u8 {
|
||||
return @as(*volatile const u8, @ptrFromInt(addr)).*;
|
||||
}
|
||||
|
||||
inline fn readU16(addr: usize) u16 {
|
||||
return @as(*volatile const u16, @ptrFromInt(addr)).*;
|
||||
}
|
||||
|
||||
inline fn readU32(addr: u32) u32 {
|
||||
return @as(*volatile const u32, @ptrFromInt(addr)).*;
|
||||
}
|
||||
|
||||
inline fn readU32usize(addr: usize) u32 {
|
||||
return @as(*volatile const u32, @ptrFromInt(addr)).*;
|
||||
}
|
||||
|
||||
inline fn writeU32(addr: u32, value: u32) void {
|
||||
@as(*volatile u32, @ptrFromInt(addr)).* = value;
|
||||
}
|
||||
|
||||
inline fn getGlobalState(L: u32) u32 {
|
||||
return readU32(L + 0x10);
|
||||
}
|
||||
|
||||
fn validateClient() bool {
|
||||
if (readU16(EXPECTED_IMAGE_BASE) != 0x5A4D) return false; // MZ
|
||||
|
||||
const pe_off = readU32usize(EXPECTED_IMAGE_BASE + 0x3C);
|
||||
const pe = EXPECTED_IMAGE_BASE + pe_off;
|
||||
if (readU32usize(pe) != 0x00004550) return false; // PE\0\0
|
||||
if (readU16(pe + 4) != 0x014C) return false; // IMAGE_FILE_MACHINE_I386
|
||||
if (readU16(pe + 24) != 0x010B) return false; // PE32 optional header
|
||||
|
||||
const image_size = readU32usize(pe + 24 + 56);
|
||||
return image_size == EXPECTED_IMAGE_SIZE;
|
||||
}
|
||||
|
||||
fn findNth(head: u32, n: u32) struct { obj: u32, count: u32 } {
|
||||
var obj = head;
|
||||
var i: u32 = 0;
|
||||
while (obj != 0 and i < n) : (i += 1) {
|
||||
const next = readU32(obj + OBJ_NEXT);
|
||||
if (next == 0) return .{ .obj = 0, .count = i + 1 };
|
||||
obj = next;
|
||||
}
|
||||
return .{ .obj = obj, .count = i };
|
||||
}
|
||||
|
||||
fn findTail(head: u32) u32 {
|
||||
var obj = head;
|
||||
if (obj == 0) return 0;
|
||||
while (true) {
|
||||
const next = readU32(obj + OBJ_NEXT);
|
||||
if (next == 0) return obj;
|
||||
obj = next;
|
||||
}
|
||||
}
|
||||
|
||||
fn acquireBirthMark() bool {
|
||||
if (birth_mark_owned) return readU8(BIRTH_MARK_ADDR) == 0x01;
|
||||
|
||||
const current = readU8(BIRTH_MARK_ADDR);
|
||||
if (current != 0x00) return false;
|
||||
|
||||
birth_mark_original = current;
|
||||
const patch = [1]u8{0x01};
|
||||
hook.writeProtected(BIRTH_MARK_ADDR, &patch);
|
||||
|
||||
if (readU8(BIRTH_MARK_ADDR) != 0x01) return false;
|
||||
birth_mark_owned = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
fn releaseBirthMark() void {
|
||||
if (!birth_mark_owned) return;
|
||||
|
||||
// Restore only while we still own exactly the byte we installed.
|
||||
if (readU8(BIRTH_MARK_ADDR) == 0x01) {
|
||||
const patch = [1]u8{birth_mark_original};
|
||||
hook.writeProtected(BIRTH_MARK_ADDR, &patch);
|
||||
}
|
||||
birth_mark_owned = false;
|
||||
}
|
||||
|
||||
fn resetSweepState() void {
|
||||
sweeping = false;
|
||||
swept_head = 0;
|
||||
swept_tail = 0;
|
||||
unswept_rest = 0;
|
||||
saved_g = 0;
|
||||
releaseBirthMark();
|
||||
}
|
||||
|
||||
fn reconnectSweep() void {
|
||||
if (!sweeping or saved_g == 0) {
|
||||
resetSweepState();
|
||||
return;
|
||||
}
|
||||
|
||||
const g = saved_g;
|
||||
var tail = findTail(readU32(g + GS_ROOTGC));
|
||||
|
||||
if (unswept_rest != 0) {
|
||||
if (tail != 0) {
|
||||
writeU32(tail + OBJ_NEXT, unswept_rest);
|
||||
} else {
|
||||
writeU32(g + GS_ROOTGC, unswept_rest);
|
||||
}
|
||||
tail = findTail(unswept_rest);
|
||||
}
|
||||
|
||||
if (swept_head != 0) {
|
||||
if (tail != 0) {
|
||||
writeU32(tail + OBJ_NEXT, swept_head);
|
||||
} else {
|
||||
writeU32(g + GS_ROOTGC, swept_head);
|
||||
}
|
||||
}
|
||||
|
||||
resetSweepState();
|
||||
}
|
||||
|
||||
fn detachSweptAndRestore(g: u32) void {
|
||||
const current_head = readU32(g + GS_ROOTGC);
|
||||
if (current_head != 0) {
|
||||
const tail = findTail(current_head);
|
||||
if (swept_head == 0) {
|
||||
swept_head = current_head;
|
||||
swept_tail = tail;
|
||||
} else {
|
||||
writeU32(swept_tail + OBJ_NEXT, current_head);
|
||||
swept_tail = tail;
|
||||
}
|
||||
}
|
||||
|
||||
writeU32(g + GS_ROOTGC, unswept_rest);
|
||||
unswept_rest = 0;
|
||||
}
|
||||
|
||||
fn nativeFallback(L: u32) void {
|
||||
reconnectSweep();
|
||||
collect_hook.callOriginal(.{L});
|
||||
}
|
||||
|
||||
fn collectGarbageDetour(L: u32) callconv(X86_FASTCALL) void {
|
||||
if (closing_lua) {
|
||||
collect_hook.callOriginal(.{L});
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep GlueXML/login/character-select on WoW's native GC.
|
||||
// The experiment is intentionally gameplay-only.
|
||||
if (readU32(IS_IN_WORLD_ADDR) == 0) {
|
||||
collect_hook.callOriginal(.{L});
|
||||
return;
|
||||
}
|
||||
if (in_gc) return;
|
||||
if (L == 0) return;
|
||||
if (readU32(L + 0x60) == 0) return;
|
||||
|
||||
in_gc = true;
|
||||
defer in_gc = false;
|
||||
|
||||
const g = getGlobalState(L);
|
||||
if (g == 0) {
|
||||
collect_hook.callOriginal(.{L});
|
||||
return;
|
||||
}
|
||||
|
||||
// Never carry private list state into another Lua global_State.
|
||||
if (sweeping and g != saved_g) {
|
||||
resetSweepState();
|
||||
collect_hook.callOriginal(.{L});
|
||||
return;
|
||||
}
|
||||
|
||||
// If another module changed the birth byte during our split cycle,
|
||||
// reconnect immediately and hand this collection back to WoW.
|
||||
if (sweeping and (!birth_mark_owned or readU8(BIRTH_MARK_ADDR) != 0x01)) {
|
||||
nativeFallback(L);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sweeping) {
|
||||
// Keep WoW's native mark, userdata sweep, and string sweep.
|
||||
lua_gc_full_collection(L);
|
||||
_ = lua_gc_remove_objects(L, g + GS_ROOTUDATA, 0);
|
||||
lua_gc_sweep_all_lists(L, 0);
|
||||
|
||||
swept_head = 0;
|
||||
swept_tail = 0;
|
||||
|
||||
const rootgc_head = readU32(g + GS_ROOTGC);
|
||||
const result = findNth(rootgc_head, CHUNK_SIZE);
|
||||
|
||||
// Small rootgc lists remain one-shot, matching native behavior closely.
|
||||
if (result.obj == 0) {
|
||||
_ = lua_gc_remove_objects(L, g + GS_ROOTGC, 0);
|
||||
lua_gc_shrink_memory(L);
|
||||
luaCallUserDataGC(L);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the birth marker is unavailable, do not split this collection.
|
||||
if (!acquireBirthMark()) {
|
||||
_ = lua_gc_remove_objects(L, g + GS_ROOTGC, 0);
|
||||
lua_gc_shrink_memory(L);
|
||||
luaCallUserDataGC(L);
|
||||
return;
|
||||
}
|
||||
|
||||
unswept_rest = readU32(result.obj + OBJ_NEXT);
|
||||
writeU32(result.obj + OBJ_NEXT, 0);
|
||||
sweeping = true;
|
||||
saved_g = g;
|
||||
|
||||
_ = lua_gc_remove_objects(L, g + GS_ROOTGC, 0);
|
||||
detachSweptAndRestore(g);
|
||||
|
||||
const totalbytes = readU32(g + GS_TOTALBYTES);
|
||||
writeU32(g + GS_GCTHRESHOLD, totalbytes + BATCH_HEADROOM);
|
||||
return;
|
||||
}
|
||||
|
||||
const rootgc_head = readU32(g + GS_ROOTGC);
|
||||
const result = findNth(rootgc_head, CHUNK_SIZE);
|
||||
|
||||
if (result.obj == 0) {
|
||||
_ = lua_gc_remove_objects(L, g + GS_ROOTGC, 0);
|
||||
|
||||
const current_head = readU32(g + GS_ROOTGC);
|
||||
if (swept_head != 0) {
|
||||
if (current_head != 0) {
|
||||
writeU32(swept_tail + OBJ_NEXT, current_head);
|
||||
}
|
||||
writeU32(g + GS_ROOTGC, swept_head);
|
||||
}
|
||||
|
||||
resetSweepState();
|
||||
lua_gc_shrink_memory(L);
|
||||
luaCallUserDataGC(L);
|
||||
return;
|
||||
}
|
||||
|
||||
unswept_rest = readU32(result.obj + OBJ_NEXT);
|
||||
writeU32(result.obj + OBJ_NEXT, 0);
|
||||
|
||||
_ = lua_gc_remove_objects(L, g + GS_ROOTGC, 0);
|
||||
detachSweptAndRestore(g);
|
||||
|
||||
const totalbytes = readU32(g + GS_TOTALBYTES);
|
||||
writeU32(g + GS_GCTHRESHOLD, totalbytes + BATCH_HEADROOM);
|
||||
}
|
||||
|
||||
fn luaCloseDetour(L: u32) callconv(X86_FASTCALL) void {
|
||||
// lua_close may run native sweep paths that bypass luaC_collectgarbage.
|
||||
// Reconnect every private fragment while the old Lua state is still valid.
|
||||
closing_lua = true;
|
||||
reconnectSweep();
|
||||
in_gc = false;
|
||||
|
||||
lua_close_hook.callOriginal(.{L});
|
||||
|
||||
closing_lua = false;
|
||||
}
|
||||
|
||||
fn install() void {
|
||||
if (installed) return;
|
||||
if (!validateClient()) return;
|
||||
|
||||
// Transactional install: lua_close guard first, collector second.
|
||||
// If the collector cannot attach, roll the close hook back immediately.
|
||||
if (lua_close_hook.attach(LUA_CLOSE_ADDR, &luaCloseDetour) != .ok) return;
|
||||
|
||||
if (collect_hook.attach(LUA_COLLECT_GARBAGE_ADDR, &collectGarbageDetour) != .ok) {
|
||||
lua_close_hook.detach();
|
||||
return;
|
||||
}
|
||||
|
||||
installed = true;
|
||||
}
|
||||
|
||||
const version: [*:0]const u8 = "2.4-B1-gc-safe-sweep-abi";
|
||||
|
||||
pub export fn WeirdPerformanceGC24B_GetVersion() callconv(.c) [*:0]const u8 {
|
||||
return version;
|
||||
}
|
||||
|
||||
pub export fn WeirdPerformanceGC24B_IsActive() callconv(.c) i32 {
|
||||
return if (installed) 1 else 0;
|
||||
}
|
||||
|
||||
pub export fn DllMain(
|
||||
_: ?*anyopaque,
|
||||
reason: u32,
|
||||
_: ?*anyopaque,
|
||||
) callconv(WINAPI) std.os.windows.BOOL {
|
||||
if (reason == 1) install();
|
||||
|
||||
// Process-lifetime A/B companion. We intentionally do not hot-unhook
|
||||
// detours during process teardown.
|
||||
return @enumFromInt(1);
|
||||
}
|
||||
+4
-113
@@ -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,14 +942,11 @@ comptime {
|
||||
pub export fn DllMain(
|
||||
_: ?*anyopaque,
|
||||
reason: u32,
|
||||
reserved: ?*anyopaque,
|
||||
_: ?*anyopaque,
|
||||
) callconv(WINAPI) std.os.windows.BOOL {
|
||||
switch (reason) {
|
||||
1 => install(),
|
||||
0 => {
|
||||
// Process-exit safety only; runtime Outline behavior is unchanged.
|
||||
if (reserved == null) uninstall();
|
||||
},
|
||||
0 => uninstall(),
|
||||
else => {},
|
||||
}
|
||||
return @enumFromInt(1);
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
+191
-478
File diff suppressed because it is too large
Load Diff
@@ -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,8 +81,6 @@ fn renderDrawDetour(this: u32, view_matrix: u32, batch_data: u32, batch_indices:
|
||||
api.initD3D9Deferred();
|
||||
}
|
||||
|
||||
// Isolation test: keep the initial deferred D3D9 hook install, but do not
|
||||
// repeatedly verify/rewrite the live vtable while rendering.
|
||||
// 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