From 50772ffc4628fc8dad1516dff0e607e6383e888b Mon Sep 17 00:00:00 2001 From: Mooshua <43320783+mooshua@users.noreply.github.com> Date: Wed, 24 Apr 2024 19:48:14 -0700 Subject: [PATCH] Introduce varardic hook macros This is a large refactor that splits our mega-template into a few smaller ones. First off, the PLUGIN_GLOBALVARS() helpers were put in the SourceHook namespace. - HookHandlerImpl: Responsible for the lowered delegates (post-vafmt), can be used independently of other templates added here. Relies on parent HookManager class to handle the unlowered original invocation logic. (As a template parameter) - HookCoreImpl: Adds a public interface & glue layer beterrn managers and HookHandlerImpl - HookImpl: non-varardic hook manager - FmtHookImpl: format-string hook manager FmtHookImpl was tested by hooking IVEngineServer::ClientCommand. --- core/ISmmPlugin.h | 24 +- core/metamod_provider.h | 13 +- core/provider/source/provider_source.cpp | 10 +- core/sourcehook/generate/sourcehook.hxx | 626 ++++++++++++++++------- core/sourcehook/sourcehook.h | 626 ++++++++++++++++------- 5 files changed, 891 insertions(+), 408 deletions(-) diff --git a/core/ISmmPlugin.h b/core/ISmmPlugin.h index 1b73b05..d8d849d 100644 --- a/core/ISmmPlugin.h +++ b/core/ISmmPlugin.h @@ -475,14 +475,24 @@ using namespace SourceMM; /** * @brief This should be in one of your header files, if you wish * to use values like g_SHPtr in other files. + * This also creates helpers for accessing the SourceHook templates. */ -#define PLUGIN_GLOBALVARS() \ - extern SourceHook::ISourceHook *g_SHPtr; \ - extern ISmmAPI *g_SMAPI; \ - extern ISmmPlugin *g_PLAPI; \ - extern PluginId g_PLID; \ - template \ - struct Hook : public ::SourceHook::HookImpl<&g_SHPtr, &g_PLID, Interface, Method, Result, Args...> {}; +#define PLUGIN_GLOBALVARS() \ + extern SourceHook::ISourceHook *g_SHPtr; \ + extern ISmmAPI *g_SMAPI; \ + extern ISmmPlugin *g_PLAPI; \ + extern PluginId g_PLID; \ + namespace SourceHook \ + { \ + template \ + struct Hook : public ::SourceHook::HookImpl<&g_SHPtr, &g_PLID, Interface, Method, Result, Args...> \ + { \ + }; \ + template \ + struct FmtHook : public ::SourceHook::FmtHookImpl<&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 576077f..d326edd 100644 --- a/core/metamod_provider.h +++ b/core/metamod_provider.h @@ -328,8 +328,17 @@ 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...> {}; +namespace SourceHook +{ + template + struct Hook : public ::SourceHook::HookImpl<&g_SHPtr, &g_PLID, Interface, Method, Result, Args...> + { + }; + template + struct FmtHook : public ::SourceHook::FmtHookImpl<&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 21b0122..3a9f124 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 = Hook::Make(); -auto OnLevelInit = Hook::Make(); -auto OnLevelShutdown = Hook::Make(); +auto OnGameInit = SourceHook::Hook::Make(); +auto OnLevelInit = SourceHook::Hook::Make(); +auto OnLevelShutdown = SourceHook::Hook::Make(); #if SOURCE_ENGINE >= SE_ORANGEBOX -auto OnClientCommand = Hook::Make(); +auto OnClientCommand = SourceHook::Hook::Make(); #else -auto OnClientCommand = Hook::Make(); +auto OnClientCommand = SourceHook::Hook::Make(); #endif void SourceProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, diff --git a/core/sourcehook/generate/sourcehook.hxx b/core/sourcehook/generate/sourcehook.hxx index f98c878..067fc9d 100755 --- a/core/sourcehook/generate/sourcehook.hxx +++ b/core/sourcehook/generate/sourcehook.hxx @@ -116,6 +116,7 @@ #define SH_PTRSIZE sizeof(void*) +#include #include #include "sh_memfuncinfo.h" #include "FastDelegate.h" @@ -665,6 +666,10 @@ namespace SourceHook * Original - call the original method and safely handle the return type * Dereference - dereference the return type pointer for hook return semantics * + * OriginalRaised - a little special, we call a static func from the parent hook + * manager class to raise lowered arguments passed to the core delegates. This is + * used when the core delegates receive different args than the root proto (eg, varargs!) + * */ template struct BaseMethodInvoker @@ -692,6 +697,13 @@ namespace SourceHook (self->*mfp)(args...); } + template + static void OriginalRaised( void (*Invoker)(Self, Mfp, Args...), Self self, Mfp mfp, void* result, Args... args ) + { + // Do not touch return type: It's void! + Invoker(self, mfp, args...); + } + static void Dereference(const void* arg) { return; @@ -716,11 +728,206 @@ namespace SourceHook *result = (self->*mfp)(args...); } + template + static void OriginalRaised( Result (*Invoker)(Self, Mfp, Args...), Self self, Mfp mfp, Result* result, Args... args ) + { + *result = Invoker(self, mfp, args...); + } + static Result Dereference(const Result* arg) { return *arg; } }; + + /** + * @brief defines the interface from the hook manager -> user code + * + * This is invoked from the hook manager to call SourceHook delegates once + * the hook manager has lowered arguments/etc into the receiving delegate types. + */ + template + struct HookHandlerImpl + { + public: + /** + * @brief The delegate type that SourceHook will invoke (user code) + */ + typedef typename fastdelegate::FastDelegate Delegate; + + struct IMyDelegate : ::SourceHook::ISHDelegate + { + public: + virtual Result Call(Args... args) = 0; + }; + + struct CMyDelegateImpl : IMyDelegate + { + public: + Delegate _delegate; + + CMyDelegateImpl(Delegate deleg) : _delegate(deleg) + {} + virtual~CMyDelegateImpl() {} + Result Call(Args... args) { return _delegate(args...); } + void DeleteThis() { delete this; } + bool IsEqual(ISHDelegate *pOtherDeleg) { return _delegate == static_cast(pOtherDeleg)->_delegate; } + + }; + + /** + * @brief An object containing a safely-allocatable return type + * + * Used when stack-allocating the return type; + * void returns are never written to so using void* is safe. + * when the return is not zero-sized, use the return itself. + */ + typedef typename metaprogramming::if_else< + std::is_void::value, + void*, + 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; + + ::SourceHook::ProtoInfo Proto; + + /** + * @brief Build the ProtoInfo for this hook + */ + constexpr HookHandlerImpl() + { + // build protoinfo + Proto.numOfParams = sizeof...(Args); + Proto.convention = Convention; + + if constexpr (std::is_void_v) { + Proto.retPassInfo = {0, 0, 0}; + } else { + Proto.retPassInfo = { sizeof(Result), ::SourceHook::GetPassInfo< Result >::type, ::SourceHook::GetPassInfo< Result >::flags }; + } + + // Iterate over the args... type pack + auto paramsPassInfo = new PassInfo[sizeof...(Args) + 1]; + paramsPassInfo[0] = { 1, 0, 0 }; + Proto.paramsPassInfo = paramsPassInfo; + + detail::PrototypeBuilderFunctor argsBuilder(paramsPassInfo); + metaprogramming::for_each_template_nullable(&argsBuilder); + + + // Build the backwards compatible paramsPassInfoV2 field + Proto.retPassInfo2 = {0, 0, 0, 0}; + auto paramsPassInfo2 = new PassInfo::V2Info[sizeof...(Args) + 1]; + Proto.paramsPassInfo2 = paramsPassInfo2; + + for (int i = 0; i /* lte to include thisptr! */ <= sizeof...(Args); ++i) { + paramsPassInfo2[i] = { 0, 0, 0, 0 }; + } + + } + + template + static Result HookImplCore(InstType* Instance, void* self, 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( + *reinterpret_cast(reinterpret_cast(self) + Instance->MFI.vtbloffs) + Instance->MFI.vtblindex); + void *vfnptr_origentry; + + META_RES status = MRES_IGNORED; + META_RES prev_res; + META_RES cur_res; + + ResultType original_ret; + ResultType override_ret; + ResultType current_ret; + + IMyDelegate *iter; + IHookContext *context = (*SH)->SetupHookLoop( + Instance->HI, + ourvfnptr, + reinterpret_cast(self), + &vfnptr_origentry, + &status, + &prev_res, + &cur_res, + &original_ret, + &override_ret); + + // Call all pre-hooks + prev_res = MRES_IGNORED; + while ((iter = static_cast(context->GetNext()))) { + cur_res = MRES_IGNORED; + Invoker::Invoke(iter, ¤t_ret, args...); + prev_res = cur_res; + + if (cur_res > status) + status = cur_res; + + if (cur_res >= MRES_OVERRIDE) + *reinterpret_cast(context->GetOverrideRetPtr()) = current_ret; + } + + // Call original method + if (status != MRES_SUPERCEDE && context->ShouldCallOrig()) { + typename Invoker::base::EmptyDelegate original; + reinterpret_cast(&original)[0] = vfnptr_origentry; + + // A little hacky, but I think this is probably the best way to go about this. + // the parent ("InstType") is capable of lowering arguments for us; in other words, + // they'll take tough ABI semantics like varargs and crunch them into an object we can + // actually pass around. Unfortunately, that means we can't call the original delegate, + // as then we'd be trying to give it the "lowered" argument that we gave it. + // + // To work around this, we've exposed the unlowered types to the implementation core here, + // and we're going to give control of actually invoking the original to the hook manager + // that actually lowered the args for us. + // + // These semantics are a little rough but it makes more sense from the parent-class side of things. + + typename InstType::UnloweredSelf* known_self = reinterpret_cast(self); + typename InstType::UnloweredDelegate known_mfp = reinterpret_cast(original); + + Invoker::OriginalRaised( &InstType::InvokeUnlowered, known_self, known_mfp, &original_ret, args... ); + } else { + // TODO: Do we use context->GetOriginalRetPtr() here? + // this is inherited from the macro versions to prevent semantic differences. + original_ret = override_ret; + } + + // Call all post-hooks + prev_res = MRES_IGNORED; + while ((iter = static_cast(context->GetNext()))) { + cur_res = MRES_IGNORED; + Invoker::Invoke(iter, ¤t_ret, args...); + prev_res = cur_res; + + if (cur_res > status) + status = cur_res; + + if (cur_res >= MRES_OVERRIDE) + *reinterpret_cast(context->GetOverrideRetPtr()) = current_ret; + } + + const ResultType* result_ptr = reinterpret_cast((status >= MRES_OVERRIDE) + ? context->GetOverrideRetPtr() + : context->GetOrigRetPtr()); + + (*SH)->EndContext(context); + + return Invoker::Dereference(result_ptr); + } + }; } /** @@ -730,8 +937,8 @@ namespace SourceHook { public: HookInstance(ISourceHook** sh, int hookid) - : SH(sh) - , _hookid(hookid) + : SH(sh) + , _hookid(hookid) {} protected: // The global pointer to the SourceHook API @@ -746,37 +953,25 @@ namespace SourceHook * @brief Returns true if the hook was successfully placed. * @return */ - bool Ok() - { - return _hookid != 0; - } + bool Ok() { return _hookid != 0; } /** * @brief Pause the hook, preventing it from being called until unpaused. * @return */ - bool Pause() - { - return (*SH)->PauseHookByID(_hookid); - } + bool Pause() { return (*SH)->PauseHookByID(_hookid); } /** * @brief Unpause the hook if it is currently paused * @return */ - bool Unpause() - { - return (*SH)->UnpauseHookByID(_hookid); - } + bool Unpause() { return (*SH)->UnpauseHookByID(_hookid); } /** * @brief Remove the hook, permanently preventing it from being invoked. * @return */ - bool Remove() - { - return (*SH)->RemoveHookByID(_hookid); - } + bool Remove() { return (*SH)->RemoveHookByID(_hookid); } }; /** @@ -786,79 +981,80 @@ namespace SourceHook * and prototype in the template arguments. Any derived class of the interface * can be hooked using this manager. */ - template - struct HookImpl + template< + // SourceHook core + ISourceHook** SH, Plugin* PL, + // Hooked object + typename Interface, auto Method, + // Hooked object type + typename MemberMethod, ProtoInfo::CallConvention Convention, + // Parent where lowering will occur + typename Parent, + // Delegate type + typename Result, typename... Args> + struct HookCoreImpl { + protected: + typedef typename ::SourceHook::detail::HookHandlerImpl HookHandlerImpl; + typedef typename HookHandlerImpl::Delegate Delegate; + typedef typename HookHandlerImpl::IMyDelegate IMyDelegate; + typedef typename HookHandlerImpl::CMyDelegateImpl CMyDelegateImpl; + + friend HookHandlerImpl; + + + /** + * @brief An object containing methods to safely handle the return type + * + * This allows us to safely handle zero-sized types as return values. + */ + typedef typename metaprogramming::if_else< + std::is_void::value, + detail::VoidMethodInvoker, + detail::ReturningMethodInvoker + >::type Invoker; + /** * @brief The type we expect the template arg "Method" to be. + * Method is the MFP we will be hooking, so they need to be exact! */ - typedef Result (Interface::*MemberMethod)(Args...); - typedef fastdelegate::FastDelegate Delegate; + // typedef Result (Interface::*MemberMethod)(Args...); typedef decltype(Method) MethodType; static_assert( std::is_same::type::value, "Mismatched template parameters!" ); + // uh oh, Parent is technically uninitialized here. + // should find a workaround, this would be nice to have. + //static_assert( std::is_base_of::type::value, "Mismatched hookman parent type! (INTERNAL ERROR - REPORT THIS AS A BUG)"); + + // Members ::SourceHook::MemFuncInfo MFI; ::SourceHook::IHookManagerInfo *HI; - ::SourceHook::ProtoInfo Proto; + + HookHandlerImpl HookHandler; // Singleton instance // Initialized below! - static HookImpl Instance; + static Parent Instance; - private: - HookImpl(HookImpl& other) = delete; + protected: + HookCoreImpl(HookCoreImpl& other) = delete; - /** - * @brief Build the ProtoInfo for this hook - */ - constexpr HookImpl() + constexpr HookCoreImpl() + // Build the ProtoInfo object + : HookHandler(HookHandlerImpl()) { - // build protoinfo - Proto.numOfParams = sizeof...(Args); - Proto.convention = ProtoInfo::CallConv_Unknown; - - if constexpr (std::is_void_v) { - Proto.retPassInfo = {0, 0, 0}; - } else { - Proto.retPassInfo = { sizeof(Result), ::SourceHook::GetPassInfo< Result >::type, ::SourceHook::GetPassInfo< Result >::flags }; - } - - // Iterate over the args... type pack - auto paramsPassInfo = new PassInfo[sizeof...(Args) + 1]; - paramsPassInfo[0] = { 1, 0, 0 }; - Proto.paramsPassInfo = paramsPassInfo; - - detail::PrototypeBuilderFunctor argsBuilder(paramsPassInfo); - metaprogramming::for_each_template_nullable(&argsBuilder); - - - // Build the backwards compatible paramsPassInfoV2 field - Proto.retPassInfo2 = {0, 0, 0, 0}; - auto paramsPassInfo2 = new PassInfo::V2Info[sizeof...(Args) + 1]; - Proto.paramsPassInfo2 = paramsPassInfo2; - - for (int i = 0; i /* lte to include thisptr! */ <= sizeof...(Args); ++i) { - paramsPassInfo2[i] = { 0, 0, 0, 0 }; - } } - public: - static constexpr HookImpl* Make() - { - HookImpl::Instance = HookImpl(); - return &HookImpl::Instance; - } public: // Public Interface /** * @brief Add an instance of this hook to the specified interface * - * @param id the g_PLID value for the plugin creating the hook * @param iface the interface pointer to hook * @param post true when post-hooking, false when pre-hooking. * @param handler the handler that will be called in place of the original method @@ -869,19 +1065,19 @@ namespace SourceHook using namespace ::SourceHook; MemFuncInfo mfi = {true, -1, 0, 0}; GetFuncInfo(Method, mfi); + if (mfi.thisptroffs < 0 || !mfi.isVirtual) - return HookInstance(SH, 0); + return {SH, false}; CMyDelegateImpl* delegate = new CMyDelegateImpl(handler); - int id = (*SH)->AddHook(*PL, mode, iface, mfi.thisptroffs, HookImpl::HookManPubFunc, delegate, post); + int id = (*SH)->AddHook(*PL, mode, iface, mfi.thisptroffs, HookCoreImpl::HookManPubFunc, delegate, post); - return HookInstance(SH, id); + return {SH, id}; } /** * @brief Remove an existing hook handler from this interface * - * @param id the g_PLID value for the plugin the hook was created under * @param iface the interface to be unhooked * @param post true if this was a post hook, false otherwise (pre-hook) * @param handler the handler that will be removed from this hook. @@ -894,10 +1090,17 @@ namespace SourceHook // Temporary delegate for .IsEqual() comparison. CMyDelegateImpl temp(handler); - return (*SH)->RemoveHook(*PL, iface, mfi.thisptroffs, HookImpl::HookManPubFunc, &temp, post); + return (*SH)->RemoveHook(*PL, iface, mfi.thisptroffs, HookCoreImpl::HookManPubFunc, &temp, post); } protected: + /** + * @brief Configure the hookmangen for this hook manager + * + * @param store + * @param hi + * @return int Zero on success + */ static int HookManPubFunc(bool store, ::SourceHook::IHookManagerInfo *hi) { // Build the MemberFuncInfo for the hooked method @@ -912,155 +1115,184 @@ namespace SourceHook if (hi) { // Build a memberfuncinfo for our hook processor. MemFuncInfo our_mfi = {true, -1, 0, 0}; - GetFuncInfo(&HookImpl::Func, our_mfi); + GetFuncInfo(&Parent::Hook, 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); + hi->SetInfo(SH_HOOKMAN_VERSION, Instance.MFI.vtbloffs, Instance.MFI.vtblindex, &Instance.HookHandler.Proto, us); } return 0; } - struct IMyDelegate : ::SourceHook::ISHDelegate - { - virtual Result Call(Args... args) = 0; - }; - - struct CMyDelegateImpl : IMyDelegate - { - Delegate _delegate; - - CMyDelegateImpl(Delegate deleg) : _delegate(deleg) - {} - virtual~CMyDelegateImpl() {} - Result Call(Args... args) { return _delegate(args...); } - void DeleteThis() { delete this; } - bool IsEqual(ISHDelegate *pOtherDeleg) { return _delegate == static_cast(pOtherDeleg)->_delegate; } - - }; - protected: + static Result InvokeDelegates(void* self, Args... args) + { + return HookHandlerImpl::template HookImplCore(&Instance, self, args...); + } - /** - * @brief An object containing methods to safely handle the return type - * - * This allows us to safely handle zero-sized types as return values. - */ - typedef typename metaprogramming::if_else< - std::is_void::value, - detail::VoidMethodInvoker, - detail::ReturningMethodInvoker - >::type Invoker; - /** - * @brief An object containing a safely-allocatable return type - * - * Used when stack-allocating the return type; - * void returns are never written to so using void* is safe. - * when the return is not zero-sized, use the return itself. - */ - typedef typename metaprogramming::if_else< - std::is_void::value, - void*, - 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; + + // 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 + // I hate C++. + template + Parent HookCoreImpl::Instance; + + + /************************************************************************/ + /* Templated hook managers/argument lowering */ + /************************************************************************/ + + // How it works: + // + // C++ has no way to pass varargs to a lower method (unfortunately). + // all we can do is give the lower method a pointer to our va_fmt object. + // This is bad for us because it means there's no way to call the original method, + // which requests varargs and not the va_fmt. + // + // To work around this, we introduce "argument lowering": + // The hook managers (defined below) have the option to translate the + // arguments passed to the method to an easier-to-use form for the + // core implementation (defined above). + // + // Thus, the hook managers are simply responsible for packing and weird + // or unorthodox arguments into a generally safe form, and then they are + // responsible for unpacking these arguments back into their unsafe form + // when it's time to call the original. + // + // To make your own hook manager, you need to do the following things: + // - Inherit from HookCoreImpl<>, passing your (INCOMPLETE!) type as an argument for the + // "Parent" typename in the HookCoreImpl template. + // - Pass the LOWERED args to HookCoreImpl<>'s args thing! + // - Pass the UNLOWERED/BAD SEMANTICS args to MemberMethod template param + // - Expose a mfp "UnloweredDelegate" typename (aka, Method) + // - Expose a "UnloweredSelf" typename (aka, interface) + // - Expose a virtual Result Hook(ORIGINAL/UNSAFE ARGS HERE) method + // - That calls return InvokeDelegates(this, SAFE ARGS PASSED TO HOOKCOREIMPL); + // - Expose a static Result InvokeUnlowered(UnloweredSelf* self, UnloweredDelegate mfp, Args... args) method + // + // That's it, you're done! + // As long as you don't invoke any undefined behavior while doing any of the above, + // you should be able to get away with pretty much anything. + // + // As an example, the SourceHook FmtHookImpl below does the following operations: + // - Passes as args to HookCoreImpl, + // - Has a virtual Result Hook(Args..., const char* fmt, ...) + // - That calls vsnprintf(buf, fmt, ...) + // - That passes the result to InvokeUnlowered(Args..., buf); + // - Exposes a InvokeUnlowered(self, mfp, args..., const char* buf) + // - That calls self->mfp(args...,"%s", buf) + // By using printf(buf, fmt, ...) and then passing "%s", buf to the original, + // we've preserved semantics across the entire SourceHook call! + // + // I should probably be killed for writing this code, but life is short anyways. + // TODO: Add manual hook support to all of this shenanigans + + /** + * @brief Non-vararg hook implementation + * + * Performs no argument lowering. + * + * @tparam SH A pointer to the SourceHook interface + * @tparam PL A pointer to our PluginId + * @tparam Interface The interface to hook + * @tparam Method The interface method pointer to hook + * @tparam Result The return type + * @tparam Args All arguments passed to the original + */ + template + struct HookImpl : HookCoreImpl< + SH, PL, + Interface, Method, Result (Interface::*)(Args...), ProtoInfo::CallConv_ThisCall, + HookImpl, + Result, Args...> + { + public: + typedef Result (Interface::*UnloweredDelegate)(Args...); + typedef Interface UnloweredSelf; /** * @brief Hook handler virtual */ - virtual Result Func(Args... args) + virtual Result Hook(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( - *reinterpret_cast(reinterpret_cast(this) + Instance.MFI.vtbloffs) + Instance.MFI.vtblindex); - void *vfnptr_origentry; - - META_RES status = MRES_IGNORED; - META_RES prev_res; - META_RES cur_res; - - ResultType original_ret; - ResultType override_ret; - ResultType current_ret; - - IMyDelegate *iter; - IHookContext *context = (*SH)->SetupHookLoop( - Instance.HI, - ourvfnptr, - reinterpret_cast(this), - &vfnptr_origentry, - &status, - &prev_res, - &cur_res, - &original_ret, - &override_ret); - - // Call all pre-hooks - prev_res = MRES_IGNORED; - while ((iter = static_cast(context->GetNext()))) { - cur_res = MRES_IGNORED; - Invoker::Invoke(iter, ¤t_ret, args...); - prev_res = cur_res; - - if (cur_res > status) - status = cur_res; - - if (cur_res >= MRES_OVERRIDE) - *reinterpret_cast(context->GetOverrideRetPtr()) = current_ret; - } - - // Call original method - if (status != MRES_SUPERCEDE && context->ShouldCallOrig()) { - typename Invoker::base::EmptyDelegate original; - reinterpret_cast(&original)[0] = vfnptr_origentry; - Invoker::Original( reinterpret_cast(this), original, &original_ret, args...); - } else { - // TODO: Do we use context->GetOriginalRetPtr() here? - // this is inherited from the macro versions to prevent semantic differences. - original_ret = override_ret; - } - - // Call all post-hooks - prev_res = MRES_IGNORED; - while ((iter = static_cast(context->GetNext()))) { - cur_res = MRES_IGNORED; - Invoker::Invoke(iter, ¤t_ret, args...); - prev_res = cur_res; - - if (cur_res > status) - status = cur_res; - - if (cur_res >= MRES_OVERRIDE) - *reinterpret_cast(context->GetOverrideRetPtr()) = current_ret; - } - - const ResultType* result_ptr = reinterpret_cast((status >= MRES_OVERRIDE) - ? context->GetOverrideRetPtr() - : context->GetOrigRetPtr()); - - (*SH)->EndContext(context); - - return Invoker::Dereference(result_ptr); + return InvokeDelegates(this, args...); } + /** + * @brief Call the original method by raising our lowered arguments back to the originals + * @param unlowered + * @param args + */ + static Result InvokeUnlowered(UnloweredSelf* self, UnloweredDelegate mfp, Args... args) + { + return (self->*mfp)(args...); + } + public: + static constexpr HookImpl* Make() + { + HookImpl::Instance = HookImpl(); + + return &HookImpl::Instance; + } }; - // 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++. + /** + * @brief Format string hook implementation + * + * Lowers const char* fmt and ... into const char* buffer + * + * @tparam SH A pointer to the SourceHook interface + * @tparam PL A pointer to our PluginId + * @tparam Interface The interface to hook + * @tparam Method The interface method pointer to hook + * @tparam Result The return type + * @tparam Args All arguments passed to the original, except the last const char* fmt and ... + */ template - HookImpl HookImpl::Instance; + struct FmtHookImpl : HookCoreImpl< + SH, PL, + Interface, Method, Result (Interface::*)(Args..., const char*, ...), ProtoInfo::CallConv_HasVafmt, + FmtHookImpl, + Result, Args..., const char*> + { + typedef Result (Interface::*UnloweredDelegate)(Args..., const char*, ...); + typedef Interface UnloweredSelf; + + /** + * @brief Hook handler virtual + */ + virtual Result Hook(Args... args, const char* fmt, ...) + { + char buf[::SourceHook::STRBUF_LEN]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + buf[sizeof(buf) - 1] = 0; + va_end(ap); + return InvokeDelegates(this, args..., buf); + } + + /** + * @brief Call the original method by raising our lowered arguments back to the originals + * @param unlowered + * @param args + */ + static Result InvokeUnlowered(UnloweredSelf* self, UnloweredDelegate mfp, Args... args, const char* buffer) + { + return (self->*mfp)(args..., "%s", buffer); + } + + public: + static constexpr FmtHookImpl* Make() + { + FmtHookImpl::Instance = FmtHookImpl(); + + return &FmtHookImpl::Instance; + } + }; } diff --git a/core/sourcehook/sourcehook.h b/core/sourcehook/sourcehook.h index 0496403..04e5a0d 100644 --- a/core/sourcehook/sourcehook.h +++ b/core/sourcehook/sourcehook.h @@ -116,6 +116,7 @@ #define SH_PTRSIZE sizeof(void*) +#include #include #include "sh_memfuncinfo.h" #include "FastDelegate.h" @@ -665,6 +666,10 @@ namespace SourceHook * Original - call the original method and safely handle the return type * Dereference - dereference the return type pointer for hook return semantics * + * OriginalRaised - a little special, we call a static func from the parent hook + * manager class to raise lowered arguments passed to the core delegates. This is + * used when the core delegates receive different args than the root proto (eg, varargs!) + * */ template struct BaseMethodInvoker @@ -692,6 +697,13 @@ namespace SourceHook (self->*mfp)(args...); } + template + static void OriginalRaised( void (*Invoker)(Self, Mfp, Args...), Self self, Mfp mfp, void* result, Args... args ) + { + // Do not touch return type: It's void! + Invoker(self, mfp, args...); + } + static void Dereference(const void* arg) { return; @@ -716,11 +728,206 @@ namespace SourceHook *result = (self->*mfp)(args...); } + template + static void OriginalRaised( Result (*Invoker)(Self, Mfp, Args...), Self self, Mfp mfp, Result* result, Args... args ) + { + *result = Invoker(self, mfp, args...); + } + static Result Dereference(const Result* arg) { return *arg; } }; + + /** + * @brief defines the interface from the hook manager -> user code + * + * This is invoked from the hook manager to call SourceHook delegates once + * the hook manager has lowered arguments/etc into the receiving delegate types. + */ + template + struct HookHandlerImpl + { + public: + /** + * @brief The delegate type that SourceHook will invoke (user code) + */ + typedef typename fastdelegate::FastDelegate Delegate; + + struct IMyDelegate : ::SourceHook::ISHDelegate + { + public: + virtual Result Call(Args... args) = 0; + }; + + struct CMyDelegateImpl : IMyDelegate + { + public: + Delegate _delegate; + + CMyDelegateImpl(Delegate deleg) : _delegate(deleg) + {} + virtual~CMyDelegateImpl() {} + Result Call(Args... args) { return _delegate(args...); } + void DeleteThis() { delete this; } + bool IsEqual(ISHDelegate *pOtherDeleg) { return _delegate == static_cast(pOtherDeleg)->_delegate; } + + }; + + /** + * @brief An object containing a safely-allocatable return type + * + * Used when stack-allocating the return type; + * void returns are never written to so using void* is safe. + * when the return is not zero-sized, use the return itself. + */ + typedef typename metaprogramming::if_else< + std::is_void::value, + void*, + 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; + + ::SourceHook::ProtoInfo Proto; + + /** + * @brief Build the ProtoInfo for this hook + */ + constexpr HookHandlerImpl() + { + // build protoinfo + Proto.numOfParams = sizeof...(Args); + Proto.convention = Convention; + + if constexpr (std::is_void_v) { + Proto.retPassInfo = {0, 0, 0}; + } else { + Proto.retPassInfo = { sizeof(Result), ::SourceHook::GetPassInfo< Result >::type, ::SourceHook::GetPassInfo< Result >::flags }; + } + + // Iterate over the args... type pack + auto paramsPassInfo = new PassInfo[sizeof...(Args) + 1]; + paramsPassInfo[0] = { 1, 0, 0 }; + Proto.paramsPassInfo = paramsPassInfo; + + detail::PrototypeBuilderFunctor argsBuilder(paramsPassInfo); + metaprogramming::for_each_template_nullable(&argsBuilder); + + + // Build the backwards compatible paramsPassInfoV2 field + Proto.retPassInfo2 = {0, 0, 0, 0}; + auto paramsPassInfo2 = new PassInfo::V2Info[sizeof...(Args) + 1]; + Proto.paramsPassInfo2 = paramsPassInfo2; + + for (int i = 0; i /* lte to include thisptr! */ <= sizeof...(Args); ++i) { + paramsPassInfo2[i] = { 0, 0, 0, 0 }; + } + + } + + template + static Result HookImplCore(InstType* Instance, void* self, 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( + *reinterpret_cast(reinterpret_cast(self) + Instance->MFI.vtbloffs) + Instance->MFI.vtblindex); + void *vfnptr_origentry; + + META_RES status = MRES_IGNORED; + META_RES prev_res; + META_RES cur_res; + + ResultType original_ret; + ResultType override_ret; + ResultType current_ret; + + IMyDelegate *iter; + IHookContext *context = (*SH)->SetupHookLoop( + Instance->HI, + ourvfnptr, + reinterpret_cast(self), + &vfnptr_origentry, + &status, + &prev_res, + &cur_res, + &original_ret, + &override_ret); + + // Call all pre-hooks + prev_res = MRES_IGNORED; + while ((iter = static_cast(context->GetNext()))) { + cur_res = MRES_IGNORED; + Invoker::Invoke(iter, ¤t_ret, args...); + prev_res = cur_res; + + if (cur_res > status) + status = cur_res; + + if (cur_res >= MRES_OVERRIDE) + *reinterpret_cast(context->GetOverrideRetPtr()) = current_ret; + } + + // Call original method + if (status != MRES_SUPERCEDE && context->ShouldCallOrig()) { + typename Invoker::base::EmptyDelegate original; + reinterpret_cast(&original)[0] = vfnptr_origentry; + + // A little hacky, but I think this is probably the best way to go about this. + // the parent ("InstType") is capable of lowering arguments for us; in other words, + // they'll take tough ABI semantics like varargs and crunch them into an object we can + // actually pass around. Unfortunately, that means we can't call the original delegate, + // as then we'd be trying to give it the "lowered" argument that we gave it. + // + // To work around this, we've exposed the unlowered types to the implementation core here, + // and we're going to give control of actually invoking the original to the hook manager + // that actually lowered the args for us. + // + // These semantics are a little rough but it makes more sense from the parent-class side of things. + + typename InstType::UnloweredSelf* known_self = reinterpret_cast(self); + typename InstType::UnloweredDelegate known_mfp = reinterpret_cast(original); + + Invoker::OriginalRaised( &InstType::InvokeUnlowered, known_self, known_mfp, &original_ret, args... ); + } else { + // TODO: Do we use context->GetOriginalRetPtr() here? + // this is inherited from the macro versions to prevent semantic differences. + original_ret = override_ret; + } + + // Call all post-hooks + prev_res = MRES_IGNORED; + while ((iter = static_cast(context->GetNext()))) { + cur_res = MRES_IGNORED; + Invoker::Invoke(iter, ¤t_ret, args...); + prev_res = cur_res; + + if (cur_res > status) + status = cur_res; + + if (cur_res >= MRES_OVERRIDE) + *reinterpret_cast(context->GetOverrideRetPtr()) = current_ret; + } + + const ResultType* result_ptr = reinterpret_cast((status >= MRES_OVERRIDE) + ? context->GetOverrideRetPtr() + : context->GetOrigRetPtr()); + + (*SH)->EndContext(context); + + return Invoker::Dereference(result_ptr); + } + }; } /** @@ -730,8 +937,8 @@ namespace SourceHook { public: HookInstance(ISourceHook** sh, int hookid) - : SH(sh) - , _hookid(hookid) + : SH(sh) + , _hookid(hookid) {} protected: // The global pointer to the SourceHook API @@ -746,37 +953,25 @@ namespace SourceHook * @brief Returns true if the hook was successfully placed. * @return */ - bool Ok() - { - return _hookid != 0; - } + bool Ok() { return _hookid != 0; } /** * @brief Pause the hook, preventing it from being called until unpaused. * @return */ - bool Pause() - { - return (*SH)->PauseHookByID(_hookid); - } + bool Pause() { return (*SH)->PauseHookByID(_hookid); } /** * @brief Unpause the hook if it is currently paused * @return */ - bool Unpause() - { - return (*SH)->UnpauseHookByID(_hookid); - } + bool Unpause() { return (*SH)->UnpauseHookByID(_hookid); } /** * @brief Remove the hook, permanently preventing it from being invoked. * @return */ - bool Remove() - { - return (*SH)->RemoveHookByID(_hookid); - } + bool Remove() { return (*SH)->RemoveHookByID(_hookid); } }; /** @@ -786,79 +981,80 @@ namespace SourceHook * and prototype in the template arguments. Any derived class of the interface * can be hooked using this manager. */ - template - struct HookImpl + template< + // SourceHook core + ISourceHook** SH, Plugin* PL, + // Hooked object + typename Interface, auto Method, + // Hooked object type + typename MemberMethod, ProtoInfo::CallConvention Convention, + // Parent where lowering will occur + typename Parent, + // Delegate type + typename Result, typename... Args> + struct HookCoreImpl { + protected: + typedef typename ::SourceHook::detail::HookHandlerImpl HookHandlerImpl; + typedef typename HookHandlerImpl::Delegate Delegate; + typedef typename HookHandlerImpl::IMyDelegate IMyDelegate; + typedef typename HookHandlerImpl::CMyDelegateImpl CMyDelegateImpl; + + friend HookHandlerImpl; + + + /** + * @brief An object containing methods to safely handle the return type + * + * This allows us to safely handle zero-sized types as return values. + */ + typedef typename metaprogramming::if_else< + std::is_void::value, + detail::VoidMethodInvoker, + detail::ReturningMethodInvoker + >::type Invoker; + /** * @brief The type we expect the template arg "Method" to be. + * Method is the MFP we will be hooking, so they need to be exact! */ - typedef Result (Interface::*MemberMethod)(Args...); - typedef fastdelegate::FastDelegate Delegate; + // typedef Result (Interface::*MemberMethod)(Args...); typedef decltype(Method) MethodType; static_assert( std::is_same::type::value, "Mismatched template parameters!" ); + // uh oh, Parent is technically uninitialized here. + // should find a workaround, this would be nice to have. + //static_assert( std::is_base_of::type::value, "Mismatched hookman parent type! (INTERNAL ERROR - REPORT THIS AS A BUG)"); + + // Members ::SourceHook::MemFuncInfo MFI; ::SourceHook::IHookManagerInfo *HI; - ::SourceHook::ProtoInfo Proto; + + HookHandlerImpl HookHandler; // Singleton instance // Initialized below! - static HookImpl Instance; + static Parent Instance; - private: - HookImpl(HookImpl& other) = delete; + protected: + HookCoreImpl(HookCoreImpl& other) = delete; - /** - * @brief Build the ProtoInfo for this hook - */ - constexpr HookImpl() + constexpr HookCoreImpl() + // Build the ProtoInfo object + : HookHandler(HookHandlerImpl()) { - // build protoinfo - Proto.numOfParams = sizeof...(Args); - Proto.convention = ProtoInfo::CallConv_Unknown; - - if constexpr (std::is_void_v) { - Proto.retPassInfo = {0, 0, 0}; - } else { - Proto.retPassInfo = { sizeof(Result), ::SourceHook::GetPassInfo< Result >::type, ::SourceHook::GetPassInfo< Result >::flags }; - } - - // Iterate over the args... type pack - auto paramsPassInfo = new PassInfo[sizeof...(Args) + 1]; - paramsPassInfo[0] = { 1, 0, 0 }; - Proto.paramsPassInfo = paramsPassInfo; - - detail::PrototypeBuilderFunctor argsBuilder(paramsPassInfo); - metaprogramming::for_each_template_nullable(&argsBuilder); - - - // Build the backwards compatible paramsPassInfoV2 field - Proto.retPassInfo2 = {0, 0, 0, 0}; - auto paramsPassInfo2 = new PassInfo::V2Info[sizeof...(Args) + 1]; - Proto.paramsPassInfo2 = paramsPassInfo2; - - for (int i = 0; i /* lte to include thisptr! */ <= sizeof...(Args); ++i) { - paramsPassInfo2[i] = { 0, 0, 0, 0 }; - } } - public: - static constexpr HookImpl* Make() - { - HookImpl::Instance = HookImpl(); - return &HookImpl::Instance; - } public: // Public Interface /** * @brief Add an instance of this hook to the specified interface * - * @param id the g_PLID value for the plugin creating the hook * @param iface the interface pointer to hook * @param post true when post-hooking, false when pre-hooking. * @param handler the handler that will be called in place of the original method @@ -869,19 +1065,19 @@ namespace SourceHook using namespace ::SourceHook; MemFuncInfo mfi = {true, -1, 0, 0}; GetFuncInfo(Method, mfi); + if (mfi.thisptroffs < 0 || !mfi.isVirtual) - return HookInstance(SH, 0); + return {SH, false}; CMyDelegateImpl* delegate = new CMyDelegateImpl(handler); - int id = (*SH)->AddHook(*PL, mode, iface, mfi.thisptroffs, HookImpl::HookManPubFunc, delegate, post); + int id = (*SH)->AddHook(*PL, mode, iface, mfi.thisptroffs, HookCoreImpl::HookManPubFunc, delegate, post); - return HookInstance(SH, id); + return {SH, id}; } /** * @brief Remove an existing hook handler from this interface * - * @param id the g_PLID value for the plugin the hook was created under * @param iface the interface to be unhooked * @param post true if this was a post hook, false otherwise (pre-hook) * @param handler the handler that will be removed from this hook. @@ -894,10 +1090,17 @@ namespace SourceHook // Temporary delegate for .IsEqual() comparison. CMyDelegateImpl temp(handler); - return (*SH)->RemoveHook(*PL, iface, mfi.thisptroffs, HookImpl::HookManPubFunc, &temp, post); + return (*SH)->RemoveHook(*PL, iface, mfi.thisptroffs, HookCoreImpl::HookManPubFunc, &temp, post); } protected: + /** + * @brief Configure the hookmangen for this hook manager + * + * @param store + * @param hi + * @return int Zero on success + */ static int HookManPubFunc(bool store, ::SourceHook::IHookManagerInfo *hi) { // Build the MemberFuncInfo for the hooked method @@ -912,155 +1115,184 @@ namespace SourceHook if (hi) { // Build a memberfuncinfo for our hook processor. MemFuncInfo our_mfi = {true, -1, 0, 0}; - GetFuncInfo(&HookImpl::Func, our_mfi); + GetFuncInfo(&Parent::Hook, 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); + hi->SetInfo(SH_HOOKMAN_VERSION, Instance.MFI.vtbloffs, Instance.MFI.vtblindex, &Instance.HookHandler.Proto, us); } return 0; } - struct IMyDelegate : ::SourceHook::ISHDelegate - { - virtual Result Call(Args... args) = 0; - }; - - struct CMyDelegateImpl : IMyDelegate - { - Delegate _delegate; - - CMyDelegateImpl(Delegate deleg) : _delegate(deleg) - {} - virtual~CMyDelegateImpl() {} - Result Call(Args... args) { return _delegate(args...); } - void DeleteThis() { delete this; } - bool IsEqual(ISHDelegate *pOtherDeleg) { return _delegate == static_cast(pOtherDeleg)->_delegate; } - - }; - protected: + static Result InvokeDelegates(void* self, Args... args) + { + return HookHandlerImpl::template HookImplCore(&Instance, self, args...); + } - /** - * @brief An object containing methods to safely handle the return type - * - * This allows us to safely handle zero-sized types as return values. - */ - typedef typename metaprogramming::if_else< - std::is_void::value, - detail::VoidMethodInvoker, - detail::ReturningMethodInvoker - >::type Invoker; - /** - * @brief An object containing a safely-allocatable return type - * - * Used when stack-allocating the return type; - * void returns are never written to so using void* is safe. - * when the return is not zero-sized, use the return itself. - */ - typedef typename metaprogramming::if_else< - std::is_void::value, - void*, - 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; + + // 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 + // I hate C++. + template + Parent HookCoreImpl::Instance; + + + /************************************************************************/ + /* Templated hook managers/argument lowering */ + /************************************************************************/ + + // How it works: + // + // C++ has no way to pass varargs to a lower method (unfortunately). + // all we can do is give the lower method a pointer to our va_fmt object. + // This is bad for us because it means there's no way to call the original method, + // which requests varargs and not the va_fmt. + // + // To work around this, we introduce "argument lowering": + // The hook managers (defined below) have the option to translate the + // arguments passed to the method to an easier-to-use form for the + // core implementation (defined above). + // + // Thus, the hook managers are simply responsible for packing and weird + // or unorthodox arguments into a generally safe form, and then they are + // responsible for unpacking these arguments back into their unsafe form + // when it's time to call the original. + // + // To make your own hook manager, you need to do the following things: + // - Inherit from HookCoreImpl<>, passing your (INCOMPLETE!) type as an argument for the + // "Parent" typename in the HookCoreImpl template. + // - Pass the LOWERED args to HookCoreImpl<>'s args thing! + // - Pass the UNLOWERED/BAD SEMANTICS args to MemberMethod template param + // - Expose a mfp "UnloweredDelegate" typename (aka, Method) + // - Expose a "UnloweredSelf" typename (aka, interface) + // - Expose a virtual Result Hook(ORIGINAL/UNSAFE ARGS HERE) method + // - That calls return InvokeDelegates(this, SAFE ARGS PASSED TO HOOKCOREIMPL); + // - Expose a static Result InvokeUnlowered(UnloweredSelf* self, UnloweredDelegate mfp, Args... args) method + // + // That's it, you're done! + // As long as you don't invoke any undefined behavior while doing any of the above, + // you should be able to get away with pretty much anything. + // + // As an example, the SourceHook FmtHookImpl below does the following operations: + // - Passes as args to HookCoreImpl, + // - Has a virtual Result Hook(Args..., const char* fmt, ...) + // - That calls vsnprintf(buf, fmt, ...) + // - That passes the result to InvokeUnlowered(Args..., buf); + // - Exposes a InvokeUnlowered(self, mfp, args..., const char* buf) + // - That calls self->mfp(args...,"%s", buf) + // By using printf(buf, fmt, ...) and then passing "%s", buf to the original, + // we've preserved semantics across the entire SourceHook call! + // + // I should probably be killed for writing this code, but life is short anyways. + // TODO: Add manual hook support to all of this shenanigans + + /** + * @brief Non-vararg hook implementation + * + * Performs no argument lowering. + * + * @tparam SH A pointer to the SourceHook interface + * @tparam PL A pointer to our PluginId + * @tparam Interface The interface to hook + * @tparam Method The interface method pointer to hook + * @tparam Result The return type + * @tparam Args All arguments passed to the original + */ + template + struct HookImpl : HookCoreImpl< + SH, PL, + Interface, Method, Result (Interface::*)(Args...), ProtoInfo::CallConv_ThisCall, + HookImpl, + Result, Args...> + { + public: + typedef Result (Interface::*UnloweredDelegate)(Args...); + typedef Interface UnloweredSelf; /** * @brief Hook handler virtual */ - virtual Result Func(Args... args) + virtual Result Hook(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( - *reinterpret_cast(reinterpret_cast(this) + Instance.MFI.vtbloffs) + Instance.MFI.vtblindex); - void *vfnptr_origentry; - - META_RES status = MRES_IGNORED; - META_RES prev_res; - META_RES cur_res; - - ResultType original_ret; - ResultType override_ret; - ResultType current_ret; - - IMyDelegate *iter; - IHookContext *context = (*SH)->SetupHookLoop( - Instance.HI, - ourvfnptr, - reinterpret_cast(this), - &vfnptr_origentry, - &status, - &prev_res, - &cur_res, - &original_ret, - &override_ret); - - // Call all pre-hooks - prev_res = MRES_IGNORED; - while ((iter = static_cast(context->GetNext()))) { - cur_res = MRES_IGNORED; - Invoker::Invoke(iter, ¤t_ret, args...); - prev_res = cur_res; - - if (cur_res > status) - status = cur_res; - - if (cur_res >= MRES_OVERRIDE) - *reinterpret_cast(context->GetOverrideRetPtr()) = current_ret; - } - - // Call original method - if (status != MRES_SUPERCEDE && context->ShouldCallOrig()) { - typename Invoker::base::EmptyDelegate original; - reinterpret_cast(&original)[0] = vfnptr_origentry; - Invoker::Original( reinterpret_cast(this), original, &original_ret, args...); - } else { - // TODO: Do we use context->GetOriginalRetPtr() here? - // this is inherited from the macro versions to prevent semantic differences. - original_ret = override_ret; - } - - // Call all post-hooks - prev_res = MRES_IGNORED; - while ((iter = static_cast(context->GetNext()))) { - cur_res = MRES_IGNORED; - Invoker::Invoke(iter, ¤t_ret, args...); - prev_res = cur_res; - - if (cur_res > status) - status = cur_res; - - if (cur_res >= MRES_OVERRIDE) - *reinterpret_cast(context->GetOverrideRetPtr()) = current_ret; - } - - const ResultType* result_ptr = reinterpret_cast((status >= MRES_OVERRIDE) - ? context->GetOverrideRetPtr() - : context->GetOrigRetPtr()); - - (*SH)->EndContext(context); - - return Invoker::Dereference(result_ptr); + return InvokeDelegates(this, args...); } + /** + * @brief Call the original method by raising our lowered arguments back to the originals + * @param unlowered + * @param args + */ + static Result InvokeUnlowered(UnloweredSelf* self, UnloweredDelegate mfp, Args... args) + { + return (self->*mfp)(args...); + } + public: + static constexpr HookImpl* Make() + { + HookImpl::Instance = HookImpl(); + + return &HookImpl::Instance; + } }; - // 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++. + /** + * @brief Format string hook implementation + * + * Lowers const char* fmt and ... into const char* buffer + * + * @tparam SH A pointer to the SourceHook interface + * @tparam PL A pointer to our PluginId + * @tparam Interface The interface to hook + * @tparam Method The interface method pointer to hook + * @tparam Result The return type + * @tparam Args All arguments passed to the original, except the last const char* fmt and ... + */ template - HookImpl HookImpl::Instance; + struct FmtHookImpl : HookCoreImpl< + SH, PL, + Interface, Method, Result (Interface::*)(Args..., const char*, ...), ProtoInfo::CallConv_HasVafmt, + FmtHookImpl, + Result, Args..., const char*> + { + typedef Result (Interface::*UnloweredDelegate)(Args..., const char*, ...); + typedef Interface UnloweredSelf; + + /** + * @brief Hook handler virtual + */ + virtual Result Hook(Args... args, const char* fmt, ...) + { + char buf[::SourceHook::STRBUF_LEN]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + buf[sizeof(buf) - 1] = 0; + va_end(ap); + return InvokeDelegates(this, args..., buf); + } + + /** + * @brief Call the original method by raising our lowered arguments back to the originals + * @param unlowered + * @param args + */ + static Result InvokeUnlowered(UnloweredSelf* self, UnloweredDelegate mfp, Args... args, const char* buffer) + { + return (self->*mfp)(args..., "%s", buffer); + } + + public: + static constexpr FmtHookImpl* Make() + { + FmtHookImpl::Instance = FmtHookImpl(); + + return &FmtHookImpl::Instance; + } + }; }