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>
47 lines
1.6 KiB
Python
47 lines
1.6 KiB
Python
"""list_imports.py <pe.exe> — list all DLLs imported by a PE file."""
|
|
import struct, sys
|
|
|
|
with open(sys.argv[1], "rb") as f:
|
|
data = f.read()
|
|
pe_off = struct.unpack_from("<I", data, 0x3c)[0]
|
|
opt_off = pe_off + 24
|
|
num_sec = struct.unpack_from("<H", data, pe_off + 6)[0]
|
|
size_opt = struct.unpack_from("<H", data, pe_off + 20)[0]
|
|
sec_off = opt_off + size_opt
|
|
imp_rva, imp_size = struct.unpack_from("<II", data, opt_off + 96 + 1*8)
|
|
|
|
# Build RVA->offset map
|
|
secs = []
|
|
for i in range(num_sec):
|
|
sh = sec_off + i*40
|
|
vaddr = struct.unpack_from("<I", data, sh+12)[0]
|
|
vsize = struct.unpack_from("<I", data, sh+8)[0]
|
|
rsize = struct.unpack_from("<I", data, sh+16)[0]
|
|
roff = struct.unpack_from("<I", data, sh+20)[0]
|
|
secs.append((vaddr, max(vsize, rsize), roff, bytes(data[sh:sh+8]).rstrip(b"\0").decode()))
|
|
|
|
def rva2off(rva):
|
|
for vaddr, vsize, roff, _ in secs:
|
|
if vaddr <= rva < vaddr + vsize:
|
|
return roff + (rva - vaddr)
|
|
return None
|
|
|
|
print(f"IMPORT dir RVA=0x{imp_rva:08x} size={imp_size}")
|
|
print(f"sections:")
|
|
for v, sz, r, n in secs:
|
|
print(f" {n:>10} vaddr=0x{v:08x} vsize={sz:>8} roff=0x{r:x}")
|
|
|
|
print("imports:")
|
|
pos = rva2off(imp_rva)
|
|
i = 0
|
|
while True:
|
|
desc = data[pos:pos+20]
|
|
if desc == b"\0"*20:
|
|
print(f" [{i}] (null terminator)")
|
|
break
|
|
ilt_rva, ts, fwc, name_rva, iat_rva = struct.unpack("<IIIII", desc)
|
|
name_off = rva2off(name_rva)
|
|
name = bytes(data[name_off:data.index(0, name_off)]).decode("ascii", "replace") if name_off else "?"
|
|
print(f" [{i}] {name} (ILT=0x{ilt_rva:08x} IAT=0x{iat_rva:08x})")
|
|
pos += 20
|
|
i += 1
|