import sys, struct, ctypes, os
from ctypes import wintypes

output_file = r"C:\Projects\JX\logs\scan_exact_stats_output.txt"

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

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

    # Enable SeDebugPrivilege
    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 info from user:
    # BoCuaCacCon:
    # Stamina (Thể lực): 846 / 846
    # HP (Sinh lực): 3215 / 3215
    # Mana (Nội lực): ~853..924 / 924
    # Level: 75
    # Static name at 0x006DEC44
    
    # Let's find PID for BoCuaCacCon
    target_pids = [32572, 31164, 30076, 12768]
    
    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 target_pids:
        hProc = kernel32.OpenProcess(0x0010 | 0x0400 | 0x0008, False, pid)
        if not hProc:
            log(f"PID {pid}: OpenProcess FAILED (Error {kernel32.GetLastError()})")
            continue

        # 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')
        
        # 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]
        
        log(f"\n=======================================================")
        log(f"PID {pid} -> Name: '{char_name}', Level: {char_lvl}")
        log(f"=======================================================")

        if "BoCuaCacCon" not in char_name and pid != 32572:
            kernel32.CloseHandle(hProc)
            continue

        # 1. First, inspect static data around 0x006DEC00 - 0x006DF800
        log("--- KIỂM TRA PHÂN VÙNG DỮ LIỆU TĨNH 0x006DEC00 - 0x006DF800 ---")
        static_size = 0x1000
        buf_static = (ctypes.c_char * static_size)()
        if kernel32.ReadProcessMemory(hProc, ctypes.c_void_p(0x006DEC00), buf_static, static_size, ctypes.byref(bytesRead)):
            sdata = bytes(buf_static)[:bytesRead.value]
            # search for 3215, 846, 924
            for off in range(0, len(sdata) - 4, 2):
                val_i16 = struct.unpack_from('<h', sdata, off)[0]
                val_i32 = struct.unpack_from('<i', sdata, off)[0] if off <= len(sdata)-4 else 0
                addr = 0x006DEC00 + off
                if val_i32 in (3215, 846, 924) or val_i16 in (3215, 846, 924):
                    log(f"  [STATIC HIT] Addr 0x{addr:08X} (offset +0x{off:X}): i32={val_i32}, i16={val_i16}")

        # 2. Quét toàn bộ Committed Memory tìm cụm cấu trúc HP=3215, Stamina=846, Mana=924
        log("\n--- QUÉT COMMITTED MEMORY TÌM STRUCT CHỨA (HP=3215, STAMINA=846, MANA=924) ---")
        mbi = MEMORY_BASIC_INFORMATION()
        cur_addr = 0
        matches = []

        pat_3215 = struct.pack('<i', 3215)
        pat_846 = struct.pack('<i', 846)
        pat_924 = struct.pack('<i', 924)

        while 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 * 16)
                mbuf = (ctypes.c_char * region_size)()
                if kernel32.ReadProcessMemory(hProc, ctypes.c_void_p(cur_addr), mbuf, region_size, ctypes.byref(bytesRead)):
                    rdata = bytes(mbuf)[:bytesRead.value]
                    pos = 0
                    while True:
                        pos = rdata.find(pat_3215, pos)
                        if pos == -1:
                            break
                        hit_addr = cur_addr + pos
                        # Kiểm tra xung quanh +/- 128 bytes xem có chứa 846 hoặc 924 không
                        window_start = max(0, pos - 128)
                        window_end = min(len(rdata), pos + 128)
                        window = rdata[window_start:window_end]
                        
                        has_stamina = pat_846 in window
                        has_mana = pat_924 in window
                        
                        if has_stamina and has_mana:
                            log(f"\n>>> [STRUCT MATCH ALL] Tìm thấy cụm HP+Stamina+Mana tại 0x{hit_addr:08X}!")
                            # Dump +/- 64 bytes theo int32
                            d_start = max(0, pos - 64)
                            d_end = min(len(rdata)-4, pos + 80)
                            for k in range(d_start, d_end, 4):
                                a = cur_addr + k
                                val = struct.unpack_from('<i', rdata, k)[0]
                                u16 = struct.unpack_from('<H', rdata, k)[0]
                                note = ""
                                if val == 3215: note = "<=== SINH LỰC (HP: 3215)"
                                elif val == 846: note = "<=== THỂ LỰC (Stamina: 846)"
                                elif val == 924: note = "<=== NỘI LỰC TỐI ĐA (Max Mana: 924)"
                                elif 800 <= val <= 924: note = f"<=== NỘI LỰC HIỆN TẠI ({val})"
                                log(f"    0x{a:08X} [rel {a-hit_addr:+4d}]: i32 = {val:<8} (u16 = {u16:<5}) {note}")
                            matches.append(hit_addr)
                        elif has_stamina or has_mana:
                            log(f"  [PARTIAL MATCH] tại 0x{hit_addr:08X} (HP + {'Stamina' if has_stamina else 'Mana'})")
                            
                        pos += 4
            cur_addr += mbi.RegionSize
            if cur_addr >= 0x7FFFFFFF:
                break

        log(f"\nTổng số struct trùng khớp hoàn toàn: {len(matches)}")

        # 3. Với mỗi match, tìm xem có con trỏ tĩnh nào trỏ tới match này không!
        if matches:
            log("\n--- TÌM KIẾM CON TRỎ TRỎ TỚI STRUCT NÀY (POINTER SEARCH) ---")
            for m in matches:
                target_ptr = struct.pack('<I', m)
                cur_addr2 = 0
                while kernel32.VirtualQueryEx(hProc, ctypes.c_void_p(cur_addr2), ctypes.byref(mbi), ctypes.sizeof(mbi)):
                    if mbi.State == 0x1000 and (mbi.Protect & 0xEE) != 0 and (mbi.Protect & 0x01) == 0:
                        rsize = min(mbi.RegionSize, 1024 * 1024 * 16)
                        pbuf = (ctypes.c_char * rsize)()
                        if kernel32.ReadProcessMemory(hProc, ctypes.c_void_p(cur_addr2), pbuf, rsize, ctypes.byref(bytesRead)):
                            pdata = bytes(pbuf)[:bytesRead.value]
                            ppos = 0
                            while True:
                                ppos = pdata.find(target_ptr, ppos)
                                if ppos == -1: break
                                ptr_addr = cur_addr2 + ppos
                                is_static = (0x00400000 <= ptr_addr <= 0x00900000)
                                tag = "[STATIC BASE POINTER!]" if is_static else "[DYNAMIC POINTER]"
                                log(f"  {tag} Con trỏ tại 0x{ptr_addr:08X} -> trỏ tới struct 0x{m:08X}")
                                ppos += 4
                    cur_addr2 += mbi.RegionSize
                    if cur_addr2 >= 0x7FFFFFFF:
                        break

        kernel32.CloseHandle(hProc)

    with open(output_file, 'w', encoding='utf-8') as f:
        f.write("\n".join(lines))
    print(f"\nĐã ghi kết quả vào {output_file}")

if __name__ == '__main__':
    run()
