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>
52 lines
1.9 KiB
Python
52 lines
1.9 KiB
Python
"""
|
|
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")
|