Difference between revisions of "SDKTools (SourceMod Scripting)"

From AlliedModders Wiki
Jump to: navigation, search
(initial import)
 
(Add CRC32 section specialization)
 
(48 intermediate revisions by 11 users not shown)
Line 1: Line 1:
 
The SDKTools extension is an extension for various functions provided by the Source SDK.  Additionally, it has the ability to dynamically call many C++ functions in the SDK, for example, from CBaseEntity or any derived class.
 
The SDKTools extension is an extension for various functions provided by the Source SDK.  Additionally, it has the ability to dynamically call many C++ functions in the SDK, for example, from CBaseEntity or any derived class.
 +
 +
SDKTools contains simplified versions of SDK functions pre-written for ease of use.  These can be found in the additional include files:
 +
*<tt>sdktools_functions.inc</tt> - Popular functions from the SDK.
 +
*<tt>sdktools_sound.inc</tt> - [[#Sound Functions|Sound-related functions]].
 +
*<tt>sdktools_tempents.inc</tt> - [[#TempEnt Functions|TempEnt-related functions]].
 +
*<tt>sdktools_tempents_stocks.inc</tt> - [[TempEnts (SourceMod SDKTools)|TempEnt Stocks for convenience]].
 +
 +
SDKTools is powered by "BinTools," a powerful extension for creating dynamic C/C++ calls.  BinTools is automatically loaded by SDKTools.
  
 
=GameConfigs=
 
=GameConfigs=
 +
GameConfig files go into the "gamedata" folder under "sourcemod."  In older revisions it was under "sourcemod/configs" which is now deprecated.
 +
 
==Virtual Offsets==
 
==Virtual Offsets==
 
For adding virtual offsets, add sub-sections to the "Offsets" section of your game config file.  For example:
 
For adding virtual offsets, add sub-sections to the "Offsets" section of your game config file.  For example:
Line 8: Line 18:
 
"Games"
 
"Games"
 
{
 
{
"cstrike"
+
  "cstrike"
{
+
  {
"Offsets"
+
    "Offsets"
{
+
    {
"GiveNamedItem"
+
      "GiveNamedItem"
{
+
      {
"windows" "329"
+
        "windows" "329"
"linux" "330"
+
        "linux"   "330"
}
+
      }
}
+
      "EyePosition"
}
+
      {
 +
        "windows" "117"
 +
        "linux"  "118"
 +
      }
 +
 
 +
      // Get offset of CPlayerResource::m_iBotDifficulty sendprop.
 +
      // Dynamic way for FindSendPropInfo(class, prop);
 +
      "BotDifficulty"
 +
      {
 +
        "class" "CPlayerResource"
 +
        "prop"    "m_iBotDifficulty"
 +
      }
 +
    }
 +
  }
 
}</pre>
 
}</pre>
  
 
==Signature Scans==
 
==Signature Scans==
For automated signature scans, add sub-sections to the "Signatures" section of your game config file.  There are three properties for a signature:
+
For automated signature scans, add sub-sections to the "Signatures" section of your game config file.  These are the properties for a signature:
*<tt>library</tt>: Must be "server" right now.
+
*<tt>library</tt>: The library to search for the pattern or symbol for: "server", "engine" or "matchmaking_ds" (defaults to "server").
*<tt>windows</tt>: A signature written in binary; for example, "\x56" instead of "0x56" -- the '*' character is a single byte wildcard.
+
*<tt>windows</tt>, <tt>linux</tt> or <tt>mac</tt>: A pattern or symbol for that platform:
*<tt>linux</tt>: Either a signature as with <tt>windows</tt>, or a named symbol.  To use a named symbol, start the string with the '@' character.  If a signature begins with '@', write the character in hexadecimal as above.
+
 
 +
You can put a pattern to search for in memory or a symbol name to lookup:
 +
*A signature written in binary; for example, "\x56" instead of "0x56" -- the '*' (\x2A) character is a single byte wildcard.
 +
*A named symbol.  To use a named symbol, start the string with the '@' character.  If a signature begins with '@', write the character in hexadecimal as above.
 +
 
 +
Example:
 +
<pre>
 +
"Games"
 +
{
 +
  "cstrike"
 +
  {
 +
    "Signatures"
 +
    {
 +
      "RoundRespawn"
 +
      {
 +
        "library" "server"
 +
        "windows" "\x56\x8B\xF1\x8B\x06\xFF\x90*\x04\x00\x00\x8B\x86*\x0D\x00"
 +
        "linux"  "@_ZN9CCSPlayer12RoundRespawnEv"
 +
      }
 +
    }
 +
  }
 +
}</pre>
 +
 
 +
==Raw address lookups==
 +
Lets you peek and poke (limitation may apply) the Source Dedicated Server memory space, and get a raw address value in an automated way.
 +
 
 +
Example:
 +
<pre>
 +
"Games"
 +
{
 +
  "left4dead2"
 +
  {
 +
    "Addresses"
 +
    {
 +
      "CDirector"
 +
      {
 +
        "windows"
 +
        {
 +
          "signature" "DirectorMusicBanks_OnRoundStart"
 +
          "read" "8"
 +
        }
 +
        "linux"
 +
        {
 +
          "signature" "TheDirector"
 +
        }
 +
        "read" "0"
 +
        // Since SourceMod 1.9
 +
        "offset" "4"
 +
      }
 +
    }
 +
    "Signatures"
 +
    {
 +
      /* Used solely to get the offset for TheDirector */
 +
      "DirectorMusicBanks_OnRoundStart"
 +
      {
 +
        "library" "server"
 +
        "windows"      "\x83\xEC\x14\x57\x8B\xF9\x8B\x0D\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\x84\xC0\x0F\x2A\x2A\x2A\x2A\x2A\x53\x55\x6A\x24\xE8"
 +
        /* 83 EC 14 57 8B F9 8B 0D ? ? ? ? E8 ? ? ? ? 84 C0 0F ? ? ? ? ? 53 55 6A 24 E8 */
 +
      }
 +
     
 +
      /* Find the Director/ZombieManager singleton classes */
 +
     
 +
      "TheDirector"
 +
      {
 +
        "library" "server"
 +
        "linux"  "@TheDirector"
 +
      }
 +
    }
 +
  }
 +
}
 +
</pre>
 +
 
 +
The data in an Address entry is parse from top to bottom as commands. This example would accomplish the same thing as the following pseudo-C++ code:
 +
 
 +
<pre>
 +
BYTE * GetCDirectorAddress()
 +
{
 +
    BYTE * pAddr;
 +
 
 +
#if WINDOWS
 +
 
 +
    pAddr = FindSignature("DirectorMusicBanks_OnRoundStart");
 +
    pAddr = *(pAddr + 8);
 +
 
 +
#elif LINUX
 +
 
 +
    pAddr = FindSignature("TheDirector");
 +
 
 +
#endif
 +
 
 +
    pAddr = *(pAddr + 0);
 +
    // Since SourceMod 1.9
 +
    pAddr = pAddr + 4;
 +
 
 +
    return pAddr;
 +
}</pre>
 +
 
 +
To invoke this address lookup in your Sourcepawn code, you would simply set up your GameConf handle and run something like the following:
 +
 
 +
<pre>
 +
stock Address:GetTheDirector()
 +
{
 +
  return GameConfGetAddress(g_hMyGameConf, "CDirector");
 +
}</pre>
 +
 
 +
For more code examples, [https://github.com/confoglteam/l4d2_direct l4d2_direct] relies heavily on Address: functions.
 +
 
 +
==Custom Key Values==
 +
To read arbitary key value pairs from a gamedata file, add them to the "Keys" section of your game config file.
 +
You can have different values per platform or have a key only be available on selected platforms.
  
 
Example:
 
Example:
Line 31: Line 163:
 
"Games"
 
"Games"
 
{
 
{
"cstrike"
+
  "cstrike"
{
+
  {
"Signatures"
+
    "Keys"
{
+
    {
"RoundRespawn"
+
      "INTERFACEVERSION_GAMEEVENTSMANAGER2" "GAMEEVENTSMANAGER002"
{
+
 
"library" "server"
+
      // Different values per platform (since SourceMod 1.10+)
"windows" "\x56\x8B\xF1\x8B\x06\xFF\x90*\x04\x00\x00\x8B\x86*\x0D\x00"
+
      "MyGreatKey"
"linux" "@_ZN9CCSPlayer12RoundRespawnEv"
+
      {
}
+
        "windows" "this is wundows speaking"
}
+
        "linux"   "linux best linux"
}
+
        "mac"   "wat"
 +
      }
 +
 
 +
      "JustForLinux"
 +
      {
 +
        "linux"   "not relevant for other platforms!"
 +
      }
 +
 
 +
    }
 +
  }
 
}</pre>
 
}</pre>
 +
 +
You can access the keys using
 +
<pre>
 +
char buffer[512];
 +
GameConfGetKeyValue(g_hMyGameConf, "MyGreatKey", buffer, sizeof(buffer));
 +
</pre>
  
 
==Inheritance==
 
==Inheritance==
If you wish for multiple mods to inherit from one block, you can add a special "#supported" section under a "#default" section.  For example:
+
If you wish for multiple mods to inherit from one block, you can add a special "#supported" section as the first section under a "#default" section.  For example:
  
 
<pre>
 
<pre>
 
"Games"
 
"Games"
 
{
 
{
"#default"
+
  "#default"
{
+
  {
"#supported"
+
    "#supported"
{
+
    {
"game" "dod"
+
      "game"   "dod"
}
+
      "engine"    "sdk2013"
}
+
    }
 +
 
 +
    "Signatures"
 +
    {
 +
      // ...
 +
    }
 +
  }
 
}</pre>
 
}</pre>
  
This specifies that the given block will be read for Day of Defeat as well.
+
This specifies that the given block will be read for Day of Defeat as well as any game on the Source 2013 engine branch. You can add multiple "game" or "engine" keys under the "#supported" section to list all mods or engines this section should apply to.
 +
 
 +
==Specializing per game version based on CRC32 value==
 +
If a game has multiple stable versions, you can select to apply a gamedata section to a specific game version. You can add a special "CRC" section as the first section under a "#default" section, which allows you to list the hex-encoded CRC32 values of the game's server binary that the section should apply to.
 +
 
 +
<pre>
 +
"Games"
 +
{
 +
  "#default"
 +
  {
 +
    "CRC"
 +
    {
 +
      "server"
 +
      {
 +
        "linux"  "186F31F8"
 +
        "windows" "D41B675F"
 +
      }
 +
    }
 +
   
 +
    "Signatures"
 +
    {
 +
      // ...
 +
    }
 +
  }
 +
}</pre>
  
 +
The section would only apply if the game's server binary's CRC32 hash matches the given value for the current platform. At the moment, only the "server" binary is supported. You can add multiple "#default" and "CRC" section combinations to cover different game versions.
  
 
=Calling Functions=
 
=Calling Functions=
Calling functions is complicated and requires strict attention to the C++ API you are trying to use.  If you make a mistake, something bad '''will''' happen (and if not, you got lucky!)  SDKTools is more complicated than PimpinJuice's "Hacks" extension because it precompiles all of the information needed to convert from Plugin memory to C++ types.  This precompilation step ensures better safety, but it lengthens the preparation process.
+
Calling functions is complicated and requires strict attention to the C++ API you are trying to use.  If you make a mistake, something bad '''will''' happen (and if not, you got lucky!)  SDKTools is more complicated than PimpinJuice's "Hacks" extension because it precompiles all of the information needed to convert from Plugin memory to C++ types.  This precompilation step ensures better safety and speed, but it lengthens the preparation process.
  
 
With that said, let's look at an overview of the call creation process.  Before making calls, we must ''prepare'' the call.  This will give us a <tt>Handle</tt> which will let us make as many calls as we like.
 
With that said, let's look at an overview of the call creation process.  Before making calls, we must ''prepare'' the call.  This will give us a <tt>Handle</tt> which will let us make as many calls as we like.
Line 78: Line 256:
  
 
==RoundRespawn Example==
 
==RoundRespawn Example==
CCSPlayer::RestartRound is a Counter-Strike Source function which respawns players.  It used by CS:S DM.  Prototype:
+
CCSPlayer::RoundRespawn is a Counter-Strike Source function which respawns players.  It used by CS:S DM.  Prototype:
 
<pre>void CCSPlayer::RoundRespawn()</pre>
 
<pre>void CCSPlayer::RoundRespawn()</pre>
  
 
SDKCall example:
 
SDKCall example:
<pawn>#include <sourcemod>
+
<sourcepawn>#include <sourcemod>
 
#include <sdktools>
 
#include <sdktools>
  
new Handle:hGameConf;
+
Handle hGameConf;
new Handle:hRoundRespawn;
+
Handle hRoundRespawn;
  
public OnPluginInit()
+
public void OnPluginStart()
 
{
 
{
hGameConf = LoadGameConfigFile("plugin.sdkexamples");
+
  hGameConf = LoadGameConfigFile("plugin.sdkexamples");
StartPrepSDKCall(SDKCall_Player);
+
  StartPrepSDKCall(SDKCall_Player);
PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "RoundRespawn");
+
  PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "RoundRespawn");
hRoundRespawn = EndPropSDKCall();
+
  hRoundRespawn = EndPrepSDKCall();
 
}
 
}
  
RespawnPlayer(client)
+
void RespawnPlayer(int client)
 
{
 
{
SDKCall(hRoundRespawn, client);
+
  SDKCall(hRoundRespawn, client);
}</pawn>
+
}</sourcepawn>
  
 
Note that we did not have to set any extra information for parameters or return info, since CCSPlayer::RoundRespawn has no parameters and has a void return.
 
Note that we did not have to set any extra information for parameters or return info, since CCSPlayer::RoundRespawn has no parameters and has a void return.
Line 107: Line 285:
 
<pre>virtual CBaseEntity *CBasePlayer::GiveNamedItem(const char *item, int iSubType = 0);</pre>
 
<pre>virtual CBaseEntity *CBasePlayer::GiveNamedItem(const char *item, int iSubType = 0);</pre>
  
<pawn>#include <sourcemod>
+
<sourcepawn>#include <sourcemod>
 +
#include <sdktools>
 +
 
 +
Handle hGameConf;
 +
Handle hGiveNamedItem;
 +
 
 +
public OnPluginStart()
 +
{
 +
  hGameConf = LoadGameConfigFile("plugin.sdkexamples");
 +
 
 +
  StartPrepSDKCall(SDKCall_Player);
 +
  PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, "GiveNamedItem");
 +
  PrepSDKCall_SetReturnInfo(SDKType_CBaseEntity, SDKPass_Pointer);
 +
  PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer);
 +
  PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
 +
  hGiveNamedItem = EndPrepSDKCall();
 +
}
 +
 
 +
any GiveItem(int client, const char[] item)
 +
{
 +
  return SDKCall(hGiveNamedItem, client, item, 0);
 +
}</sourcepawn>
 +
 
 +
==GetEyePosition Example==
 +
Prototype:
 +
<pre>virtual Vector CBaseEntity::EyePosition();</pre>
 +
 
 +
<sourcepawn>#include <sourcemod>
 
#include <sdktools>
 
#include <sdktools>
  
new Handle:hGameConf;
+
Handle hGameConf;
new Handle:hGiveNamedItem;
+
Handle hGetEyePosition;
  
public OnPluginInit()
+
public void OnPluginStart()
 
{
 
{
hGameConf = LoadGameConfigFile("plugin.sdkexamples");
+
  hGameConf = LoadGameConfigFile("plugin.sdkexamples");
  
StartPrepSDKCall(SDKCall_Player);
+
  StartPrepSDKCall(SDKCall_Player);
PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, "GiveNamedItem");
+
  PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, "EyePosition");
PrepSDKCall_SetReturnInfo(SDKType_CBaseEntity, SDKPass_Pointer);
+
  PrepSDKCall_SetReturnInfo(SDKType_Vector, SDKPass_ByValue);
PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer);
+
  hGetEyePosition= EndPrepSDKCall();
PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
 
hGiveNamedItem = EndPropSDKCall();
 
 
}
 
}
  
GiveItem(client, const String:item[])
+
any GetEyePosition(int client, float pos[3])
 +
{
 +
  SDKCall(hGetEyePosition, client, pos);
 +
}</sourcepawn>
 +
 
 +
Note that the vector is returned as the third parameter (first parameter after the <tt>this</tt> pointer).
 +
 
 +
=TempEnt Functions=
 +
TempEnts, also called "temporary entities," "tempentities," or "TEs," are quick graphical displays that are too simple to justify the overhead of a <tt>CBaseEntity</tt> instantiation or full server-side networkability.  In the SDK, they are statically defined instances that can be "played back" to the client by simply sending a copy of its network class properties.
 +
 
 +
Temporary Entities are mod-dependent, in that a mod can change any of their properties or even the <tt>ITempEnts</tt> class definition.  Because of this, SourceMod takes a very flexible approach:
 +
*<b>First</b>, a temp entity is ''started'' by name using <tt>TE_Start</tt>.  A list of all tempent names and their network classes can be dumped with <tt>sm_print_telist</tt> (SDKTools must be loaded).
 +
*<b>Second</b>, all of the tempent's properties are set via the <tt>TE_Write</tt> natives.  All tempent properties can be dumped to a file using <tt>sm_dump_teprops</tt>.
 +
*<b>Third</b>, the properties are transmitted using <tt>TE_Send</tt>, <tt>TE_SendToAll</tt>, or <tt>TE_SendToClient</tt>.
 +
 
 +
As a convenience, SourceMod provides a number of pre-written stocks for SDK-defined temporary entities.  While the <tt>TE_*</tt> natives are guaranteed to work on any fully-supported SourceMod mod, the <tt>TE_Setup*</tt> stocks are not, as a mod might change the property layouts or the functionalities of the SDK-defined tempents.
 +
 
 +
Some examples are below.  For pictures, see [[TempEnts (SourceMod SDKTools)]].
 +
 
 +
Using stocks:
 +
<sourcepawn>void SparkClient(int client, const float pos[3], const float dir[3])
 
{
 
{
SDKCall(hGiveNamedItem, client, item, 0);
+
  TE_SetupMetalSparks(pos, dir);
}</pawn>
+
  TE_SendToClient(client);
 +
}</sourcepawn>
 +
 
 +
Not using stocks:
 +
<sourcepawn>void SparkClient(int client, const float pos[3], const float dir[3])
 +
{
 +
  TE_Start("Metal Sparks");
 +
  TE_WriteVector("m_vecPos", pos);
 +
  TE_WriteVector("m_vecDir", dir);
 +
  TE_SendToClient(client);
 +
}</sourcepawn>
 +
 
 +
=Sound Functions=
 +
SDKTools exposes various sound-related members of IVEngineServer and IEngineSound.  The basic functions are in <tt>sdktools_sound.inc</tt>:
 +
*<tt>EmitAmbientSound</tt>: Plays an ambient sound from an origin.
 +
*<tt>EmitSound, EmitSoundToClient, EmitSoundToAll</tt>: Plays a sound to a list of clients from a specific entity.
 +
 
 +
There are a few important properties about sounds:
 +
*<b>Entity</b>: The entity the sound should come from.  When playing a generic sound file to player(s), you should use <tt>SOUND_FROM_PLAYER</tt>, which is a special SourceMod macro for making the sound come from the player target.  For ambient sounds you'd use <tt>SOUND_FROM_WORLD</tt>, and for sounds from an entity you'd use the entity index.
 +
*<b>Channel</b>: Exposed as <tt>SNDCHAN_*</tt>, normally you only need to use <tt>SNDCHAN_AUTO</tt>.  The channel affects how the sound is spatialized[?]
 +
*<b>Level</b>: The decibel level.  There are a number of predefined levels as <tt>SNDLEVEL_*</tt> -- the standard one is <tt>SNDLEVEL_NORMAL</tt>.  Half-Life 1 used attenuation values instead; you can convert from these using <tt>ATTN_TO_SNDLEVEL()</tt>.
 +
*<b>Volume</b>: The volume, on a scale of 0.0 to 1.0.  To use a volume other than the default, the <tt>SND_CHANGEVOL</tt> flag should be included in the "flags" parameter.  The default volume is <tt>SNDVOL_NORMAL</tt>.
 +
*<b>Pitch</b>: The pitch; predefined values are <tt>SNDPITCH_*</tt> with <tt>SNDPITCH_NORMAL</tt> being the default.  <tt>SND_CHANGEPITCH</tt> should be passed to use a non-default pitch.
 +
*<b>Origin</b>: If specified, an origin to play the sound from.
 +
 
 +
Some very simple examples:
 +
<sourcepawn>
 +
EmitSoundToClient(client, "bot/affirmative.wav");
 +
EmitSoundToAll("radio/terwin.wav");
 +
</sourcepawn>
 +
 
 +
=Supported Mods=
 +
SDKTools must have specific support added for each mod.  So far, default support exists for the following modifications:
 +
*Counter-Strike: Source
 +
*Day of Defeat: Source
 +
*Dystopia
 +
*Half-Life 2: Deathmatch
 +
*Insurgency (only some of the CBasePlayer SDK functions are available)
 +
*Pirates, Vikings, and Knights II
 +
*The Ship
 +
*SourceForts
  
 +
=See Also=
 +
*[[Vfunc_offsets_%28SourceMM%29|Virtual Functions]]
 +
*[[TempEnts (SourceMod SDKTools)|TempEnts]]
 +
*[[Useful_Signatures_%28Source%29|Useful Signatures]]
 +
*[[Signature_Scanning|Signature Scanning]]
  
 
[[Category:SourceMod Scripting]]
 
[[Category:SourceMod Scripting]]
[[Category:SourceMod Development]]
 

Latest revision as of 11:33, 28 October 2021

The SDKTools extension is an extension for various functions provided by the Source SDK. Additionally, it has the ability to dynamically call many C++ functions in the SDK, for example, from CBaseEntity or any derived class.

SDKTools contains simplified versions of SDK functions pre-written for ease of use. These can be found in the additional include files:

SDKTools is powered by "BinTools," a powerful extension for creating dynamic C/C++ calls. BinTools is automatically loaded by SDKTools.

GameConfigs

GameConfig files go into the "gamedata" folder under "sourcemod." In older revisions it was under "sourcemod/configs" which is now deprecated.

Virtual Offsets

For adding virtual offsets, add sub-sections to the "Offsets" section of your game config file. For example:

"Games"
{
  "cstrike"
  {
    "Offsets"
    {
      "GiveNamedItem"
      {
        "windows" "329"
        "linux"   "330"
      }
      "EyePosition"
      {
        "windows" "117"
        "linux"   "118"
      }

      // Get offset of CPlayerResource::m_iBotDifficulty sendprop.
      // Dynamic way for FindSendPropInfo(class, prop);
      "BotDifficulty"
      {
        "class" "CPlayerResource"
        "prop"    "m_iBotDifficulty"
      }
    }
  }
}

Signature Scans

For automated signature scans, add sub-sections to the "Signatures" section of your game config file. These are the properties for a signature:

  • library: The library to search for the pattern or symbol for: "server", "engine" or "matchmaking_ds" (defaults to "server").
  • windows, linux or mac: A pattern or symbol for that platform:

You can put a pattern to search for in memory or a symbol name to lookup:

  • A signature written in binary; for example, "\x56" instead of "0x56" -- the '*' (\x2A) character is a single byte wildcard.
  • A named symbol. To use a named symbol, start the string with the '@' character. If a signature begins with '@', write the character in hexadecimal as above.

Example:

"Games"
{
  "cstrike"
  {
    "Signatures"
    {
      "RoundRespawn"
      {
        "library" "server"
        "windows" "\x56\x8B\xF1\x8B\x06\xFF\x90*\x04\x00\x00\x8B\x86*\x0D\x00"
        "linux"   "@_ZN9CCSPlayer12RoundRespawnEv"
      }
    }
  }
}

Raw address lookups

Lets you peek and poke (limitation may apply) the Source Dedicated Server memory space, and get a raw address value in an automated way.

Example:

"Games"
{
  "left4dead2"
  {
    "Addresses"
    {
      "CDirector"
      {
        "windows"
        {
          "signature" "DirectorMusicBanks_OnRoundStart"
          "read" "8"
        }
        "linux"
        {
          "signature" "TheDirector"
        }
        "read" "0"
        // Since SourceMod 1.9
        "offset" "4"
      }
    }
    "Signatures"
    {
      /* Used solely to get the offset for TheDirector */
      "DirectorMusicBanks_OnRoundStart"
      {
        "library" "server"
        "windows"       "\x83\xEC\x14\x57\x8B\xF9\x8B\x0D\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\x84\xC0\x0F\x2A\x2A\x2A\x2A\x2A\x53\x55\x6A\x24\xE8"
        /* 83 EC 14 57 8B F9 8B 0D ? ? ? ? E8 ? ? ? ? 84 C0 0F ? ? ? ? ? 53 55 6A 24 E8 */
      }
      
      /* Find the Director/ZombieManager singleton classes */
      
      "TheDirector"
      {
        "library" "server"
        "linux"   "@TheDirector"
      }
    }
  }
}

The data in an Address entry is parse from top to bottom as commands. This example would accomplish the same thing as the following pseudo-C++ code:

BYTE * GetCDirectorAddress()
{
    BYTE * pAddr;

#if WINDOWS

    pAddr = FindSignature("DirectorMusicBanks_OnRoundStart");
    pAddr = *(pAddr + 8);

#elif LINUX

    pAddr = FindSignature("TheDirector");

#endif

    pAddr = *(pAddr + 0);
    // Since SourceMod 1.9
    pAddr = pAddr + 4;

    return pAddr;
}

To invoke this address lookup in your Sourcepawn code, you would simply set up your GameConf handle and run something like the following:

stock Address:GetTheDirector()
{
   return GameConfGetAddress(g_hMyGameConf, "CDirector");
}

For more code examples, l4d2_direct relies heavily on Address: functions.

Custom Key Values

To read arbitary key value pairs from a gamedata file, add them to the "Keys" section of your game config file. You can have different values per platform or have a key only be available on selected platforms.

Example:

"Games"
{
  "cstrike"
  {
    "Keys"
    {
      "INTERFACEVERSION_GAMEEVENTSMANAGER2" "GAMEEVENTSMANAGER002"

      // Different values per platform (since SourceMod 1.10+)
      "MyGreatKey"
      {
        "windows" "this is wundows speaking"
        "linux"   "linux best linux"
        "mac"   "wat"
      }

      "JustForLinux"
      {
        "linux"   "not relevant for other platforms!"
      }

    }
  }
}

You can access the keys using

char buffer[512];
GameConfGetKeyValue(g_hMyGameConf, "MyGreatKey", buffer, sizeof(buffer));

Inheritance

If you wish for multiple mods to inherit from one block, you can add a special "#supported" section as the first section under a "#default" section. For example:

"Games"
{
  "#default"
  {
    "#supported"
    {
      "game"    "dod"
      "engine"    "sdk2013"
    }

    "Signatures"
    {
      // ...
    }
  }
}

This specifies that the given block will be read for Day of Defeat as well as any game on the Source 2013 engine branch. You can add multiple "game" or "engine" keys under the "#supported" section to list all mods or engines this section should apply to.

Specializing per game version based on CRC32 value

If a game has multiple stable versions, you can select to apply a gamedata section to a specific game version. You can add a special "CRC" section as the first section under a "#default" section, which allows you to list the hex-encoded CRC32 values of the game's server binary that the section should apply to.

"Games"
{
  "#default"
  {
    "CRC"
    {
      "server"
      {
        "linux"  "186F31F8"
        "windows" "D41B675F"
      }
    }
    
    "Signatures"
    {
      // ...
    }
  }
}

The section would only apply if the game's server binary's CRC32 hash matches the given value for the current platform. At the moment, only the "server" binary is supported. You can add multiple "#default" and "CRC" section combinations to cover different game versions.

Calling Functions

Calling functions is complicated and requires strict attention to the C++ API you are trying to use. If you make a mistake, something bad will happen (and if not, you got lucky!) SDKTools is more complicated than PimpinJuice's "Hacks" extension because it precompiles all of the information needed to convert from Plugin memory to C++ types. This precompilation step ensures better safety and speed, but it lengthens the preparation process.

With that said, let's look at an overview of the call creation process. Before making calls, we must prepare the call. This will give us a Handle which will let us make as many calls as we like.

  1. Init - The preparation must be initialized. Here we can specify the calling convention, which is either static, or BaseEntity/BasePlayer-based. BasePlayer should be used when only player entities should be allowed as the this pointer.
  2. Destination - The call must have a destination address or virtual index to use. This is normally done via PrepSDKCall_SetFromConf which ties into gameconf files.
  3. Return Info - If the call has return information, it must be set via PrepSDKCall_SetReturnInfo.
  4. Parameters - Each parameter (if any) must be added with PrepSDKCall_AddParameter.
  5. Finalize - The call must be finalized with EndPrepSDKCall, which will return a Handle. If it doesn't, one or more of the preparation operations failed.

For the examples below, consider a plugin called "sdkexamples.sp" which has a gamedata file of "plugin.sdkexamples.txt" containing the definitions in the first section.

RoundRespawn Example

CCSPlayer::RoundRespawn is a Counter-Strike Source function which respawns players. It used by CS:S DM. Prototype:

void CCSPlayer::RoundRespawn()

SDKCall example:

#include <sourcemod>
#include <sdktools>
 
Handle hGameConf;
Handle hRoundRespawn;
 
public void OnPluginStart()
{
  hGameConf = LoadGameConfigFile("plugin.sdkexamples");
  StartPrepSDKCall(SDKCall_Player);
  PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "RoundRespawn");
  hRoundRespawn = EndPrepSDKCall();
}
 
void RespawnPlayer(int client)
{
  SDKCall(hRoundRespawn, client);
}

Note that we did not have to set any extra information for parameters or return info, since CCSPlayer::RoundRespawn has no parameters and has a void return.

GiveNamedItem Example

GiveNamedItem will give a player a named item. Prototype:

virtual CBaseEntity *CBasePlayer::GiveNamedItem(const char *item, int iSubType = 0);
#include <sourcemod>
#include <sdktools>
 
Handle hGameConf;
Handle hGiveNamedItem;
 
public OnPluginStart()
{
  hGameConf = LoadGameConfigFile("plugin.sdkexamples");
 
  StartPrepSDKCall(SDKCall_Player);
  PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, "GiveNamedItem");
  PrepSDKCall_SetReturnInfo(SDKType_CBaseEntity, SDKPass_Pointer);
  PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer);
  PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
  hGiveNamedItem = EndPrepSDKCall();
}
 
any GiveItem(int client, const char[] item)
{
  return SDKCall(hGiveNamedItem, client, item, 0);
}

GetEyePosition Example

Prototype:

virtual Vector CBaseEntity::EyePosition();
#include <sourcemod>
#include <sdktools>
 
Handle hGameConf;
Handle hGetEyePosition;
 
public void OnPluginStart()
{
  hGameConf = LoadGameConfigFile("plugin.sdkexamples");
 
  StartPrepSDKCall(SDKCall_Player);
  PrepSDKCall_SetFromConf(hGameConf, SDKConf_Virtual, "EyePosition");
  PrepSDKCall_SetReturnInfo(SDKType_Vector, SDKPass_ByValue);
  hGetEyePosition= EndPrepSDKCall();
}
 
any GetEyePosition(int client, float pos[3])
{
  SDKCall(hGetEyePosition, client, pos);
}

Note that the vector is returned as the third parameter (first parameter after the this pointer).

TempEnt Functions

TempEnts, also called "temporary entities," "tempentities," or "TEs," are quick graphical displays that are too simple to justify the overhead of a CBaseEntity instantiation or full server-side networkability. In the SDK, they are statically defined instances that can be "played back" to the client by simply sending a copy of its network class properties.

Temporary Entities are mod-dependent, in that a mod can change any of their properties or even the ITempEnts class definition. Because of this, SourceMod takes a very flexible approach:

  • First, a temp entity is started by name using TE_Start. A list of all tempent names and their network classes can be dumped with sm_print_telist (SDKTools must be loaded).
  • Second, all of the tempent's properties are set via the TE_Write natives. All tempent properties can be dumped to a file using sm_dump_teprops.
  • Third, the properties are transmitted using TE_Send, TE_SendToAll, or TE_SendToClient.

As a convenience, SourceMod provides a number of pre-written stocks for SDK-defined temporary entities. While the TE_* natives are guaranteed to work on any fully-supported SourceMod mod, the TE_Setup* stocks are not, as a mod might change the property layouts or the functionalities of the SDK-defined tempents.

Some examples are below. For pictures, see TempEnts (SourceMod SDKTools).

Using stocks:

void SparkClient(int client, const float pos[3], const float dir[3])
{
  TE_SetupMetalSparks(pos, dir);
  TE_SendToClient(client);
}

Not using stocks:

void SparkClient(int client, const float pos[3], const float dir[3])
{
  TE_Start("Metal Sparks");
  TE_WriteVector("m_vecPos", pos);
  TE_WriteVector("m_vecDir", dir);
  TE_SendToClient(client);
}

Sound Functions

SDKTools exposes various sound-related members of IVEngineServer and IEngineSound. The basic functions are in sdktools_sound.inc:

  • EmitAmbientSound: Plays an ambient sound from an origin.
  • EmitSound, EmitSoundToClient, EmitSoundToAll: Plays a sound to a list of clients from a specific entity.

There are a few important properties about sounds:

  • Entity: The entity the sound should come from. When playing a generic sound file to player(s), you should use SOUND_FROM_PLAYER, which is a special SourceMod macro for making the sound come from the player target. For ambient sounds you'd use SOUND_FROM_WORLD, and for sounds from an entity you'd use the entity index.
  • Channel: Exposed as SNDCHAN_*, normally you only need to use SNDCHAN_AUTO. The channel affects how the sound is spatialized[?]
  • Level: The decibel level. There are a number of predefined levels as SNDLEVEL_* -- the standard one is SNDLEVEL_NORMAL. Half-Life 1 used attenuation values instead; you can convert from these using ATTN_TO_SNDLEVEL().
  • Volume: The volume, on a scale of 0.0 to 1.0. To use a volume other than the default, the SND_CHANGEVOL flag should be included in the "flags" parameter. The default volume is SNDVOL_NORMAL.
  • Pitch: The pitch; predefined values are SNDPITCH_* with SNDPITCH_NORMAL being the default. SND_CHANGEPITCH should be passed to use a non-default pitch.
  • Origin: If specified, an origin to play the sound from.

Some very simple examples:

EmitSoundToClient(client, "bot/affirmative.wav");
EmitSoundToAll("radio/terwin.wav");

Supported Mods

SDKTools must have specific support added for each mod. So far, default support exists for the following modifications:

  • Counter-Strike: Source
  • Day of Defeat: Source
  • Dystopia
  • Half-Life 2: Deathmatch
  • Insurgency (only some of the CBasePlayer SDK functions are available)
  • Pirates, Vikings, and Knights II
  • The Ship
  • SourceForts

See Also