$npx -y skills add HLND2T/CS2_VibeSignatures --skill dump-vtablesBatch-dump vtables from IDA Pro MCP by searching mangled symbol patterns, then write a merged YAML file beside the binary. Use this skill when you need to find and export all vtables matching a name pattern (e.g., all GameSystem vtables) in one shot. Triggers: dump vtables, batch
| 1 | # Dump VTables by Symbol Pattern |
| 2 | |
| 3 | Search for vtable symbols matching a mangled name glob pattern via IDA Pro MCP, read all their entries, and write a merged YAML file beside the binary. |
| 4 | |
| 5 | ## Prerequisites |
| 6 | |
| 7 | - An IDA Pro MCP instance with the target binary loaded |
| 8 | |
| 9 | ## Required Parameters |
| 10 | |
| 11 | | Parameter | Description | Example | |
| 12 | |-----------|-------------|---------| |
| 13 | | `symbol_pattern` | Mangled symbol glob pattern for vtables | `??_7C*System@@6B@` | |
| 14 | | `output_name` | Base name for the output YAML file (without extension) | `IGameSystem_vtables` | |
| 15 | |
| 16 | ## Method |
| 17 | |
| 18 | ### Step 1: Search for matching vtable symbols |
| 19 | |
| 20 | Use `mcp__ida-pro-mcp__entity_query` with kind `names` and a glob filter to find all matching mangled vtable symbols: |
| 21 | |
| 22 | ``` |
| 23 | mcp__ida-pro-mcp__entity_query queries={"kind": "names", "filter": "<symbol_pattern>", "count": 0} |
| 24 | ``` |
| 25 | |
| 26 | This returns all matching symbol names, addresses, and segments. |
| 27 | |
| 28 | ### Step 2: Read vtable entries and write merged YAML |
| 29 | |
| 30 | Run a single `mcp__ida-pro-mcp__py_eval` script that: |
| 31 | 1. Iterates each discovered vtable address |
| 32 | 2. Reads consecutive qword pointers until hitting a non-code address or 0 |
| 33 | 3. Gets `func.size()` for each entry |
| 34 | 4. Writes a merged YAML list to disk |
| 35 | |
| 36 | ```python |
| 37 | mcp__ida-pro-mcp__py_eval code=""" |
| 38 | import idaapi |
| 39 | import ida_bytes |
| 40 | import ida_name |
| 41 | import os |
| 42 | import yaml |
| 43 | |
| 44 | # === REQUIRED: Replace these values === |
| 45 | output_name = "<output_name>" # e.g., "IGameSystem_vtables" |
| 46 | |
| 47 | # Populate from Step 1 results: list of (address, class_name, mangled_symbol) |
| 48 | vtables = [ |
| 49 | # (0x181538bb8, "CCSGCServerSystem", "??_7CCSGCServerSystem@@6B@"), |
| 50 | # ...add all matches from entity_query results... |
| 51 | ] |
| 52 | # ====================================== |
| 53 | |
| 54 | image_base = idaapi.get_imagebase() |
| 55 | ptr_size = 8 if idaapi.inf_is_64bit() else 4 |
| 56 | |
| 57 | input_file = idaapi.get_input_file_path() |
| 58 | dir_path = os.path.dirname(input_file) |
| 59 | platform = 'windows' if input_file.endswith('.dll') else 'linux' |
| 60 | |
| 61 | all_vtables = [] |
| 62 | for vt_addr, vt_name, vt_symbol in vtables: |
| 63 | entries = [] |
| 64 | for i in range(1000): |
| 65 | if ptr_size == 8: |
| 66 | ptr_value = ida_bytes.get_qword(vt_addr + i * ptr_size) |
| 67 | else: |
| 68 | ptr_value = ida_bytes.get_dword(vt_addr + i * ptr_size) |
| 69 | |
| 70 | if ptr_value == 0 or ptr_value == 0xFFFFFFFFFFFFFFFF: |
| 71 | break |
| 72 | |
| 73 | func = idaapi.get_func(ptr_value) |
| 74 | if func is None: |
| 75 | flags = ida_bytes.get_full_flags(ptr_value) |
| 76 | if not ida_bytes.is_code(flags): |
| 77 | break |
| 78 | |
| 79 | entries.append((ptr_value, func)) |
| 80 | |
| 81 | count = len(entries) |
| 82 | vtable_size = count * ptr_size |
| 83 | vt_rva = vt_addr - image_base |
| 84 | |
| 85 | entries_dict = {} |
| 86 | for i, (ptr_value, func) in enumerate(entries): |
| 87 | func_size = func.size() if func else 0 |
| 88 | entries_dict[i] = f"{hex(ptr_value)} size={hex(func_size)}" |
| 89 | |
| 90 | yaml_data = { |
| 91 | 'vtable_class': vt_name, |
| 92 | 'vtable_symbol': vt_symbol, |
| 93 | 'vtable_va': hex(vt_addr), |
| 94 | 'vtable_rva': hex(vt_rva), |
| 95 | 'vtable_size': hex(vtable_size), |
| 96 | 'vtable_numvfunc': count, |
| 97 | 'vtable_entries': entries_dict |
| 98 | } |
| 99 | all_vtables.append(yaml_data) |
| 100 | |
| 101 | yaml_path = os.path.join(dir_path, f"{output_name}.{platform}.yaml") |
| 102 | with open(yaml_path, 'w', encoding='utf-8') as f: |
| 103 | yaml.dump(all_vtables, f, default_flow_style=False, sort_keys=False, allow_unicode=True) |
| 104 | |
| 105 | print(f"Written {len(all_vtables)} vtables to {yaml_path}") |
| 106 | """ |
| 107 | ``` |
| 108 | |
| 109 | ## Deriving `vtable_class` from the Mangled Symbol |
| 110 | |
| 111 | For MSVC mangled vtable symbols (`??_7<ClassName>@@6B@`), extract the class name by stripping the `??_7` prefix and `@@6B@` suffix. |
| 112 | |
| 113 | For nested classes like `??_7CServerSideClient_GameEventLegacyProxy@CSource1LegacyGameEventGameSystem@@6B@`, use the outermost class or a descriptive name (e.g., `CSource1LegacyGameEventGameSystem_Proxy`). |
| 114 | |
| 115 | ## Output File Naming Convention |
| 116 | |
| 117 | - `<output_name>.<platform>.yaml` |
| 118 | - Written to the same directory as the input binary |
| 119 | |
| 120 | Examples: |
| 121 | - `IGameSystem_vtables.windows.yaml` |
| 122 | - `IGameSystem_vtables.linux.yaml` |
| 123 | |
| 124 | ## Output YAML Format |
| 125 | |
| 126 | The file is a YAML list. Each entry follows the `write-vtable-as-yaml` convention with an added `size=` annotation per vfunc: |
| 127 | |
| 128 | ```yaml |
| 129 | - vtable_class: CCSGCServerSystem |
| 130 | vtable_symbol: ??_7CCSGCServerSystem@@6B@ |
| 131 | vtable_va: '0x181538bb8' |
| 132 | vtable_rva: '0x1538bb8' |
| 133 | vtable_size: '0x238' |
| 134 | vtable_numvfunc: 71 |
| 135 | vtable_entries: |
| 136 | 0: 0x180ea9370 size=0x21 |
| 137 | 1: 0x1801b7cd0 size=0x5 |
| 138 | 2: 0x1801b88d0 size=0xb0 |
| 139 | |
| 140 | - vtable_class: CBotGameSystem |
| 141 | vtable_symbol: ??_7CBotGameSystem@@6B@ |
| 142 | vtable_va: '0x18156c280' |
| 143 | vtable_rva: '0x156c280' |
| 144 | vtable_size: '0x1f8' |
| 145 | vtable_numvfunc: 63 |
| 146 | vtable_entries: |