-
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:
do
{
v24 = *(_QWORD *)v23;
if ( (*(unsigned __int8 (__fastcall **)(_QWORD))(**(_QWORD **)(*(_QWORD *)v23 + 24LL) + <IsAlive_offset>))(*(_QWORD *)(*(_QWORD *)v23 + 24LL)) )
{
(*(void (__fastcall **)(_QWORD, _QWORD, _QWORD))(**(_QWORD **)(v24 + 24) + <CommitSuicide_offset>))(
*(_QWORD *)(v24 + 24),
0LL,
0LL);
if ( !v5 )
break;
}
++v21;
v23 += 8;
}
while ( 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:
char __fastcall CBasePlayerPawn_CommitSuicide(float *a1, unsigned __int8 a2, char a3)
{
__int64 v4;
char result;
_BYTE v7[112];
__int64 v8;
int v9;
char v10;
v4 = a2;
result = (*(__int64 (__fastcall **)(float *))(*(_QWORD *)a1 + 1336LL))(a1);
if ( result )
{
sub_XXX(&v9, *(_DWORD *)(*((_QWORD *)a1 + 2) + 56LL));
result = sub_XXX(a1 + 824, (float *)&v9);
if ( !result || a3 )
{
sub_XXX(&v9, *(_DWORD *)(*((_QWORD *)a1 + 2) + 56LL));
a1[824] = *(float *)sub_XXX(&v10, &v9);
sub_XXX((unsigned int)v7, (_DWORD)a1, (_DWORD)a1, 0, 1065353216, (_DWORD)v4 << 6, 0);
v8 |= (32 * (v4 ^ 1) + 32) | 0x116;
sub_XXX(a1, (__int64)v7, 0LL);
return sub_XXX((__int64)v7);
}
}
result;
}
-
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