HamSandwich General Usage (AMX Mod X)
Contents
About Ham Sandwich
Ham Sandwich is a module that provides additional hooking and function calling capabilities to AMX Mod X. It is scheduled to be included with the 1.8 branch of AMX Mod X.
Instead of hooking and executing Half-Life engine calls, like the modules FakeMeta and Engine do, this hooks and executes virtual function calls.
What is a virtual function?
A virtual function is a member function of a class in C++. In the case of Ham Sandwich, the virtual function would be a member function of a game entity. The virtual functions deal with various events in game, such as damage, and using entities.
Unlike normal member functions, virtual functions can be changed by class derivatives. For example, a typical hierarchy of entities in Half-Life game dlls look something like this:
CBaseEntity CBaseAnimating CBaseMonster CZombie CHeadCrab CBarnacle CBasePlayer CBot
Now for example, if CBaseEntity has the virtual function called "FunctionA", and that function does nothing. But, CBarnacle also declares the function "FunctionA", and instead of doing nothing it causes the barnacle to die.
If something like this were to be executed:
CBaseEntity *ent=GET_PRIVATE(entity); ent->FunctionA();
And the entity were a barnacle, the barnacle would die. If the entity was a zombie, nothing would happen.
What are some limitations?
Dealing with virtual functions have a couple minor drawbacks.
Custom Entities
Hooking virtual functions, such as TakeDamage, that get called on custom entities is a little bit awkward.
Because of the way where classes derive, and they get their own virtual table for each class, the type of class that the game dll allocates will be whatever gets hooked for a particular hook.
For example, say you have a plugin like this:
#include <amxmodx> #include <fakemeta> #include <hamsandwich> new myStr; public plugin_init() { myStr = engfunc( EngFunc_AllocString, "info_target" ); register_clcmd( "make_entity", "cmdMakeEntity" ); RegisterHam(Ham_TakeDamage, "damageHook", "MyEntityClass"); // Error! } public cmdMakeEntity( id ) { new ent = engfunc( EngFunc_CreateEntity, myStr ); set_pev( ent, pev_classname, "MyEntityClass" ); // ... }
That will not work, because the class "MyEntityClass" is not native to the gamedll. Instead, you would need to hook TakeDamage for "info_target", and do some form of parsing on the entities that get passed to it (such as checking classname), if you want to deal with custom entity hooking.
End of Chain Derivatives
The normal way virtual functions get called is by a virtual table that resides somewhere in the class. A magical index is assigned to each function, and then it looks up the function pointer inside of the virtual table, and executes the required function.
However, compilers are smart enough to optimize away the virtual function lookup if it knows two things about a call:
- The exact class type of the entity.
- That the entity is not derived any further.
An example would be using the hierarchy shown earlier. If, inside of CBot::TakeDamage a call to the virtual function CBot::Killed is made, most compilers will optimize the virtual function lookup out, so that CBot::TakeDamage would then directly call CBot::Killed. This means that the hook would never be called.
Fortunately, these instances are kind of rare.
Supported Mods
Mostly Supported
These mods have a very large list of functions that are usable in the module.
- Counter-Strike 1.6
- Condition Zero
- Day of Defeat
- Team Fortress Classic
- The Specialists
- Natural-Selection
Weakly Supported
Because these mods do not contain a Linux binary, they are much harder to disassemble. There is only plans for disassembling two functions for each (TakeDamage and Use).
- Sven-Coop
- Earth's Special Forces
Hooking Functions
The big feature for Ham Sandwich is the ability to hook virtual functions.
Creating the Hook
Creating a hook is a lot like creating a hook in Fakemeta.
#include <amxmodx> #include <hamsandwich> public plugin_init() { RegisterHam(Ham_TakeDamage, "player_hurt", "player"); } public player_hurt(this, inflictor, attacker, Float:damage, damagebits) { // Stuff... }
This creates a basic hook which is called any time a player takes damage. Look in ham_const.inc for forward parameters for each hook.
Blocking the Hook
Like Fakemeta and Engine, it is possible to stop a function from being called. This is called superceding.
Blocking in HamSandwich is identical to how it is accomplished in Fakemeta. In each hook, you return one of four special values:
- HAM_IGNORED - Nothing happened, the call continues.
- HAM_HANDLED - You did something, but the call continues.
- HAM_OVERRIDE - The call will still be executed, but instead you will change the return value.
- HAM_SUPERCEDE - The call is not executed, and you use your return value, if applicable.
You can use the native GetHamReturnStatus() to check the current return status of the hook.
Return Values
You can get or change the return values for all the hooks available in HamSandwich. Look in ham_const.inc for a list of all hooks and their return values.
These natives will get any modified return values.
- native GetHamReturnInteger(&output);
- native GetHamReturnFloat(&Float:output);
- native GetHamReturnVector(Float:output[3]);
- native GetHamReturnEntity(&output);
- native GetHamReturnString(output[], size);
These natives will get the original return value from the function.
- native GetOrigHamReturnInteger(&output);
- native GetOrigHamReturnFloat(&Float:output);
- native GetOrigHamReturnVector(Float:output[3]);
- native GetOrigHamReturnCbase(&output);
- native GetOrigHamReturnString(output[], size);
These natives will change the return value to whatever you please.
- native SetHamReturnInteger(value);
- native SetHamReturnFloat(Float:value);
- native SetHamReturnVector(const Float:value[3]);
- native SetHamReturnEntity(value);
- native SetHamReturnString(const value[]);