Metamod:Source Development

From AlliedModders Wiki
Revision as of 16:15, 29 May 2007 by Pc0Q1v (talk | contribs)
Jump to: navigation, search

This article is an introduction to SourceMM coding.

Note: The majority of this documentation should be merged or moved into an article about SourceHook.

Note: This article probably needs some better formatting.

Requirements

You must have the Valve HL2SDK, available from your Steam Menu. For Windows, you must have Microsoft Visual Studio 7.0 or 7.1 (we have not tried 6.0). For Linux, Valve requires you use at least GCC 3.4.1. You should also have a copy of the Metamod:Source source code, available here (sourcemm/sourcehook and sourcemm/sourcemm).

For Microsoft Visual Studio, you should make sure that you have the following HL2SDK paths in your include settings (Tools -> Options -> Projects, VC Directories, Include Files), as well as the "sourcehook" and "sourcemm" folders:

  • dlls
  • public
  • public\vstdlib
  • public\tier1
  • public\tier0
  • public\engine
  • public\dlls
  • tier1
  • lib\public (this should be in your Library Paths!)

Plugin API

In order to write a plugin, you must implement the ISmmPlugin interface, similar to IServerPluginCallbacks. Each Metamod:Source release has a minimum required interface version and a current version. The minimum version is guaranteed to be upward compatible to the current, however, it may be lacking some features.

Once you've implemented the interface, you must also have a global singleton of your plugin available. There are a few macros to assist you in properly exposing the interface as a DLL and setting up the API states.

  • PLUGIN_GLOBALVARS() - Place in header. Declares the global variables that some API calls require (such as g_SHPtr and g_PLAPI).
  • PLUGIN_EXPOSE(class, singleton) - Place in .cpp file. Declares the external CreateInterface function which exposes the API.
  • PLUGIN_SAVEVARS() - Use first thing in ISmmPlugin::Load(), saves the global variables sent from SourceMM.

The actual plugin API you must implement as of this writing is version 004. To see a description of each of the functions, you can view the doxygen information here. Note that the Load, Unload, Pause, and Unpause functions allow you to refuse the action and copy an error message (you should check to make sure error is not NULL - it can be).

SourceHook (Hooking)

SourceHook is the engine used to intercept function calls, much like Metamod. The difference with SourceHook is that it can intercept any virtual function in any class that, at compile time, you have the header for. SourceHook has the following steps of operation:

  • Declare the prototype of the function you are going to hook. This generates compile-time code that is able to pinpoint exactly how to go about hooking the function.
  • Hook the function - as a member function of another class or a regular static function.
  • Before the hooked function is called, all of the "pre" hook handlers attached to it are called. Each hook can set a special flag, the highest of which is chosen as a final operation. This flag specifies whether the original function should be called or not.
  • Once all the hooks have been called, SourceHook decides whether to call the original function. Another set of hooks are called directly after, called "post" hook handlers. You can specify whether each hook is a post or pre hook - it simply changes whether it's called before or after the original call is made.
  • After you are done using a hook, you can safely remove it.

For example, let's say you wanted to intercept log messages in VEngineServer. Observe the prototype:

virtual void		LogPrint( const char *msg ) = 0;

This is a virtual, public function of a class we have an instance of (let's say in IVEngineServer *m_Engine) and that we have the header for. It can be intercepted.

The first step is to figure out how to declare its prototype to SourceHook. This function is void, and has one parameter. The declaration macro follows these formats:

  • SH_DECL_HOOKn - n is the number of parameters
    • The parameters are: Class name, member function name, attributes, overloaded?, the return type, and a list of the parameter types.
  • SH_DECL_HOOKn_void - n is the number of parameters
    • _void specifies that the function does not return a value. The format is the same as above except the "return type" parameter is missing.
  • Note: Not covered here are the SH_DECL_HOOKn[_void]_vafmt hooks. These can hook string-formattable variable argument lists. You do not pass the string format or ellipses parameter. SourceHook will automatically format the string for your hook.

Our macro will look like this:

SH_DECL_HOOK1_void(IVEngineServer, LogPrint, SH_NOATTRIB, 0, const char *);

This line must appear outside of a function. It means "Declare a hook prototype for LogPrint in IVEngineServer, which is a void function that has one parameter, which is a const char *".

Next we must actually hook the function. You can do this wherever you want to begin the interception. The two macros for hooking look like this:

  • SH_ADD_HOOK_STATICFUNC(class, memberfunction, instance_pointer, handler, post) Hooks a virtual function to a static/global function.
    • class - The name of the class
    • memberfunction - The name of the member function
    • instance - A pointer to an instance of the class
    • handler - Your function that will be called on hooking
    • post - true for post operation, false for pre operation
  • SH_ADD_HOOK_MEMFUNC(class, memberfunction, instance, myinstance, myfunction, post) Hooks a virtual function to a member function of another class.
    • class - The name of the class
    • memberfunction - The name of the member function
    • instance - An pointer to an instance of the class
    • myinstance - A pointer to an instance of the class that has the handler member function
    • myfunction - The name of the handler member function in your class
    • post - true for post operation, false for pre-operation

Let's continue with the example. To hook LogPrint to a function in your class, CMetaHooks (instance, g_MetaHooks), you would use:

SH_ADD_HOOK_MEMFUNC(IVEngineServer, LogPrint, m_Engine,