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

log_path = r"c:\Projects\JX\logs\character_coords_and_stats.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)

    # We know the 4 PIDs and their names
    chars = [
        (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, name, exp_hp, exp_mana in chars:
        hProc = kernel32.OpenProcess(0x0010 | 0x0400 | 0x0008, False, pid)
        if not hProc:
            log(f"PID {pid}: OpenProcess FAILED")
            continue

        # Static name
        buf_name = (ctypes.c_char * 32)()
        bytesRead = ctypes.c_size_t()
        kernel32.ReadProcessMemory(hProc, ctypes.c_void_p(0x006DEC44), buf_name, 32, ctypes.byref(bytesRead))
        char_name = bytes(buf_name).split(b'\0')[0].decode('latin1', errors='ignore')

        # Static level
        buf_lvl = (ctypes.c_short * 1)()
        kernel32.ReadProcessMemory(hProc, ctypes.c_void_p(0x006DEC66), buf_lvl, 2, ctypes.byref(bytesRead))
        char_lvl = buf_lvl[0]

        # Static map ID
        buf_map = (ctypes.c_int * 1)()
        kernel32.ReadProcessMemory(hProc, ctypes.c_void_p(0x006966CC), buf_map, 4, ctypes.byref(bytesRead))
        map_id = buf_map[0]

        log(f"\n=======================================================")
        log(f"PID {pid} | Name: {char_name} | Level: {char_lvl} | MapId: {map_id}")

        # Search coordinates: X around 56000-58000, Y around 110000-115000 (HudX ~ 220, HudY ~ 220)
        # Or let's search where (CoordX, CoordY) are stored adjacent to each other
        mbi = MEMORY_BASIC_INFORMATION()
        cur_addr = 0x00400000
        max_addr = 0x20000000

        coord_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)()
                if kernel32.ReadProcessMemory(hProc, ctypes.c_void_p(cur_addr), mbuf, region_size, ctypes.byref(bytesRead)):
                    data = bytes(mbuf)[:bytesRead.value]
                    for off in range(0, len(data) - 8, 4):
                        x = struct.unpack_from('<i', data, off)[0]
                        if 40000 < x < 80000: # X in engine pixels
                            y = struct.unpack_from('<i', data, off + 4)[0]
                            if 80000 < y < 140000: # Y in engine pixels
                                hudX = round(x / 256.0)
                                hudY = round(y / 512.0)
                                if 180 <= hudX <= 260 and 180 <= hudY <= 260:
                                    coord_hits.append((cur_addr + off, x, y, hudX, hudY))
            cur_addr += mbi.RegionSize

        log(f"Found {len(coord_hits)} coordinate candidates:")
        for addr, x, y, hx, hy in coord_hits[:15]:
            log(f"  Addr 0x{addr:08X} -> Engine ({x}, {y}) => HUD ({hx}/{hy})")

        kernel32.CloseHandle(hProc)

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

if __name__ == "__main__":
    run()
