"""check_orphan_refcounts.py For D3DXMesh instances with NO pointers in heap memory, read their internal refcount (COM-style at +0x?? — let's check several offsets). If refcount > 0, something outside heap (stack/static globals) references them. If refcount == 0, they're truly leaked. """ import ctypes, ctypes.wintypes as wt, struct, sys from collections import Counter VTABLE = 0x007ed3b0 PROCESS_VM_READ=0x10; PROCESS_QUERY_INFORMATION=0x400 MEM_COMMIT=0x1000; MEM_PRIVATE=0x20000 class MBI(ctypes.Structure): _fields_ = [('BaseAddress',ctypes.c_void_p),('AllocationBase',ctypes.c_void_p), ('AllocationProtect',wt.DWORD),('PartitionId',wt.WORD),('RegionSize',ctypes.c_size_t), ('State',wt.DWORD),('Protect',wt.DWORD),('Type',wt.DWORD)] k = ctypes.windll.kernel32 k.OpenProcess.argtypes=[wt.DWORD,wt.BOOL,wt.DWORD]; k.OpenProcess.restype=wt.HANDLE k.ReadProcessMemory.argtypes=[wt.HANDLE,wt.LPCVOID,wt.LPVOID,ctypes.c_size_t,ctypes.POINTER(ctypes.c_size_t)] k.ReadProcessMemory.restype=wt.BOOL k.VirtualQueryEx.argtypes=[wt.HANDLE,ctypes.c_void_p,ctypes.POINTER(MBI),ctypes.c_size_t] k.VirtualQueryEx.restype=ctypes.c_size_t pid = int(sys.argv[1]) h = k.OpenProcess(PROCESS_VM_READ|PROCESS_QUERY_INFORMATION, False, pid) # Pass 1: enumerate all RW regions and find mesh addrs + collect data rw_regions = [] mbi=MBI(); addr=0 while k.VirtualQueryEx(h, addr, ctypes.byref(mbi), ctypes.sizeof(mbi)): if mbi.State==MEM_COMMIT and mbi.Type==MEM_PRIVATE and (mbi.Protect&0xff) in (0x04, 0x40): buf=(ctypes.c_ubyte*mbi.RegionSize)(); sz=ctypes.c_size_t(0) if k.ReadProcessMemory(h, mbi.BaseAddress, buf, mbi.RegionSize, ctypes.byref(sz)): rw_regions.append((mbi.BaseAddress, bytes(buf[:sz.value]))) addr=(mbi.BaseAddress or 0)+mbi.RegionSize if addr>=0x80000000: break # Find mesh addresses + their data mesh_data = {} # addr -> first 64 bytes for base, data in rw_regions: end = (len(data)//4)*4 for off in range(0, end-0x40, 4): if struct.unpack_from(' 0] print(f'orphans (0 heap refs): {len(orphans)}') print(f'held: {len(held)}') # For each orphan, dump the first 0x40 bytes and try to find a refcount-looking field # COM objects typically have a refcount at +0x04 or +0x08 print() print('=== Orphan mesh refcount candidates (DWORDs at +0x04, +0x08, +0x0c, +0x10, +0x14) ===') hist_off04 = Counter() hist_off08 = Counter() hist_off0c = Counter() hist_off10 = Counter() hist_off14 = Counter() for a in orphans: d = mesh_data[a] v04 = struct.unpack_from('