sparhawk wrote:
Can you tell us how this exactly works? I don't want to look to the code right now, just to see how it works. One point I'm really interested in is, wether it is possible to also hook into functions that are inaccessible because we don't have the sourcecode for it. From your description here in the postings it is not clear to me what exactly is possible and what not.
It still sounds pretty cool to me.

I didn't write SourceHook (the part of the code that's responsible for actually seeking out and modifying the vtable pointers), but from what the author tells me you can hook any function where you have:
- a virtual function you want to hook
- the function prototype (in the SDK headers)
- a pointer to an instance of the object you want to hook
Most of the stuff from the engine API are in the form of virtual functions, as is the game API.
If you don't have the SDK handy, here's a real quick breakdown of those.
The game API:
Code:
class idGame {
public:
virtual ~idGame() {}
virtual void Init( void ) = 0;
virtual void Shutdown( void ) = 0;
virtual void SetLocalClient( int clientNum ) = 0;
virtual const idDict * SetUserInfo( int clientNum, const idDict &userInfo, bool isClient, bool canModify ) = 0;
virtual const idDict * GetUserInfo( int clientNum ) = 0;
virtual void ThrottleUserInfo( void ) = 0;
virtual void SetServerInfo( const idDict &serverInfo ) = 0;
virtual const idDict & GetPersistentPlayerInfo( int clientNum ) = 0;
virtual void SetPersistentPlayerInfo( int clientNum, const idDict &playerInfo ) = 0;
virtual void InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, bool isServer, bool isClient, int randseed ) = 0;
virtual bool InitFromSaveGame( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, idFile *saveGameFile ) = 0;
virtual void SaveGame( idFile *saveGameFile ) = 0;
virtual void MapShutdown( void ) = 0;
virtual void CacheDictionaryMedia( const idDict *dict ) = 0;
virtual void SpawnPlayer( int clientNum ) = 0;
virtual gameReturn_t RunFrame( const usercmd_t *clientCmds ) = 0;
virtual bool Draw( int clientNum ) = 0;
virtual escReply_t HandleESC( idUserInterface **gui ) = 0;
virtual idUserInterface * StartMenu() = 0;
virtual const char * HandleGuiCommands( const char *menuCommand ) = 0;
virtual allowReply_t ServerAllowClient( int numClients, const char *IP, const char *guid, const char *password, char reason[MAX_STRING_CHARS] ) = 0;
virtual void ServerClientConnect( int clientNum ) = 0;
virtual void ServerClientBegin( int clientNum ) = 0;
virtual void ServerClientDisconnect( int clientNum ) = 0;
virtual void ServerWriteInitialReliableMessages( int clientNum ) = 0;
virtual void ServerWriteSnapshot( int clientNum, int sequence, idBitMsg &msg, byte *clientInPVS, int numPVSClients ) = 0;
virtual bool ServerApplySnapshot( int clientNum, int sequence ) = 0;
virtual void ServerProcessReliableMessage( int clientNum, const idBitMsg &msg ) = 0;
virtual void ClientReadSnapshot( int clientNum, int sequence, const int gameFrame, const int gameTime, const int dupeUsercmds, const int aheadOfServer, const idBitMsg &msg ) = 0;
virtual bool ClientApplySnapshot( int clientNum, int sequence ) = 0;
virtual void ClientProcessReliableMessage( int clientNum, const idBitMsg &msg ) = 0;
virtual gameReturn_t ClientPrediction( int clientNum, const usercmd_t *clientCmds ) = 0;
virtual void SelectTimeGroup( int timeGroup ) = 0;
virtual int GetTimeGroupTime( int timeGroup ) = 0;
virtual idStr GetBestGameType( const char* map, const char* gametype ) = 0;
virtual void GetClientStats( int clientNum, char *data, const int len ) = 0;
virtual void SwitchTeam( int clientNum, int team ) = 0;
virtual bool DownloadRequest( const char *IP, const char *guid, const char *paks, char urls[ MAX_STRING_CHARS ] ) = 0;
};
Unfortunately game_local (the actual game class) inherits from this class and therefore the newly added functions are not virtual and unhookable. However, any engine to game calls will only use the above functions, as the engine doesn't know about how game_local is constructed. Therefore you should still have complete access to everything game-wise.
The engine code is much more in depth, but here's a quick rundown of that section. The following structure is given to a game from the engine when it is loaded:
Code:
typedef struct {
int version; // API version
idSys * sys; // non-portable system services
idCommon * common; // common
idCmdSystem * cmdSystem; // console command system
idCVarSystem * cvarSystem; // console variable system
idFileSystem * fileSystem; // file system
idNetworkSystem * networkSystem; // network system
idRenderSystem * renderSystem; // render system
idSoundSystem * soundSystem; // sound system
idRenderModelManager * renderModelManager; // render model manager
idUserInterfaceManager * uiManager; // user interface manager
idDeclManager * declManager; // declaration manager
idAASFileManager * AASFileManager; // AAS file manager
idCollisionModelManager * collisionModelManager; // collision model manager
} gameImport_t;
Most (if not all, I don't recall off the top of my head) are virtual interfaces. A quick example:
Code:
class idCommon {
public:
virtual ~idCommon( void ) {}
virtual void Init( int argc, const char **argv, const char *cmdline ) = 0;
virtual void Shutdown( void ) = 0;
virtual void Quit( void ) = 0;
virtual bool IsInitialized( void ) const = 0;
virtual void Frame( void ) = 0;
virtual void GUIFrame( bool execCmd, bool network ) = 0;
virtual void Async( void ) = 0;
virtual void StartupVariable( const char *match, bool once ) = 0;
virtual void InitTool( const toolFlag_t tool, const idDict *dict ) = 0;
virtual void ActivateTool( bool active ) = 0;
virtual void WriteConfigToFile( const char *filename ) = 0;
virtual void WriteFlaggedCVarsToFile( const char *filename, int flags, const char *setCmd ) = 0;
virtual void BeginRedirect( char *buffer, int buffersize, void (*flush)( const char * ) ) = 0;
virtual void EndRedirect( void ) = 0;
virtual void SetRefreshOnPrint( bool set ) = 0;
virtual void Printf( const char *fmt, ... )id_attribute((format(printf,2,3))) = 0;
virtual void VPrintf( const char *fmt, va_list arg ) = 0;
virtual void DPrintf( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0;
virtual void Warning( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0;
virtual void DWarning( const char *fmt, ...) id_attribute((format(printf,2,3))) = 0;
virtual void PrintWarnings( void ) = 0;
virtual void ClearWarnings( const char *reason ) = 0;
virtual void Error( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0;
virtual void FatalError( const char *fmt, ... ) id_attribute((format(printf,2,3))) = 0;
virtual const idLangDict * GetLanguageDict( void ) = 0;
};
In my previous post I used an example where I was hooking idCommon::Printf() that was supplied by the engine from the gameImport_t structure

Hopefully this answers your question. It's pretty nice just about everything in terms of engine<->game interaction is provided in a virtual context, so DMM plugins should have the ability to do just about anything you would want to do with them.
The execution of DMM is relatively straight forward. Normally...
- the engine finds and loads the game, passing to it the engine API
- the game does all its startup stuff
- the game returns its API to the engine
With DMM in the mix, the procedure is modified to be:
- the engine finds and loads DMM, passing to it the engine API
- DMM finds and loads the game, passing to it the engine API
- the game does all its startup stuff
- the game returns its API to DMM
- DMM finds all plugins
- DMM loads a plugin, passing to it the engine API and game API
- go back to the previous step until all plugins are loaded
- DMM returns the game API to the engine
After loading, besides unloading on shutdown, DMM's job is to manage how plugins should behave (ie, paused, unpaused, etc) and provide a console interface for user control. Other smaller abilities may be added if required, but DMM is more-or-less idle after loading, the plugins are where all the "work" is done.
editsparhawk wrote:
One point I'm really interested in is, wether it is possible to also hook into functions that are inaccessible because we don't have the sourcecode for it.
You don't need the source code of a function to hook it, just an instance to the object and the prototype. For example, no source to the idCommon::Printf() function is available, but the prototype and instance is so it is hookable via SourceHook.
If you're interested in how SourceHook works, I have a very basic understanding of it. You can email the author for a detailed explaination, but this might help also...
To hook a function you must declare with a macro (see my previous post) what the function you are hooking is called, and the parameters and return types it takes. This macro creates a compile-time function which is used to determine how to proceed with handling the hook you make to it later.
When you actually declare a hook with SourceHook, SourceHook will go to the address in memory of the instance of the object you specify, and attempt to locate its vtable. Within the vtable it will look for the function you asked it to, and modify the pointer to the function normally called to point to SourceHook. When that function is called, since the vtable pointer is now directed at SourceHook, SourceHook will go through a list it has stored internally of what functions should be called at this point and in what order. Typically, there are pre-functions, the real function, then post-functions, called in that order. When you hook a function you can specify if you want it to be pre or post executed. Unhooking a function will instruct SourceHook to relocate the vtable entry it found previously, and modify the pointer to the hooked function to be the original pointer. This in effect causes no additional overhead of invoking a hooked function (except the little that is produced by SourceHook's internal list, and that of the actual functions that have to be invoked).