Find and identify the CBaseTrigger::StartTouch and CBaseTrigger::EndTouch overridden virtual functions in the CS2 server binary using IDA Pro MCP. Use this skill when reverse engineering server.dll or libserver.so to locate the trigger-volume touch-list add/remove handlers by RTTI-walking CBaseTrigger's and CBaseEntity's primary vtables and diffing them slot-by-slot to find CBaseTrigger's overrides, then identifying which two overridden slots implement "add pOther to the touching-entities lis...
Scanned 9/27/2026
Install to Claude Code
npx -y skills add mrc4tt/CS2_VibeSignatures --skill find-CBaseTrigger_StartTouch-AND-CBaseTrigger_EndTouch --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Find CBaseTrigger StartTouch AND CBaseTrigger EndTouch?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/mrc4tt-find-cbasetrigger-starttouch-and-cbasetrigger-endt)More formats (shields.io, HTML) on the badges page.
---
name: find-CBaseTrigger_StartTouch-AND-CBaseTrigger_EndTouch
description: |
Find and identify the CBaseTrigger::StartTouch and CBaseTrigger::EndTouch overridden virtual functions in the
CS2 server binary using IDA Pro MCP. Use this skill when reverse engineering server.dll or libserver.so to
locate the trigger-volume touch-list add/remove handlers by RTTI-walking CBaseTrigger's and CBaseEntity's
primary vtables and diffing them slot-by-slot to find CBaseTrigger's overrides, then identifying which two
overridden slots implement "add pOther to the touching-entities list" (StartTouch) versus "remove pOther from
the touching-entities list" (EndTouch).
Trigger: CBaseTrigger_StartTouch, CBaseTrigger_EndTouch
disable-model-invocation: true
---
# Find CBaseTrigger_StartTouch and CBaseTrigger_EndTouch
Locate `CBaseTrigger::StartTouch` and `CBaseTrigger::EndTouch` in CS2 `server.dll` / `libserver.so` using IDA
Pro MCP tools.
**Note**: unlike most sibling skills, there is no known reference signature to validate against for these two
symbols — they are discovered fresh via vtable-diffing plus body-shape confirmation.
## Method
### 1. Resolve CBaseTrigger's and CBaseEntity's Primary VTables
**ALWAYS** Use SKILL `/get-vtable-address` with `class_name=CBaseTrigger` and again with `class_name=CBaseEntity`
(RTTI walk: typeinfo name string `"12CBaseTrigger"` / `"11CBaseEntity"` → typeinfo → primary vtable, the
candidate whose `offset_to_top` qword is `0` and whose first few slots point into executable memory).
> Linux 14168 reference: `CBaseTrigger` primary vtable `0x2396298`, `CBaseEntity` primary vtable `0x23cc9b8`.
### 2. Diff the Two VTables Slot-by-Slot
`CBaseTrigger` derives (indirectly) from `CBaseEntity`, so under the Itanium ABI every virtual function keeps
the **same slot index** across the inheritance chain. Read both vtables' function-pointer qwords at
`vt + 0x10 + n*8` for a generous slot range (e.g. `n` in `0..500`) and collect every `n` where the two addresses
differ — those are exactly the slots `CBaseTrigger` overrides.
```text
mcp__ida-pro-mcp__idalib py_eval code="<loop reading both vtables, diffing qwords>"
```
> Linux 14168 reference: differing slots in range 0..500 were `[125, 127, 147, 149, 150, 156, 194, 197, 198]`
> (9 overrides total — `CBaseTrigger` overrides `Spawn`/`Activate`/touch handlers/damage-related methods, etc.).
### 3. Identify StartTouch and EndTouch Among the Overridden Slots
Decompile every differing slot's `CBaseTrigger`-side function and look for the touch-list add/remove shape.
`CBaseTrigger` maintains an internal growable array of currently-touching entities (count field + pointer field,
a `CUtlVector`-shaped pair a fixed struct-offset apart, e.g. count at `this+2952`/`this+2960` in the 14168
build). The two candidates you want:
- **StartTouch**: searches the array for `pOther`'s handle; if absent, **appends** it (grows the array, bumps
the count), and — only when the count transitions `0 → 1` — fires an additional "first toucher" callback
(guarded by a `!= nullsub_*` check against a vtable slot, e.g. `OnStartTouchAll`). Also fires the trigger's
`m_OnStartTouch`/output-system calls via `sub_2132220`-shaped helpers.
- **EndTouch**: searches the array for `pOther`'s handle; if found, **removes** it (shifts the tail down, shrinks
the count), then fires the trigger's `m_OnEndTouch`/output-system calls the same way.
Both take `(CBaseTrigger *this, CBaseEntity *pOther)`. Reject any other overridden slot that doesn't touch this
array (e.g. slots that are trivial `return 0;`/`return 1;` stubs, or large functions unrelated to a touch list —
those are unrelated overrides like `ShouldCollide`/network-group helpers picked up by the same slot range scan).
**Cross-check** (do this — strong independent confirmation): decompile the **un-overridden** `CBaseEntity`-side
function at each of these two slots. Both should have the same generic shape — a short "forward the call to
some related singleton object at the *same* vtable slot" stub — which is the expected base-class placeholder
behavior for virtuals that `CBaseTrigger` (and other touch-list-owning classes) meaningfully override, while
`CBaseEntity` itself doesn't maintain a touch list.
> Linux 14168 reference: `CBaseTrigger` overrides slot **147** with `0xd54900` (StartTouch — appends to the
> touch array at `this+2952`/`this+2960`, fires `this+2168`/`this+2176`-guarded callbacks and
> `sub_2132220(this+2784/2760/2928, ...)` output calls) and slot **149** with a thin wrapper `0xd24190`
> (`if (a2) return sub_D23E70();`) that tail-calls `sub_D23E70` — which **removes** `pOther` from the same
> touch array and fires the paired `m_OnEndTouch`-style outputs at `this+2808/2928/2832` — i.e. EndTouch. Both
> un-overridden `CBaseEntity` slots (`0xd1dfa0` for 147, `0xd1e090` for 149) share the identical
> "forward to `sub_16B0920()`'s object at the same vtable offset" placeholder shape, confirming these two slots
> are the touch-list virtuals and that `CBaseTrigger` is the class that gives them real bodies.
### 4. Generate a Fresh Function Signature
Since there is no reference signature for either symbol, generate and simply record what's found:
**ALWAYS** Use SKILL `/generate-signature-for-function` with `addr=<CBaseTrigger_StartTouch_addr>`.
**ALWAYS** Use SKILL `/generate-signature-for-function` with `addr=<CBaseTrigger_EndTouch_addr>`.
> Linux 14168 reference sigs actually generated (no ground truth to compare against — reported as-is):
> - `CBaseTrigger_StartTouch` (`0xd54900`): `55 48 89 E5 41 56 41 55 49 89 F5 41 54 53 48 89 FB 48 83 EC ? 48 8B 07 FF 90 ? ? ? ? 84 C0`
> - `CBaseTrigger_EndTouch` (`0xd24190`): `48 85 F6 74 ? E9 ? ? ? ? 66 0F 1F 44 00 ? C3 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC 48 8B 07`
> — note `EndTouch`'s real body is only 0x11 bytes (`test rsi,rsi; jz; jmp sub_D23E70`), so the signature
> generator had to grow past the function's own `int3` padding into the *next* function's first bytes
> (`48 8B 07`) to reach uniqueness; this is expected/benign for such a tiny thin-wrapper function and does not
> indicate a bad signature, but it does mean the sig's tail is technically borrowed from the following symbol.
### 5. Write IDA Analysis Output as YAML
**ALWAYS** Use SKILL `/write-vfunc-as-yaml` twice, once for each symbol:
- `func_name`: `CBaseTrigger_StartTouch`, `func_addr`: `<StartTouch_addr>`, `func_sig`: from step 4,
`vtable_name`: `CBaseTrigger`, `vfunc_offset`: `0x498` (`147*8`), `vfunc_index`: `147`.
- `func_name`: `CBaseTrigger_EndTouch`, `func_addr`: `<EndTouch_addr>`, `func_sig`: from step 4,
`vtable_name`: `CBaseTrigger`, `vfunc_offset`: `0x4A8` (`149*8`), `vfunc_index`: `149`.
## Function Characteristics
- **Purpose**: `StartTouch`/`EndTouch` maintain `CBaseTrigger`'s per-instance list of entities currently
overlapping the trigger volume, and fire the corresponding `m_OnStartTouch`/`m_OnEndTouch` (and
`...All` variants on first/last toucher) I/O outputs.
- **Binary**: `server.dll` / `libserver.so`
- **Parameters**: `(CBaseTrigger *this, CBaseEntity *pOther)`
- **Return value**: `void`
- **VTable**: `CBaseTrigger`, slots 147 (`StartTouch`) and 149 (`EndTouch`) on the Linux 14168 reference build —
these slot indices are inherited unchanged from `CBaseEntity::StartTouch`/`EndTouch` (Itanium ABI keeps virtual
slot indices stable across the inheritance chain), so re-deriving them via `/get-vtable-index` against
whichever build you're on is safer than hardcoding `147`/`149`.
## Discovery Strategy
1. RTTI-walk both `CBaseTrigger`'s and `CBaseEntity`'s primary vtables — no string/xref anchor is needed for
either class since Itanium typeinfo name strings (`"12CBaseTrigger"`, `"11CBaseEntity"`) are always present
even in a stripped binary.
2. Diff the two vtables slot-by-slot; every differing slot is a genuine `CBaseTrigger` override, independent of
any single function's absolute address.
3. Among the overrides, identify the touch-list add/remove pair by body shape (grows vs. shrinks the same
internal array, fires the paired Start/End output-event helpers) rather than by hardcoding a slot number —
this survives the touch-list's own internal struct-offset layout changing across builds, as long as the
*shape* of "search array, mutate count, conditionally fire paired outputs" stays recognizable.
4. Cross-checking against `CBaseEntity`'s own (un-overridden) placeholder bodies at the same two slots is a
free, independent sanity check that costs nothing extra since both vtables were already resolved in step 1.
## Output YAML Format
The output YAML filenames depend on the platform:
- `server.dll` -> `CBaseTrigger_StartTouch.windows.yaml`, `CBaseTrigger_EndTouch.windows.yaml`
- `libserver.so` -> `CBaseTrigger_StartTouch.linux.yaml`, `CBaseTrigger_EndTouch.linux.yaml`
Fields (both files): `func_name`, `func_va`, `func_rva`, `func_size`, `func_sig`, `vtable_name`, `vfunc_offset`,
`vfunc_index`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!