PDA

View Full Version : Planned SourceHook changes


PM
05-10-2007, 13:21
Hello!

We are planning some SourceHook changes for SourceMM 1.5.

First of all, all hooks now have an id. Consequently, the SH_ADD_HOOK macros return a non-zero integral hook id. A return value of 0 means failure.
This change of the return value (int->bool) may lead to warnings on some platforms if you are saving the return value in a bool variable.

Secondly, the macros have changed:
Now we have:

SH_ADD_HOOK(class name, function name, instance pointer, handler, post or not?)
SH_ADD_VPHOOK(class name, function name, representative instance pointer, handler, post or not?)
SH_ADD_DVPHOOK(class name, function name, vtable pointer, handler, post or not?)
SH_ADD_MANUALHOOK(manual hook name, instance pointer, handler, post or not?)
SH_ADD_MANUALVPHOOK(manual hook name, representative instance pointer, handler, post or not?)
SH_ADD_MANUALDVPHOOK(manual hook name, vtable pointer, handler, post or not?)


handler can be either SH_STATIC(non-member function / static member function) or SH_MEMBER(instance, &Class::Function)

Of course, the old macros with _STATICFUNC / _MEMFUNC will continue working. I just wanted to get rid of that because otherwise we'd have 16 ADD_HOOK_ macros now. I like the new way much more.

There is also SH_REMOVE_HOOK_ID which removes a hook by id. This means that you can store the return value of the SH_ADD_*HOOK macro and then pass this to SH_REMOVE_HOOK_ID. The old SH_REMOVE_HOOK macros continue working as well - however, SH_REMOVE_HOOK_ID is the only way to remove VP hooks and manual VP hooks.

Also note that now it is safe to remove hooks even on instances which have been already deleted.

VP hooks behave like normal hooks - except for the fact that they are not bound to a specific instance of the class but are called everytime the Virtual function Pointer (that's why they are called VP) gets executed. The "representative instance pointer" is a pointer to an instance of the class on whose vtable you want to install the hook. I think it's easiest to see this on a code example:


class Base
{
public:
virtual void Function() = 0;
};

class Derived1 : public Base
{
public:
void Function()
{
// Function 1
}
};

class Derived2 : public Base
{
public:
void Function()
{
// Function 2
}
};

void HookFunctionNormal()
{
printf("Normal hook Called!\n");
}

void HookFunctionVP()
{
printf("VP hook Called!\n");
}

void Test()
{
// Both Derived1 and Derived2 implement Function()

Derived1 d1_instance1;
Derived1 d1_instance2;
Derived2 d2_instance1;

Base *pd1_instance1 = &d1_instance1;
Base *pd1_instance2 = &d1_instance2;
Base *pd2_instance1 = &d2_instance1;

// Now add a normal hook on d1_instance1
int hook1 = SH_ADD_HOOK(Base, Function, pd1_instance1, SH_STATIC(HookFunctionNormal), false);

// And add a VP hook.
int hook2 = SH_ADD_VPHOOK(Base, Function, pd1_instance1, SH_STATIC(HookFunctionVP), false);

// Try to call some functions.
pd1_instance1->Function(); // Prints "VP hook Called", then "Normal hook called"

pd1_instance2->Function(); // Only prints "VP hook Called"

pd2_instance1->Function(); // Doesn't print anything.

SH_REMOVE_HOOK_ID(hook1);
SH_REMOVE_HOOK_ID(hook2);

pd1_instance1->Function(); // Prints nothing
pd1_instance2->Function(); // Prints nothing
pd2_instance1->Function(); // Prints nothing
}

(no, I haven't even compiled this, but it should work)

So, you see, normal hooks are only executed on one specific instance; VP hooks are executed on all instances of a class (attention; if you have a magic pointer from somewhere, and this pointer is in fact a DERIVED class of what you think it is, it has its own vtable and thus VP hooks on what you think it is aren't called on it!).

Alternatively, you can use "direct vp hooks" (SH_ADD_DVPHOOK or SH_ADD_MANUALDVPHOOK): here, you give sourcemod a pointer to the vtable instead of a representative instance pointer. This lets you compute the vtable address using an offset without having a real instance. Note that SH expects a pointer to the beginning of the vtable - it then still adds vtable index * sizeof(void*) to it.

It's also pretty important to note that vp hooks are called in the order they were installed (which is not necessarily the order of their hook ids - ids get reused), but ALWAYS before SH starts calling normal hooks. This is also the reason why the above example first prints "VP hook called" even though the VP hook was the second declared hook.

There's also one very important change: The concept of SH_CALL and callclasses was bound to interfaces. This means that it did not work anymore for VP hooks.
As of today, CallClasses are NOT used anymore. SH_CALL's first parameter is now the this pointer. You do NOT need to request and release a call class amymore. I've done some template and macro magic in order to make the new system source compatible (so the old code with SH_GET_CALLCLASS etc. will compile, but it is completely unnecessary and I'd encourage you to convert the code to the new method). Also, the ISourceHook interface continues to provide support for CallClasses so that old plugins should still work (however, if a new plugin uses a VP hook, old plugins might cause the hook to be called even if using SH_CALL).

In order to use this, you will need the newest sourcehook.h from SVN (http://svn.alliedmods.net/viewvc.cgi/trunk/sourcehook/sourcehook.h?root=sourcemm&view=co). Plugins compiled with this file need a new version of sourcemm, which you can get in my testing post in this forum. Before we release this, we need to be sure that it's backwards compatible with older plugins.

So, have fun with it :)

L. Duke
05-10-2007, 18:15
nice!

This will make hooking player functions much easier!

I assume each weapon "type" will have to be hooked separately though (USP, AWP, etc.)?

BAILOPAN
05-11-2007, 09:06
Yes, because each one has a different vtable.

PM
05-12-2007, 05:29
Edited the above post. Re-read please, look for "direct VP hooks" and "new SH_CALL".

Note that I'll have to wait for bail to come back so that he can run the build scripts before there will be a new test binary which supports the new SH_CALL and direct VP hooks in the other post.

c0ldfyr3
05-13-2007, 10:17
Can someone let me know when they've successfully implemented this? This looks really neat as in a few of my plugins I hook a function for all clients and this would make it a lot easier than hooking and unhooking each client as they connect.

I have one question though, if my understanding is correct, if i hook a CBE function it will be hooked on all clients that connect without any interaction from me after the initial hook? Secondly, as Duke asked about hooking each weapon separately, if I hook one instance of CBaseCombatKnife (not sure that's correct but u know what I'm talking about), will it hook all instances of the knife that will be created during the game without further interaction?

Thanks!

BAILOPAN
05-13-2007, 11:12
It has all been implemented as of revision 396 in the sourcemm nightly builds.

L. Duke
05-13-2007, 11:17
That's my understanding. The "CBE function" on the player would be on CCSPlayer and catch all clients, and "CBaseCombatKnife" would work on all of the knives.

I haven't had time to test this yet, but I hope to soon as I hook every player and weapon in a couple of my plugins.

c0ldfyr3
05-14-2007, 05:50
Ok excellent, Duke can you let me know when you've successfully tested it? I know you use pretty much the same methods as I do in ZM so if it works for you it'll work for me ;)

PM
05-14-2007, 13:46
That's my understanding. The "CBE function" on the player would be on CCSPlayer and catch all clients, and "CBaseCombatKnife" would work on all of the knives.

I haven't had time to test this yet, but I hope to soon as I hook every player and weapon in a couple of my plugins.

Are the classes derived from CBaseCombatKnife? (that's what the "Base" thing suggests)

If yes, you might have to hook each derived class' vtable separately.
The easiest way to find out is to try it out :)

mooman2
05-30-2007, 01:24
PM: How do you configure which vfunc index you want to hook when using SH_ADD_MANUALDVPHOOK? I tried using MANUALHOOK_RECONFIGURE but that gave me random crashes. I passed into that stuff a pointer to the vtable obtained from dlsym() and the vfunc index. Or am I just getting all of this wrong?

edit: BTW, you need to add testvphooks.cpp to the test suite vcproj :p

PM
05-30-2007, 08:14
Hello dear human being,

if I recall correctly, SH_MANUALHOOK_RECONFIGURE expects a vtable index, a vtable offset, and a this pointer offset; not a vtable pointer.


I passed into that stuff a pointer to the vtable obtained from dlsym() and the vfunc index.
Ah, so you have a vtable pointer and an index. And you want a VP hook. In this case, I guess this is the correct way to do it:


// First, declare a manual hook
SH_DECL_MANUALHOOK*(MyHook, [your vtable index here], 0, 0 [, possibly parameters and rettype etc] );

// If you do not know the vtable index at compile-time, pass some dummy value (probably something big like 0xdeadbeef so that you get immediate crashes if you forget to set it later) to SH_DECL_MANUALHOOK and later do:
// SH_MANUALHOOK_RECONFIGURE(MyHook, [your vtable index], 0, 0);
// do NOT pass vtable pointers to SH_MANUALHOOK_RECONFIGURE !

// Then, retreive the vtable pointer, and do this:
int hookid = SH_ADD_MANUALDVPHOOK(MyHook, [your vtable pointer here], [your handler], [pre/post]);

If that gives you crashes, please post / private message the relevant parts of your code as it may be a pretty serious bug :)

Thank you very much,
PM

EDIT:
Ah, yes, you are right, testvphooks.cpp is only in the MSVC 7.1 project file; I won't add it to the 8.0 file because I don't have a Visual Studio version which could open such files and I'm also afraid of editing them manually. If you could send me the original 8.0 file with added testvphooks.cpp, I'd be very thankful :)

mooman2
05-30-2007, 17:33
Ok, I'll try passing in the vfunc index at compile time rather then at load. :)

And here's the VS8.0 vcproj including testvphooks.cpp

PM
05-30-2007, 17:37
Ok, I'll try passing in the vfunc index at compile time rather then at load. :)

And here's the VS8.0 vcproj including testvphooks.cpp

:O shouldn't be a problem; could you post your original DECL and RECONFIGURE and ADD_HOOK lines please? :)

mooman2
05-30-2007, 18:08
Ok, I just tried again with passing the index in manually and it still didn't work.

On the mod Zombie Master, I tried this originally and it crashed whenever I shot a zombie:


SH_DECL_MANUALHOOK2(BecomeRagdoll_hook, 0, 0, 0, bool, CTakeDamageInfo const&, Vector const&);

SH_MANUALHOOK_RECONFIGURE(BecomeRagdoll_hook, idxBecomeRagdoll, 0, 0);

BecomeRagdollHook = SH_ADD_MANUALDVPHOOK(BecomeRagdoll_hook, vtblCNPC_BaseZombie, SH_STATIC(BecomeRagdoll_handler), false);

If it also interests you, I tried hooking CBasePlayer::Weapon_Equip using what you told me to do in the latest post and that also crashed whenever I shot a zombie.

EDIT: It could be a problem with sourcehook backwards compatability since I'm also using deprecated methods to hook stuff DispatchEffect.

RE-EDIT: Ok, I definitely think it's a problem with sourcehook backwards compatability. CentOS server is running MM:S 1.5. I add a plugin that uses 1.4 methods to hook ITempEntsSystem:: DispatchEffect. It crashes whenever I shoot something that uses DispatchEffect. Now I comment out the DispatchEffect hooks and reload the plugin and it doesn't crash.

YET ANOTHER EDIT: I figured out its not the hooks causing the crash, but deprecated callclasses which I think are supposed to still be backwards-compatible?

EDIT #2415: after fixing the crash and trying a lot of random stuff, I still can't get DVPHOOK to work. Does it really not need a pointer to an already existing instance?

PM
05-31-2007, 04:29
Ok, I just tried again with passing the index in manually and it still didn't work.

On the mod Zombie Master, I tried this originally and it crashed whenever I shot a zombie:


SH_DECL_MANUALHOOK2(BecomeRagdoll_hook, 0, 0, 0, bool, CTakeDamageInfo const&, Vector const&);

SH_MANUALHOOK_RECONFIGURE(BecomeRagdoll_hook, idxBecomeRagdoll, 0, 0);

BecomeRagdollHook = SH_ADD_MANUALDVPHOOK(BecomeRagdoll_hook, vtblCNPC_BaseZombie, SH_STATIC(BecomeRagdoll_handler), false);

If it also interests you, I tried hooking CBasePlayer::Weapon_Equip using what you told me to do in the latest post and that also crashed whenever I shot a zombie.

EDIT: It could be a problem with sourcehook backwards compatability since I'm also using deprecated methods to hook stuff DispatchEffect.

RE-EDIT: Ok, I definitely think it's a problem with sourcehook backwards compatability. CentOS server is running MM:S 1.5. I add a plugin that uses 1.4 methods to hook ITempEntsSystem:: DispatchEffect. It crashes whenever I shoot something that uses DispatchEffect. Now I comment out the DispatchEffect hooks and reload the plugin and it doesn't crash.

YET ANOTHER EDIT: I figured out its not the hooks causing the crash, but deprecated callclasses which I think are supposed to still be backwards-compatible?

EDIT #2415: after fixing the crash and trying a lot of random stuff, I still can't get DVPHOOK to work. Does it really not need a pointer to an already existing instance?

Whoa, that doesn't sound healhty.
I'll try to reproduce your situation here, so I'll have to ask some questions in order to get a precise picture of what is going on :)

Are you running only this plugin? Or also other plugins (which hook the function in question?)

Does your plugin use deprecated callclasses (SH_GET_CALLCLASS and stuff)? And then SH_CALL on them crashes? Do you do this when there is a hook on the function? Also, is the plugin which causes the callclass-related crashes compiled against 1.5 or 1.4 headers? Thanks :)
EDIT: Does it crash if you have a plugin running which "uses 1.4 methods to hook DispatchEffect"? Was that plugin compiled against the 1.4 SDK? Or was it recompiled? (it is important that not-recompiled plugins don't crash) Or does it only crash if you have BOTH your new plugin and an old plugin running?

It could be possible that I've made some error when writing those source backwards compat quirks.... I'm not sure yet, I'll have to test it.

However it worries me that you can't get VP hooks working :-O
There are two kinds of VP hooks:

normal VP hooks expect a pointer to an already existing instance (internally, they add the thispointeroffset and vtable offset to the this pointer, then dereference this computer "vtable pointer pointer" and use that as a direct VP hook. the thispointeroffset and vtable offset are either given manually (manual hook) or are discovered automatically from a member function pointer (non-manual hook) ).

direct VP hooks expect a pointer to the vtable. They only do vtable[vtblidx] in order to refer to the virtual function pointer then. This means that vtblidx is not the offset from the vtable beginning, but the offset / sizeof(void*), but you probably know that anyway.

/* It is important that the function/"method" you want to hook is called through this vtable (ie. it is possible that you hook a function in one vtable; but a derived class has its own vtable and thus when someone calls baseClassPointer->Function() and baseClassPointer is in fact a pointer to a derived class which overrides one or more functions from the base class (and it doesn't matter if the function is the function you are hooking or not) your hook will not be called). But you are probably doing that correctly as BaseZombie doesn't appear to be the lowest base class having a virtual BecomeRagdoll member. */ (EDIT2: I took a look at the SDK and found out that what I wrote here was unimportant for this discussion)

The source-level backwards compatibility callclasses are only template hacks so that old plugins compile without changing code; basically SH_GET_CALLCLASS stores the this pointer in some fake callclass instance and then SH_CALL extracts it again. So it should be the same as calling SH_CALL with the this pointer directly unless I've made some fault on the way.

Sorry for my confusion, it's early in the morning ;)

mooman2
05-31-2007, 10:27
Whoa, that doesn't sound healhty.
I'll try to reproduce your situation here, so I'll have to ask some questions in order to get a precise picture of what is going on :)

Are you running only this plugin? Or also other plugins (which hook the function in question?)

Does your plugin use deprecated callclasses (SH_GET_CALLCLASS and stuff)? And then SH_CALL on them crashes? Do you do this when there is a hook on the function? Also, is the plugin which causes the callclass-related crashes compiled against 1.5 or 1.4 headers? Thanks :)
EDIT: Does it crash if you have a plugin running which "uses 1.4 methods to hook DispatchEffect"? Was that plugin compiled against the 1.4 SDK? Or was it recompiled? (it is important that not-recompiled plugins don't crash) Or does it only crash if you have BOTH your new plugin and an old plugin running?

It could be possible that I've made some error when writing those source backwards compat quirks.... I'm not sure yet, I'll have to test it.

However it worries me that you can't get VP hooks working :-O
There are two kinds of VP hooks:

normal VP hooks expect a pointer to an already existing instance (internally, they add the thispointeroffset and vtable offset to the this pointer, then dereference this computer "vtable pointer pointer" and use that as a direct VP hook. the thispointeroffset and vtable offset are either given manually (manual hook) or are discovered automatically from a member function pointer (non-manual hook) ).

direct VP hooks expect a pointer to the vtable. They only do vtable[vtblidx] in order to refer to the virtual function pointer then. This means that vtblidx is not the offset from the vtable beginning, but the offset / sizeof(void*), but you probably know that anyway.

/* It is important that the function/"method" you want to hook is called through this vtable (ie. it is possible that you hook a function in one vtable; but a derived class has its own vtable and thus when someone calls baseClassPointer->Function() and baseClassPointer is in fact a pointer to a derived class which overrides one or more functions from the base class (and it doesn't matter if the function is the function you are hooking or not) your hook will not be called). But you are probably doing that correctly as BaseZombie doesn't appear to be the lowest base class having a virtual BecomeRagdoll member. */ (EDIT2: I took a look at the SDK and found out that what I wrote here was unimportant for this discussion)

The source-level backwards compatibility callclasses are only template hacks so that old plugins compile without changing code; basically SH_GET_CALLCLASS stores the this pointer in some fake callclass instance and then SH_CALL extracts it again. So it should be the same as calling SH_CALL with the this pointer directly unless I've made some fault on the way.

Sorry for my confusion, it's early in the morning ;)

I am currently running BAT, one other plugin compiled for 1.4, and this plugin.

This is the only plugin that hooks this stuff.

Yeah, my plugin uses SH_GET_CALLCLASS, but that doesn't make it crash- it's when SH_CALL's first parameter is that callclass. To fix it I changed it to the actual interface.

Yes, I do this inside a hook.

I compiled this against 1.5 headers.

EDIT: Does it crash if you have a plugin running which "uses 1.4 methods to hook DispatchEffect"? Was that plugin compiled against the 1.4 SDK? Or was it recompiled? (it is important that not-recompiled plugins don't crash) Or does it only crash if you have BOTH your new plugin and an old plugin running?I can't remember exactly, but I have old versions compiled against 1.4 which do the same thing and I'll see if they crash on 1.5.

What do you suggest I do to try and fix it?