Raised This Month: $51 Target: $400
 12% 

Hooking functions in CBasePlayer


Post New Thread Reply   
 
Thread Tools Display Modes
Author Message
TommyV
Member
Join Date: Nov 2005
Old 04-05-2006 , 09:12   Hooking functions in CBasePlayer
Reply With Quote #1

I'm having trouble understanding some concepts re function hooking in SourceMM. I want to hook the function Touch in CBasePlayer so I can intercept weapon pick ups and decide if I want to let them through or not.

So I declare the hook
Code:
SH_DECL_HOOK1_void(CBasePlayer, Touch, SH_NOATTRIB, 0, CBaseEntity *pEntity);
But where and when do I apply the hook? I know I need the line
Code:
SH_ADD_HOOK_MEMFUNC(CBasePlayer, Touch, anInstanceOfCBP, &MyClass, &MyClass::MyFunction, false);
But I cant do this when the plugin loads as I dont have an instance of CBasePlayer. Do I need to apply the hook to every instance of CBasePlayer or if I hook one, say the first player to join, will it hook all? i.e. is the function being hooked related to the instance or the class?

I'm also a bit worried about the effects my function could have on the game, if MyClass::MyFunction is something like...
Code:
if (iDontWantYouToPickUpTheWeapon)
    RETURN_META(MRES_SUPERCEDE);
else
    RETURN_META(MRES_IGNORED);
...could I be ignoring other important code that I dont know about as well as the stuff I want to overwrite?

Or am I barking up the wrong tree and looking at this all the wrong way?
TommyV is offline
BAILOPAN
Join Date: Jan 2004
Old 04-05-2006 , 11:05  
Reply With Quote #2

Yup, you need every instance of CBasePlayer. A good way to do this is on client connect.

The technical reason for this is that every this pointer can technically have a different vtable. For example, a CBasePlayer can have a base table of CCSPlayer or a CCSBot - two separate virtual tables.

You should only supercede something if you know the action is dismissable. For example, intercepting a function that is guaranteed to return a valid pointer, and making it return NULL, would cause trouble.
__________________
egg
BAILOPAN is offline
L. Duke
Veteran Member
Join Date: Apr 2005
Location: Walla Walla
Old 04-05-2006 , 11:09  
Reply With Quote #3

You're looking at it right. You need to hook each player that you want to intercept functions for. You'll also need to unhook each player as they leave, and maybe everyone at the end of the map, before it changes.

Also, I don't know if Touch() will work because the SDK headers for CBaseEntity don't match the latest CSS and DODS updates. You may have to hook it manually.

I do something similar to what you're trying, but I return false to Weapon_CanUse() if a weapon is unavailable. (If you use this function, make sure you drop the weapon first if it's one the player already holds.)

meta_hooks.h
Code:
// manual hooks
#ifdef WIN32
    #define VTABLE_OFFSET 0
#else
    #define VTABLE_OFFSET 1
#endif
SH_DECL_MANUALHOOK1(WeaponCanUse_hook, 200 + VTABLE_OFFSET, 0, 0, bool, CBaseCombatWeapon *);
myplugin.cpp
Code:
SH_ADD_MANUALHOOK_MEMFUNC(WeaponCanUse_hook, pBase, &g_VipMod, &VipMod::WeaponCanUse_handler, false);
The virtual function offsets for CSS on windows can be found here.

EDIT: BAILOPAN snuck in there first (or is it pirate gaben?).
L. Duke is offline
TommyV
Member
Join Date: Nov 2005
Old 04-05-2006 , 12:23  
Reply With Quote #4

Thanks for the advice guys.

I still dont understand why I need to hook every instance though. I get that different instances of CBasePlayer can have different virtual tables because they come from different classes which inherit from CBasePlayer. But if I hook a CBasePlayer (which in fact is actually a CSSPlayer) will it hook all CSSPlayers?

Perhaps a better way of putting is can I hook just one instance of a class?

Nice tip re the headers. It's a bit annoying that headers dont match the latest updates, I wanted to use SourceMM specifically to avoid using offsets. Do they do this on purpose, and if so do they mess about with the offsets as well just to make sure everything breaks on an update?
TommyV is offline
c0ldfyr3
AlliedModders Donor
Join Date: Aug 2005
Location: Ireland
Old 04-05-2006 , 15:31  
Reply With Quote #5

Quote:
Originally Posted by TommyV
Thanks for the advice guys.

I still dont understand why I need to hook every instance though. I get that different instances of CBasePlayer can have different virtual tables because they come from different classes which inherit from CBasePlayer. But if I hook a CBasePlayer (which in fact is actually a CSSPlayer) will it hook all CSSPlayers?

Perhaps a better way of putting is can I hook just one instance of a class?

Nice tip re the headers. It's a bit annoying that headers dont match the latest updates, I wanted to use SourceMM specifically to avoid using offsets. Do they do this on purpose, and if so do they mess about with the offsets as well just to make sure everything breaks on an update?
#1 Yes you need to hook each instance. When you hook a CBE instance, you are hooking it alone and nothing else. So say, even if you only hook one player, and they leave, and someone else joins in their slot, the hook is invalid. You need to hook each player in ClientPutInServer, and unhook in ClientDisconnect. Its also good practice to loop through valid players both to hook on "Late Loading" and to unhook on LevelShutdown just incase.

#2 Yes when you hook a function in CBaseEntity, if a CSSPlayer function is fired which is inherited from CBE then of course. But some functions are only specific to CSSPlayer.

#3 The offsets are in the order the functions are layed out in the header. So if they move a function up or down, its offset moves up or down. SourceMM provides hooking using vtable offsets via MANUAL_HOOK. Check out the docs.
__________________
c0ldfyr3 is offline
Send a message via MSN to c0ldfyr3 Send a message via Yahoo to c0ldfyr3
TommyV
Member
Join Date: Nov 2005
Old 04-05-2006 , 16:31  
Reply With Quote #6

Thanks Cold.

What I have now is: Declare the hooks
Code:
SH_DECL_HOOK1_void(CBasePlayer, Touch, SH_NOATTRIB, 0, CBaseEntity *);
SH_DECL_HOOK5(IServerGameClients, ClientConnect, SH_NOATTRIB, 0, bool, edict_t *, char const *, char const *, char *, int);
SH_DECL_HOOK1_void(IServerGameClients, ClientDisconnect, SH_NOATTRIB, 0, edict_t *);
Hook connect and disconnect
Code:
bool TouchCatcher::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool late)
{

    ...
    SH_ADD_HOOK_MEMFUNC(IServerGameClients, ClientDisconnect, m_ServerClients, &g_TouchCatcher, &TouchCatcher::ClientDisconnect, true);
    SH_ADD_HOOK_MEMFUNC(IServerGameClients, ClientConnect, m_ServerClients, &g_TouchCatcher, &TouchCatcher::ClientConnect, true);
    ...
}
Then hook and unhook Touch on connect and disconnect
Code:
void TouchCatcher::Touch_handler(CBaseEntity *pOther) {
	RETURN_META(MRES_IGNORED);
}

bool TouchCatcher::ClientConnect(edict_t *pEntity, char const *pszName, char const *pszAddress, char *reject, int maxrejectlen)
{
    CBaseEntity *pBaseEntity = pEntity->GetUnknown()->GetBaseEntity();
    CBasePlayer *pPlayer = dynamic_cast<CBasePlayer *>(pBaseEntity);
    SH_ADD_HOOK_MEMFUNC(CBasePlayer, Touch, pPlayer, &g_TouchCatcher, &TouchCatcher::Touch_handler, false);

    RETURN_META_VALUE(MRES_IGNORED, true);
}

void TouchCatcher::ClientDisconnect(edict_t *pEntity)
{
    CBaseEntity *pBaseEntity = pEntity->GetUnknown()->GetBaseEntity();
    CBasePlayer *pPlayer = dynamic_cast<CBasePlayer *>(pBaseEntity);
    SH_REMOVE_HOOK_MEMFUNC(CBasePlayer, Touch, pPlayer, &g_TouchCatcher, &TouchCatcher::Touch_handler, false);

    RETURN_META(MRES_IGNORED);
}
This actually causes a pure virtual function call runtime error, but I'm assuming this is due to the incorrect header Duke pointed out so I'll convert to manual hooking. Is this the right way to go about it in principle though?
TommyV is offline
showdax
Senior Member
Join Date: Dec 2004
Old 04-05-2006 , 16:55  
Reply With Quote #7

http://dackz.net/etc/ghostchat_mm/CHybridPlugin.cpp This is how I do hooking a function on every player. It uses ClientActive and ClientDisconnect, and hooks and unhooks all existing players on Load and Unload (as well as Pause and Unpause).

You can look at other files in that directory for how I deal with offsets and all that good stuff. It also only hooks once it's configured (as in it has a usable function address) on Windows.
showdax is offline
Send a message via MSN to showdax
PM
hello, i am pm
Join Date: Jan 2004
Location: Canalization
Old 04-05-2006 , 18:27  
Reply With Quote #8

On a side note, I will maybe implement hooking a vtable entry alone; not tied to a class instance. Still not sure whether I should; because it isn't guaranteed that all instances will then be hooked; for example when inheritance is used. But I guess with clear docs that shouldn't be a problem.

I may also implement manual callclasses one day. It's just; it's just that I'm a lazy ass ;]
__________________
hello, i am pm
PM is offline
L. Duke
Veteran Member
Join Date: Apr 2005
Location: Walla Walla
Old 04-05-2006 , 18:29  
Reply With Quote #9

Not sure, but ClientConnect() may be too early. I record the client info on "player_connect" event and then wait to hook them until they have spawned in game and are alive (i.e. I don't hook them in spectator mode, I wait until they join a team and spawn.)

Looks like showdax is using ClientActive() succesfully, so maybe I should have tried that.

EDIT: Read BAILOPAN's post (about the third one down) in this thread.
L. Duke is offline
BAILOPAN
Join Date: Jan 2004
Old 04-05-2006 , 22:40  
Reply With Quote #10

Quote:
Originally Posted by PM
On a side note, I will maybe implement hooking a vtable entry alone; not tied to a class instance. Still not sure whether I should; because it isn't guaranteed that all instances will then be hooked; for example when inheritance is used. But I guess with clear docs that shouldn't be a problem.

I may also implement manual callclasses one day. It's just; it's just that I'm a lazy ass ;]
OnoTo-san: I'd say you're need a new API to do it that way, like SH_VADD for virtual. otherwise you'd break backwards compat. The reason I think it's necessary to have this functionality, is some instances are very hard to know when they're created/destroyed, so you either can't hook them or simply leak memory.
__________________
egg
BAILOPAN is offline
Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -4. The time now is 16:35.


Powered by vBulletin®
Copyright ©2000 - 2024, vBulletin Solutions, Inc.
Theme made by Freecode