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

sys.stdout.reconfigure(encoding='utf-8', errors='replace')

log_file_path = r"C:\Projects\JX\logs\ram_character_stats.log"
_orig_print = print
def print(*args, **kwargs):
    msg = " ".join(str(a) for a in args)
    _orig_print(msg, **kwargs)
    try:
        with open(log_file_path, "a", encoding="utf-8") as f:
            f.write(msg + "\n")
    except Exception:
        pass

try:
    if os.path.exists(log_file_path):
        os.remove(log_file_path)
except Exception:
    pass

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

def 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 TOKEN_PRIVILEGES(ctypes.Structure):
                _fields_ = [
                    ('PrivilegeCount', wintypes.DWORD),
                    ('Luid', wintypes.LARGE_INTEGER),
                    ('Attributes', wintypes.DWORD)
                ]
            tp = TOKEN_PRIVILEGES(1, luid, 0x00000002)
            advapi32.AdjustTokenPrivileges(hToken, False, ctypes.byref(tp), ctypes.sizeof(tp), None, None)
        kernel32.CloseHandle(hToken)

enable_debug_privilege()

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

KNOWN_CHARS = {
    "BoCuaCacCon": {"level": 75, "hp": 846, "mp": 2409, "sta": 924},
    "ConCuaBoMe":  {"level": 49, "hp": 612, "mp": 1419, "sta": 592},
    "ConGailCung": {"level": 46, "hp": 540, "mp": 1203, "sta": 687},
    "MeCuaCacCon": {"level": 47, "hp": 548, "mp": 2455, "sta": 709},
}

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

print(f"=== QUET BO NHO TIEN TRINH GAME (PIDs: {vggame_procs}) ===")

results = []

for pid in vggame_procs:
    hProcess = kernel32.OpenProcess(0x0010 | 0x0400, False, pid) # PROCESS_VM_READ | PROCESS_QUERY_INFORMATION
    if not hProcess:
        err = kernel32.GetLastError()
        print(f"PID {pid}: Khong the OpenProcess (Error {err})")
        results.append({"pid": pid, "status": "access_denied", "error": err})
        continue

    print(f"\n-> Dang doc RAM PID {pid}...")
    mbi = MEMORY_BASIC_INFORMATION()
    addr = 0
    pid_data = {"pid": pid, "status": "ok", "characters_found": []}

    while kernel32.VirtualQueryEx(hProcess, ctypes.c_void_p(addr), ctypes.byref(mbi), ctypes.sizeof(mbi)):
        # MEM_COMMIT = 0x1000, readable pages
        if mbi.State == 0x1000 and (mbi.Protect & 0xEE) != 0 and (mbi.Protect & 0x01) == 0:
            size = min(mbi.RegionSize, 1024 * 1024 * 4) # 4MB max chunk
            buf = (ctypes.c_char * size)()
            bytesRead = ctypes.c_size_t()

            if kernel32.ReadProcessMemory(hProcess, ctypes.c_void_p(addr), buf, size, ctypes.byref(bytesRead)):
                chunk = bytes(buf)[:bytesRead.value]

                for char_name, expected in KNOWN_CHARS.items():
                    name_bytes = char_name.encode('ascii')
                    idx = 0
                    while True:
                        idx = chunk.find(name_bytes, idx)
                        if idx == -1:
                            break

                        match_addr = addr + idx
                        # Scan nearby bytes (-256 to +512) for HP, MP, Level stats
                        start_offset = max(0, idx - 256)
                        end_offset = min(len(chunk), idx + 512)
                        context_bytes = chunk[start_offset:end_offset]

                        # Search for integers matching expected stats
                        found_stats = {}
                        for offset in range(0, len(context_bytes) - 4, 4):
                            val = struct.unpack_from('<i', context_bytes, offset)[0]
                            if val == expected["hp"] and "hp" not in found_stats:
                                found_stats["hp"] = {"val": val, "rel_offset": (start_offset + offset) - idx}
                            elif val == expected["mp"] and "mp" not in found_stats:
                                found_stats["mp"] = {"val": val, "rel_offset": (start_offset + offset) - idx}
                            elif val == expected["sta"] and "sta" not in found_stats:
                                found_stats["sta"] = {"val": val, "rel_offset": (start_offset + offset) - idx}
                            elif val == expected["level"] and "level" not in found_stats:
                                found_stats["level"] = {"val": val, "rel_offset": (start_offset + offset) - idx}

                        if char_name not in [c["name"] for c in pid_data["characters_found"]]:
                            entry = {
                                "name": char_name,
                                "name_addr": hex(match_addr),
                                "stats": found_stats,
                                "expected": expected
                            }
                            pid_data["characters_found"].append(entry)
                            print(f"   [TIM THAY] Nhan vat '{char_name}' tai RAM 0x{match_addr:08X}!")
                            print(f"              Chi so tim thay: {found_stats}")

                        idx += len(name_bytes)

        addr += mbi.RegionSize
        if addr >= 0x7FFFFFFF:
            break

    kernel32.CloseHandle(hProcess)
    results.append(pid_data)

out_json = r"C:\Projects\JX\logs\ram_character_stats.json"
with open(out_json, "w", encoding="utf-8") as f:
    json.dump(results, f, ensure_ascii=False, indent=2)

print(f"\n=== HOAN TAT! Da luu ket qua vao: {out_json} ===")
