"""Dump clamp site and nearby camera loads. 11/09/2026."""
from __future__ import annotations

import ctypes
import ctypes.wintypes as w
import struct
import sys

kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
psapi = ctypes.WinDLL("psapi", use_last_error=True)
kernel32.OpenProcess.restype = w.HANDLE
kernel32.ReadProcessMemory.argtypes = [
    w.HANDLE, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)
]
kernel32.ReadProcessMemory.restype = w.BOOL


class MODULEINFO(ctypes.Structure):
    _fields_ = [
        ("lpBaseOfDll", ctypes.c_void_p),
        ("SizeOfImage", w.DWORD),
        ("EntryPoint", ctypes.c_void_p),
    ]


def rpm(h, addr, n):
    buf = (ctypes.c_ubyte * n)()
    got = ctypes.c_size_t(0)
    if not kernel32.ReadProcessMemory(h, ctypes.c_void_p(addr), buf, n, ctypes.byref(got)):
        return None
    return bytes(buf[: got.value])


def main_module(h):
    mods = (ctypes.c_uint64 * 1024)()
    needed = w.DWORD()
    psapi.EnumProcessModulesEx(h, ctypes.byref(mods), ctypes.sizeof(mods), ctypes.byref(needed), 0x03)
    name = ctypes.create_unicode_buffer(260)
    psapi.GetModuleBaseNameW.argtypes = [w.HANDLE, ctypes.c_void_p, w.LPWSTR, w.DWORD]
    psapi.GetModuleInformation.argtypes = [w.HANDLE, ctypes.c_void_p, ctypes.c_void_p, w.DWORD]
    for i in range(needed.value // 8):
        psapi.GetModuleBaseNameW(h, mods[i], name, 260)
        if name.value.lower().startswith("pathofexile"):
            mi = MODULEINFO()
            psapi.GetModuleInformation(h, ctypes.c_void_p(mods[i]), ctypes.byref(mi), ctypes.sizeof(mi))
            return int(mi.lpBaseOfDll or 0), int(mi.SizeOfImage)
    return 0, 0


def dump(h, addr, n, label):
    b = rpm(h, addr, n)
    print(f"\n=== {label} @ {addr:#x} n={n} ===")
    if not b:
        print("  unreadable")
        return
    for i in range(0, len(b), 16):
        chunk = b[i:i+16]
        hx = " ".join(f"{x:02x}" for x in chunk)
        print(f"  {addr+i:016x}  {hx}")


def try_follow_lea_or_mov(h, blob, insn_addr, blob_base):
    # scan for 48 8B / 48 8D rip-relative in blob
    i = 0
    while i + 7 <= len(blob):
        if blob[i] in (0x48, 0x4C) and blob[i+1] in (0x8B, 0x8D) and blob[i+2] in (0x05, 0x0D, 0x15, 0x1D, 0x25, 0x2D, 0x35, 0x3D):
            disp = struct.unpack_from("<i", blob, i + 3)[0]
            tgt = insn_addr + (i - 0 if blob_base == insn_addr else (blob_base - insn_addr)) 
            # insn VA = blob_base + i when blob starts at blob_base
            va = blob_base + i
            rip = va = blob_base + i
            target = va + 7 + disp
            ptr = rpm(h, target, 8)
            val = struct.unpack_from("<Q", ptr, 0)[0] if ptr else 0
            print(f"  rip-rel {blob[i]:02x} {blob[i+1]:02x} {blob[i+2]:02x} va={va:#x} slot={target:#x} *={val:#x}")
            if 0x10000 < val < 0x7fffffffffff:
                obj = rpm(h, val, 0x50)
                if obj:
                    vt = struct.unpack_from("<Q", obj, 0)[0]
                    floats = struct.unpack_from("<ffffff", obj, 0x2C)
                    print(f"    obj vt={vt:#x} cur={floats[0]:.2f} min={floats[1]:.2f} max={floats[2]:.2f} fov={floats[3]:.4f} zn={floats[4]:.3f} zf={floats[5]:.1f}")
            i += 7
            continue
        i += 1


def main():
    pid = int(sys.argv[1])
    h = kernel32.OpenProcess(0x10 | 0x400, False, pid)
    base, size = main_module(h)
    print(f"base={base:#x} size={size:#x}")

    clamp = 0x7FF6AFE69CAD
    dump(h, clamp - 0x80, 0xC0, "clamp-0x80")
    try_follow_lea_or_mov(h, rpm(h, clamp - 0x80, 0x80) or b"", clamp - 0x80, clamp - 0x80)

    dump(h, 0x7FF6B1481000, 0x100, "movss rcx+34 cluster")
    dump(h, 0x7FF6B14AEB00, 0x80, "movss rax+2C")

    # also dump current locked fake camera vs a few bytes
    fake = 0xB25BDEEE80
    dump(h, fake, 0x50, "LOCKED fake camera")


if __name__ == "__main__":
    main()
