From d96b3adb0aae4f6bf0a966f98f921e4d0cde24a6 Mon Sep 17 00:00:00 2001 From: Mooshua <43320783+mooshua@users.noreply.github.com> Date: Wed, 24 Apr 2024 00:05:15 -0700 Subject: [PATCH] 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. --- core/ISmmPlugin.h | 4 +- core/metamod_provider.h | 2 + core/provider/source/provider_source.cpp | 24 +++--- core/sourcehook/generate/sourcehook.hxx | 97 ++++++++++++++++++++---- core/sourcehook/sourcehook.h | 97 ++++++++++++++++++++---- 5 files changed, 179 insertions(+), 45 deletions(-) diff --git a/core/ISmmPlugin.h b/core/ISmmPlugin.h index f0d185d7..1b73b059 100644 --- a/core/ISmmPlugin.h +++ b/core/ISmmPlugin.h @@ -480,7 +480,9 @@ using namespace SourceMM; extern SourceHook::ISourceHook *g_SHPtr; \ extern ISmmAPI *g_SMAPI; \ extern ISmmPlugin *g_PLAPI; \ - extern PluginId g_PLID; + extern PluginId g_PLID; \ + template \ + struct Hook : public ::SourceHook::HookImpl<&g_SHPtr, &g_PLID, Interface, Method, Result, Args...> {}; /** * @brief This should be the first line in your Load callback. diff --git a/core/metamod_provider.h b/core/metamod_provider.h index 2c12a2e4..576077f8 100644 --- a/core/metamod_provider.h +++ b/core/metamod_provider.h @@ -328,6 +328,8 @@ extern PluginId g_PLID; extern SourceHook::ISourceHook *g_SHPtr; extern SourceMM::IMetamodSourceProvider *provider; extern SourceMM::ISmmAPI *g_pMetamod; +template \ +struct Hook : public ::SourceHook::HookImpl<&g_SHPtr, &g_PLID, Interface, Method, Result, Args...> {}; #endif //_INCLUDE_METAMOD_SOURCE_SUPPORT_H_ diff --git a/core/provider/source/provider_source.cpp b/core/provider/source/provider_source.cpp index ff44bd05..21b01223 100644 --- a/core/provider/source/provider_source.cpp +++ b/core/provider/source/provider_source.cpp @@ -44,14 +44,14 @@ static SourceProvider g_SourceProvider; IMetamodSourceProvider* provider = &g_SourceProvider; -auto OnGameInit = SourceHook::Hook<&g_SHPtr, 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 OnLevelShutdown = SourceHook::Hook<&g_SHPtr, IServerGameDLL, &IServerGameDLL::LevelShutdown, void>::Make(); +auto OnGameInit = Hook::Make(); +auto OnLevelInit = Hook::Make(); +auto OnLevelShutdown = Hook::Make(); #if SOURCE_ENGINE >= SE_ORANGEBOX -auto OnClientCommand = SourceHook::Hook<&g_SHPtr, IServerGameClients, &IServerGameClients::ClientCommand, void, edict_t*, const CCommand&>::Make(); +auto OnClientCommand = Hook::Make(); #else -auto OnClientCommand = SourceHook::Hook<&g_SHPtr, IServerGameClients, &IServerGameClients::ClientCommand, void, edict_t*>::Make(); +auto OnClientCommand = Hook::Make(); #endif void SourceProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, @@ -119,19 +119,19 @@ void SourceProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, #endif 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)); - OnLevelInit->Add(g_PLID, server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelInit)); - OnLevelShutdown->Add(g_PLID, server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown)); + OnGameInit->Add(server, false, SH_MEMBER(this, &SourceProvider::Hook_GameInit)); + OnLevelInit->Add(server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelInit)); + OnLevelShutdown->Add(server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown)); } void SourceProvider::Notify_DLLShutdown_Pre() { - OnGameInit->Remove(g_PLID, server, false, SH_MEMBER(this, &SourceProvider::Hook_GameInit)); - OnLevelInit->Remove(g_PLID, server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelInit)); - OnLevelShutdown->Remove(g_PLID, server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown)); + OnGameInit->Remove(server, false, SH_MEMBER(this, &SourceProvider::Hook_GameInit)); + OnLevelInit->Remove(server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelInit)); + OnLevelShutdown->Remove(server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown)); m_ConVarAccessor.RemoveMetamodCommands(); diff --git a/core/sourcehook/generate/sourcehook.hxx b/core/sourcehook/generate/sourcehook.hxx index 919f4d78..f98c8786 100755 --- a/core/sourcehook/generate/sourcehook.hxx +++ b/core/sourcehook/generate/sourcehook.hxx @@ -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. @@ -731,8 +786,8 @@ namespace SourceHook * and prototype in the template arguments. Any derived class of the interface * can be hooked using this manager. */ - template - struct Hook + template + struct HookImpl { /** * @brief The type we expect the template arg "Method" to be. @@ -750,15 +805,15 @@ namespace SourceHook // Singleton instance // Initialized below! - static Hook Instance; + static HookImpl Instance; private: - Hook(Hook& other) = delete; + HookImpl(HookImpl& other) = delete; /** * @brief Build the ProtoInfo for this hook */ - constexpr Hook() + constexpr HookImpl() { // build protoinfo Proto.numOfParams = sizeof...(Args); @@ -791,11 +846,11 @@ namespace SourceHook } public: - static constexpr Hook* Make() + static constexpr HookImpl* Make() { - Hook::Instance = Hook(); + HookImpl::Instance = HookImpl(); - return &Hook::Instance; + return &HookImpl::Instance; } public: // Public Interface @@ -809,16 +864,18 @@ namespace SourceHook * @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. */ - 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; MemFuncInfo mfi = {true, -1, 0, 0}; GetFuncInfo(Method, mfi); if (mfi.thisptroffs < 0 || !mfi.isVirtual) - return false; + return HookInstance(SH, 0); 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 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; MemFuncInfo mfi = {true, -1, 0, 0}; @@ -837,7 +894,7 @@ namespace SourceHook // Temporary delegate for .IsEqual() comparison. 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: @@ -855,7 +912,7 @@ namespace SourceHook if (hi) { // Build a memberfuncinfo for our hook processor. MemFuncInfo our_mfi = {true, -1, 0, 0}; - GetFuncInfo(&Hook::Func, our_mfi); + GetFuncInfo(&HookImpl::Func, our_mfi); void* us = reinterpret_cast(reinterpret_cast(&Instance) + our_mfi.vtbloffs)[our_mfi.vtblindex]; hi->SetInfo(SH_HOOKMAN_VERSION, Instance.MFI.vtbloffs, Instance.MFI.vtblindex, &Instance.Proto, us); @@ -908,6 +965,11 @@ namespace SourceHook Result >::type VoidSafeResult; + /** + * @brief A return type that is C++-reference-safe. + * + * Prevents us from doing silly things with byref-passed values. + */ typedef typename ReferenceCarrier::type ResultType; /** @@ -915,6 +977,9 @@ namespace SourceHook */ 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; void *ourvfnptr = reinterpret_cast( @@ -994,8 +1059,8 @@ namespace SourceHook // 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 // Yes, I also happen to hate C++. - template - Hook Hook::Instance; + template + HookImpl HookImpl::Instance; } diff --git a/core/sourcehook/sourcehook.h b/core/sourcehook/sourcehook.h index 398fa95c..04964037 100644 --- a/core/sourcehook/sourcehook.h +++ b/core/sourcehook/sourcehook.h @@ -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. @@ -731,8 +786,8 @@ namespace SourceHook * and prototype in the template arguments. Any derived class of the interface * can be hooked using this manager. */ - template - struct Hook + template + struct HookImpl { /** * @brief The type we expect the template arg "Method" to be. @@ -750,15 +805,15 @@ namespace SourceHook // Singleton instance // Initialized below! - static Hook Instance; + static HookImpl Instance; private: - Hook(Hook& other) = delete; + HookImpl(HookImpl& other) = delete; /** * @brief Build the ProtoInfo for this hook */ - constexpr Hook() + constexpr HookImpl() { // build protoinfo Proto.numOfParams = sizeof...(Args); @@ -791,11 +846,11 @@ namespace SourceHook } public: - static constexpr Hook* Make() + static constexpr HookImpl* Make() { - Hook::Instance = Hook(); + HookImpl::Instance = HookImpl(); - return &Hook::Instance; + return &HookImpl::Instance; } public: // Public Interface @@ -809,16 +864,18 @@ namespace SourceHook * @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. */ - 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; MemFuncInfo mfi = {true, -1, 0, 0}; GetFuncInfo(Method, mfi); if (mfi.thisptroffs < 0 || !mfi.isVirtual) - return false; + return HookInstance(SH, 0); 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 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; MemFuncInfo mfi = {true, -1, 0, 0}; @@ -837,7 +894,7 @@ namespace SourceHook // Temporary delegate for .IsEqual() comparison. 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: @@ -855,7 +912,7 @@ namespace SourceHook if (hi) { // Build a memberfuncinfo for our hook processor. MemFuncInfo our_mfi = {true, -1, 0, 0}; - GetFuncInfo(&Hook::Func, our_mfi); + GetFuncInfo(&HookImpl::Func, our_mfi); void* us = reinterpret_cast(reinterpret_cast(&Instance) + our_mfi.vtbloffs)[our_mfi.vtblindex]; hi->SetInfo(SH_HOOKMAN_VERSION, Instance.MFI.vtbloffs, Instance.MFI.vtblindex, &Instance.Proto, us); @@ -908,6 +965,11 @@ namespace SourceHook Result >::type VoidSafeResult; + /** + * @brief A return type that is C++-reference-safe. + * + * Prevents us from doing silly things with byref-passed values. + */ typedef typename ReferenceCarrier::type ResultType; /** @@ -915,6 +977,9 @@ namespace SourceHook */ 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; void *ourvfnptr = reinterpret_cast( @@ -994,8 +1059,8 @@ namespace SourceHook // 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 // Yes, I also happen to hate C++. - template - Hook Hook::Instance; + template + HookImpl HookImpl::Instance; }