-
Search for the bot_kill command string:
mcp__ida-pro-mcp__find_regex pattern="bot_kill.*all"
This should find a string like:
"bot_kill <all> <t|ct> <type> <difficulty> <name> - Kills a specific bot, or all bots, matching the given criteria."
-
Find cross-references to the string:
mcp__ida-pro-mcp__xrefs_to addrs="<string_addr>"
This leads to a ConCommand registration function. Decompile it to find the command handler (callback) address — the first argument stored before the description string in the registration call.
-
Decompile the bot_kill command handler and locate the kill loop:
mcp__ida-pro-mcp__decompile addr="<handler_addr>"
Look for a loop pattern like this:
c
1do
2{
3 v24 = *(_QWORD *)v23;
4 if ( (*(unsigned __int8 (__fastcall **)(_QWORD))(**(_QWORD **)(*(_QWORD *)v23 + 24LL) + <IsAlive_offset>))(*(_QWORD *)(*(_QWORD *)v23 + 24LL)) )
5 {
6 (*(void (__fastcall **)(_QWORD, _QWORD, _QWORD))(**(_QWORD **)(v24 + 24) + <CommitSuicide_offset>))(
7 *(_QWORD *)(v24 + 24),
8 0LL,
9 0LL);
10 if ( !v5 )
11 break;
12 }
13 ++v21;
14 v23 += 8;
15}
16while ( v21 < v20 );
The loop iterates over matched bots. For each bot:
*(v24 + 24) dereferences the PlayerPawn pointer
- The first vfunc call (with
<IsAlive_offset>) checks if the pawn is alive
- The second vfunc call (with
<CommitSuicide_offset>) calls pPlayerPawn->CommitSuicide(false, false)
- If not in "all" mode (
!v5), it breaks after the first kill
Extract <CommitSuicide_offset> from the decompiled code (e.g., 3200LL = 0xC80).
-
Get CBasePlayerPawn vtable information:
ALWAYS Use SKILL /get-vtable-from-yaml with class_name=CBasePlayerPawn.
Extract vtable_va, vtable_numvfunc and vtable_entries from the result.
-
Map the vfunc offset to a vtable index and resolve the function address:
vfunc_index = <CommitSuicide_offset> / 8
Look up vtable_entries[vfunc_index] to get the function address.
-
Verify function characteristics to confirm CBasePlayerPawn::CommitSuicide:
Decompile the resolved function address. The function should match:
Windows:
c
1char __fastcall CBasePlayerPawn_CommitSuicide(float *a1, unsigned __int8 a2, char a3)
2{
3 __int64 v4; // rbp
4 char result; // al
5 _BYTE v7[112]; // [rsp+40h] [rbp-138h] BYREF
6 __int64 v8; // [rsp+B0h] [rbp-C8h]
7 int v9; // [rsp+180h] [rbp+8h] BYREF
8 char v10; // [rsp+198h] [rbp+20h] BYREF
9
10 v4 = a2;
11 result = (*(__int64 (__fastcall **)(float *))(*(_QWORD *)a1 + 1336LL))(a1); // IsAlive check
12 if ( result )
13 {
14 sub_XXX(&v9, *(_DWORD *)(*((_QWORD *)a1 + 2) + 56LL));
15 result = sub_XXX(a1 + 824, (float *)&v9);
16 if ( !result || a3 )
17 {
18 sub_XXX(&v9, *(_DWORD *)(*((_QWORD *)a1 + 2) + 56LL));
19 a1[824] = *(float *)sub_XXX(&v10, &v9);
20 sub_XXX((unsigned int)v7, (_DWORD)a1, (_DWORD)a1, 0, 1065353216, (_DWORD)v4 << 6, 0);
21 v8 |= (32 * (v4 ^ 1) + 32) | 0x116;
22 sub_XXX(a1, (__int64)v7, 0LL); // CBaseEntity::TakeDamageOld
23 return sub_XXX((__int64)v7);
24 }
25 }
26 return result;
27}
Linux:
c
1void __fastcall CBasePlayerPawn_CommitSuicide(__int64 a1, unsigned __int8 a2, char a3)
2{
3 unsigned __int8 (*v4)(void); // rax
4 _BYTE v5[112]; // [rsp+0h] [rbp-140h] BYREF
5 __int64 v6; // [rsp+70h] [rbp-D0h]
6
7 v4 = *(unsigned __int8 (**)(void))(*(_QWORD *)a1 + <IsAlive_offset>);
8 if ( (char *)v4 == (char *)CBaseEntity_IsPlayerPawn )
9 {
10 if ( *(_BYTE *)(a1 + 1472) )
11 return;
12 }
13 else if ( !v4() )
14 {
15 return;
16 }
17 if ( *(float *)(a1 + 4072) <= sub_XXX(...) || a3 )
18 {
19 *(float *)(a1 + 4072) = sub_XXX(...) + 5.0;
20 sub_XXX(v5, a1, a1, 0LL, a2 << 6, 0LL, 1.0);
21 v6 |= (a2 == 0 ? 64LL : 32LL) | 0x116;
22 sub_XXX(a1, v5, 0LL); // CBaseEntity::TakeDamageOld
23 sub_XXX(v5);
24 }
25}
Key verification points:
- Calls
IsAlive via vtable at the start
- Constructs a
CTakeDamageInfo on the stack (112-byte buffer)
- Sets damage flags with
| 0x116
- Calls
CBaseEntity::TakeDamageOld (verify by checking for string "CBaseEntity::TakeDamageOld: damagetype %d with info.GetDamagePosition() == Vector::vZero\n" in its callee)
If the code pattern matches, proceed to rename.
-
Rename the function:
mcp__ida-pro-mcp__rename batch={"func": [{"addr": "<function_addr>", "name": "CBasePlayerPawn_CommitSuicide"}]}
-
Generate and validate unique signature:
ALWAYS Use SKILL /generate-signature-for-function to generate a robust and unique signature for the function.
-
Write IDA analysis output as YAML beside the binary:
ALWAYS Use SKILL /write-vfunc-as-yaml to write the analysis results.
Required parameters:
func_name: CBasePlayerPawn_CommitSuicide
func_addr: The function address from step 5
func_sig: The validated signature from step 8
VTable parameters:
vtable_name: CBasePlayerPawn
vfunc_index: The vtable index from step 5
vfunc_offset: vfunc_offset = vfunc_index * 8