Addresses

ISource2ModAddresses and the ADDR_* macros — calling and hooking the engine functions Source2Mod resolves, SOURCE2MOD_ORIGINAL, and HookChain on schema helpers.

Source2Mod scans a set of game functions once at startup and hands them out, typed, through ISource2ModAddresses. Each one has a shorthand macro, ADDR_*(), that returns a function pointer ready to call — or to hook.

#include "source2mod/ISource2ModAddresses.h"

if (auto pfnSwitchTeam = ADDR_SWITCH_TEAM())
    pfnSwitchTeam(pController, 3);

The macros read g_pSource2ModAddresses, which SOURCE2MOD_SAVEVARS() fills in Load() (and SOURCE2MOD_GLOBALVARS() declares in your other files). Module inspection, pattern scanning and pointer arithmetic are not here — they are on ISource2ModModule and ISource2ModMemory. Reference: ISource2ModAddresses.

Three ways to reach a game function

you want to…use
do something the SDK already wraps (give an item, switch team, end the round)the schema helper — pItemServices->GiveNamedItem("weapon_ak47")
call a resolved function the helpers do not wrap, or with arguments they do not exposeADDR_*()
call it past every plugin's hooksSOURCE2MOD_ORIGINAL(ADDR_*()), or the helper with HookChain::Bypass
hook itKHOOK_MEMBER(m_hX, ADDR_*(), ...) — see Function hooks
reach a function Source2Mod does not resolveyour own gamedata entry and GAMECONFIG_RESOLVE(name) — see Gamedata

The sample plugin shows the first two side by side:

plugin.cpp
// Way one: Source2Mod resolved it and knows the type.
if (auto pfnSwitchTeam = ADDR_SWITCH_TEAM())
{
    pfnSwitchTeam(pCaller, nNewTeam);
    return;
}

// Way two: any gamedata entry, with the type spelled out by you.
using SwitchTeam_t = void (FASTCALL *)(CCSPlayerController *, unsigned char);

if (auto pfnSwitchTeam = GAMECONFIG_RESOLVE("CCSPlayerController::SwitchTeam").RCast<SwitchTeam_t>())
    pfnSwitchTeam(pCaller, nNewTeam);

// Way three: pCaller->SwitchTeam(nNewTeam) does exactly the above.

Calling past the hooks

An ADDR_* macro hands back the address the signature scan found. That is what you want to hook, but not always what you want to call: once any plugin inline-hooks the function, its first bytes are a jump into the detour, so calling it runs the whole Pre/Post chain.

That is right when you stand in for the game — a plugin dealing damage as if a weapon fired wants the other plugins' rules to apply. It is wrong when you are asking for the function itself — an admin command, a forced round end — where another plugin's rules have no business vetoing what the server was told to do. For that, wrap the call in SOURCE2MOD_ORIGINAL:

SOURCE2MOD_ORIGINAL(ADDR_TAKE_DAMAGE_OLD())(pPawn, &info, &result);

SOURCE2MOD_ORIGINAL(fn) casts KHook::FindOriginal(fn) back to fn's type. It is signature-blind, so it works whatever prototype you hold and however the plugin that hooked the function declared its hook. Unhooked functions come back unchanged, so it is always safe to apply.

Resolve it at the call; do not cache the result in a global. The address is only the original from the moment a hook exists, and hooks are installed long after these addresses are scanned.

HookChain on schema helpers

The schema helpers make the same choice through their last argument, a HookChain (ISource2ModTypes.h):

valuethe call goes
HookChain::Runthrough the Pre/Post chain, as if the game made the call
HookChain::Bypassstraight to the original, past every hook — inline detours and vtable hooks alike

The default of each helper is what it always did, so existing calls keep their behaviour. CCSGameRules::TerminateRound and CCSPlayerController::TakeDamage default to Bypass (the server was told to end the round or deal the damage); every other helper defaults to Run.

pItemServices->GiveNamedItem("weapon_ak47");                        // hooks run
pController->TakeDamage(pAttacker, 50, DMG_BULLET, HookChain::Run); // opt in to the hooks
pGameRules->TerminateRound(5.0f, nReason);                          // bypasses by default

Helpers that take a HookChain:

classhelpers
CBaseEntityAcceptInput, AddEntityIOEvent, DispatchSpawn, Teleport, Remove, DispatchParticleEffect
CBaseModelEntitySetModel
CBasePlayerControllerSetPawn
CBasePlayerPawnCommitSuicide, SnapViewAngles
CCSPlayerPawnPostThink
CCSPlayerControllerTakeDamage (Bypass), Respawn, SwitchTeam, ChangeTeam
CCSGameRulesTerminateRound (Bypass), GoToIntermission
CPlayer_ItemServicesGiveNamedItem, DropActivePlayerWeapon, RemoveWeapons
CPlayer_WeaponServicesDropWeapon, BumpWeapon, SelectItem, Destroy
CAttributeListSetOrAddAttribute, SetOrAddAttributeValueByName
grenade projectilesCHEGrenadeProjectile, CFlashbangProjectile, CSmokeGrenadeProjectile, CDecoyProjectile, CMolotovProjectile — EmitGrenade

When to pick which

  • Standing in for the game (a custom weapon dealing damage, a round reward handing out an item): Run. Other plugins' damage rules, item restrictions and so on should apply.
  • Doing what the server was told (admin command, forced round end, a gamemode's own rule): Bypass.
  • Inside a hook on the same function: Bypass, always — Run would re-enter your own handler. See Recursion.

For your own calls through a vtable index, CALL_VIRTUAL_CHAIN(retType, index, eChain, this, args...) (utils/virtual.h) does the same for virtual functions; ResolveHookChain(fn, eChain) does it for a function pointer.

The ADDR_* macros

Every macro calls the ISource2ModAddresses method of the same name and returns the typedef declared above it in ISource2ModAddresses.h — read that typedef before you call or hook the function, it carries the checked prototype and the platform differences.

Required functions must resolve for Source2Mod to start, so they are never null while it runs. Optional ones are plugin-only hook targets resolved leniently: a pattern that stops matching after a game update leaves them null instead of blocking startup — check before you call or hook them (a hook on a null target is logged and skipped).

Entities and players (required)

macrofunctiongamedata key
ADDR_CREATE_ENTITY_BY_NAME()CreateEntityByNameUTIL::CreateEntityByName
ADDR_DISPATCH_SPAWN()CBaseEntity::DispatchSpawnCBaseEntity::DispatchSpawn
ADDR_TAKE_DAMAGE_OLD()CBaseEntity::TakeDamageOldCBaseEntity::TakeDamageOld
ADDR_TAKE_DAMAGE_INFO()CTakeDamageInfo constructorCTakeDamageInfo::Constructor
ADDR_SET_MODEL()CBaseModelEntity::SetModelCBaseModelEntity::SetModel
ADDR_SET_PAWN()CBasePlayerController::SetPawnCBasePlayerController::SetPawn
ADDR_SNAP_VIEW_ANGLES()CBasePlayerPawn::SnapViewAnglesCBasePlayerPawn::SnapViewAngles
ADDR_SWITCH_TEAM()CCSPlayerController::SwitchTeamCCSPlayerController::SwitchTeam
ADDR_WEAPON_SERVICES_DESTROY()CPlayer_WeaponServices::DestroyCCSPlayer_WeaponServices::Destroy
ADDR_GIVE_NAMED_ITEM()CCSPlayer_ItemServices::GiveNamedItem (the full implementation)CCSPlayer_ItemServices::GiveNamedItem
ADDR_POST_THINK()CCSPlayerPawn::PostThinkCCSPlayerPawn::PostThink
ADDR_CAN_MOVE()CCSPlayerPawnBase::CanMoveCCSPlayerPawnBase::CanMove
ADDR_UTIL_REMOVE()UTIL_RemoveUTIL::Remove
ADDR_SET_OR_ADD_ATTRIBUTE()CAttributeList::SetOrAddAttributeValueByNameCAttributeList::SetOrAddAttributeValueByName
ADDR_GET_WEAPON_CS_DATA_FROM_KEY()GetWeaponCSDataFromKeyGetWeaponCSDataFromKey
ADDR_DISPATCH_PARTICLE_EFFECT()DispatchParticleEffectDispatchParticleEffect
ADDR_EMIT_HEGRENADE()CHEGrenadeProjectile::EmitGrenadeCHEGrenadeProjectile::EmitGrenade
ADDR_EMIT_FLASHBANG()CFlashbangProjectile::EmitGrenadeCFlashbangProjectile::EmitGrenade
ADDR_EMIT_SMOKE()CSmokeGrenadeProjectile::EmitGrenadeCSmokeGrenadeProjectile::EmitGrenade
ADDR_EMIT_DECOY()CDecoyProjectile::EmitGrenadeCDecoyProjectile::EmitGrenade
ADDR_EMIT_MOLOTOV()CMolotovProjectile::EmitGrenadeCMolotovProjectile::EmitGrenade

Game rules, entity I/O and networking (required)

macrofunctiongamedata key
ADDR_TERMINATE_ROUND()CCSGameRules::TerminateRound — argument order differs per platformCCSGameRules::TerminateRound
ADDR_ACCEPT_INPUT()CEntityInstance::AcceptInputCEntityInstance::AcceptInput
ADDR_FIRE_OUTPUT_INTERNAL()CEntityIOOutput::FireOutputInternalCEntityIOOutput::FireOutputInternal
ADDR_ADD_ENTITY_IO_EVENT()CEntitySystem::AddEntityIOEventCEntitySystem::AddEntityIOEvent
ADDR_FIND_ENTITY_BY_CLASSNAME()CGameEntitySystem::FindEntityByClassNameCGameEntitySystem::FindEntityByClassName
ADDR_FIND_ENTITY_BY_NAME()CGameEntitySystem::FindEntityByNameCGameEntitySystem::FindEntityByName
ADDR_LEGACY_GAME_EVENT_LISTENER()LegacyGameEventListenerLegacyGameEventListener
ADDR_FILTER_MESSAGE()INetworkMessageProcessingPreFilter::FilterMessageINetworkMessageProcessingPreFilter::FilterMessage

Optional — may be null

macrofunction
ADDR_IDENTITY_ACCEPT_INPUT()CEntityIdentity::AcceptInput
ADDR_CAN_ACQUIRE()CCSPlayer_ItemServices::CanAcquire
ADDR_PROCESS_USER_CMD()CCSPlayerController::ProcessUserCmd
ADDR_ON_SIMULATE_USER_COMMANDS()CBasePlayerController::OnSimulateUserCommands
ADDR_GET_NAV_MESH_DATA()CSource2Server::GetNavMeshData
ADDR_LOG_DIRECT()CLoggingSystem::LogDirect (tier0)
ADDR_CMD_EXECUTE_COMMAND()Cmd_ExecuteCommand (engine2)

Movement (CCSPlayer_MovementServices::*, all optional):

macrofunction
ADDR_AIR_ACCELERATE()AirAccelerate
ADDR_AIR_MOVE()AirMove
ADDR_CAN_UNDUCK()CanUnduck
ADDR_CATEGORIZE_POSITION()CategorizePosition
ADDR_CHECK_FALLING()CheckFalling
ADDR_CHECK_PARAMETERS()CheckParameters
ADDR_CHECK_VELOCITY()CheckVelocity
ADDR_CHECK_WATER()CheckWater
ADDR_DUCK()Duck
ADDR_FRICTION()Friction
ADDR_FULL_WALK_MOVE()FullWalkMove
ADDR_GROUND_ACCELERATE()GroundAccelerate — argument order differs per platform
ADDR_LADDER_MOVE()LadderMove
ADDR_MOVE_INIT()MoveInit
ADDR_PLAYER_MOVE()PlayerMove
ADDR_PROCESS_MOVEMENT()ProcessMovement
ADDR_SETUP_MOVE()SetupMove
ADDR_TRY_PLAYER_MOVE()TryPlayerMove
ADDR_WALK_MOVE()WalkMove
ADDR_WATER_MOVE()WaterMove

Jumping (members of the jump components, not of the movement services — all optional):

macrofunction
ADDR_ON_JUMP_LEGACY()CCSPlayerLegacyJump::OnJump
ADDR_ON_JUMP_MODERN()CCSPlayerModernJump::OnJump
ADDR_CHECK_JUMP_BUTTON_LEGACY()CCSPlayerLegacyJump::CheckJumpButton
ADDR_CHECK_JUMP_BUTTON_MODERN()CCSPlayerModernJump::CheckJumpButton

The gamedata key of each optional function is its function name as listed (CCSPlayer_MovementServices::AirAccelerate, CCSPlayerLegacyJump::OnJump, …).

Hooks.h
// Optional target: if the signature broke, KHOOK_INIT() logs it and skips this hook.
KHook::Return<void> OnCheckJumpButtonLegacy(CCSPlayerLegacyJump* pThis, CMoveData* pMove);

KHOOK_MEMBER(m_hCheckJumpButtonLegacy, ADDR_CHECK_JUMP_BUTTON_LEGACY(), &CHooks::OnCheckJumpButtonLegacy, nullptr);

On this page