$npx -y skills add HLND2T/CS2_VibeSignatures --skill create-cpp-testsCreate a new cpp_tests entry for validating a C++ interface vtable layout against binary reference YAMLs. Creates the .cpp test file in cpp_tests/ and appends a configs/<GAMEVER>.yaml entry under cpp_tests:. Use when a user asks to add vtable layout validation for a new hl2sdk_cs
| 1 | # Create cpp_tests for Interface Vtable Validation |
| 2 | |
| 3 | Create a `cpp_tests/<interface_lowercase>.cpp` test file and append a matching `configs/<GAMEVER>.yaml` |
| 4 | entry so that `run_cpp_tests.py` can compile the header with clang, dump the vtable layout, |
| 5 | and compare it against reference YAML files from the binary analysis. |
| 6 | |
| 7 | Resolve `GAMEVER` from the user's explicit request or `CS2VIBE_GAMEVER`, set the edit target to |
| 8 | `configs/$GAMEVER.yaml`, and stop if that exact file does not exist. |
| 9 | |
| 10 | ## When to Use |
| 11 | |
| 12 | - User asks to create cpp_tests for an interface (e.g., "create cpp_tests for ILoopMode") |
| 13 | - User provides or references a header file from `hl2sdk_cs2/` |
| 14 | |
| 15 | ## Inputs |
| 16 | |
| 17 | The user will provide some or all of: |
| 18 | |
| 19 | | Field | Required | Description | Example | |
| 20 | |-------|----------|-------------|---------| |
| 21 | | **Interface name** | Yes | The abstract class to validate | `ILoopMode` | |
| 22 | | **Header path** | Yes | Path to the header in hl2sdk_cs2 | `hl2sdk_cs2/public/iloopmode.h` | |
| 23 | | **Alias symbols** | No | Concrete class name(s) used in binary YAML files | `CLoopModeGame` | |
| 24 | | **Reference modules** | No | Modules where vtable YAMLs live | `client`, `server` | |
| 25 | |
| 26 | ## Step-by-Step Procedure |
| 27 | |
| 28 | ### Step 1: Read the target header |
| 29 | |
| 30 | Read the header file to understand: |
| 31 | - What `#include` directives it uses |
| 32 | - What types are referenced in virtual method signatures |
| 33 | - Whether it inherits from another interface (e.g., `IAppSystem`) |
| 34 | |
| 35 | ### Step 2: Identify compilation dependencies |
| 36 | |
| 37 | Check if the header's transitive includes will compile cleanly with the standard include set: |
| 38 | - `hl2sdk_cs2/game/shared` |
| 39 | - `hl2sdk_cs2/public` |
| 40 | - `hl2sdk_cs2/public/tier0` |
| 41 | - `hl2sdk_cs2/public/tier1` |
| 42 | |
| 43 | Common issues to watch for: |
| 44 | - **Missing types** referenced in method signatures (e.g., `CSplitScreenSlot` needs `<tier1/convar.h>`) |
| 45 | - **Heavy transitive includes** that pull in protobuf or other unavailable deps (e.g., `eiface.h` -> protobuf chain) |
| 46 | |
| 47 | For heavy transitive includes, pre-define include guards to block them and forward-declare/stub the minimum needed types. See the `inetworkmessages.cpp` and `inetworksystem.cpp` examples for this technique: |
| 48 | |
| 49 | ```cpp |
| 50 | // Example: blocking heavy include chains |
| 51 | #define EIFACE_H |
| 52 | #define INETCHANNEL_H |
| 53 | enum NetChannelBufType_t : int8 {}; |
| 54 | ``` |
| 55 | |
| 56 | ### Step 3: Create the cpp test file |
| 57 | |
| 58 | Create `cpp_tests/<interface_lowercase>.cpp` following this template: |
| 59 | |
| 60 | ```cpp |
| 61 | #include <tier0/platform.h> |
| 62 | #undef RESTRICT |
| 63 | #define RESTRICT |
| 64 | |
| 65 | // Add any extra includes needed for types in the interface signatures |
| 66 | // Add any include-guard stubs to block heavy transitive includes |
| 67 | |
| 68 | #include <path/to/interface_header.h> |
| 69 | |
| 70 | InterfaceName * instanceptr(); |
| 71 | |
| 72 | int main() { |
| 73 | |
| 74 | instanceptr()->SomeMethod(); |
| 75 | |
| 76 | return 0; |
| 77 | } |
| 78 | ``` |
| 79 | |
| 80 | Key rules: |
| 81 | - Always start with the `platform.h` + `RESTRICT` preamble |
| 82 | - The extern function declaration (e.g., `ILoopMode * loopmode();`) forces the compiler to emit vtable info |
| 83 | - `main()` must call at least one virtual method to trigger vtable layout dump |
| 84 | - Pick a simple void-returning method with no complex args for the call in `main()` |
| 85 | |
| 86 | ### Step 4: Determine alias_symbols and reference_modules |
| 87 | |
| 88 | If the user didn't provide these, discover them: |
| 89 | |
| 90 | 1. **alias_symbols**: Search for existing vtable YAML files: |
| 91 | ``` |
| 92 | bin/**/*<ClassName>*vtable* |
| 93 | ``` |
| 94 | The `vtable_class` field in those YAMLs gives the alias symbol (typically the concrete class name like `CLoopModeGame` for `ILoopMode`). |
| 95 | |
| 96 | 2. **reference_modules**: The subdirectories under `bin/{gamever}/` where vtable YAMLs exist (e.g., `client`, `server`, `engine`, `networksystem`). |
| 97 | |
| 98 | ### Step 5: Append configs/<GAMEVER>.yaml entry |
| 99 | |
| 100 | Append a new entry at the end of the `cpp_tests:` section in `configs/<GAMEVER>.yaml`: |
| 101 | |
| 102 | ```yaml |
| 103 | - name: {InterfaceName}_MSVC |
| 104 | symbol: {InterfaceName} |
| 105 | alias_symbols: # omit this block if no aliases |
| 106 | - {ConcreteClassName} |
| 107 | cpp: cpp_tests/{interface_lowercase}.cpp |
| 108 | headers: |
| 109 | - {header_path} # Used by the fix-cppheaders SKILL |
| 110 | target: x86_64-pc-windows-msvc |
| 111 | include_directories: |
| 112 | - hl2sdk_cs2/game/shared |
| 113 | - hl2sdk_cs2/public |
| 114 | - hl2sdk_cs2/public/tier0 |
| 115 | - hl2sdk_cs2/public/tier1 |
| 116 | defines: |
| 117 | - COMPILER_MSVC=1 |
| 118 | - COMPILER_MSVC64=1 |
| 119 | - _MSVC_STL_USE_ABORT_AS_DOOM_FUNCTION |
| 120 | additional_compiler_options: |
| 121 | - fms-extensions |
| 122 | - fms-compatibility |
| 123 | - Xclang |
| 124 | - fdump-vtable-layouts |
| 125 | reference_modules: |
| 126 | - {module1} # bin/{gamever}/{module1}/{AliasOrSymbol}_*.{platform}.yaml |
| 127 | - {module2} |
| 128 | ``` |
| 129 | |
| 130 | Notes: |
| 131 | - `include_directories`, `defines`, and `additional_compiler_options` are always the same standard set |
| 132 | - |