Hook->HookImpl, expose "public" hook template in PLUGIN_EXPOSE

This allows us to specify the SH pointer and the Plugin ID pointer in the template, so when the plugin uses Hook<> it doesn't have to touch g_PLID or g_SHPtr.

Also added a little struct that wraps ISourceHook->xHookById methods.
This commit is contained in:
Mooshua 2024-04-24 00:05:15 -07:00
parent e391dc5b14
commit d96b3adb0a
5 changed files with 179 additions and 45 deletions

View File

@ -480,7 +480,9 @@ using namespace SourceMM;
extern SourceHook::ISourceHook *g_SHPtr; \ extern SourceHook::ISourceHook *g_SHPtr; \
extern ISmmAPI *g_SMAPI; \ extern ISmmAPI *g_SMAPI; \
extern ISmmPlugin *g_PLAPI; \ extern ISmmPlugin *g_PLAPI; \
extern PluginId g_PLID; extern PluginId g_PLID; \
template<typename Interface, auto Method, typename Result, typename... Args> \
struct Hook : public ::SourceHook::HookImpl<&g_SHPtr, &g_PLID, Interface, Method, Result, Args...> {};
/** /**
* @brief This should be the first line in your Load callback. * @brief This should be the first line in your Load callback.

View File

@ -328,6 +328,8 @@ extern PluginId g_PLID;
extern SourceHook::ISourceHook *g_SHPtr; extern SourceHook::ISourceHook *g_SHPtr;
extern SourceMM::IMetamodSourceProvider *provider; extern SourceMM::IMetamodSourceProvider *provider;
extern SourceMM::ISmmAPI *g_pMetamod; extern SourceMM::ISmmAPI *g_pMetamod;
template<typename Interface, auto Method, typename Result, typename... Args> \
struct Hook : public ::SourceHook::HookImpl<&g_SHPtr, &g_PLID, Interface, Method, Result, Args...> {};
#endif //_INCLUDE_METAMOD_SOURCE_SUPPORT_H_ #endif //_INCLUDE_METAMOD_SOURCE_SUPPORT_H_

View File

@ -44,14 +44,14 @@ static SourceProvider g_SourceProvider;
IMetamodSourceProvider* provider = &g_SourceProvider; IMetamodSourceProvider* provider = &g_SourceProvider;
auto OnGameInit = SourceHook::Hook<&g_SHPtr, IServerGameDLL, &IServerGameDLL::GameInit, bool>::Make(); auto OnGameInit = Hook<IServerGameDLL, &IServerGameDLL::GameInit, bool>::Make();
auto OnLevelInit = SourceHook::Hook<&g_SHPtr, IServerGameDLL, &IServerGameDLL::LevelInit, bool, const char*, const char*, const char*, const char*, bool, bool>::Make(); auto OnLevelInit = Hook<IServerGameDLL, &IServerGameDLL::LevelInit, bool, const char*, const char*, const char*, const char*, bool, bool>::Make();
auto OnLevelShutdown = SourceHook::Hook<&g_SHPtr, IServerGameDLL, &IServerGameDLL::LevelShutdown, void>::Make(); auto OnLevelShutdown = Hook<IServerGameDLL, &IServerGameDLL::LevelShutdown, void>::Make();
#if SOURCE_ENGINE >= SE_ORANGEBOX #if SOURCE_ENGINE >= SE_ORANGEBOX
auto OnClientCommand = SourceHook::Hook<&g_SHPtr, IServerGameClients, &IServerGameClients::ClientCommand, void, edict_t*, const CCommand&>::Make(); auto OnClientCommand = Hook<IServerGameClients, &IServerGameClients::ClientCommand, void, edict_t*, const CCommand&>::Make();
#else #else
auto OnClientCommand = SourceHook::Hook<&g_SHPtr, IServerGameClients, &IServerGameClients::ClientCommand, void, edict_t*>::Make(); auto OnClientCommand = Hook<IServerGameClients, &IServerGameClients::ClientCommand, void, edict_t*>::Make();
#endif #endif
void SourceProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, void SourceProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory,
@ -119,19 +119,19 @@ void SourceProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory,
#endif #endif
if (gameclients) { if (gameclients) {
OnClientCommand->Add(g_PLID, gameclients, false, SH_MEMBER(this, &SourceProvider::Hook_ClientCommand)); OnClientCommand->Add(gameclients, false, SH_MEMBER(this, &SourceProvider::Hook_ClientCommand));
} }
OnGameInit->Add(g_PLID, server, false, SH_MEMBER(this, &SourceProvider::Hook_GameInit)); OnGameInit->Add(server, false, SH_MEMBER(this, &SourceProvider::Hook_GameInit));
OnLevelInit->Add(g_PLID, server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelInit)); OnLevelInit->Add(server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelInit));
OnLevelShutdown->Add(g_PLID, server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown)); OnLevelShutdown->Add(server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown));
} }
void SourceProvider::Notify_DLLShutdown_Pre() void SourceProvider::Notify_DLLShutdown_Pre()
{ {
OnGameInit->Remove(g_PLID, server, false, SH_MEMBER(this, &SourceProvider::Hook_GameInit)); OnGameInit->Remove(server, false, SH_MEMBER(this, &SourceProvider::Hook_GameInit));
OnLevelInit->Remove(g_PLID, server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelInit)); OnLevelInit->Remove(server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelInit));
OnLevelShutdown->Remove(g_PLID, server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown)); OnLevelShutdown->Remove(server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown));
m_ConVarAccessor.RemoveMetamodCommands(); m_ConVarAccessor.RemoveMetamodCommands();

View File

@ -723,6 +723,61 @@ namespace SourceHook
}; };
} }
/**
* A reference to an active SourceHook
*/
struct HookInstance
{
public:
HookInstance(ISourceHook** sh, int hookid)
: SH(sh)
, _hookid(hookid)
{}
protected:
// The global pointer to the SourceHook API
ISourceHook** SH;
// The ID of this specific hook
int _hookid;
public:
/**
* @brief Returns true if the hook was successfully placed.
* @return
*/
bool Ok()
{
return _hookid != 0;
}
/**
* @brief Pause the hook, preventing it from being called until unpaused.
* @return
*/
bool Pause()
{
return (*SH)->PauseHookByID(_hookid);
}
/**
* @brief Unpause the hook if it is currently paused
* @return
*/
bool Unpause()
{
return (*SH)->UnpauseHookByID(_hookid);
}
/**
* @brief Remove the hook, permanently preventing it from being invoked.
* @return
*/
bool Remove()
{
return (*SH)->RemoveHookByID(_hookid);
}
};
/** /**
* @brief A hook manager, used to hook instances of a specific interface. * @brief A hook manager, used to hook instances of a specific interface.
@ -731,8 +786,8 @@ namespace SourceHook
* and prototype in the template arguments. Any derived class of the interface * and prototype in the template arguments. Any derived class of the interface
* can be hooked using this manager. * can be hooked using this manager.
*/ */
template<ISourceHook** SH, typename Interface, auto Method, typename Result, typename... Args> template<ISourceHook** SH, Plugin* PL, typename Interface, auto Method, typename Result, typename... Args>
struct Hook struct HookImpl
{ {
/** /**
* @brief The type we expect the template arg "Method" to be. * @brief The type we expect the template arg "Method" to be.
@ -750,15 +805,15 @@ namespace SourceHook
// Singleton instance // Singleton instance
// Initialized below! // Initialized below!
static Hook Instance; static HookImpl Instance;
private: private:
Hook(Hook& other) = delete; HookImpl(HookImpl& other) = delete;
/** /**
* @brief Build the ProtoInfo for this hook * @brief Build the ProtoInfo for this hook
*/ */
constexpr Hook() constexpr HookImpl()
{ {
// build protoinfo // build protoinfo
Proto.numOfParams = sizeof...(Args); Proto.numOfParams = sizeof...(Args);
@ -791,11 +846,11 @@ namespace SourceHook
} }
public: public:
static constexpr Hook* Make() static constexpr HookImpl* Make()
{ {
Hook::Instance = Hook(); HookImpl::Instance = HookImpl();
return &Hook::Instance; return &HookImpl::Instance;
} }
public: // Public Interface public: // Public Interface
@ -809,16 +864,18 @@ namespace SourceHook
* @param handler the handler that will be called in place of the original method * @param handler the handler that will be called in place of the original method
* @param mode the hook mode - Hook_Normal will only hook this instance, while others alter the global virtual table. * @param mode the hook mode - Hook_Normal will only hook this instance, while others alter the global virtual table.
*/ */
int Add(Plugin id, Interface* iface, bool post, Delegate handler, ::SourceHook::ISourceHook::AddHookMode mode = ISourceHook::AddHookMode::Hook_Normal) HookInstance Add(Interface* iface, bool post, Delegate handler, ::SourceHook::ISourceHook::AddHookMode mode = ISourceHook::AddHookMode::Hook_Normal)
{ {
using namespace ::SourceHook; using namespace ::SourceHook;
MemFuncInfo mfi = {true, -1, 0, 0}; MemFuncInfo mfi = {true, -1, 0, 0};
GetFuncInfo(Method, mfi); GetFuncInfo(Method, mfi);
if (mfi.thisptroffs < 0 || !mfi.isVirtual) if (mfi.thisptroffs < 0 || !mfi.isVirtual)
return false; return HookInstance(SH, 0);
CMyDelegateImpl* delegate = new CMyDelegateImpl(handler); CMyDelegateImpl* delegate = new CMyDelegateImpl(handler);
return (*SH)->AddHook(id, mode, iface, mfi.thisptroffs, Hook::HookManPubFunc, delegate, post); int id = (*SH)->AddHook(*PL, mode, iface, mfi.thisptroffs, HookImpl::HookManPubFunc, delegate, post);
return HookInstance(SH, id);
} }
/** /**
@ -829,7 +886,7 @@ namespace SourceHook
* @param post true if this was a post hook, false otherwise (pre-hook) * @param post true if this was a post hook, false otherwise (pre-hook)
* @param handler the handler that will be removed from this hook. * @param handler the handler that will be removed from this hook.
*/ */
int Remove(Plugin id, Interface* iface, bool post, Delegate handler) int Remove(Interface* iface, bool post, Delegate handler)
{ {
using namespace ::SourceHook; using namespace ::SourceHook;
MemFuncInfo mfi = {true, -1, 0, 0}; MemFuncInfo mfi = {true, -1, 0, 0};
@ -837,7 +894,7 @@ namespace SourceHook
// Temporary delegate for .IsEqual() comparison. // Temporary delegate for .IsEqual() comparison.
CMyDelegateImpl temp(handler); CMyDelegateImpl temp(handler);
return (*SH)->RemoveHook(id, iface, mfi.thisptroffs, Hook::HookManPubFunc, &temp, post); return (*SH)->RemoveHook(*PL, iface, mfi.thisptroffs, HookImpl::HookManPubFunc, &temp, post);
} }
protected: protected:
@ -855,7 +912,7 @@ namespace SourceHook
if (hi) { if (hi) {
// Build a memberfuncinfo for our hook processor. // Build a memberfuncinfo for our hook processor.
MemFuncInfo our_mfi = {true, -1, 0, 0}; MemFuncInfo our_mfi = {true, -1, 0, 0};
GetFuncInfo(&Hook::Func, our_mfi); GetFuncInfo(&HookImpl::Func, our_mfi);
void* us = reinterpret_cast<void **>(reinterpret_cast<char *>(&Instance) + our_mfi.vtbloffs)[our_mfi.vtblindex]; void* us = reinterpret_cast<void **>(reinterpret_cast<char *>(&Instance) + our_mfi.vtbloffs)[our_mfi.vtblindex];
hi->SetInfo(SH_HOOKMAN_VERSION, Instance.MFI.vtbloffs, Instance.MFI.vtblindex, &Instance.Proto, us); hi->SetInfo(SH_HOOKMAN_VERSION, Instance.MFI.vtbloffs, Instance.MFI.vtblindex, &Instance.Proto, us);
@ -908,6 +965,11 @@ namespace SourceHook
Result Result
>::type VoidSafeResult; >::type VoidSafeResult;
/**
* @brief A return type that is C++-reference-safe.
*
* Prevents us from doing silly things with byref-passed values.
*/
typedef typename ReferenceCarrier<VoidSafeResult>::type ResultType; typedef typename ReferenceCarrier<VoidSafeResult>::type ResultType;
/** /**
@ -915,6 +977,9 @@ namespace SourceHook
*/ */
virtual Result Func(Args... args) virtual Result Func(Args... args)
{ {
// Note to all ye who enter here:
// Do not, do NOT--DO NOT: touch "this" or any of our member variables.
// Hands off bucko. USE THE STATIC INSTANCE! (thisptr is undefined!!)
using namespace ::SourceHook; using namespace ::SourceHook;
void *ourvfnptr = reinterpret_cast<void *>( void *ourvfnptr = reinterpret_cast<void *>(
@ -994,8 +1059,8 @@ namespace SourceHook
// You're probably wondering what the hell this does. // You're probably wondering what the hell this does.
// https://stackoverflow.com/questions/11709859/how-to-have-static-data-members-in-a-header-only-library/11711082#11711082 // https://stackoverflow.com/questions/11709859/how-to-have-static-data-members-in-a-header-only-library/11711082#11711082
// Yes, I also happen to hate C++. // Yes, I also happen to hate C++.
template<ISourceHook** SH, typename Interface, auto Method, typename Result, typename... Args> template<ISourceHook** SH, Plugin* PL, typename Interface, auto Method, typename Result, typename... Args>
Hook<SH, Interface, Method, Result, Args...> Hook<SH, Interface, Method, Result, Args...>::Instance; HookImpl<SH, PL, Interface, Method, Result, Args...> HookImpl<SH, PL, Interface, Method, Result, Args...>::Instance;
} }

View File

@ -723,6 +723,61 @@ namespace SourceHook
}; };
} }
/**
* A reference to an active SourceHook
*/
struct HookInstance
{
public:
HookInstance(ISourceHook** sh, int hookid)
: SH(sh)
, _hookid(hookid)
{}
protected:
// The global pointer to the SourceHook API
ISourceHook** SH;
// The ID of this specific hook
int _hookid;
public:
/**
* @brief Returns true if the hook was successfully placed.
* @return
*/
bool Ok()
{
return _hookid != 0;
}
/**
* @brief Pause the hook, preventing it from being called until unpaused.
* @return
*/
bool Pause()
{
return (*SH)->PauseHookByID(_hookid);
}
/**
* @brief Unpause the hook if it is currently paused
* @return
*/
bool Unpause()
{
return (*SH)->UnpauseHookByID(_hookid);
}
/**
* @brief Remove the hook, permanently preventing it from being invoked.
* @return
*/
bool Remove()
{
return (*SH)->RemoveHookByID(_hookid);
}
};
/** /**
* @brief A hook manager, used to hook instances of a specific interface. * @brief A hook manager, used to hook instances of a specific interface.
@ -731,8 +786,8 @@ namespace SourceHook
* and prototype in the template arguments. Any derived class of the interface * and prototype in the template arguments. Any derived class of the interface
* can be hooked using this manager. * can be hooked using this manager.
*/ */
template<ISourceHook** SH, typename Interface, auto Method, typename Result, typename... Args> template<ISourceHook** SH, Plugin* PL, typename Interface, auto Method, typename Result, typename... Args>
struct Hook struct HookImpl
{ {
/** /**
* @brief The type we expect the template arg "Method" to be. * @brief The type we expect the template arg "Method" to be.
@ -750,15 +805,15 @@ namespace SourceHook
// Singleton instance // Singleton instance
// Initialized below! // Initialized below!
static Hook Instance; static HookImpl Instance;
private: private:
Hook(Hook& other) = delete; HookImpl(HookImpl& other) = delete;
/** /**
* @brief Build the ProtoInfo for this hook * @brief Build the ProtoInfo for this hook
*/ */
constexpr Hook() constexpr HookImpl()
{ {
// build protoinfo // build protoinfo
Proto.numOfParams = sizeof...(Args); Proto.numOfParams = sizeof...(Args);
@ -791,11 +846,11 @@ namespace SourceHook
} }
public: public:
static constexpr Hook* Make() static constexpr HookImpl* Make()
{ {
Hook::Instance = Hook(); HookImpl::Instance = HookImpl();
return &Hook::Instance; return &HookImpl::Instance;
} }
public: // Public Interface public: // Public Interface
@ -809,16 +864,18 @@ namespace SourceHook
* @param handler the handler that will be called in place of the original method * @param handler the handler that will be called in place of the original method
* @param mode the hook mode - Hook_Normal will only hook this instance, while others alter the global virtual table. * @param mode the hook mode - Hook_Normal will only hook this instance, while others alter the global virtual table.
*/ */
int Add(Plugin id, Interface* iface, bool post, Delegate handler, ::SourceHook::ISourceHook::AddHookMode mode = ISourceHook::AddHookMode::Hook_Normal) HookInstance Add(Interface* iface, bool post, Delegate handler, ::SourceHook::ISourceHook::AddHookMode mode = ISourceHook::AddHookMode::Hook_Normal)
{ {
using namespace ::SourceHook; using namespace ::SourceHook;
MemFuncInfo mfi = {true, -1, 0, 0}; MemFuncInfo mfi = {true, -1, 0, 0};
GetFuncInfo(Method, mfi); GetFuncInfo(Method, mfi);
if (mfi.thisptroffs < 0 || !mfi.isVirtual) if (mfi.thisptroffs < 0 || !mfi.isVirtual)
return false; return HookInstance(SH, 0);
CMyDelegateImpl* delegate = new CMyDelegateImpl(handler); CMyDelegateImpl* delegate = new CMyDelegateImpl(handler);
return (*SH)->AddHook(id, mode, iface, mfi.thisptroffs, Hook::HookManPubFunc, delegate, post); int id = (*SH)->AddHook(*PL, mode, iface, mfi.thisptroffs, HookImpl::HookManPubFunc, delegate, post);
return HookInstance(SH, id);
} }
/** /**
@ -829,7 +886,7 @@ namespace SourceHook
* @param post true if this was a post hook, false otherwise (pre-hook) * @param post true if this was a post hook, false otherwise (pre-hook)
* @param handler the handler that will be removed from this hook. * @param handler the handler that will be removed from this hook.
*/ */
int Remove(Plugin id, Interface* iface, bool post, Delegate handler) int Remove(Interface* iface, bool post, Delegate handler)
{ {
using namespace ::SourceHook; using namespace ::SourceHook;
MemFuncInfo mfi = {true, -1, 0, 0}; MemFuncInfo mfi = {true, -1, 0, 0};
@ -837,7 +894,7 @@ namespace SourceHook
// Temporary delegate for .IsEqual() comparison. // Temporary delegate for .IsEqual() comparison.
CMyDelegateImpl temp(handler); CMyDelegateImpl temp(handler);
return (*SH)->RemoveHook(id, iface, mfi.thisptroffs, Hook::HookManPubFunc, &temp, post); return (*SH)->RemoveHook(*PL, iface, mfi.thisptroffs, HookImpl::HookManPubFunc, &temp, post);
} }
protected: protected:
@ -855,7 +912,7 @@ namespace SourceHook
if (hi) { if (hi) {
// Build a memberfuncinfo for our hook processor. // Build a memberfuncinfo for our hook processor.
MemFuncInfo our_mfi = {true, -1, 0, 0}; MemFuncInfo our_mfi = {true, -1, 0, 0};
GetFuncInfo(&Hook::Func, our_mfi); GetFuncInfo(&HookImpl::Func, our_mfi);
void* us = reinterpret_cast<void **>(reinterpret_cast<char *>(&Instance) + our_mfi.vtbloffs)[our_mfi.vtblindex]; void* us = reinterpret_cast<void **>(reinterpret_cast<char *>(&Instance) + our_mfi.vtbloffs)[our_mfi.vtblindex];
hi->SetInfo(SH_HOOKMAN_VERSION, Instance.MFI.vtbloffs, Instance.MFI.vtblindex, &Instance.Proto, us); hi->SetInfo(SH_HOOKMAN_VERSION, Instance.MFI.vtbloffs, Instance.MFI.vtblindex, &Instance.Proto, us);
@ -908,6 +965,11 @@ namespace SourceHook
Result Result
>::type VoidSafeResult; >::type VoidSafeResult;
/**
* @brief A return type that is C++-reference-safe.
*
* Prevents us from doing silly things with byref-passed values.
*/
typedef typename ReferenceCarrier<VoidSafeResult>::type ResultType; typedef typename ReferenceCarrier<VoidSafeResult>::type ResultType;
/** /**
@ -915,6 +977,9 @@ namespace SourceHook
*/ */
virtual Result Func(Args... args) virtual Result Func(Args... args)
{ {
// Note to all ye who enter here:
// Do not, do NOT--DO NOT: touch "this" or any of our member variables.
// Hands off bucko. USE THE STATIC INSTANCE! (thisptr is undefined!!)
using namespace ::SourceHook; using namespace ::SourceHook;
void *ourvfnptr = reinterpret_cast<void *>( void *ourvfnptr = reinterpret_cast<void *>(
@ -994,8 +1059,8 @@ namespace SourceHook
// You're probably wondering what the hell this does. // You're probably wondering what the hell this does.
// https://stackoverflow.com/questions/11709859/how-to-have-static-data-members-in-a-header-only-library/11711082#11711082 // https://stackoverflow.com/questions/11709859/how-to-have-static-data-members-in-a-header-only-library/11711082#11711082
// Yes, I also happen to hate C++. // Yes, I also happen to hate C++.
template<ISourceHook** SH, typename Interface, auto Method, typename Result, typename... Args> template<ISourceHook** SH, Plugin* PL, typename Interface, auto Method, typename Result, typename... Args>
Hook<SH, Interface, Method, Result, Args...> Hook<SH, Interface, Method, Result, Args...>::Instance; HookImpl<SH, PL, Interface, Method, Result, Args...> HookImpl<SH, PL, Interface, Method, Result, Args...>::Instance;
} }