import ctypes
from ctypes import wintypes
import struct
import sys
import os
import psutil

PROCESS_VM_READ = 0x0010
PROCESS_QUERY_INFORMATION = 0x0400
kernel32 = ctypes.windll.kernel32

def open_proc(pid):
    return kernel32.OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, False, pid)

def read_bytes(h, addr, size):
    buf = (ctypes.c_char * size)()
    n = ctypes.c_size_t()
    if kernel32.ReadProcessMemory(h, ctypes.c_void_p(addr), buf, size, ctypes.byref(n)):
        return bytes(buf[:n.value])
    return None

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),
    ]

def get_readable_regions(h):
    regions = []
    addr = 0
    mbi = MEMORY_BASIC_INFORMATION()
    while kernel32.VirtualQueryEx(h, ctypes.c_void_p(addr), ctypes.byref(mbi), ctypes.sizeof(mbi)):
        base = mbi.BaseAddress or 0
        size = mbi.RegionSize
        state = mbi.State
        protect = mbi.Protect
        if state == 0x1000 and (protect & (2 | 4 | 0x20 | 0x40)) and not (protect & (0x100 | 1)):
            regions.append((base, size))
        addr = base + size
    return regions

def main():
    target_pid = None
    for p in psutil.process_iter(['pid', 'name']):
        if p.info['name'] and 'pathofexile' in p.info['name'].lower():
            target_pid = p.info['pid']
            break
            
    out_lines = []
    out_lines.append(f"Target PID: {target_pid}")
    if not target_pid:
        print("PathOfExile not found!")
        return

    h = open_proc(target_pid)
    if not h:
        out_lines.append(f"Failed to open process {target_pid} (needs admin)")
        with open("vitals_inspection_result.txt", "w", encoding="utf-8") as f:
            f.write("\n".join(out_lines))
        return

    player_base = 0x268EB951C00
    out_lines.append(f"Inspecting player base: {hex(player_base)}")
    
    flat_data = read_bytes(h, player_base - 0x400, 0xC00)
    if flat_data:
        out_lines.append("--- Flat scan [-0x400, +0x800] ---")
        for off in range(0, len(flat_data) - 8, 4):
            rel_off = off - 0x400
            val1, val2 = struct.unpack_from("<II", flat_data, off)
            if val1 in (621, 450, 355) or val2 in (621, 450, 355):
                out_lines.append(f"Match at offset {rel_off:+d} (0x{player_base + rel_off:X}): {val1} / {val2}")

    out_lines.append("--- Pointer dereferencing from player_base [0, +0x200] ---")
    ptr_data = read_bytes(h, player_base, 0x200)
    if ptr_data:
        for off in range(0, len(ptr_data) - 8, 8):
            ptr_val = struct.unpack_from("<Q", ptr_data, off)[0]
            if 0x10000 <= ptr_val <= 0x7FFFFFFEFFFF:
                target_buf = read_bytes(h, ptr_val, 512)
                if target_buf:
                    for toff in range(0, len(target_buf) - 8, 4):
                        tv1, tv2 = struct.unpack_from("<II", target_buf, toff)
                        if tv1 in (621, 450, 355) or tv2 in (621, 450, 355):
                            out_lines.append(f"Ptr at player+{off} (0x{off:X}) -> 0x{ptr_val:X} + {toff} (0x{toff:X}): {tv1} / {tv2}")

    out_lines.append("--- Global Region Scan for Shield (450) and Mana (355) ---")
    regions = get_readable_regions(h)
    out_lines.append(f"Total readable regions: {len(regions)}")
    
    shield_matches = []
    mana_matches = []
    hp_matches = []
    
    s_bytes = struct.pack("<II", 450, 450)
    m_bytes = struct.pack("<II", 355, 355)
    hp_bytes = struct.pack("<II", 621, 621)
    
    for base, size in regions:
        chunk_size = 4 * 1024 * 1024
        for c_off in range(0, size, chunk_size):
            read_sz = min(chunk_size, size - c_off)
            buf = read_bytes(h, base + c_off, read_sz)
            if not buf:
                continue
            idx = 0
            while True:
                idx = buf.find(s_bytes, idx)
                if idx == -1:
                    break
                addr = base + c_off + idx
                dist = addr - player_base
                shield_matches.append((addr, dist))
                idx += 4
                
            idx = 0
            while True:
                idx = buf.find(m_bytes, idx)
                if idx == -1:
                    break
                addr = base + c_off + idx
                dist = addr - player_base
                mana_matches.append((addr, dist))
                idx += 4

            idx = 0
            while True:
                idx = buf.find(hp_bytes, idx)
                if idx == -1:
                    break
                addr = base + c_off + idx
                dist = addr - player_base
                hp_matches.append((addr, dist))
                idx += 4

    out_lines.append(f"HP (621/621) matches: {len(hp_matches)}")
    for a, d in hp_matches[:10]:
        out_lines.append(f"  HP at 0x{a:X} (dist from player: {d:+d})")

    out_lines.append(f"Shield (450/450) matches: {len(shield_matches)}")
    for a, d in shield_matches[:10]:
        out_lines.append(f"  Shield at 0x{a:X} (dist from player: {d:+d})")

    out_lines.append(f"Mana (355/355) matches: {len(mana_matches)}")
    for a, d in mana_matches[:10]:
        out_lines.append(f"  Mana at 0x{a:X} (dist from player: {d:+d})")

    with open("vitals_inspection_result.txt", "w", encoding="utf-8") as f:
        f.write("\n".join(out_lines))
    print("Done inspection. Wrote to vitals_inspection_result.txt")

if __name__ == "__main__":
    main()
