"""probe_rtd3d_lost.py Walk s_Resources, find RenderTextureD3D entries (GR-view vfptr=0x00801A18), characterize buffer state on both LOST and LIVE entries. Entry stored in s_Resources is the GR-view = primary + 0x30. Buffers / fields tracked (offsets from PRIMARY in parens, accessed as entry+offset-0x30): primary+0x98 -> m_p2DTextureD3D (entry+0x68) primary+0x9c -> m_pCubeTextureD3D (entry+0x6c) primary+0xa0 -> m_D3DSurfaces.m_data (SmartArray data ptr) (entry+0x70) primary+0xa4 -> m_D3DSurfaces size+flag (high bit = own-buffer) (entry+0x74) primary+0x114 -> RenderSurface m_pSurfaceBits (entry+0xE4) primary+0x64 -> RenderSurface sourceData inner ptr (entry+0x34) primary+0x7c -> RenderTexture m_DBLevelInfos data ptr (entry+0x4c) primary+0x84 -> RenderTexture m_DBLevelInfos num (entry+0x54) The "would benefit from a v5/v20-style fix" question: - If lost entries have m_p2DTextureD3D non-NULL -> PurgeResource never ran (engine state) - If buf-fields non-NULL but D3D is NULL -> CPU shells with CPU buffers (v5-style WIN) - If everything NULL -> inert shells, same shape as RSD3D (need deleting dtor, v19-class) """ import ctypes, ctypes.wintypes as wt, sys, struct PROCESS_VM_READ = 0x10 PROCESS_QUERY_INFORMATION = 0x400 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 S_RESOURCES_M_DATA = 0x008398C4 S_RESOURCES_M_NUM = 0x008398CC RTD3D_VTABLE = 0x00801A18 def rd(h, va, n): buf = (ctypes.c_ubyte * n)(); sz = ctypes.c_size_t(0) if not k.ReadProcessMemory(h, va, buf, n, ctypes.byref(sz)): return None return bytes(buf[:sz.value]) def rd_u32(h, va): b = rd(h, va, 4); return struct.unpack('> 31 if sn else 0 print(f" entry=0x{e:08x} 2dTex=0x{d2:08x} cube=0x{dc:08x} " f"surfData=0x{sd:08x} num&flag=0x{sn:08x}(own={own}) " f"surfBits=0x{sb:08x} srcInner=0x{si:08x} " f"dbLvl=0x{dl:08x} dbLvlN={dn}")