import sys, struct, ctypes
from ctypes import wintypes
import psutil

log_path = r"c:\Projects\JX\logs\exact_char_struct_search.txt"

def run():
    lines = []
    def log(msg):
        print(msg)
        lines.append(str(msg))

    kernel32 = ctypes.windll.kernel32
    advapi32 = ctypes.windll.advapi32

    # Enable debug privilege
    hToken = wintypes.HANDLE()
    if advapi32.OpenProcessToken(kernel32.GetCurrentProcess(), 0x0020 | 0x0008, ctypes.byref(hToken)):
        luid = wintypes.LARGE_INTEGER()
        if advapi32.LookupPrivilegeValueW(None, "SeDebugPrivilege", ctypes.byref(luid)):
            class TP(ctypes.Structure):
                _fields_ = [('Count', wintypes.DWORD), ('Luid', wintypes.LARGE_INTEGER), ('Attr', wintypes.DWORD)]
            tp = TP(1, luid, 2)
            advapi32.AdjustTokenPrivileges(hToken, False, ctypes.byref(tp), ctypes.sizeof(tp), None, None)
        kernel32.CloseHandle(hToken)

    # Character expected values (or search around these)
    # ConGailCung: HP ~ 3213, Mana ~ 980
    # BoCuaCacCon: HP ~ 5969, Mana ~ 0
    # ConCuaBoMe: HP ~ 5899, Mana ~ 910
    # MeCuaCacCon: HP ~ 4354, Mana ~ 998
    target_data = {
        12392: ("ConGailCung", 3213, 980),
        45384: ("BoCuaCacCon", 5969, 0),
        54828: ("ConCuaBoMe", 5899, 910),
        62736: ("MeCuaCacCon", 4354, 998),
    }

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

    for pid, (char_name, target_hp, target_mana) in target_data.items():
        hProc = kernel32.OpenProcess(0x0010 | 0x0400 | 0x0008, False, pid)
        if not hProc:
            log(f"PID {pid}: OpenProcess FAILED")
            continue

        log(f"\n=======================================================")
        log(f"Searching for {char_name} (PID {pid}) | Target HP: {target_hp}, Mana: {target_mana}")

        mbi = MEMORY_BASIC_INFORMATION()
        cur_addr = 0x00400000
        max_addr = 0x30000000
        pat_hp = struct.pack('<i', target_hp)

        hits = []
        while cur_addr < max_addr and kernel32.VirtualQueryEx(hProc, ctypes.c_void_p(cur_addr), ctypes.byref(mbi), ctypes.sizeof(mbi)):
            if mbi.State == 0x1000 and (mbi.Protect & 0xEE) != 0 and (mbi.Protect & 0x01) == 0:
                region_size = min(mbi.RegionSize, 1024 * 1024 * 8)
                mbuf = (ctypes.c_char * region_size)()
                bytesRead = ctypes.c_size_t()
                if kernel32.ReadProcessMemory(hProc, ctypes.c_void_p(cur_addr), mbuf, region_size, ctypes.byref(bytesRead)):
                    data = bytes(mbuf)[:bytesRead.value]
                    pos = 0
                    while True:
                        idx = data.find(pat_hp, pos)
                        if idx == -1:
                            break
                        hit_addr = cur_addr + idx
                        # Look at surrounding memory (-64 to +128)
                        window_start = max(0, idx - 64)
                        window_end = min(len(data), idx + 128)
                        chunk = data[window_start:window_end]
                        rel_idx = idx - window_start
                        hits.append((hit_addr, chunk, rel_idx))
                        pos = idx + 4
            cur_addr += mbi.RegionSize

        log(f"Found {len(hits)} occurrences of HP={target_hp}")
        for hit_addr, chunk, rel_idx in hits[:10]:
            # Inspect offsets relative to hit_addr
            # Let's see integers at offset -32, -28, -24, ..., +64
            dump_ints = []
            for off in range(-32, 64, 4):
                c_pos = rel_idx + off
                if 0 <= c_pos <= len(chunk) - 4:
                    val = struct.unpack_from('<i', chunk, c_pos)[0]
                    dump_ints.append(f"{off:+d}:{val}")
            log(f"  Hit 0x{hit_addr:08X} -> {' '.join(dump_ints)}")

        kernel32.CloseHandle(hProc)

    with open(log_path, "w", encoding="utf-8") as f:
        f.write("\n".join(lines))

if __name__ == "__main__":
    run()
