import ctypes
import struct

k32 = ctypes.windll.kernel32
pid = 43084
h = k32.OpenProcess(0x10 | 0x400, False, pid)
assert h, f"Cannot open PID {pid}"

base = 0x7FF6AFC20000
root_rva = 0x45CCFE0
buf = (ctypes.c_char * 8)()
br = ctypes.c_size_t()

def read_u64(addr):
    b = (ctypes.c_char * 8)()
    if k32.ReadProcessMemory(h, ctypes.c_void_p(addr), b, 8, ctypes.byref(br)):
        return struct.unpack('<Q', bytes(b))[0]
    return 0

def read_u32(addr):
    b = (ctypes.c_char * 4)()
    if k32.ReadProcessMemory(h, ctypes.c_void_p(addr), b, 4, ctypes.byref(br)):
        return struct.unpack('<I', bytes(b))[0]
    return 0

def read_str(addr, maxlen=64):
    b = (ctypes.c_char * maxlen)()
    if k32.ReadProcessMemory(h, ctypes.c_void_p(addr), b, maxlen, ctypes.byref(br)):
        raw = bytes(b).split(b'\x00')[0]
        return raw.decode('ascii', errors='ignore')
    return ""

def get_rtti_name(vtable_addr):
    # In MSVC x64:
    # vtable_addr[-1] (vtable_addr - 8) is a pointer to RTTICompleteObjectLocator
    col_addr = read_u64(vtable_addr - 8)
    if not col_addr:
        return ""
    # CompleteObjectLocator struct in x64:
    # dword signature
    # dword offset
    # dword cdOffset
    # dword typeDescriptor (RVA from base)
    # dword classDescriptor (RVA from base)
    # dword objectBase (RVA from base)
    sig = read_u32(col_addr)
    if sig != 1: # in 64-bit MSVC, signature is 1
        # maybe absolute or different?
        pass
    td_rva = read_u32(col_addr + 12)
    obj_base = read_u64(col_addr + 20) if sig == 1 else base
    # In MSVC 64-bit, td_rva is relative to module base or objectBase (col_addr + 20)
    td_addr = obj_base + td_rva if obj_base else base + td_rva
    # TypeDescriptor has pVFTable (8 bytes), spare (8 bytes), name (string)
    name = read_str(td_addr + 16)
    if not name:
        # try relative to module base directly
        name = read_str(base + td_rva + 16)
    return name

ingame = read_u64(base + root_rva)
player = read_u64(ingame + 0xBE0)

print(f"InGameState: 0x{ingame:X}, RTTI: {get_rtti_name(read_u64(ingame))}")
print(f"PlayerEntity: 0x{player:X}, RTTI: {get_rtti_name(read_u64(player))}")

# Let's inspect all fields of PlayerEntity
print("\n--- Scanning PlayerEntity for Components ---")
pbuf = (ctypes.c_char * 0x1000)()
k32.ReadProcessMemory(h, ctypes.c_void_p(player), pbuf, 0x1000, ctypes.byref(br))
praw = bytes(pbuf)

seen_ptrs = set()
for off in range(0, 0x1000, 8):
    val = struct.unpack_from('<Q', praw, off)[0]
    if 0x10000 <= val <= 0x7FFFFFFFFFFF and val not in seen_ptrs:
        seen_ptrs.add(val)
        vt = read_u64(val)
        if 0x7FF600000000 <= vt <= 0x7FF6FFFFFFFF:
            rtti = get_rtti_name(vt)
            if rtti:
                print(f"  Offset +0x{off:03X} -> ptr 0x{val:X}, vt=0x{vt:X}: {rtti}")

k32.CloseHandle(h)
