$npx -y skills add HLND2T/CS2_VibeSignatures --skill write-vfunc-as-yamlWrite virtual function analysis results as YAML file beside the binary using IDA Pro MCP. Use this skill after completing virtual function identification, signature generation, and vtable analysis to persist the results in a standardized YAML format.
| 1 | # Write Virtual Function IDA Analysis Output as YAML |
| 2 | |
| 3 | Persist virtual function analysis results to a YAML file beside the binary using IDA Pro MCP. |
| 4 | |
| 5 | ## Prerequisites |
| 6 | |
| 7 | Before using this skill, you should have: |
| 8 | 1. Identified and renamed the target virtual function |
| 9 | 2. Generated a unique signature using `/generate-signature-for-function` |
| 10 | 3. Obtained vtable information using `/get-vtable-index` |
| 11 | |
| 12 | ## Required Parameters |
| 13 | |
| 14 | | Parameter | Description | Example | |
| 15 | |-----------|-------------|---------| |
| 16 | | `func_name` | Name of the function | `CCSPlayerController_ChangeTeam` | |
| 17 | | `vtable_name` | Class name for vtable | `CCSPlayerController` | |
| 18 | | `vfunc_offset` | Offset from vtable start | `0x330` | |
| 19 | | `vfunc_index` | Index in vtable | `102` | |
| 20 | |
| 21 | ## Optional Parameters |
| 22 | |
| 23 | | Parameter | Description | Example | |
| 24 | |-----------|-------------|---------| |
| 25 | | `func_addr` | Virtual address of the function (use `None` to omit) | `0x180999830` | |
| 26 | | `func_sig` | Unique byte signature to locate function body (use `None` to omit) | `48 89 5C 24 08` | |
| 27 | | `vfunc_sig` | Unique byte signature to determine vfunc offset (use `None` to omit) | `FF 90 80 04 00 00 4C 8B AC 24 ?? ?? ?? ??` | |
| 28 | | `vfunc_sig_disp` | Byte displacement from signature start to the target instruction. `0` or `None` means signature starts at the target instruction. Non-zero means backward expansion was used by `/generate-signature-for-vfuncoffset`. (use `None` to omit) | `3` | |
| 29 | |
| 30 | When `func_addr` is `None`, the following fields will be omitted from output: `func_va`, `func_rva`, `func_size`. |
| 31 | When `func_sig` is `None`, the `func_sig` field will be omitted from output. |
| 32 | When `vfunc_sig` is `None`, the `vfunc_sig` field will be omitted from output. |
| 33 | When `vfunc_sig_disp` is `None` or `0`, the `vfunc_sig_disp` field will be omitted from output. |
| 34 | |
| 35 | ## Method |
| 36 | |
| 37 | ```python |
| 38 | mcp__ida-pro-mcp__py_eval code=""" |
| 39 | import idaapi |
| 40 | import os |
| 41 | import yaml |
| 42 | |
| 43 | # === REQUIRED: Replace these values === |
| 44 | func_name = "<func_name>" # e.g., "CCSPlayerController_ChangeTeam" |
| 45 | # ====================================== |
| 46 | |
| 47 | # === OPTIONAL: Set to None to omit from output === |
| 48 | func_addr = <func_addr> # e.g., 0x180999830 or None |
| 49 | func_sig = <func_sig> # e.g., "48 89 5C 24 08" or None |
| 50 | vfunc_sig = <vfunc_sig> # e.g., "FF 90 80 04 00 00 4C 8B AC 24 ?? ?? ?? ??" or None |
| 51 | vfunc_sig_disp = <vfunc_sig_disp> # e.g., 3 or None (0 also omitted) |
| 52 | # ================================================= |
| 53 | |
| 54 | # === VTABLE INFO: Replace these values === |
| 55 | vtable_name = "<vtable_name>" # e.g., "CCSPlayerController" |
| 56 | vfunc_offset = <vfunc_offset> # e.g., 0x330 |
| 57 | vfunc_index = <vfunc_index> # e.g., 102 |
| 58 | # ========================================= |
| 59 | |
| 60 | # Get binary path and determine platform |
| 61 | input_file = idaapi.get_input_file_path() |
| 62 | dir_path = os.path.dirname(input_file) |
| 63 | |
| 64 | if input_file.endswith('.dll'): |
| 65 | platform = 'windows' |
| 66 | image_base = idaapi.get_imagebase() |
| 67 | else: |
| 68 | platform = 'linux' |
| 69 | image_base = 0x0 |
| 70 | |
| 71 | # Build data dictionary conditionally |
| 72 | data = {} |
| 73 | |
| 74 | data['func_name'] = func_name |
| 75 | |
| 76 | if func_addr is not None: |
| 77 | func = idaapi.get_func(func_addr) |
| 78 | func_size = func.size() if func else 0 |
| 79 | func_rva = func_addr - image_base |
| 80 | data['func_va'] = hex(func_addr) |
| 81 | data['func_rva'] = hex(func_rva) |
| 82 | data['func_size'] = hex(func_size) |
| 83 | |
| 84 | if func_sig is not None: |
| 85 | data['func_sig'] = func_sig |
| 86 | |
| 87 | if vfunc_sig is not None: |
| 88 | data['vfunc_sig'] = vfunc_sig |
| 89 | |
| 90 | if vfunc_sig_disp is not None and vfunc_sig_disp > 0: |
| 91 | data['vfunc_sig_disp'] = vfunc_sig_disp |
| 92 | |
| 93 | data['vtable_name'] = vtable_name |
| 94 | data['vfunc_offset'] = hex(vfunc_offset) |
| 95 | data['vfunc_index'] = vfunc_index |
| 96 | |
| 97 | yaml_path = os.path.join(dir_path, f"{func_name}.{platform}.yaml") |
| 98 | with open(yaml_path, 'w', encoding='utf-8') as f: |
| 99 | yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) |
| 100 | print(f"Written to: {yaml_path}") |
| 101 | """ |
| 102 | ``` |
| 103 | |
| 104 | ## Output File Naming Convention |
| 105 | |
| 106 | The output YAML filename follows this pattern: |
| 107 | - `<func_name>.<platform>.yaml` |
| 108 | |
| 109 | Examples: |
| 110 | - `server.dll` → `CCSPlayerController_ChangeTeam.windows.yaml` |
| 111 | - `libserver.so` / `libserver.so` → `CCSPlayerController_ChangeTeam.linux.yaml` |
| 112 | |
| 113 | ## Output YAML Format |
| 114 | |
| 115 | Full output (with `func_addr`, `func_sig`, `vfunc_sig`, and `vfunc_sig_disp` provided): |
| 116 | ```yaml |
| 117 | func_name: CCSPlayerController_ChangeTeam |
| 118 | func_va: 0x180999830 # Virtual address - changes with game updates (optional) |
| 119 | func_rva: 0x999830 # Relative virtual address (VA - image base) - changes with game updates (optional) |
| 120 | func_size: 0x301 # Function size in bytes - changes with game updates (optional) |
| 121 | func_sig: 48 89 5C 24 08 # Unique byte signature (optional) |
| 122 | vfunc_sig: FF 90 30 03 00 00 4C 8B AC |