"""Inspect AreaInstance in live POE2 (PID 37676) for Terrain and Minimap Exploration.
11/09/2026.
"""
import ctypes
import ctypes.wintypes as w
import struct
import subprocess

k32 = ctypes.WinDLL("kernel32", 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

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

def rpm_u64(h, addr):
    b = rpm(h, addr, 8)
    return struct.unpack("<Q", b)[0] if b and len(b) == 8 else 0

def rpm_u32(h, addr):
    b = rpm(h, addr, 4)
    return struct.unpack("<I", b)[0] if b and len(b) == 4 else 0

out = subprocess.check_output(['powershell', '-Command', '(Get-Process -Name *PathOfExile*).Id'])
pid = int(out.decode().split()[0])
h = k32.OpenProcess(0x1F0FFF, False, pid)

# From our earlier probe:
igs = 0x48514e50f10
print(f"InGameState: {igs:#x}")

# Check AreaInstance offsets: 0x2A0, 0x290, 0x2B0
for aio in [0x2A0, 0x290, 0x280, 0x2B0, 0x310]:
    ai = rpm_u64(h, igs + aio)
    if 0x100000000 <= ai <= 0x7FFFFFFFFFF:
        print(f"Candidate AreaInstance @ igs+{aio:#x}: {ai:#x}")
        
        # Read area info at ai + 0x98 or +0xA0
        for info_off in [0x98, 0xA0, 0x90]:
            info_ptr = rpm_u64(h, ai + info_off)
            if 0x100000000 <= info_ptr <= 0x7FFFFFFFFFF:
                # String pointers
                code_ptr = rpm_u64(h, info_ptr)
                name_ptr = rpm_u64(h, info_ptr + 8)
                code_str = ""
                name_str = ""
                if 0x100000000 <= code_ptr <= 0x7FFFFFFFFFF:
                    cb = rpm(h, code_ptr, 64)
                    if cb:
                        code_str = cb.split(b"\x00\x00")[0].decode("utf-16le", errors="ignore")
                if 0x100000000 <= name_ptr <= 0x7FFFFFFFFFF:
                    nb = rpm(h, name_ptr, 64)
                    if nb:
                        name_str = nb.split(b"\x00\x00")[0].decode("utf-16le", errors="ignore")
                if code_str or name_str:
                    print(f"  AreaInfo (ai+{info_off:#x}): code='{code_str}', name='{name_str}'")

        # Check TerrainMetadata at ai + 0x8D0, 0x8B8, 0x8A0
        for to in [0x8D0, 0x8B8, 0x8A0, 0x8C8]:
            t_ptr = ai + to
            # In PoE, TerrainStruct can be inline or pointer
            # Layout: cols, rows, bytesPerRow ...
            t_data = rpm(h, t_ptr, 0x80)
            if t_data:
                # Look for cols, rows
                c, r = struct.unpack_from("<II", t_data, 0)
                if 20 <= c <= 2000 and 20 <= r <= 2000:
                    print(f"  INLINE TERRAIN at ai+{to:#x}: cols={c}, rows={r}")
                # If pointer:
                ptr_val = struct.unpack_from("<Q", t_data, 0)[0]
                if 0x100000000 <= ptr_val <= 0x7FFFFFFFFFF:
                    td = rpm(h, ptr_val, 0x80)
                    if td:
                        c2, r2 = struct.unpack_from("<II", td, 0)
                        if 20 <= c2 <= 2000 and 20 <= r2 <= 2000:
                            print(f"  POINTER TERRAIN at ai+{to:#x} -> {ptr_val:#x}: cols={c2}, rows={r2}")

