PDA

View Full Version : YAVFTH - linux: Virtual function table hook, no assembler...


XAD
01-20-2005, 11:46
vancelorgin asked me for my version so thought I posted it here (this is more C than C++ :wink: )...

This line will hook all calls to the engine->LogPrint function and call the local MetaLogPrint function instead.
SET_ENGINEFUNC(LogPrint,MetaLogPrint);

/X

------

These are my macros and a code example to hook "engine->LogPrint()" (taken from my code)...
#define VNAME MyPlugin

#define ENGINEFUNC(func) ( (int)( (void**)&g_pOrigEngine.func - (void**)&g_pOrigEngine ) )
#define ENGINEFUNCSIZE ( sizeof(ivengineserver_t) )
#define SET_ENGINEFUNC(func,myfunc) ( (*(void***)engine)[ENGINEFUNC(func)] = (void*)&myfunc )

static ivengineserver_t g_pOrigEngine;

static void MetaLogPrint( void *pVTable, const char *msg );

// Initialize engine hook support
static void InitHookEngine()
{
static bool g_bHookEngineInd = false;

Msg( "[%s] Initialized Engine hooks\n", VNAME );

// Bail out if alread initialized
if( g_bHookEngineInd )
return;

// Block another initialization call
g_bHookEngineInd = true;

// Unprotect the virtual table and copy it to the local struct
UTIL_VClassUnprotect( (void*)engine, ENGINEFUNCSIZE );
memcpy( (void*)&g_pOrigEngine, *(void**)engine, ENGINEFUNCSIZE );

// Test hook !! REPEAT FOR EACH HOOK !!
SET_ENGINEFUNC(LogPrint,MetaLogPrint);

// Protect the virtual table again
UTIL_VClassProtect( (void*)engine, ENGINEFUNCSIZE );
}

// Print the log data to the console
static void MetaLogPrint( void *pVTable, const char *msg )
{
Msg( "[%s] DEBUG: LogPrint (%08Xh): %s", VNAME, pVTable, msg );
g_pOrigEngine.LogPrint( pVTable, msg );
}


This is the struct needed, which is a minor modified version of the eiface IVEngineServer class...
#ifndef INTERFACE_HOOK_H
#define INTERFACE_HOOK_H

#include <stdio.h>
#include <sys/mman.h>
#include <errno.h>

#include "enginecallback.h"

//-----------------------------------------------------------------------------
// Purpose: Support engine hook
//-----------------------------------------------------------------------------
typedef struct ivengineserver_s
{
// Tell engine to change level ( "changelevel s1\n" or "changelevel2 s1 s2\n" )
void (*ChangeLevel)( void *pVTable, const char *s1, const char *s2 );

// Ask engine whether the specified map is a valid map file (exists and has valid version number).
int (*IsMapValid)( void *pVTable, const char *filename );

// Is this a dedicated server?
bool (*IsDedicatedServer)( void *pVTable );

// Is in Hammer editing mode?
int (*IsInEditMode)( void *pVTable );

// Add to the server/client lookup/precache table, the specified string is given a unique index
// NOTE: The indices for PrecacheModel are 1 based
// a 0 returned from those methods indicates the model or sound was not correctly precached
// However, generic and decal are 0 based
// If preload is specified, the file is loaded into the server/client's cache memory before level startup, otherwise
// it'll only load when actually used (which can cause a disk i/o hitch if it occurs during play of a level).
int (*PrecacheModel)( void *pVTable, const char *s, bool preload /* = false */ );
int (*PrecacheSentenceFile)( void *pVTable, const char *s, bool preload /* = false */ );
int (*PrecacheDecal)( void *pVTable, const char *name, bool preload /* = false */ );
int (*PrecacheGeneric)( void *pVTable, const char *s, bool preload /* = false */ );

// Check's if the name is precached, but doesn't actually precache the name if not...
bool (*IsModelPrecached)( void *pVTable, char const *s ) /* const */;
bool (*IsDecalPrecached)( void *pVTable, char const *s ) /* const */;
bool (*IsGenericPrecached)( void *pVTable, char const *s ) /* const */;

// Note that sounds are precached using the IEngineSound interface

// Special purpose PVS checking
// Get the cluster # for the specified position
int (*GetClusterForOrigin)( void *pVTable, const Vector &org );
// Get the PVS bits for a specified cluster and copy the bits into outputpvs. Returns the number of bytes needed to pack the PVS
int (*GetPVSForCluster)( void *pVTable, int cluster, int outputpvslength, unsigned char *outputpvs );
// Check whether the specified origin is inside the specified PVS
bool (*CheckOriginInPVS)( void *pVTable, const Vector &org, const unsigned char *checkpvs, int checkpvssize );
// Check whether the specified worldspace bounding box is inside the specified PVS
bool (*CheckBoxInPVS)( void *pVTable, const Vector &mins, const Vector &maxs, const unsigned char *checkpvs, int checkpvssize );

// Returns the server assigned userid for this player. Useful for logging frags, etc.
// returns -1 if the edict couldn't be found in the list of players.
int (*GetPlayerUserId)( void *pVTable, const edict_t *e );
const char *(*GetPlayerNetworkIDString)( void *pVTable, const edict_t *e );

// Return the current number of used edict slots
int (*GetEntityCount)( void *pVTable );
// Given an edict, returns the entity index
int (*IndexOfEdict)( void *pVTable, const edict_t *pEdict );
// Given and entity index, returns the corresponding edict pointer
edict_t *(*PEntityOfEntIndex)( void *pVTable, int iEntIndex );

// Get stats info interface for a client netchannel
INetChannelInfo* (*GetPlayerNetInfo)( void *pVTable, int playerIndex );

// Allocate space for string and return index/offset of string in global string list
// If iForceEdictIndex is not -1, then it will return the edict with that index. If that edict index
// is already used, it'll return null.
edict_t *(*CreateEdict)( void *pVTable, int iForceEdictIndex /* = -1 */ );
// Remove the specified edict and place back into the free edict list
void (*RemoveEdict)( void *pVTable, edict_t *e );

// Memory allocation for entity class data
void *(*PvAllocEntPrivateData)( void *pVTable, long cb );
void (*FreeEntPrivateData)( void *pVTable, void *pEntity );

// Save/restore uses a special memory allocator (which zeroes newly allocated memory, etc.)
void *(*SaveAllocMemory)( void *pVTable, size_t num, size_t size );
void (*SaveFreeMemory)( void *pVTable, void *pSaveMem );

// Emit an ambient sound associated with the specified entity
void (*EmitAmbientSound)( void *pVTable, int entindex, const Vector &pos, const char *samp, float vol, soundlevel_t soundlevel, int fFlags, int pitch, float delay /* = 0.0f */ );

// Fade out the client's volume level toward silence (or fadePercent)
void (*FadeClientVolume)( void *pVTable, const edict_t *pEdict, float fadePercent, float fadeOutSeconds, float holdTime, float fadeInSeconds );

// Sentences / sentence groups
int (*SentenceGroupPick)( void *pVTable, int groupIndex, char *name, int nameBufLen );
int (*SentenceGroupPickSequential)( void *pVTable, int groupIndex, char *name, int nameBufLen, int sentenceIndex, int reset );
int (*SentenceIndexFromName)( void *pVTable, const char *pSentenceName );
const char *(*SentenceNameFromIndex)( void *pVTable, int sentenceIndex );
int (*SentenceGroupIndexFromName)( void *pVTable, const char *pGroupName );
const char *(*SentenceGroupNameFromIndex)( void *pVTable, int groupIndex );
float (*SentenceLength)( void *pVTable, int sentenceIndex );

// Issue a command to the command parser as if it was typed at the server console.
void (*ServerCommand)( void *pVTable, const char *str );
// Execute any commands currently in the command parser immediately (instead of once per frame)
void (*ServerExecute)( void *pVTable );
// Issue the specified command to the specified client (mimics that client typing the command at the console).
void (*ClientCommand)( void *pVTable, edict_t *pEdict, const char *szFmt, ... );

// Set the lightstyle to the specified value and network the change to any connected clients. Note that val must not
// change place in memory (use MAKE_STRING) for anything that's not compiled into your mod.
void (*LightStyle)( void *pVTable, int style, const char *val );

// Project a static decal onto the specified entity / model (for level placed decals in the .bsp)
void (*StaticDecal)( void *pVTable, const Vector &originInEntitySpace, int decalIndex, int entityIndex, int modelIndex, bool lowpriority );

// Given the current PVS(or PAS) and origin, determine which players should hear/receive the message
void (*Message_DetermineMulticastRecipients)( void *pVTable, bool usepas, const Vector& origin, CBitVec< ABSOLUTE_PLAYER_LIMIT >& playerbits );

// Begin a message from a server side entity to its client side counterpart (func_breakable glass, e.g.)
bf_write *(*EntityMessageBegin)( void *pVTable, int ent_index, ServerClass * ent_class, bool reliable );
// Begin a usermessage from the server to the client .dll
bf_write *(*UserMessageBegin)( void *pVTable, IRecipientFilter *filter, int msg_type );
// Finish the Entity or UserMessage and dispatch to network layer
void (*MessageEnd)( void *pVTable );

// Print szMsg to the client console.
void (*ClientPrintf)( void *pVTable, edict_t *pEdict, const char *szMsg );

// SINGLE PLAYER/LISTEN SERVER ONLY (just matching the client .dll api for this)
// Prints the formatted string to the notification area of the screen ( down the right hand edge
// numbered lines starting at position 0
void (*Con_NPrintf)( void *pVTable, int pos, const char *fmt, ... );
// SINGLE PLAYER/LISTEN SERVER ONLY(just matching the client .dll api for this)
// Similar to Con_NPrintf, but allows specifying custom text color and duration information
void (*Con_NXPrintf)( void *pVTable, const struct con_nprint_s *info, const char *fmt, ... );

// For ConCommand parsing or parsing client commands issued by players typing at their console
// Retrieves the raw command string (untokenized)
const char *(*Cmd_Args)( void *pVTable );
// Returns the number of tokens in the command string
int (*Cmd_Argc)( void *pVTable );
// Retrieves a specified token
char *(*Cmd_Argv)( void *pVTable, int argc );

// Change a specified player's "view entity" (i.e., use the view entity position/orientation for rendering the client view)
void (*SetView)( void *pVTable, const edict_t *pClient, const edict_t *pViewent );

// Get a high precision timer for doing profiling work
float (*Time)( void *pVTable );

// Set the player's crosshair angle
void (*CrosshairAngle)( void *pVTable, const edict_t *pClient, float pitch, float yaw );

// Get the current game directory (hl2, tf2, hl1, cstrike, etc.)
void (*GetGameDir)( void *pVTable, char *szGetGameDir, int maxlength );

// Used by AI node graph code to determine if .bsp and .ain files are out of date
int (*CompareFileTime)( void *pVTable, const char *filename1, const char *filename2, int *iCompare );

// Locks/unlocks the network string tables (.e.g, when adding bots to server, this needs to happen).
// Be sure to reset the lock after executing your code!!!
bool (*LockNetworkStringTables)( void *pVTable, bool lock );

// Create a bot with the given name. Returns NULL if fake client can't be created
edict_t *(*CreateFakeClient)( void *pVTable, const char *netname );

// Get a convar keyvalue for s specified client
const char *(*GetClientConVarValue)( void *pVTable, int clientIndex, const char *name );

// Parse a token from a file
const char *(*ParseFile)( void *pVTable, const char *data, char *token, int maxlen );
// Copies a file
bool (*CopyFile)( void *pVTable, const char *source, const char *destination );

// Reset the pvs, pvssize is the size in bytes of the buffer pointed to by pvs.
// This should be called right before any calls to AddOriginToPVS
void (*ResetPVS)( void *pVTable, byte *pvs, int pvssize );
// Merge the pvs bits into the current accumulated pvs based on the specified origin ( not that each pvs origin has an 8 world unit fudge factor )
void (*AddOriginToPVS)( void *pVTable, const Vector &origin );
// Mark a specified area portal as open/closes
void (*SetAreaPortalState)( void *pVTable, int portalNumber, int isOpen );
// Queue a temp entity for transmission
void (*PlaybackTempEntity)( void *pVTable, IRecipientFilter& filter, float delay, const void *pSender, const SendTable *pST, int classID );
// Given a node number and the specified PVS, return with the node is in the PVS
int (*CheckHeadnodeVisible)( void *pVTable, int nodenum, const byte *pvs, int vissize );
// Using area bits, cheeck whether area1 flows into area2 and vice versa (depends on area portal state)
int (*CheckAreasConnected)( void *pVTable, int area1, int area2 );
// Given an origin, determine which area index the origin is within
int (*GetArea)( void *pVTable, const Vector &origin );
// Get area portal bit set
void (*GetAreaBits)( void *pVTable, int area, unsigned char *bits, int buflen );
// Given a view origin (which tells us the area to start looking in) and a portal key,
// fill in the plane that leads out of this area (it points into whatever area it leads to).
bool (*GetAreaPortalPlane)( void *pVTable, Vector const &vViewOrigin, int portalKey, VPlane *pPlane );

// Apply a modification to the terrain.
void (*ApplyTerrainMod)( void *pVTable, TerrainModType type, CTerrainModParams const &params );

// Save/restore wrapper - FIXME: At some point we should move this to it's own interface
bool (*LoadGameState)( void *pVTable, char const *pMapName, bool createPlayers );
void (*LoadAdjacentEnts)( void *pVTable, const char *pOldLevel, const char *pLandmarkName );
void (*ClearSaveDir)( void *pVTable );

// Get the pristine map entity lump string. (e.g., used by CS to reload the map entities when restarting a round.)
const char* (*GetMapEntitiesString)( void *pVTable );

// Text message system -- lookup the text message of the specified name
client_textmessage_t *(*TextMessageGet)( void *pVTable, const char *pName );

// Print a message to the server log file
void (*LogPrint)( void *pVTable, const char *msg );

// Builds PVS information for an entity
void (*BuildEntityClusterList)( void *pVTable, edict_t *pEdict, PVSInfo_t *pPVSInfo );

// A solid entity moved, update spatial partition
void (*SolidMoved)( void *pVTable, edict_t *pSolidEnt, ICollideable *pSolidCollide, const Vector* pPrevAbsOrigin );
// A trigger entity moved, update spatial partition
void (*TriggerMoved)( void *pVTable, edict_t *pTriggerEnt );

// Create/destroy a custom spatial partition
ISpatialPartition *(*CreateSpatialPartition)( void *pVTable, const Vector& worldmin, const Vector& worldmax );
void (*DestroySpatialPartition)( void *pVTable, ISpatialPartition * );

// Draw the brush geometry in the map into the scratch pad.
// Flags is currently unused.
void (*DrawMapToScratchPad)( void *pVTable, IScratchPad3D *pPad, unsigned long iFlags );

// This returns which entities, to the best of the server's knowledge, the client currently knows about.
// This is really which entities were in the snapshot that this client last acked.
// This returns a bit vector with one bit for each entity.
//
// USE WITH CARE. Whatever tick the client is really currently on is subject to timing and
// ordering differences, so you should account for about a quarter-second discrepancy in here.
// Also, this will return NULL if the client doesn't exist or if this client hasn't acked any frames yet.
//
// iClientIndex is the CLIENT index, so if you use pPlayer->entindex(), subtract 1.
const CBitVec<MAX_EDICTS>* (*GetEntityTransmitBitsForClient)( void *pVTable, int iClientIndex );

// Is the game paused?
bool (*IsPaused)( void *pVTable );

// Marks the filename for consistency checking. This should be called after precaching the file.
void (*ForceExactFile)( void *pVTable, const char *s );
void (*ForceModelBounds)( void *pVTable, const char *s, const Vector &mins, const Vector &maxs );
} ivengineserver_t;

#endif // INTERFACE_HOOK_H


The protect / unprotect functions...
#ifndef PAGESIZE
#define PAGESIZE 4096
#endif

inline void UTIL_VClassUnprotect( void *pObject, int iMLen )
{
char *p;
p = (char*)((int)(*(void**)pObject) & ~(PAGESIZE-1));
if( mprotect( p, (int)(*(char**)pObject - p) + iMLen, PROT_READ | PROT_WRITE | PROT_EXEC ) )
{
perror( "Virtual Class memory unprotect failed (using mprotect)" );
}
}

inline void UTIL_VClassProtect( void *pObject, int iMLen )
{
char *p;
p = (char*)((int)(*(void**)pObject) & ~(PAGESIZE-1));
if( mprotect( p, (int)(*(char**)pObject - p) + iMLen, PROT_READ | PROT_EXEC ) )
{
perror( "Virtual Class memory protect failed (using mprotect)" );
}
}

BAILOPAN
01-20-2005, 11:57
Great :)

LogPrint was actually the only engine function I was annoyed at not being able to intercept ;]

XAD
01-20-2005, 12:07
Great :)
LogPrint was actually the only engine function I was annoyed at not being able to intercept ;]
No it's not (but you don't know it yet :wink: )... I promise you, you WANT at least 3 more... additional 2 more for a proper MOTD fix...
(Connect "[plan9] Penthouse" and check the MOTD. Don't care about the HTML itself but the top right corner.)

/X

Geesu
01-20-2005, 14:05
Wow I really wish I was this smart :(

vancelorgin
01-20-2005, 16:32
tyvm xad - now I can port my macros to linux properly

nostalgic how you treat it like a function pointer struct

also, have you tried replacing the vtable ptr in the class, rather than overwriting the existing one? this allows for single class instance function hooking e.g. custom entities (make a cbaseentity, replace the vtable with one with a ptr to your think, your ontouch, whathaveyou)

heh - can't do it in windows without asm :P

c0ldfyr3
09-08-2005, 08:01
Linux only ?

I changed #include <sys/mman.h> to

#ifndef __linux__
#define WINDOWS_LEAN_AND_MEAN
#include <windows.h>
#define PATH_MAX MAXPATHLEN
#else
#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif
#endif

But it doesnt have some of the definitions....

c0ldfyr3
09-08-2005, 09:10
Ok wait, I can get this to work on Windows no problem.

BUT..as per all things I take off here, the only problem is a Link error..

Hostitron error LNK2001: unresolved external symbol "class IVEngineServer * engine" (?engine@@3PAVIVEngineServer@@A)

I know what it means, I just dont know howto fix it.

engine has never been defined in my project, and im just wondering if anyone could please tell me the procedure :?: :?:

If its simply the normal definition then thats ok, it compiles using g_Hostitron.mEngine instead but its crashing and im wondering if its because of my method...

c0ldfyr3
09-08-2005, 09:40
In MetaLogPrint, if i change this
g_pOrigEngine.LogPrint( pVTable, msg );
to this
std::string LoL;
LoL.append("Ours...");
LoL.append(msg);
g_pOrigEngine.LogPrint( pVTable, LoL.c_str() );

It errors during SET_ENGINEFUNC(LogPrint,MetaLogPrint);