Install
$ agentstack add skill-hlnd2t-cs2-vibesignatures-create-preprocessor-scripts Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Dangerous shell/eval execution.
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ● Dynamic code execution Used
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Create Preprocessor Scripts from Scratch
Create an ida_preprocessor_scripts/find-XXXX.py preprocessor script and add the corresponding config.yaml entries for a newly requested function, vtable, or struct member offset.
When to Use
- A GitHub issue or user instruction requests adding support for finding a new function/symbol
- No existing
.claude/skills/find-XXXX/SKILL.mdneeds conversion (for that, useconvert-finder-skill-to-preprocessor-scripts)
Inputs
The user or issue will provide some or all of:
| Field | Description | Example | |-------|-------------|---------| | Function name(s) | Target symbol(s) to find | CPlayer_MovementServices_PlayWaterStepSound | | Module | Which DLL/SO the function lives in | server, engine, networksystem, client | | Category | Symbol type | func, vfunc, structmember, patch, vtable | | xrefstrings | Debug strings for xref-based discovery | "CT_Water.StepLeft" | | xrefgvs | Global variable VA (e.g. vtable address) to find functions that reference it | vtable VA from SomeClass_vtable.{platform}.yaml | | xreffuncs | Known callee function name to find its callers | "CPlayerCommandQueue_ctor" | | Predecessor function | Function to decompile for LLMDECOMPILE patterns | CBaseEntity_TakeDamageOld | | VTable class | Class owning the vtable (for vfuncs) | CBasePlayerPawn | | Desired YAML fields | Which fields the output YAML needs | func_name, func_sig, func_va, func_rva, func_size | | Dependencies | Input YAMLs this skill depends on | CCSPlayer_MovementServices_vtable.{platform}.yaml | | Aliases | Alternative names for the symbol | CPlayer_MovementServices::PlayWaterStepSound |
Overview
Eleven preprocessor patterns exist. The discovery method and target type determine which to use:
| Pattern | Discovery Method | Has FUNCXREFS | Has LLMDECOMPILE | Has INHERITVFUNCS | Has FUNCVTABLERELATIONS | preprocessskill has llmconfig | |---------|-----------------|-----------------|---------------------|--------------------|---------------------------|-------------------------------| | A -- Regular function via xref strings | find_regex + xrefs_to on debug strings | Yes | No | No | No | No | | B -- Virtual function via xref strings | Same as A, but function is in a vtable | Yes | No | No | Yes | No | | C -- Virtual function via LLMDECOMPILE | Decompile a known predecessor function, identify vfunc call offsets | No | Yes | No | Yes | Yes | | D -- Regular function via LLMDECOMPILE | Decompile a known predecessor function, identify direct call targets | No | Yes | No | No | Yes | | E -- Struct member offset via LLMDECOMPILE | Decompile a known predecessor function, identify struct field access offsets | No | Yes | No | No | Yes | | F -- Virtual function via INHERITVFUNCS | Inherit vtable slot index from a known base-class vfunc, look up same slot in derived-class vtable (standard); or slot-only mode for abstract/interface vfuncs where only offset/index is needed | No | No | Yes | No | No | | G -- ConCommand handler function | Find the handler callback registered via RegisterConCommand by matching command name and help string | No (uses COMMANDNAME/HELPSTRING) | No | No | No | No | | H -- Secondary (ordinal) vtable | Locate a class's secondary vtable via mangled symbol (Windows) or offset-to-top (Linux) | No | No | No | No | No | | I -- Interface vfunc offset via thunk instruction walk | Walk a known concrete-class thunk via py_eval + idaapi.decode_insn, extract jmp [reg+disp] displacement as vfuncoffset | No | No | No | No | No | | J -- IGameSystem vfunc via dispatch scan | Scan IGameSystem_DispatchCall(idx, callback, ...) call sites in a known predecessor; map targets by scan/index order using _igamesystem_dispatch_common | No | No | No | No | No | | K -- IGameSystem vfunc via slot dispatch scan | Walk an IGameSystem_Loop*AllSystems dispatcher function body; extract [rax+offset] vtable call displacements via _igamesystem_slot_dispatch_common; output is slot-only (no func_sig) | No | No | No | No | No |
Additionally, struct member offsets can be mixed into any pattern as a secondary target (see "Struct Member Mixin" section below).
Step 1: Determine the Pattern
From the user's input, determine:
- Is the target a function, vfunc, or struct member offset?
- Has
xref_strings+ categoryfunc-> Pattern A - Has
xref_strings+ categoryvfunc-> Pattern B - Has
xref_gvs(vtable VA from a vtable YAML) + categoryfunc-> Pattern A with dynamic FUNCXREFS (read vtable VA at runtime; see "Dynamic FUNCXREFS via xref_gvs" note) - Has
xref_gvs(vtable VA from a vtable YAML) + categoryvfunc-> Pattern B with dynamic FUNC_XREFS - Has
xref_funcs(known callee function name) + categoryfunc-> Pattern A (static FUNCXREFS; see "xreffuncs: finding callers of a known function" note) - Has
xref_funcs(known callee function name) + categoryvfunc-> Pattern B (static FUNC_XREFS) - Has predecessor function + category
vfunc-> Pattern C (vfunc_sigis ALWAYS required inGENERATE_YAML_DESIRED_FIELDS-- see "vfunc_sig is MANDATORY for Pattern C" note below) - Has predecessor function + category
func-> Pattern D - Has predecessor function + category
structmember-> Pattern E - Has base vfunc name + category
vfunc(derived-class override of known base vfunc) -> Pattern F - If the target is an abstract/interface vfunc (no real function body, only
vfunc_offset/vfunc_indexneeded) -> use Pattern F slot-only:generate_func_sig=False, desired fields ={func_name, vtable_name, vfunc_offset, vfunc_index}, NO vtable YAML required for the interface class - Has
COMMAND_NAME+HELP_STRING(ConCommand handler callback) -> Pattern G - Has mangled vtable symbol / offset-to-top + category
vtable(secondary vtable for a class) -> Pattern H - Target is an interface vfunc offset with no feasible
func_sig/vfunc_sig, and the offset can be read from a concrete-class thunk'sjmp [reg+disp]instruction -> Pattern I - Target is an
IGameSystemvfunc visible as the callback argument toIGameSystem_DispatchCall(...)in a known predecessor's decompile -> Pattern J - Target is an
IGameSystemabstract vfunc (slot-only output:func_name, vtable_name, vfunc_offset, vfunc_index; nofunc_sig) dispatched by a knownIGameSystem_Loop*AllSystemsfunction that iterates all game systems via vtable; the dispatcher's output YAML (func_va) is already available -> Pattern K
- Do xref strings differ between Windows and Linux? If yes, use platform-specific
FUNC_XREFS_WINDOWS/FUNC_XREFS_LINUXvariant.
- Are there multiple functions? If they share the same discovery method and starting point, put them in the same script with
-AND-in the name. Otherwise, split into separate scripts.
CRITICAL -- LLMDECOMPILE dependency chains: When LLMDECOMPILE targets form a chain (FuncA -> FuncB -> FuncC, where each is the predecessor of the next), they MUST be in separate scripts -- one script per link in the chain. A single script CANNOT handle chained LLMDECOMPILE predecessors because the LLMDECOMPILE fallback resolves the predecessor's address from its output YAML (func_va field), and within a single script run the predecessor's output YAML doesn't exist yet. The IDA name-lookup fallback also fails because the predecessor wasn't renamed yet.
Step 2: Create the Preprocessor Script
Script location: ida_preprocessor_scripts/find-{skill_name}.py
The filename MUST match the name field in config.yaml skill entry.
Read the reference for your chosen pattern:
- [Pattern A -- Regular function via xref strings](references/pattern-A.md)
- [Pattern B -- Virtual function via xref strings](references/pattern-B.md)
- [Pattern C -- Virtual function via LLM_DECOMPILE](references/pattern-C.md)
- [Pattern D -- Regular function via LLM_DECOMPILE](references/pattern-D.md)
- [Pattern E -- Struct member offset via LLM_DECOMPILE](references/pattern-E.md)
- [Pattern F -- Virtual function via INHERIT_VFUNCS](references/pattern-F.md) (standard + slot-only variant)
- [Pattern G -- ConCommand handler function](references/pattern-G.md)
- [Pattern H -- Secondary (ordinal) vtable](references/pattern-H.md)
- [Pattern I -- Interface vfunc offset via thunk walk](references/pattern-I.md)
- [Pattern J -- IGameSystem vfunc via dispatch scan](references/pattern-J.md)
Cross-Cutting Notes
FULLMATCH: Prefix for Xref Strings (Patterns A & B)
When the xref string is short or generic (e.g. "Precache", "userid", "team"), use the FULLMATCH: prefix to require exact string matching instead of substring matching. Without it, "Precache" would match "PrecacheModel", "PrecacheSound", etc.
FUNC_XREFS = [
{
"func_name": "CEntityInstance_Precache",
"xref_strings": [
"FULLMATCH:Precache", # Only matches the exact string "Precache"
],
"xref_gvs": [], "xref_signatures": [], "xref_funcs": [],
"exclude_funcs": [], "exclude_strings": [], "exclude_gvs": [], "exclude_signatures": [],
},
]
Dynamic FUNC_XREFS via xref_gvs (vtable VA)
When the target function is the constructor (or any other function that references a class's vtable), use xref_gvs with the vtable's virtual address. Because the vtable VA is only known after IDA analysis, it cannot be hardcoded -- it must be read from the vtable's output YAML at runtime.
This requires a custom preprocess_skill that:
- Reads
vtable_vafrom{VtableClass}_vtable.{platform}.yamlinnew_binary_dir - Builds
func_xrefsdynamically with the VA inxref_gvs - Passes the dynamic list to
preprocess_common_skill
import os
try:
import yaml
except ImportError:
yaml = None
def _read_vtable_va(yaml_path):
try:
with open(yaml_path, "r", encoding="utf-8") as f:
data = yaml.safe_load(f)
if isinstance(data, dict):
va = data.get("vtable_va")
if va:
return str(va)
except Exception:
pass
return None
async def preprocess_skill(
session, skill_name, expected_outputs, old_yaml_map,
new_binary_dir, platform, image_base, debug=False,
):
vtable_yaml_path = os.path.join(new_binary_dir, f"SomeClass_vtable.{platform}.yaml")
vtable_va = _read_vtable_va(vtable_yaml_path)
if not vtable_va:
if debug:
print(" Preprocess: SomeClass_vtable vtable_va not found, cannot resolve xref_gvs")
return False
func_xrefs = [
{
"func_name": "SomeClass_ctor",
"xref_strings": [],
"xref_gvs": [str(vtable_va)],
"xref_signatures": [],
"xref_funcs": [],
"exclude_funcs": [],
"exclude_strings": [],
"exclude_gvs": [],
"exclude_signatures": [],
},
]
return await preprocess_common_skill(
session=session,
expected_outputs=expected_outputs,
old_yaml_map=old_yaml_map,
new_binary_dir=new_binary_dir,
platform=platform,
image_base=image_base,
func_names=TARGET_FUNCTION_NAMES,
func_xrefs=func_xrefs,
generate_yaml_desired_fields=GENERATE_YAML_DESIRED_FIELDS,
debug=debug,
)
config.yaml expected_input: must include the vtable YAML so it is guaranteed to be resolved before this script runs.
Multiple xrefs / exclude_signatures: If more than one function references the vtable (e.g. constructor + destructor), the intersection yields >1 result and the skill fails. Use exclude_signatures to exclude the unwanted function(s). If the ambiguity is platform-specific, make the exclusion conditional:
exclude_signatures = ["66 83 ?? FF"] if platform == "linux" else []
To find the right bytes to exclude: look up the two candidate addresses in IDA, read the first ~4 bytes of the function to exclude, and use those as the exclude_signatures pattern with ?? wildcards where needed.
xref_funcs: Finding Callers of a Known Function (Patterns A & B)
When the target function is discoverable as a caller of another already-known function, use xref_funcs with the callee's name. Unlike xref_gvs, the function name is available at script-write time, so FUNC_XREFS can be a static module-level constant -- no dynamic building required.
FUNC_XREFS = [
{
"func_name": "TargetFunc",
"xref_strings": [],
"xref_gvs": [],
"xref_signatures": [],
"xref_funcs": ["KnownCalleeFunc"], # callee that the target calls
"exclude_funcs": [],
"exclude_strings": [],
"exclude_gvs": [],
"exclude_signatures": [],
},
]
config.yaml expected_input: include the callee's output YAML to guarantee it is renamed in IDA before this script runs (the name lookup requires the rename to have happened):
expected_input:
- KnownCalleeFunc.{platform}.yaml # ensures callee is renamed first
- TargetClass_vtable.{platform}.yaml # if target is a vfunc (Pattern B)
Struct Member Mixin (for any pattern)
Struct member offsets can also be mixed into a function-finding script when they are discovered from the same function via signature matching (not LLM_DECOMPILE). Add TARGET_STRUCT_MEMBER_NAMES alongside TARGET_FUNCTION_NAMES and pass struct_member_names= to preprocess_common_skill:
TARGET_FUNCTION_NAMES = [
"SomeFunction",
]
TARGET_STRUCT_MEMBER_NAMES = [
"SomeStruct_m_someField",
]
GENERATE_YAML_DESIRED_FIELDS = [
("SomeFunction", ["func_name", "func_sig", "func_va", "func_rva", "func_size"]),
("SomeStruct_m_someField", ["struct_name", "member_name", "offset", "size", "offset_sig", "offset_sig_disp"]),
]
# In preprocess_skill:
return await preprocess_common_skill(
...
func_names=TARGET_FUNCTION_NAMES,
struct_member_names=TARGET_STRUCT_MEMBER_NAMES,
...
)
CRITICAL -- FUNCVTABLERELATIONS and vfunc fields
FUNC_VTABLE_RELATIONS is required for ANY target whose GENERATE_YAML_DESIRED_FIELDS includes vtable_name or vfunc_sig -- not just Pattern B and C. Without it, the LLM_DECOMPILE slot-only fallback fails with "slot-only fallback missing vtable_name" and the entire skill fails.
This applies even when:
- The target is a vfunc call-site offset (e.g.
call [rax+128h]) rather than an actual function body in a vtable - No vtable YAML exists for that class in config.yaml (no
expected_inputfor the vtable needed) - The script also finds non-vfunc targets (global variables, struct offsets) alongside the vfunc target
The vtable_name from FUNC_VTABLE_RELATIONS is used as metadata written to the output YAML -- it does NOT require an actual vtable lookup. For example, ("IGameTypes_CreateWorkshopMapGroup", "IGameTypes") provides the vtable class name IGameTypes even though no IGameTypes_vtable.{platform}.yaml exists.
Rule of thumb: If any field in GENERATE_YAML_DESIRED_FIELDS starts with vfunc_ or equals vtable_name, the target MUST have an entry in FUNC_VTABLE_RELATIONS.
CRITICAL -- vfunc_sig is MANDATORY for Pattern C (vfunc via LLM_DECOMPILE)
For ANY vfunc discovered via LLM_DECOMPILE (Pattern C), GENERATE_YAML_DESIRED_FIELDS MUST include vfunc_sig. This is non-negotiable -- the slot index alone is not stable across binary updates without a signature anchor on the actual vfunc body.
This rule applies to BOTH variants:
- Standard Pattern C (also a downstream predecessor): `funcname, funcva, func_rva, f
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: HLND2T
- Source: HLND2T/CS2_VibeSignatures
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.