"""trace_mesh_holder.py Find ONE D3DXMesh instance, locate all pointers to it, and dump the surrounding bytes for each pointer location so we can identify the container by direct inspection (not heuristic vtable-walking). """ 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; MEM_IMAGE=0x1000000 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) if not h: sys.exit('open fail') # Enum all regions rw_regions = [] image_ranges = [] mbi=MBI(); addr=0 while k.VirtualQueryEx(h, addr, ctypes.byref(mbi), ctypes.sizeof(mbi)): if mbi.State==MEM_COMMIT and (mbi.Protect&0xff) != 0x01: if mbi.Type==MEM_IMAGE: image_ranges.append((mbi.BaseAddress, mbi.BaseAddress+mbi.RegionSize)) elif 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 def is_image(p): for lo, hi in image_ranges: if lo <= p < hi: return True return False # Find first D3DXMesh instance mesh_addrs = [] for base, data in rw_regions: end = (len(data)//4)*4 for off in range(0, end-4, 4): if struct.unpack_from(' meshbuffer+0x00 -> mesh # Direct pointers to meshes (NOT through MeshBuffer) are the orphans. # First: find MeshBuffer addresses (every CGfxObj's +0x6c points to one) # We'll just find ALL pointers to mesh addresses and let user inspect. # Take first 10 mesh addresses and find all pointers to each mesh_addr_set = set(mesh_addrs) print(f'Analysing first 20 mesh instances and their referrers:') for mesh_addr in mesh_addrs[:20]: refs = [] for base, data in rw_regions: end = (len(data)//4)*4 for off in range(0, end-4, 4): if struct.unpack_from('= 0x00400000 and v < 0x01000000: nearest_vt = v nearest_vt_offset = ro - back break vt_str = f' nearest vtable 0x{nearest_vt:08x} at -0x{nearest_vt_offset:x}' if nearest_vt else ' no vtable in 0x100 lookback' print(f' ref@0x{ref_va:08x}:{vt_str}')