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

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

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

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

    # 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)

    # Attach to interactive desktop if needed
    hdesk = user32.OpenInputDesktop(0, False, 0x01FF)
    if hdesk:
        user32.SetThreadDesktop(hdesk)

    vggame_pids = []
    for p in psutil.process_iter(['pid', 'name']):
        name = (p.info['name'] or '').lower()
        if 'vggame' in name:
            vggame_pids.append(p.info['pid'])

    log(f"Found vggame PIDs: {vggame_pids}")

    MAP_NAMES = {
        123: "Lão Hổ Động",
        182: "Nghiệt Long Động",
        319: "Lâm Du Quan",
        1: "Phượng Tường",
        2: "Thành Đô",
        3: "Đại Lý",
        4: "Biện Kinh",
        5: "Tương Dương",
        6: "Dương Châu",
        7: "Lâm An",
        11: "Ba Lăng Huyện",
        37: "Giang Tân Thôn"
    }

    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 in vggame_pids:
        hProc = kernel32.OpenProcess(0x0010 | 0x0400 | 0x0008, False, pid)
        if not hProc:
            log(f"PID {pid}: OpenProcess FAILED (Error {kernel32.GetLastError()})")
            continue

        # 1. Read 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'\x00')[0].decode('latin1', errors='ignore')

        # 2. Read 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]

        # 3. Read static map ID
        candidate_maps = [0x006966CC, 0x006966D0, 0x0068C274, 0x0068CA74]
        found_map_id = 0
        buf_map = (ctypes.c_int * 1)()
        for m_addr in candidate_maps:
            if kernel32.ReadProcessMemory(hProc, ctypes.c_void_p(m_addr), buf_map, 4, ctypes.byref(bytesRead)):
                mid = buf_map[0]
                if 0 < mid < 2000:
                    found_map_id = mid
                    break

        map_name = MAP_NAMES.get(found_map_id, f"Map_{found_map_id}")

        log(f"\n=======================================================")
        log(f"PID {pid} | Tên: {char_name} | Cấp: {char_lvl} | Map: {map_name} (ID: {found_map_id})")

        # 4. Scan player struct for HP, Stamina, Mana, Coords
        # We search for signature pattern [0..200, 100, 0, mana, 0..200, stamina, maxStamina]
        mbi = MEMORY_BASIC_INFORMATION()
        cur_addr = 0x10000000
        max_addr = 0x13000000
        found_stats = False

        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(4, len(data) - 36, 4):
                        sigVal = struct.unpack_from('<i', data, off)[0]
                        if 0 <= sigVal < 200 and data[off+4:off+12] == b'\x64\x00\x00\x00\x00\x00\x00\x00':
                            sigA = struct.unpack_from('<i', data, off + 16)[0]
                            if 0 <= sigA < 200:
                                hp = struct.unpack_from('<i', data, off - 4)[0]
                                mana = struct.unpack_from('<i', data, off + 12)[0]
                                stamina = struct.unpack_from('<i', data, off + 20)[0]
                                maxStamina = struct.unpack_from('<i', data, off + 24)[0]
                                
                                if hp > 0 and stamina > 0 and 0 < maxStamina < 10000:
                                    stamina_addr = cur_addr + off + 20
                                    # Read coordinates at stamina_addr + 0x0270
                                    coord_buf = (ctypes.c_int * 2)()
                                    coordX, coordY = 0, 0
                                    if kernel32.ReadProcessMemory(hProc, ctypes.c_void_p(stamina_addr + 0x0270), coord_buf, 8, ctypes.byref(bytesRead)):
                                        coordX = coord_buf[0]
                                        coordY = coord_buf[1]
                                    hudX = round(coordX / 256.0) if coordX > 0 else 0
                                    hudY = round(coordY / 512.0) if coordY > 0 else 0

                                    log(f"  -> Struct Address: 0x{stamina_addr:08X}")
                                    log(f"  -> Sinh lực (HP): {hp}")
                                    log(f"  -> Nội lực (Mana): {mana}")
                                    log(f"  -> Thể lực (Stamina): {stamina}/{maxStamina}")
                                    log(f"  -> Tọa độ Engine: ({coordX}, {coordY}) | Tọa độ HUD: ({hudX}/{hudY})")
                                    found_stats = True
                                    break
            if found_stats:
                break
            cur_addr += mbi.RegionSize

        if not found_stats:
            log("  -> Không tìm thấy struct động trong dải 0x10000000 - 0x13000000 (đang quét toàn bộ...)")
            # fallback scan full
            cur_addr = 0x00400000
            max_addr = 0x30000000
            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(4, len(data) - 36, 4):
                            sigVal = struct.unpack_from('<i', data, off)[0]
                            if 0 <= sigVal < 200 and data[off+4:off+12] == b'\x64\x00\x00\x00\x00\x00\x00\x00':
                                sigA = struct.unpack_from('<i', data, off + 16)[0]
                                if 0 <= sigA < 200:
                                    hp = struct.unpack_from('<i', data, off - 4)[0]
                                    mana = struct.unpack_from('<i', data, off + 12)[0]
                                    stamina = struct.unpack_from('<i', data, off + 20)[0]
                                    maxStamina = struct.unpack_from('<i', data, off + 24)[0]
                                    
                                    if hp > 0 and stamina > 0 and 0 < maxStamina < 10000:
                                        stamina_addr = cur_addr + off + 20
                                        coord_buf = (ctypes.c_int * 2)()
                                        coordX, coordY = 0, 0
                                        if kernel32.ReadProcessMemory(hProc, ctypes.c_void_p(stamina_addr + 0x0270), coord_buf, 8, ctypes.byref(bytesRead)):
                                            coordX = coord_buf[0]
                                            coordY = coord_buf[1]
                                        hudX = round(coordX / 256.0) if coordX > 0 else 0
                                        hudY = round(coordY / 512.0) if coordY > 0 else 0

                                        log(f"  -> [SLOW PATH] Struct Address: 0x{stamina_addr:08X}")
                                        log(f"  -> Sinh lực (HP): {hp}")
                                        log(f"  -> Nội lực (Mana): {mana}")
                                        log(f"  -> Thể lực (Stamina): {stamina}/{maxStamina}")
                                        log(f"  -> Tọa độ Engine: ({coordX}, {coordY}) | Tọa độ HUD: ({hudX}/{hudY})")
                                        found_stats = True
                                        break
                if found_stats:
                    break
                cur_addr += mbi.RegionSize

        kernel32.CloseHandle(hProc)

    with open(log_path, "w", encoding="utf-8") as f:
        f.write("\n".join(lines))
    print(f"Log written to {log_path}")

if __name__ == "__main__":
    run()
