import ctypes
from ctypes import wintypes
import struct
import psutil

pids = [p.pid for p in psutil.process_iter() if 'PathOfExile' in p.name()]
if not pids:
    print('No PathOfExile.exe found!')
    exit(1)
pid = pids[0]
print(f'Scanning PID: {pid}', flush=True)

kernel32 = ctypes.windll.kernel32
handle = kernel32.OpenProcess(0x1000 | 0x0010, False, pid)
if not handle:
    print(f'OpenProcess failed: {ctypes.GetLastError()}', flush=True)
    exit(1)

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

mbi = MEMORY_BASIC_INFORMATION()
addr = 0x100000000
max_addr = 0x30000000000

CHUNK_SIZE = 2 * 1024 * 1024
buf = (ctypes.c_char * CHUNK_SIZE)()
bytes_read = ctypes.c_size_t()
sig = b'\x02\x00\x00\x00\x04\x00\x00\x00'

hits = []

while addr < max_addr:
    if not kernel32.VirtualQueryEx(handle, ctypes.c_void_p(addr), ctypes.byref(mbi), ctypes.sizeof(mbi)):
        break
    base = mbi.BaseAddress or 0
    size = mbi.RegionSize
    protect = mbi.Protect
    state = mbi.State
    addr = base + size
    if state != 0x1000 or not (protect & 0x04 or protect & 0x02):
        continue

    for off in range(0, size, CHUNK_SIZE - 64):
        read_n = min(CHUNK_SIZE, size - off)
        if not kernel32.ReadProcessMemory(handle, ctypes.c_void_p(base + off), buf, read_n, ctypes.byref(bytes_read)):
            continue
        raw = bytes(buf[:bytes_read.value])
        idx = 0
        while True:
            p = raw.find(sig, idx)
            if p == -1 or p + 56 > len(raw):
                break
            cand = base + off + p + 8
            hp, max_hp, unres = struct.unpack_from('<III', raw, p + 8)
            es_cur, es_max = struct.unpack_from('<II', raw, p + 20)
            ward_cur, ward_max = struct.unpack_from('<II', raw, p + 40)
            hits.append((cand, hp, max_hp, unres, es_cur, es_max, ward_cur, ward_max))
            idx = p + 4

kernel32.CloseHandle(handle)

print(f'Total ECS {{2, 4}} hits: {len(hits)}', flush=True)
for cand, hp, max_hp, unres, es_c, es_m, w_c, w_m in hits:
    # Filter or print interesting ones
    if hp == 1 or es_m > 100 or w_m > 50:
        print(f'  Life=0x{cand:X}: HP={hp}/{max_hp} (unres={unres}) | ES={es_c}/{es_m} | Ward={w_c}/{w_m}', flush=True)
