Initial commit — leak-hunt project complete
Five bugs identified and patched in retail Asheron's Call client: - v3b: palette refcount over-increment (3-byte NOP at two sites) - v5: RenderSurface PurgeResource no-op stub (vtable slot 2 thunk) - v11: two dangling-pointer crash guards (NULL-check + reorder) - v14: CEnvCell::Destroy ClipPlaneList leak (18-byte JMP to cleanup thunk) - v22: unpacker stale-pointer SEH guard (whole-function __try/__except) All five ship in leakfix.dll (117 KB, SHA d282f23c…) which is loaded by acclient.exe at process start via PE import table patching by tools/install_leakfix.py. Controlled 15-client fleet soak: unpatched control died at 26h with palette exhaustion; all 14 patched clients survived past that point and reached ≥5-day uptime. Residual ~15 MB/h growth traced to d3d9.dll's internal slab allocator (260KB surface backing buffers retained after Release). See REPORT.md §10 for the full investigation; conclusion is that it's unfixable from outside d3d9. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
57b5e43d0e
199 changed files with 1648333 additions and 0 deletions
52
tools/va_to_raw.py
Normal file
52
tools/va_to_raw.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
"""
|
||||
va_to_raw.py <exe> <va_hex> [count]
|
||||
Maps a virtual address to file offset using the PE section table, and
|
||||
optionally dumps `count` bytes from the file at that offset.
|
||||
"""
|
||||
import struct, sys
|
||||
|
||||
path = sys.argv[1]
|
||||
va = int(sys.argv[2], 0)
|
||||
n = int(sys.argv[3]) if len(sys.argv) > 3 else 64
|
||||
|
||||
with open(path, "rb") as f:
|
||||
d = f.read()
|
||||
|
||||
# DOS header
|
||||
e_lfanew = struct.unpack_from("<I", d, 0x3C)[0]
|
||||
# PE signature + COFF header
|
||||
assert d[e_lfanew:e_lfanew+4] == b"PE\x00\x00"
|
||||
coff = e_lfanew + 4
|
||||
num_sections = struct.unpack_from("<H", d, coff + 2)[0]
|
||||
opt_hdr_size = struct.unpack_from("<H", d, coff + 16)[0]
|
||||
opt = coff + 20
|
||||
magic = struct.unpack_from("<H", d, opt)[0]
|
||||
assert magic == 0x10b, f"only PE32 supported, got {magic:#x}"
|
||||
image_base = struct.unpack_from("<I", d, opt + 28)[0]
|
||||
sect_tbl = opt + opt_hdr_size
|
||||
|
||||
print(f"image_base = 0x{image_base:08x}, num_sections = {num_sections}")
|
||||
rva = va - image_base
|
||||
print(f"RVA = 0x{rva:08x}")
|
||||
|
||||
for i in range(num_sections):
|
||||
off = sect_tbl + i * 40
|
||||
name = d[off:off+8].rstrip(b"\x00").decode("latin1")
|
||||
virt_size = struct.unpack_from("<I", d, off + 8)[0]
|
||||
virt_addr = struct.unpack_from("<I", d, off + 12)[0]
|
||||
raw_size = struct.unpack_from("<I", d, off + 16)[0]
|
||||
raw_ptr = struct.unpack_from("<I", d, off + 20)[0]
|
||||
if virt_addr <= rva < virt_addr + max(virt_size, raw_size):
|
||||
delta = rva - virt_addr
|
||||
file_off = raw_ptr + delta
|
||||
print(f"section {name!r}: VA=0x{image_base+virt_addr:08x} raw=0x{raw_ptr:08x}")
|
||||
print(f"file offset = 0x{file_off:08x}")
|
||||
chunk = d[file_off:file_off+n]
|
||||
for j in range(0, len(chunk), 16):
|
||||
row = chunk[j:j+16]
|
||||
hexs = " ".join(f"{b:02x}" for b in row)
|
||||
asci = "".join(chr(b) if 32 <= b < 127 else "." for b in row)
|
||||
print(f" 0x{va+j:08x} {hexs:<47} {asci}")
|
||||
break
|
||||
else:
|
||||
print("VA not in any section")
|
||||
Loading…
Add table
Add a link
Reference in a new issue