import ctypes, struct
from ctypes import wintypes

k32 = ctypes.WinDLL('kernel32', use_last_error=True)
psapi = ctypes.WinDLL('psapi', use_last_error=True)

k32.OpenProcess.restype = wintypes.HANDLE
k32.ReadProcessMemory.argtypes = [
    wintypes.HANDLE, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t)
]
k32.ReadProcessMemory.restype = wintypes.BOOL

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

class MEMORY_BASIC_INFORMATION(ctypes.Structure):
    _fields_ = [
        ("BaseAddress", ctypes.c_void_p),
        ("AllocationBase", ctypes.c_void_p),
        ("AllocationProtect", wintypes.DWORD),
        ("RegionSize", ctypes.c_size_t),
        ("State", wintypes.DWORD),
        ("Protect", wintypes.DWORD),
        ("Type", wintypes.DWORD),
    ]

pid = 49116
h = k32.OpenProcess(0x10 | 0x20 | 0x8, False, pid) # VM_READ | VM_WRITE | VM_OPERATION
if not h:
    print('OpenProcess failed:', ctypes.get_last_error())
    exit(1)

mods = (ctypes.c_uint64 * 1024)()
needed = wintypes.DWORD()
psapi.EnumProcessModulesEx(h, ctypes.byref(mods), ctypes.sizeof(mods), ctypes.byref(needed), 3)
base, size = 0, 0
name_buf = ctypes.create_unicode_buffer(260)
psapi.GetModuleBaseNameW.argtypes = [wintypes.HANDLE, ctypes.c_void_p, wintypes.LPWSTR, wintypes.DWORD]
psapi.GetModuleInformation.argtypes = [wintypes.HANDLE, ctypes.c_void_p, ctypes.POINTER(MODULEINFO), wintypes.DWORD]
for i in range(needed.value // 8):
    psapi.GetModuleBaseNameW(h, ctypes.c_void_p(mods[i]), name_buf, 260)
    if 'pathofexile' in name_buf.value.lower():
        mi = MODULEINFO()
        psapi.GetModuleInformation(h, ctypes.c_void_p(mods[i]), ctypes.byref(mi), ctypes.sizeof(mi))
        base = int(mi.lpBaseOfDll or 0)
        size = int(mi.SizeOfImage)
        print(f"Main module: {name_buf.value} Base: {base:#x} Size: {size:#x}")
        break

def rpm(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])

# Scan readable heap & data memory
candidates = []
addr = 0
mbi = MEMORY_BASIC_INFORMATION()

while k32.VirtualQueryEx(h, ctypes.c_void_p(addr), ctypes.byref(mbi), ctypes.sizeof(mbi)):
    base_addr = mbi.BaseAddress or 0
    reg_size = mbi.RegionSize
    state = mbi.State
    protect = mbi.Protect
    
    # MEM_COMMIT = 0x1000, readable protect
    if state == 0x1000 and (protect & 0xEE) and not (protect & 0x100): # not PAGE_GUARD
        # Read in chunks of 4MB
        chunk_size = 4 * 1024 * 1024
        for offset in range(0, reg_size, chunk_size):
            to_read = min(chunk_size, reg_size - offset)
            curr = base_addr + offset
            blob = rpm(curr, to_read)
            if not blob:
                continue
            
            # Step by 8 bytes looking for vtable
            for i in range(0, len(blob) - 0x50, 8):
                vt = struct.unpack_from('<Q', blob, i)[0]
                # vtable must be inside main module
                if base <= vt < base + size:
                    cur, mn, mx, fov, zn, zf = struct.unpack_from('<ffffff', blob, i + 0x2C)
                    # Check camera fields with strict physical constraints
                    if (5.0 <= cur <= 120.0 and
                        5.0 <= mn <= 35.0 and
                        20.0 <= mx <= 120.0 and
                        0.4 <= fov <= 1.6 and
                        0.5 <= zn <= 10.0 and
                        50.0 <= zf <= 5000.0 and
                        mn < mx):
                        obj_addr = curr + i
                        print(f"Candidate Camera @ {obj_addr:#x}: vt={vt:#x} cur={cur:.2f} min={mn:.2f} max={mx:.2f} fov={fov:.4f} zn={zn:.2f} zf={zf:.1f}")
                        candidates.append((obj_addr, vt, cur, mn, mx, fov, zn, zf))
    
    addr = base_addr + reg_size

print(f"\nTotal candidate cameras with VALID module vtable: {len(candidates)}")
