"""patch_palette_v3b.py [--revert] v3b: extended v3 — patches BOTH makeModifiedPalette overloads. Site 1 (no-arg overload, FUN_0053efe0): addr 0x0053effe ff 40 24 -> 90 90 90 ; nop refcount++ Site 2 (2-arg overload, FUN_0053f120): addr 0x0053f19c ff 46 24 -> 90 90 90 ; nop refcount++ Both write a refcount++ on a freshly-constructed (refcount=1) palette, leaving it at refcount=2 — which is one short of being released back to zero by the typical cleanup chain (releasePalette + CSurface dtor). """ import ctypes, ctypes.wintypes as wt, sys SITES = [ (0x0053effe, bytes([0xff, 0x40, 0x24]), bytes([0x90, 0x90, 0x90])), (0x0053f19c, bytes([0xff, 0x46, 0x24]), bytes([0x90, 0x90, 0x90])), ] PROCESS_VM_READ = 0x0010 PROCESS_VM_WRITE = 0x0020 PROCESS_VM_OPERATION = 0x0008 PROCESS_QUERY_INFORMATION = 0x0400 PAGE_EXECUTE_READWRITE = 0x40 k32 = ctypes.windll.kernel32 OpenProcess = k32.OpenProcess OpenProcess.argtypes = [wt.DWORD, wt.BOOL, wt.DWORD]; OpenProcess.restype = wt.HANDLE CloseHandle = k32.CloseHandle CloseHandle.argtypes = [wt.HANDLE]; CloseHandle.restype = wt.BOOL ReadProcessMemory = k32.ReadProcessMemory ReadProcessMemory.argtypes = [wt.HANDLE, wt.LPCVOID, wt.LPVOID, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)] ReadProcessMemory.restype = wt.BOOL WriteProcessMemory = k32.WriteProcessMemory WriteProcessMemory.argtypes = [wt.HANDLE, wt.LPVOID, wt.LPCVOID, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)] WriteProcessMemory.restype = wt.BOOL VirtualProtectEx = k32.VirtualProtectEx VirtualProtectEx.argtypes = [wt.HANDLE, wt.LPVOID, ctypes.c_size_t, wt.DWORD, ctypes.POINTER(wt.DWORD)] VirtualProtectEx.restype = wt.BOOL def patch_site(h, addr, orig, patched, revert): cur = (ctypes.c_ubyte * 3)(); sz = ctypes.c_size_t(0) if not ReadProcessMemory(h, addr, cur, 3, ctypes.byref(sz)): print(f" read failed @ 0x{addr:08x}: err={ctypes.get_last_error()}"); return False cur_b = bytes(cur) target = orig if revert else patched expect_before = patched if revert else orig print(f" @ 0x{addr:08x}: cur={' '.join(f'{b:02x}' for b in cur_b)}", end="") if cur_b == target: print(f" already {'reverted' if revert else 'patched'}") return True if cur_b != expect_before: print(f" UNEXPECTED (wanted {' '.join(f'{b:02x}' for b in expect_before)}) — skip") return False old_prot = wt.DWORD(0) if not VirtualProtectEx(h, addr, 3, PAGE_EXECUTE_READWRITE, ctypes.byref(old_prot)): print(f" VirtualProtectEx failed: err={ctypes.get_last_error()}"); return False new = (ctypes.c_ubyte * 3)(*target) if not WriteProcessMemory(h, addr, new, 3, ctypes.byref(sz)): print(f" write failed: err={ctypes.get_last_error()}"); return False restored = wt.DWORD(0) VirtualProtectEx(h, addr, 3, old_prot.value, ctypes.byref(restored)) print(f" -> {' '.join(f'{b:02x}' for b in target)} ({'reverted' if revert else 'patched'})") return True def main(): pid = int(sys.argv[1]) revert = "--revert" in sys.argv h = OpenProcess(PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_VM_OPERATION|PROCESS_QUERY_INFORMATION, False, pid) if not h: print(f"OpenProcess({pid}) failed: err={ctypes.get_last_error()}"); sys.exit(2) print(f"PID {pid}:") for addr, orig, patched in SITES: patch_site(h, addr, orig, patched, revert) CloseHandle(h) if __name__ == "__main__": main()