"""Find live zoom-clamp AOB on PathOfExile.exe after 6AA2213C. 11/09/2026."""
from __future__ import annotations

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

k32 = ctypes.WinDLL("kernel32", use_last_error=True)
psapi = ctypes.WinDLL("psapi", use_last_error=True)
k32.OpenProcess.restype = w.HANDLE
k32.ReadProcessMemory.argtypes = [
    w.HANDLE, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)
]
k32.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 k32.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), 3)
    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 findall(blob, needle, limit=40):
    hits = []
    start = 0
    while len(hits) < limit:
        i = blob.find(needle, start)
        if i < 0:
            break
        hits.append(i)
        start = i + 1
    return hits


def masked_find(blob, raw, mask, limit=30):
    hits = []
    n, m = len(blob), len(raw)
    firsts = [i for i, b in enumerate(mask) if b]
    first = firsts[0]
    needle = raw[first]
    start = 0
    while len(hits) < limit:
        idx = blob.find(bytes([needle]), start + first)
        if idx < 0:
            break
        i = idx - first
        start = idx + 1
        if i < 0 or i + m > n:
            continue
        if all((not mask[j]) or blob[i + j] == raw[j] for j in range(m)):
            hits.append(i)
    return hits


def parse(spec):
    raw, mask = bytearray(), bytearray()
    for p in spec.split():
        if p == "?":
            raw.append(0)
            mask.append(0)
        else:
            raw.append(int(p, 16))
            mask.append(1)
    return bytes(raw), bytes(mask)


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


def try_cam(h, addr, label):
    b = rpm(h, addr, 0x50)
    if not b or len(b) < 0x50:
        print(f"  {label} {addr:#x} unreadable", flush=True)
        return
    vt = struct.unpack_from("<Q", b, 0)[0]
    cur, mn, mx, fov, zn, zf = struct.unpack_from("<ffffff", b, 0x2C)
    print(
        f"  {label} {addr:#x} vt={vt:#x} cur={cur:.3f} min={mn:.3f} max={mx:.3f} "
        f"fov={fov:.4f} zn={zn:.3f} zf={zf:.1f}",
        flush=True,
    )


def main():
    pid = int(sys.argv[1]) if len(sys.argv) > 1 else 3132
    t0 = time.time()
    h = k32.OpenProcess(0x410, False, pid)
    base, size = main_module(h)
    print(f"base={base:#x} size={size:#x}", flush=True)
    text_size = min(size, 0x2E86000)
    print(f"reading .text ~{text_size/1024/1024:.1f} MB", flush=True)
    blob = rpm(h, base, text_size)
    print(f"got {len(blob) if blob else 0} t={time.time()-t0:.1f}s", flush=True)
    if not blob:
        return

    # Classic clamp: movss [r+2C]; movss [r+34]; comiss
    print("\n=== nearby +2C then +34 then comiss (window 24) ===", flush=True)
    # F3 0F 10 xx 2C   opcode 4 or 5 bytes depending on ModRM
    hits_2c = 0
    for i in range(0, len(blob) - 16):
        # movss xmm, [reg+disp8] : F3 0F 10 /r  with disp8 == 0x2C
        if blob[i:i + 3] == b"\xF3\x0F\x10" and i + 4 < len(blob) and blob[i + 4] == 0x2C:
            window = blob[i:i + 24]
            if 0x34 in window[5:20] and b"\x0F\x2F" in window:
                hits_2c += 1
                if hits_2c <= 12:
                    va = base + i
                    print(f"  {va:#x} {window.hex()}", flush=True)
        if hits_2c >= 20:
            break
    print(f"hits={hits_2c}", flush=True)

    print("\n=== movss [reg+34] store (F3 0F 11 xx 34) ===", flush=True)
    n = 0
    for i in range(0, len(blob) - 5):
        if blob[i:i + 3] == b"\xF3\x0F\x11" and blob[i + 4] == 0x34:
            n += 1
            if n <= 15:
                print(f"  {base+i:#x} {blob[i:i+12].hex()}", flush=True)
    print(f"store+34 count={n}", flush=True)

    print("\n=== movss [reg+2C] store (F3 0F 11 xx 2C) ===", flush=True)
    n = 0
    for i in range(0, len(blob) - 5):
        if blob[i:i + 3] == b"\xF3\x0F\x11" and blob[i + 4] == 0x2C:
            n += 1
            if n <= 15:
                print(f"  {base+i:#x} {blob[i:i+12].hex()}", flush=True)
    print(f"store+2C count={n}", flush=True)

    print("\n=== cmp [reg+2F],0 + movss +34 (known clamp) ===", flush=True)
    raw, mask = parse("80 78 2F 00")
    for i in masked_find(blob, raw, mask, 12):
        print(f"  {base+i:#x} {blob[i:i+24].hex()}", flush=True)

    print(f"\ndone t={time.time()-t0:.1f}s", flush=True)


if __name__ == "__main__":
    main()
