import ctypes
import struct

k32 = ctypes.windll.kernel32
pid = 43084
h = k32.OpenProcess(0x10 | 0x400, False, pid)
base = 0x7FF6AFC20000

# String for SkillGemPanel
# .?AVSkillGemPanel@InGame@Controls@UI@@
target_str = b".?AVSkillGemPanel@InGame@Controls@UI@@"
br = ctypes.c_size_t()

# Let us find where target_str is in .rdata
# Read .rdata from process
rdata_base = base + 0x2E87000
rdata_size = 0xB7C086

buf = (ctypes.c_char * rdata_size)()
k32.ReadProcessMemory(h, ctypes.c_void_p(rdata_base), buf, rdata_size, ctypes.byref(br))
raw_rdata = bytes(buf)

str_pos = raw_rdata.find(target_str)
if str_pos != -1:
    str_va = rdata_base + str_pos
    td_va = str_va - 16
    td_rva = td_va - base
    print(f"TypeDescriptor: 0x{td_va:X}, RVA: 0x{td_rva:X}")

    # Search for CompleteObjectLocator in .rdata referencing td_rva
    td_bytes = struct.pack('<I', td_rva)
    col_pos = 0
    col_vas = []
    while True:
        col_pos = raw_rdata.find(td_bytes, col_pos)
        if col_pos == -1: break
        # check if offset 12 in COL
        col_va = rdata_base + col_pos - 12
        col_vas.append(col_va)
        print(f"CompleteObjectLocator candidate: 0x{col_va:X}")
        col_pos += 4

    # Search for vtable pointing to COL
    for col_va in col_vas:
        col_bytes = struct.pack('<Q', col_va)
        vt_pos = 0
        while True:
            vt_pos = raw_rdata.find(col_bytes, vt_pos)
            if vt_pos == -1: break
            vtable_va = rdata_base + vt_pos + 8
            print(f"VTABLE for SkillGemPanel: 0x{vtable_va:X}")
            vt_pos += 8

            # Search RAM for instances with this vtable!
            print("  Searching RAM for instances...")
            vt_bytes = struct.pack('<Q', vtable_va)
            addr = 0x10000
            mbi = (ctypes.c_byte * 48)()
            inst_count = 0
            while k32.VirtualQueryEx(h, ctypes.c_void_p(addr), mbi, 48):
                b_addr = struct.unpack_from('<Q', bytes(mbi), 0)[0]
                r_size = struct.unpack_from('<Q', bytes(mbi), 24)[0]
                st = struct.unpack_from('<I', bytes(mbi), 32)[0]
                prot = struct.unpack_from('<I', bytes(mbi), 36)[0]
                if st == 0x1000 and (prot & 0xEE) and not (prot & 0x101) and r_size <= 64*1024*1024:
                    chunk = (ctypes.c_char * min(r_size, 4*1024*1024))()
                    for off in range(0, r_size, len(chunk)):
                        n = min(len(chunk), r_size - off)
                        if k32.ReadProcessMemory(h, ctypes.c_void_p(b_addr + off), chunk, n, ctypes.byref(br)):
                            m = bytes(chunk)[:br.value]
                            p = 0
                            while True:
                                idx = m.find(vt_bytes, p)
                                if idx == -1: break
                                inst_addr = b_addr + off + idx
                                print(f"    FOUND SkillGemPanel instance: 0x{inst_addr:012X}")
                                inst_count += 1
                                p = idx + 8
                addr = b_addr + r_size
            print(f"  Total instances found: {inst_count}")
else:
    print("SkillGemPanel string not found in .rdata")

k32.CloseHandle(h)
