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

output_file = r"C:\Projects\JX\logs\find_map_and_coords_output.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
    gdi32 = ctypes.windll.gdi32

    # 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)]
            advapi32.AdjustTokenPrivileges(hToken, False, ctypes.byref(TP(1, luid, 2)), ctypes.sizeof(TP), None, None)
        kernel32.CloseHandle(hToken)

    # 1. First, capture top-right minimap HUD for each window to see exact visible Map Name & Coords
    # Windows:
    pids = [32572, 31164, 12768, 30076]
    
    # Let's find window handles
    def get_hwnds_for_pid(target_pid):
        hwnds = []
        def enum_proc(hwnd, lParam):
            pid = wintypes.DWORD()
            user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
            if pid.value == target_pid:
                # check class
                buf = (ctypes.c_char * 256)()
                user32.GetClassNameA(hwnd, buf, 256)
                if b'Sword3' in bytes(buf):
                    hwnds.append(hwnd)
            return True
        WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HWND, wintypes.LPARAM)
        user32.EnumWindows(WNDENUMPROC(enum_proc), 0)
        return hwnds

    # 2. Check RAM around 0x006DEC00 - 0x006E5000 (static area) and around player stamina struct
    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 pids:
        hProc = kernel32.OpenProcess(0x0010 | 0x0400, False, pid)
        if not hProc:
            log(f"PID {pid}: OpenProcess Failed (Error {kernel32.GetLastError()})")
            continue

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

        log(f"\n=======================================================")
        log(f"PID {pid} | TÊN NHÂN VẬT: '{cname}'")
        log(f"=======================================================")

        # Check static area around 0x006DEC00 - 0x006DF800 for Map / Coordinates
        s_base = 0x006DEC00
        s_size = 0x1000
        s_buf = (ctypes.c_char * s_size)()
        if kernel32.ReadProcessMemory(hProc, ctypes.c_void_p(s_base), s_buf, s_size, ctypes.byref(bytesRead)):
            s_data = bytes(s_buf)[:bytesRead.value]
            # Search for map IDs (80=Duong Chau, 182=Nghiet Long, 1=Ba Lang, 78=Tuong Duong)
            for off in range(0, len(s_data)-4, 2):
                v16 = struct.unpack_from('<h', s_data, off)[0]
                v32 = struct.unpack_from('<i', s_data, off)[0] if off <= len(s_data)-4 else 0
                addr = s_base + off
                if v32 in (80, 182, 1, 11, 78, 53) or v16 in (80, 182, 1, 11, 78, 53):
                    log(f"  [STATIC MAP ID HIT] 0x{addr:08X} (+0x{off:X}): i32={v32}, i16={v16}")

        kernel32.CloseHandle(hProc)

    # 3. Capture MiniMap crop from HWND
    try:
        from PIL import Image
        for pid in pids:
            hwnds = get_hwnds_for_pid(pid)
            if not hwnds: continue
            hwnd = hwnds[0]
            
            # PrintWindow
            class RECT(ctypes.Structure):
                _fields_ = [('left', ctypes.c_long), ('top', ctypes.c_long), ('right', ctypes.c_long), ('bottom', ctypes.c_long)]
            rc = RECT()
            user32.GetClientRect(hwnd, ctypes.byref(rc))
            w = rc.right - rc.left
            h = rc.bottom - rc.top
            if w <= 0 or h <= 0: continue

            # Get DC
            hdcScreen = user32.GetDC(hwnd)
            hdcMem = gdi32.CreateCompatibleDC(hdcScreen)
            hbm = gdi32.CreateCompatibleBitmap(hdcScreen, w, h)
            gdi32.SelectObject(hdcMem, hbm)

            # PrintWindow
            user32.PrintWindow(hwnd, hdcMem, 0)

            # Crop top-right (e.g. from w-220 to w, top 0 to 120)
            crop_w = 220
            crop_h = 100
            crop_x = max(0, w - crop_w)
            crop_y = 0

            hdcCrop = gdi32.CreateCompatibleDC(hdcScreen)
            hbmCrop = gdi32.CreateCompatibleBitmap(hdcScreen, crop_w, crop_h)
            gdi32.SelectObject(hdcCrop, hbmCrop)
            gdi32.BitBlt(hdcCrop, 0, 0, crop_w, crop_h, hdcMem, crop_x, crop_y, 0x00CC0020)

            # Read bitmap bits
            class BITMAPINFOHEADER(ctypes.Structure):
                _fields_ = [
                    ('biSize', wintypes.DWORD), ('biWidth', wintypes.LONG), ('biHeight', wintypes.LONG),
                    ('biPlanes', wintypes.WORD), ('biBitCount', wintypes.WORD), ('biCompression', wintypes.DWORD),
                    ('biSizeImage', wintypes.DWORD), ('biXPelsPerMeter', wintypes.LONG), ('biYPelsPerMeter', wintypes.LONG),
                    ('biClrUsed', wintypes.DWORD), ('biClrImportant', wintypes.DWORD)
                ]
            bmi = BITMAPINFOHEADER()
            bmi.biSize = ctypes.sizeof(BITMAPINFOHEADER)
            bmi.biWidth = crop_w
            bmi.biHeight = -crop_h # top-down
            bmi.biPlanes = 1
            bmi.biBitCount = 32
            bmi.biCompression = 0

            raw_buf = (ctypes.c_char * (crop_w * crop_h * 4))()
            gdi32.GetDIBits(hdcCrop, hbmCrop, 0, crop_h, raw_buf, ctypes.byref(bmi), 0)

            crop_img = Image.frombytes('RGBA', (crop_w, crop_h), bytes(raw_buf), 'raw', 'BGRA')
            crop_path = rf"C:\Projects\JX\logs\minimap_crop_{pid}.png"
            crop_img.save(crop_path)
            log(f"Đã lưu ảnh góc bản đồ PID {pid} sang: {crop_path}")

            # Cleanup
            gdi32.DeleteObject(hbmCrop)
            gdi32.DeleteDC(hdcCrop)
            gdi32.DeleteObject(hbm)
            gdi32.DeleteDC(hdcMem)
            user32.ReleaseDC(hwnd, hdcScreen)
    except Exception as ex:
        log(f"MiniMap Crop Error: {ex}")

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

if __name__ == '__main__':
    run()
