"""patch_v10_test.py [--revert] v10: NOP the visible=false branch of UIElement_ItemList::OnVisibilityChanged so it ALWAYS calls UpdateEmptySlots (instead of only when becoming visible). Combined with v8-minimal (which makes UpdateEmptySlots run despite the visibility check inside it), this drains the WAITING-state UIItem pool on every panel close. REQUIRES v8-minimal to be applied first (otherwise UpdateEmptySlots bails immediately on the IsVisible() check inside). Site: 0x004E49AD: 74 07 jz +0x07 → skip UpdateEmptySlots when vis==false Replace: 90 90 nop nop → fall through, always call UpdateEmptySlots Just 2 bytes changed. The simplest patch yet. Risk: - v10 alone (without v8): UpdateEmptySlots would be called on hide but immediately bail at IsVisible() — no-op. Safe degradation. - v10 + v8: existing trim logic drains pool on hide. Same code that safely runs on show transitions. """ import argparse import ctypes import ctypes.wintypes as wt import sys SITE_VA = 0x004E49AD ORIG_BYTES = bytes([0x74, 0x07]) NOP_BYTES = bytes([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 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 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 VirtualProtectEx = k32.VirtualProtectEx VirtualProtectEx.argtypes = [wt.HANDLE, wt.LPVOID, ctypes.c_size_t, wt.DWORD, ctypes.POINTER(wt.DWORD)] VirtualProtectEx.restype = wt.BOOL def read_bytes(h, addr, n): buf = (ctypes.c_ubyte * n)() sz = ctypes.c_size_t(0) if not ReadProcessMemory(h, addr, buf, n, ctypes.byref(sz)): raise OSError(f"read 0x{addr:08x} err={ctypes.get_last_error()}") return bytes(buf[:sz.value]) def write_bytes(h, addr, data): old_prot = wt.DWORD(0) if not VirtualProtectEx(h, addr, len(data), PAGE_EXECUTE_READWRITE, ctypes.byref(old_prot)): raise OSError(f"VirtualProtectEx 0x{addr:08x} err={ctypes.get_last_error()}") sz = ctypes.c_size_t(0) ok = WriteProcessMemory(h, addr, data, len(data), ctypes.byref(sz)) err = ctypes.get_last_error() if not ok else 0 restored = wt.DWORD(0) VirtualProtectEx(h, addr, len(data), old_prot.value, ctypes.byref(restored)) if not ok: raise OSError(f"write 0x{addr:08x} err={err}") def main(): ap = argparse.ArgumentParser() ap.add_argument("pid", type=int) ap.add_argument("--revert", action="store_true") args = ap.parse_args() h = OpenProcess( PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION, False, args.pid, ) if not h: print(f"OpenProcess({args.pid}) err={ctypes.get_last_error()}"); sys.exit(2) cur = read_bytes(h, SITE_VA, 2) print(f"PID {args.pid}") print(f" site @ 0x{SITE_VA:08x} current: {cur.hex()}") if args.revert: if cur == ORIG_BYTES: print(f" already original — nothing to revert") elif cur == NOP_BYTES: write_bytes(h, SITE_VA, ORIG_BYTES) print(f" reverted; bytes now: {read_bytes(h, SITE_VA, 2).hex()}") else: print(f" UNEXPECTED bytes — refusing"); sys.exit(3) CloseHandle(h); return if cur == NOP_BYTES: print(f" already patched") elif cur == ORIG_BYTES: write_bytes(h, SITE_VA, NOP_BYTES) print(f" patched; bytes now: {read_bytes(h, SITE_VA, 2).hex()}") else: print(f" UNEXPECTED bytes — refusing. Expected: {ORIG_BYTES.hex()}") sys.exit(4) CloseHandle(h) if __name__ == "__main__": main()