mirror of
https://github.com/alliedmodders/metamod-source.git
synced 2025-12-06 18:08:31 +00:00
Compare commits
19 Commits
f0f811f7a8
...
8695a16ccd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8695a16ccd | ||
|
|
07c708a59e | ||
|
|
10c0594e63 | ||
|
|
1920330307 | ||
|
|
703e733ac1 | ||
|
|
5a97a3399c | ||
|
|
c2d57ef42c | ||
|
|
4399ff0fd4 | ||
|
|
756433786c | ||
|
|
6112248775 | ||
|
|
6548671084 | ||
|
|
916f4058bf | ||
|
|
8abbfbb810 | ||
|
|
50772ffc46 | ||
|
|
d96b3adb0a | ||
|
|
e391dc5b14 | ||
|
|
11df174ab7 | ||
|
|
8e66a21d5b | ||
|
|
aa88f23880 |
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -3,4 +3,4 @@
|
||||
url = https://github.com/alliedmodders/amtl
|
||||
[submodule "hl2sdk-manifests"]
|
||||
path = hl2sdk-manifests
|
||||
url = https://github.com/alliedmodders/hl2sdk-manifests
|
||||
url = https://github.com/alliedmodders/hl2sdk-manifests
|
||||
@ -501,12 +501,20 @@ 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;
|
||||
#define PLUGIN_GLOBALVARS() \
|
||||
extern SourceHook::ISourceHook *g_SHPtr; \
|
||||
extern ISmmAPI *g_SMAPI; \
|
||||
extern ISmmPlugin *g_PLAPI; \
|
||||
extern PluginId g_PLID; \
|
||||
namespace SourceHook \
|
||||
{ \
|
||||
template <typename Interface, auto Method, typename Result, typename... Args> \
|
||||
using Hook = ::SourceHook::HookImpl<&g_SHPtr, &g_PLID, Interface, Method, Result, Args...>; \
|
||||
template <typename Interface, auto Method, typename Result, typename... Args> \
|
||||
using FmtHook = ::SourceHook::FmtHookImpl<&g_SHPtr, &g_PLID, Interface, Method, Result, Args...>; \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This should be the first line in your Load callback.
|
||||
|
||||
@ -345,6 +345,13 @@ extern PluginId g_PLID;
|
||||
extern SourceHook::ISourceHook *g_SHPtr;
|
||||
extern SourceMM::IMetamodSourceProvider *provider;
|
||||
extern SourceMM::ISmmAPI *g_pMetamod;
|
||||
namespace SourceHook
|
||||
{
|
||||
template <typename Interface, auto Method, typename Result, typename... Args>
|
||||
using Hook = ::SourceHook::HookImpl<&g_SHPtr, &g_PLID, Interface, Method, Result, Args...>;
|
||||
template <typename Interface, auto Method, typename Result, typename... Args>
|
||||
using FmtHook = ::SourceHook::FmtHookImpl<&g_SHPtr, &g_PLID, Interface, Method, Result, Args...>;
|
||||
}
|
||||
|
||||
#endif //_INCLUDE_METAMOD_SOURCE_SUPPORT_H_
|
||||
|
||||
|
||||
@ -44,14 +44,14 @@ static SourceProvider g_SourceProvider;
|
||||
|
||||
IMetamodSourceProvider* provider = &g_SourceProvider;
|
||||
|
||||
SH_DECL_HOOK0(IServerGameDLL, GameInit, SH_NOATTRIB, 0, bool);
|
||||
SH_DECL_HOOK6(IServerGameDLL, LevelInit, SH_NOATTRIB, 0, bool, const char*, const char*, const char*, const char*, bool, bool);
|
||||
SH_DECL_HOOK0_void(IServerGameDLL, LevelShutdown, SH_NOATTRIB, 0);
|
||||
auto OnGameInit = SourceHook::Hook<IServerGameDLL, &IServerGameDLL::GameInit, bool>::Make();
|
||||
auto OnLevelInit = SourceHook::Hook<IServerGameDLL, &IServerGameDLL::LevelInit, bool, const char*, const char*, const char*, const char*, bool, bool>::Make();
|
||||
auto OnLevelShutdown = SourceHook::Hook<IServerGameDLL, &IServerGameDLL::LevelShutdown, void>::Make();
|
||||
|
||||
#if SOURCE_ENGINE >= SE_ORANGEBOX
|
||||
SH_DECL_HOOK2_void(IServerGameClients, ClientCommand, SH_NOATTRIB, 0, edict_t*, const CCommand&);
|
||||
auto OnClientCommand = SourceHook::Hook<IServerGameClients, &IServerGameClients::ClientCommand, void, edict_t*, const CCommand&>::Make();
|
||||
#else
|
||||
SH_DECL_HOOK1_void(IServerGameClients, ClientCommand, SH_NOATTRIB, 0, edict_t*);
|
||||
auto OnClientCommand = SourceHook::Hook<IServerGameClients, &IServerGameClients::ClientCommand, void, edict_t*>::Make();
|
||||
#endif
|
||||
|
||||
void SourceProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory,
|
||||
@ -69,34 +69,30 @@ void SourceProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory,
|
||||
}
|
||||
}
|
||||
#else
|
||||
engine = (IVEngineServer*)((engineFactory)(INTERFACEVERSION_VENGINESERVER, NULL));
|
||||
engine = (IVEngineServer *) ((engineFactory)(INTERFACEVERSION_VENGINESERVER, NULL));
|
||||
#endif
|
||||
if (!engine)
|
||||
{
|
||||
if (!engine) {
|
||||
DisplayError("Could not find IVEngineServer! Metamod cannot load.");
|
||||
return;
|
||||
}
|
||||
|
||||
#if SOURCE_ENGINE >= SE_ORANGEBOX
|
||||
icvar = (ICvar*)((engineFactory)(CVAR_INTERFACE_VERSION, NULL));
|
||||
icvar = (ICvar *) ((engineFactory)(CVAR_INTERFACE_VERSION, NULL));
|
||||
#else
|
||||
icvar = (ICvar*)((engineFactory)(VENGINE_CVAR_INTERFACE_VERSION, NULL));
|
||||
#endif
|
||||
if (!icvar)
|
||||
{
|
||||
if (!icvar) {
|
||||
DisplayError("Could not find ICvar! Metamod cannot load.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((gameclients = (IServerGameClients*)(serverFactory("ServerGameClients003", NULL)))
|
||||
== NULL)
|
||||
{
|
||||
gameclients = (IServerGameClients*)(serverFactory("ServerGameClients004", NULL));
|
||||
if ((gameclients = (IServerGameClients *) (serverFactory("ServerGameClients003", NULL)))
|
||||
== NULL) {
|
||||
gameclients = (IServerGameClients *) (serverFactory("ServerGameClients004", NULL));
|
||||
}
|
||||
|
||||
baseFs = (IFileSystem*)((engineFactory)(FILESYSTEM_INTERFACE_VERSION, NULL));
|
||||
if (baseFs == NULL)
|
||||
{
|
||||
baseFs = (IFileSystem *) ((engineFactory)(FILESYSTEM_INTERFACE_VERSION, NULL));
|
||||
if (baseFs == NULL) {
|
||||
mm_LogMessage("Unable to find \"%s\": .vdf files will not be parsed", FILESYSTEM_INTERFACE_VERSION);
|
||||
}
|
||||
|
||||
@ -122,21 +118,20 @@ void SourceProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory,
|
||||
}
|
||||
#endif
|
||||
|
||||
if (gameclients)
|
||||
{
|
||||
SH_ADD_HOOK(IServerGameClients, ClientCommand, gameclients, SH_MEMBER(this, &SourceProvider::Hook_ClientCommand), false);
|
||||
if (gameclients) {
|
||||
OnClientCommand->Add(gameclients, false, SH_MEMBER(this, &SourceProvider::Hook_ClientCommand));
|
||||
}
|
||||
|
||||
SH_ADD_HOOK(IServerGameDLL, GameInit, server, SH_MEMBER(this, &SourceProvider::Hook_GameInit), false);
|
||||
SH_ADD_HOOK(IServerGameDLL, LevelInit, server, SH_MEMBER(this, &SourceProvider::Hook_LevelInit), true);
|
||||
SH_ADD_HOOK(IServerGameDLL, LevelShutdown, server, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown), true);
|
||||
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()
|
||||
{
|
||||
SH_REMOVE_HOOK(IServerGameDLL, GameInit, server, SH_MEMBER(this, &SourceProvider::Hook_GameInit), false);
|
||||
SH_REMOVE_HOOK(IServerGameDLL, LevelInit, server, SH_MEMBER(this, &SourceProvider::Hook_LevelInit), true);
|
||||
SH_REMOVE_HOOK(IServerGameDLL, LevelShutdown, server, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown), true);
|
||||
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();
|
||||
|
||||
|
||||
@ -98,8 +98,10 @@
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# define SH_COMP SH_COMP_MSVC
|
||||
# define SH_INLINE inline __forceinline
|
||||
#elif defined __GNUC__
|
||||
# define SH_COMP SH_COMP_GCC
|
||||
# define SH_INLINE inline __attribute__((always_inline))
|
||||
#else
|
||||
# error Unsupported compiler
|
||||
#endif
|
||||
@ -114,6 +116,9 @@
|
||||
|
||||
#define SH_PTRSIZE sizeof(void*)
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <type_traits>
|
||||
#include "sh_memfuncinfo.h"
|
||||
#include "FastDelegate.h"
|
||||
|
||||
@ -573,6 +578,777 @@ namespace SourceHook
|
||||
static const int type = 0;
|
||||
static const unsigned int flags = PassInfo::PassFlag_ByRef;
|
||||
};
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* Templated hook definition */
|
||||
/************************************************************************/
|
||||
|
||||
namespace metaprogramming
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
/**
|
||||
* @brief Iterate over all elements of a type pack
|
||||
*
|
||||
* @param functor A reference to the functor containing the step<index, now> template.
|
||||
*/
|
||||
template<class Functor, int Index, typename Now, typename... Rest>
|
||||
constexpr SH_INLINE void for_each_template_impl(Functor *f)
|
||||
{
|
||||
f->template step<Index, Now>();
|
||||
|
||||
if constexpr (sizeof...(Rest) > 0) {
|
||||
for_each_template_impl<Functor, Index + 1, Rest...>(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class Functor, typename First, typename... Rest>
|
||||
constexpr SH_INLINE void for_each_template(Functor* f)
|
||||
{
|
||||
detail::for_each_template_impl<Functor, 0, First, Rest...>(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate over all elements of a type pack
|
||||
*
|
||||
* @param functor A reference to the functor containing the step<index, now> template.
|
||||
*/
|
||||
template<class Functor, typename First, typename... Rest>
|
||||
constexpr SH_INLINE void for_each_template_nullable(Functor* f)
|
||||
{
|
||||
for_each_template<Functor, First, Rest...>(f);
|
||||
}
|
||||
|
||||
template<class Functor>
|
||||
constexpr SH_INLINE void for_each_template_nullable(Functor* f)
|
||||
{
|
||||
// Empty varargs
|
||||
}
|
||||
|
||||
template <bool Test, class Yes = void, class No = void>
|
||||
struct if_else {
|
||||
public:
|
||||
typedef No type;
|
||||
};
|
||||
|
||||
template <class Yes, class No>
|
||||
struct if_else<true, Yes, No> {
|
||||
public:
|
||||
typedef Yes type;
|
||||
};
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Build the PassInfo for a method pack.
|
||||
*
|
||||
* Iterated over using for_each_template_nullable.
|
||||
*/
|
||||
class PrototypeBuilderFunctor
|
||||
{
|
||||
public:
|
||||
constexpr PrototypeBuilderFunctor(PassInfo* p)
|
||||
: params(p) {}
|
||||
PassInfo* params;
|
||||
|
||||
template<int Index, typename Now>
|
||||
constexpr void step()
|
||||
{
|
||||
// Note: first index is always the thisptr!
|
||||
params[Index + 1] = { sizeof(Now), ::SourceHook::GetPassInfo< Now >::type, ::SourceHook::GetPassInfo< Now >::flags };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Common type for the void/non-void handling semantics.
|
||||
*
|
||||
* Basically, *(void*) is an illegal operation as it works on zero-sized
|
||||
* types. We work around this here by using these two lovely templates,
|
||||
* which both specify safe handling for void and non-void return params.
|
||||
*
|
||||
* Invoke - call the passed delegate and safely handle the return type
|
||||
* 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<typename Result, typename... Args>
|
||||
struct BaseMethodInvoker
|
||||
{
|
||||
public:
|
||||
typedef Result (EmptyClass::*EmptyDelegate)(Args...);
|
||||
};
|
||||
|
||||
template<typename IDelegate, typename... Args>
|
||||
struct VoidMethodInvoker
|
||||
{
|
||||
public:
|
||||
typedef std::bool_constant<false> has_return;
|
||||
typedef BaseMethodInvoker<void, Args...> base;
|
||||
|
||||
static void Invoke(IDelegate* delegate, void* result, Args... args)
|
||||
{
|
||||
// Do not touch return type: It's void!
|
||||
delegate->Call(args...);
|
||||
}
|
||||
|
||||
static void Original(EmptyClass* self, typename base::EmptyDelegate mfp, void* result, Args... args)
|
||||
{
|
||||
// Do not touch return type: It's void!
|
||||
(self->*mfp)(args...);
|
||||
}
|
||||
|
||||
template<typename Self, typename Mfp>
|
||||
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)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template<typename IDelegate, typename Result, typename... Args>
|
||||
struct ReturningMethodInvoker
|
||||
{
|
||||
public:
|
||||
typedef std::bool_constant<true> has_return;
|
||||
typedef BaseMethodInvoker<Result, Args...> base;
|
||||
|
||||
/**
|
||||
* A RefSafeResult handles return types that are references
|
||||
* (Which we cannot take a pointer to). This technically breaks
|
||||
* the contract of the return being "Result", but this is the type
|
||||
* the actual hook handler uses :/
|
||||
*/
|
||||
typedef typename ReferenceCarrier<Result>::type RefSafeResult;
|
||||
|
||||
|
||||
static void Invoke(IDelegate* delegate, RefSafeResult& result, Args... args)
|
||||
{
|
||||
result = delegate->Call(args...);
|
||||
}
|
||||
|
||||
static void Original(EmptyClass* self, typename base::EmptyDelegate mfp, RefSafeResult& result, Args... args)
|
||||
{
|
||||
result = (self->*mfp)(args...);
|
||||
}
|
||||
|
||||
template<typename Self, typename Mfp>
|
||||
static void OriginalRaised( Result (*Invoker)(Self, Mfp, Args...), Self self, Mfp mfp, RefSafeResult& result, Args... args )
|
||||
{
|
||||
result = Invoker(self, mfp, args...);
|
||||
}
|
||||
|
||||
static Result Dereference(const RefSafeResult* 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<ProtoInfo::CallConvention Convention, typename Result, typename... Args>
|
||||
struct HookHandlerImpl
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief The delegate type that SourceHook will invoke (user code)
|
||||
*/
|
||||
typedef typename fastdelegate::FastDelegate<Result, Args...> 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) {
|
||||
// Note: This is a safe cast because SourceHook will only call IsEqual() for delegates
|
||||
// of the same plugin/hookmangen. However, there is no tests enforcing this behavior (yet!)
|
||||
// this should never crash in the first place for differing delegates because the operator== should
|
||||
// only be using only fields in the FastDelegate<> base type, so no undefined reads?
|
||||
return _delegate == static_cast<CMyDelegateImpl *>(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<Result>::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<VoidSafeResult>::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<Result>) {
|
||||
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<detail::PrototypeBuilderFunctor, Args...>(&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<ISourceHook** SH, typename Invoker, typename InstType>
|
||||
static Result HookImplCore(InstType* Instance, void* self, Args... args)
|
||||
{
|
||||
using namespace ::SourceHook;
|
||||
|
||||
void *ourvfnptr = reinterpret_cast<void *>(
|
||||
*reinterpret_cast<void ***>(reinterpret_cast<char *>(self) + Instance->MFI.vtbloffs) + Instance->MFI.vtblindex);
|
||||
void *vfnptr_origentry;
|
||||
|
||||
META_RES status = MRES_IGNORED;
|
||||
META_RES prev_res;
|
||||
META_RES cur_res;
|
||||
|
||||
// TODO: fix MSVC warning C4700 uninitialized local variable used
|
||||
ResultType original_ret;
|
||||
ResultType override_ret;
|
||||
ResultType current_ret;
|
||||
|
||||
IMyDelegate *iter;
|
||||
IHookContext *context = (*SH)->SetupHookLoop(
|
||||
Instance->HI,
|
||||
ourvfnptr,
|
||||
reinterpret_cast<void *>(self),
|
||||
&vfnptr_origentry,
|
||||
&status,
|
||||
&prev_res,
|
||||
&cur_res,
|
||||
&original_ret,
|
||||
&override_ret);
|
||||
|
||||
// Call all pre-hooks
|
||||
prev_res = MRES_IGNORED;
|
||||
while ((iter = static_cast<IMyDelegate *>(context->GetNext()))) {
|
||||
cur_res = MRES_IGNORED;
|
||||
Invoker::Invoke(iter, current_ret, args...);
|
||||
prev_res = cur_res;
|
||||
|
||||
if (cur_res > status)
|
||||
status = cur_res;
|
||||
|
||||
if (cur_res >= MRES_OVERRIDE)
|
||||
*reinterpret_cast<ResultType *>(context->GetOverrideRetPtr()) = current_ret;
|
||||
}
|
||||
|
||||
// Call original method
|
||||
if (status != MRES_SUPERCEDE && context->ShouldCallOrig()) {
|
||||
typename Invoker::base::EmptyDelegate original;
|
||||
reinterpret_cast<void **>(&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 received.
|
||||
//
|
||||
// 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<typename InstType::UnloweredSelf*>(self);
|
||||
typename InstType::UnloweredDelegate known_mfp = reinterpret_cast<typename InstType::UnloweredDelegate>(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<IMyDelegate *>(context->GetNext()))) {
|
||||
cur_res = MRES_IGNORED;
|
||||
Invoker::Invoke(iter, current_ret, args...);
|
||||
prev_res = cur_res;
|
||||
|
||||
if (cur_res > status)
|
||||
status = cur_res;
|
||||
|
||||
if (cur_res >= MRES_OVERRIDE)
|
||||
*reinterpret_cast<ResultType *>(context->GetOverrideRetPtr()) = current_ret;
|
||||
}
|
||||
|
||||
const ResultType* result_ptr = reinterpret_cast<const ResultType *>((status >= MRES_OVERRIDE)
|
||||
? context->GetOverrideRetPtr()
|
||||
: context->GetOrigRetPtr());
|
||||
|
||||
(*SH)->EndContext(context);
|
||||
|
||||
return Invoker::Dereference(result_ptr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* You must specify the SourceHook pointer, interface, method pointer,
|
||||
* and prototype in the template arguments. Any derived class of the interface
|
||||
* can be hooked using this manager.
|
||||
*/
|
||||
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<Convention, Result, Args...> HookHandlerImpl;
|
||||
typedef typename HookHandlerImpl::Delegate Delegate;
|
||||
typedef typename HookHandlerImpl::IMyDelegate IMyDelegate;
|
||||
typedef typename HookHandlerImpl::CMyDelegateImpl CMyDelegateImpl;
|
||||
typedef typename HookHandlerImpl::ResultType HandlerResultType;
|
||||
|
||||
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<Result>::value,
|
||||
detail::VoidMethodInvoker<IMyDelegate, Args...>,
|
||||
detail::ReturningMethodInvoker<IMyDelegate, Result, Args...>
|
||||
>::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 decltype(Method) MethodType;
|
||||
|
||||
// IF YOU HAVE THIS ERROR:
|
||||
// Make sure the SECOND template argument to SourceHook::Hook and SourceHook::FmtHook
|
||||
// is a member function pointer for the interface you passed, such as:
|
||||
//
|
||||
// SourceHook::Hook<Interface, &Interface::Method, ...>
|
||||
// ^^^^^^^^^^^^^^^^^^
|
||||
// To resolve, ensure this second argument is a MEMBER function pointer to a VIRTUAL.
|
||||
// Does not work on statics/non-virtuals!
|
||||
static_assert( std::is_member_function_pointer<MethodType>::value,
|
||||
"You must specify a pointer to the hooked method (&Interface::Method) for the 'Method' argument!" );
|
||||
|
||||
// IF YOU HAVE THIS ERROR:
|
||||
// You are passing an Interface object that doesn't have a virtual table.
|
||||
// SourceHook can only hook virtual methods!
|
||||
//
|
||||
// To resolve, specify a type that actually has a virtual method.
|
||||
static_assert( std::is_polymorphic<Interface>::value,
|
||||
"Your interface is not polymorphic! Did you specify the wrong type?");
|
||||
|
||||
// IF YOU HAVE THIS ERROR:
|
||||
// Your arguments, interface, and/or return type are wrong!
|
||||
// This error occurs because the method type we expected (MemberMethod)
|
||||
// is not the same as the actual method that was passed to be hooked.
|
||||
//
|
||||
// Double check that all your arguments match up EXACTLY, as any deviation
|
||||
// can cause errors. Also make sure your Interface is correct :^)
|
||||
//
|
||||
// SourceHook::Hook<Interface, &Interface::Method, void, int&, const int*>
|
||||
// ┌───────────────────────────────────────┘ │ │
|
||||
// │ ┌───────┬─────────────────────────┴─────┘
|
||||
// V V V
|
||||
// virtual void Method(int &a, const int *b) = 0;
|
||||
//
|
||||
static_assert( std::is_same<MethodType, MemberMethod>::type::value,
|
||||
"Mismatched argument types" );
|
||||
|
||||
// uh oh, Parent is technically uninitialized here.
|
||||
// should find a workaround, this would be nice to have.
|
||||
//static_assert( std::is_base_of<HookCoreImpl, Parent>::type::value, "Mismatched hookman parent type! (INTERNAL ERROR - REPORT THIS AS A BUG)");
|
||||
|
||||
// Members
|
||||
::SourceHook::MemFuncInfo MFI;
|
||||
::SourceHook::IHookManagerInfo *HI;
|
||||
|
||||
HookHandlerImpl HookHandler;
|
||||
|
||||
// Singleton instance
|
||||
// Initialized below!
|
||||
static Parent Instance;
|
||||
|
||||
protected:
|
||||
HookCoreImpl(HookCoreImpl& other) = delete;
|
||||
|
||||
constexpr HookCoreImpl()
|
||||
// Build the ProtoInfo object
|
||||
: HookHandler(HookHandlerImpl())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public: // Public Interface
|
||||
|
||||
/**
|
||||
* @brief Add an instance of this hook to the specified interface
|
||||
*
|
||||
* @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
|
||||
* @param mode what objects the hook is invoked on - Hook_Normal for only the interface object we pass to ->Add().
|
||||
*/
|
||||
HookInstance Add(Interface* iface, bool post, Delegate handler, bool global = false)
|
||||
{
|
||||
using namespace ::SourceHook;
|
||||
MemFuncInfo mfi = {true, -1, 0, 0};
|
||||
GetFuncInfo(Method, mfi);
|
||||
|
||||
if (mfi.thisptroffs < 0 || !mfi.isVirtual)
|
||||
return {SH, false};
|
||||
|
||||
CMyDelegateImpl* delegate = new CMyDelegateImpl(handler);
|
||||
int id = (*SH)->AddHook(*PL, global ? ISourceHook::AddHookMode::Hook_VP : ISourceHook::AddHookMode::Hook_Normal, iface, mfi.thisptroffs, HookCoreImpl::HookManPubFunc, delegate, post);
|
||||
|
||||
return {SH, id};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove an existing hook handler from this interface
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
int Remove(Interface* iface, bool post, Delegate handler)
|
||||
{
|
||||
using namespace ::SourceHook;
|
||||
MemFuncInfo mfi = {true, -1, 0, 0};
|
||||
GetFuncInfo(Method, mfi);
|
||||
|
||||
// Temporary delegate for .IsEqual() comparison.
|
||||
CMyDelegateImpl temp(handler);
|
||||
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
|
||||
// TODO: Accept MFI from user code if they wish to do a manual hook
|
||||
GetFuncInfo<MemberMethod>(static_cast<MemberMethod>(Method), Instance.MFI);
|
||||
|
||||
if ((*SH)->GetIfaceVersion() != SH_IFACE_VERSION || (*SH)->GetImplVersion() < SH_IMPL_VERSION)
|
||||
return 1;
|
||||
|
||||
if (store)
|
||||
Instance.HI = hi;
|
||||
|
||||
if (hi) {
|
||||
// Build a memberfuncinfo for our hook processor.
|
||||
MemFuncInfo our_mfi = {true, -1, 0, 0};
|
||||
GetFuncInfo(&Parent::Hook, our_mfi);
|
||||
|
||||
static_assert( std::is_member_function_pointer< decltype(&Parent::Hook) >::value,
|
||||
"Internal Error - Parent::Hook is not a virtual!" );
|
||||
|
||||
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.HookHandler.Proto, us);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
static Result InvokeDelegates(void* self, Args... args)
|
||||
{
|
||||
return HookHandlerImpl::template HookImplCore<SH, Invoker, Parent>(&Instance, self, args...);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 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<ISourceHook** SH, Plugin* PL, typename Interface, auto Method, typename MemberMethod, ProtoInfo::CallConvention Convention, typename Parent, typename Result, typename... Args>
|
||||
Parent HookCoreImpl<SH, PL, Interface, Method, MemberMethod, Convention, Parent, Result, Args...>::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). In this case, they translate
|
||||
// (const char* fmt, ...) to ("%s", <formatted result>)
|
||||
//
|
||||
// Warning: Your HookManGen MUST take the exact args/protoinfo of every other
|
||||
// hookman (in other plugins) for the same method. If you have two hookmans
|
||||
// that pass different args to their delegates, and they end up hooking the same method,
|
||||
// they WILL crash because SourceHook will only pick one of the hookman to be the hook!
|
||||
// If you need to do something REALLY silly, you should be using HookManGen which allows
|
||||
// you to do some silly goose things like chuck varargs across C calls :^)
|
||||
//
|
||||
// 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 <Args..., const char* buffer> 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<ISourceHook** SH, Plugin* PL, typename Interface, auto Method, typename Result, typename... Args>
|
||||
struct HookImpl final : HookCoreImpl<
|
||||
SH, PL,
|
||||
Interface, Method, Result (Interface::*)(Args...), ProtoInfo::CallConv_ThisCall,
|
||||
HookImpl<SH, PL, Interface, Method, Result, Args...>,
|
||||
Result, Args...>
|
||||
{
|
||||
public:
|
||||
typedef Result (Interface::*UnloweredDelegate)(Args...);
|
||||
typedef Interface UnloweredSelf;
|
||||
|
||||
/**
|
||||
* @brief Hook handler virtual
|
||||
*/
|
||||
virtual Result Hook(Args... args) final
|
||||
{
|
||||
return HookImpl::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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @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<ISourceHook** SH, Plugin* PL, typename Interface, auto Method, typename Result, typename... Args>
|
||||
struct FmtHookImpl final : HookCoreImpl<
|
||||
SH, PL,
|
||||
Interface, Method, Result (Interface::*)(Args..., const char*, ...), ProtoInfo::CallConv_HasVafmt,
|
||||
FmtHookImpl<SH, PL, Interface, Method, Result, Args...>,
|
||||
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, ...) final
|
||||
{
|
||||
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 FmtHookImpl::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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
@ -98,8 +98,10 @@
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# define SH_COMP SH_COMP_MSVC
|
||||
# define SH_INLINE inline __forceinline
|
||||
#elif defined __GNUC__
|
||||
# define SH_COMP SH_COMP_GCC
|
||||
# define SH_INLINE inline __attribute__((always_inline))
|
||||
#else
|
||||
# error Unsupported compiler
|
||||
#endif
|
||||
@ -114,6 +116,9 @@
|
||||
|
||||
#define SH_PTRSIZE sizeof(void*)
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <type_traits>
|
||||
#include "sh_memfuncinfo.h"
|
||||
#include "FastDelegate.h"
|
||||
|
||||
@ -573,6 +578,777 @@ namespace SourceHook
|
||||
static const int type = 0;
|
||||
static const unsigned int flags = PassInfo::PassFlag_ByRef;
|
||||
};
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* Templated hook definition */
|
||||
/************************************************************************/
|
||||
|
||||
namespace metaprogramming
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
/**
|
||||
* @brief Iterate over all elements of a type pack
|
||||
*
|
||||
* @param functor A reference to the functor containing the step<index, now> template.
|
||||
*/
|
||||
template<class Functor, int Index, typename Now, typename... Rest>
|
||||
constexpr SH_INLINE void for_each_template_impl(Functor *f)
|
||||
{
|
||||
f->template step<Index, Now>();
|
||||
|
||||
if constexpr (sizeof...(Rest) > 0) {
|
||||
for_each_template_impl<Functor, Index + 1, Rest...>(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class Functor, typename First, typename... Rest>
|
||||
constexpr SH_INLINE void for_each_template(Functor* f)
|
||||
{
|
||||
detail::for_each_template_impl<Functor, 0, First, Rest...>(f);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Iterate over all elements of a type pack
|
||||
*
|
||||
* @param functor A reference to the functor containing the step<index, now> template.
|
||||
*/
|
||||
template<class Functor, typename First, typename... Rest>
|
||||
constexpr SH_INLINE void for_each_template_nullable(Functor* f)
|
||||
{
|
||||
for_each_template<Functor, First, Rest...>(f);
|
||||
}
|
||||
|
||||
template<class Functor>
|
||||
constexpr SH_INLINE void for_each_template_nullable(Functor* f)
|
||||
{
|
||||
// Empty varargs
|
||||
}
|
||||
|
||||
template <bool Test, class Yes = void, class No = void>
|
||||
struct if_else {
|
||||
public:
|
||||
typedef No type;
|
||||
};
|
||||
|
||||
template <class Yes, class No>
|
||||
struct if_else<true, Yes, No> {
|
||||
public:
|
||||
typedef Yes type;
|
||||
};
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Build the PassInfo for a method pack.
|
||||
*
|
||||
* Iterated over using for_each_template_nullable.
|
||||
*/
|
||||
class PrototypeBuilderFunctor
|
||||
{
|
||||
public:
|
||||
constexpr PrototypeBuilderFunctor(PassInfo* p)
|
||||
: params(p) {}
|
||||
PassInfo* params;
|
||||
|
||||
template<int Index, typename Now>
|
||||
constexpr void step()
|
||||
{
|
||||
// Note: first index is always the thisptr!
|
||||
params[Index + 1] = { sizeof(Now), ::SourceHook::GetPassInfo< Now >::type, ::SourceHook::GetPassInfo< Now >::flags };
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Common type for the void/non-void handling semantics.
|
||||
*
|
||||
* Basically, *(void*) is an illegal operation as it works on zero-sized
|
||||
* types. We work around this here by using these two lovely templates,
|
||||
* which both specify safe handling for void and non-void return params.
|
||||
*
|
||||
* Invoke - call the passed delegate and safely handle the return type
|
||||
* 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<typename Result, typename... Args>
|
||||
struct BaseMethodInvoker
|
||||
{
|
||||
public:
|
||||
typedef Result (EmptyClass::*EmptyDelegate)(Args...);
|
||||
};
|
||||
|
||||
template<typename IDelegate, typename... Args>
|
||||
struct VoidMethodInvoker
|
||||
{
|
||||
public:
|
||||
typedef std::bool_constant<false> has_return;
|
||||
typedef BaseMethodInvoker<void, Args...> base;
|
||||
|
||||
static void Invoke(IDelegate* delegate, void* result, Args... args)
|
||||
{
|
||||
// Do not touch return type: It's void!
|
||||
delegate->Call(args...);
|
||||
}
|
||||
|
||||
static void Original(EmptyClass* self, typename base::EmptyDelegate mfp, void* result, Args... args)
|
||||
{
|
||||
// Do not touch return type: It's void!
|
||||
(self->*mfp)(args...);
|
||||
}
|
||||
|
||||
template<typename Self, typename Mfp>
|
||||
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)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template<typename IDelegate, typename Result, typename... Args>
|
||||
struct ReturningMethodInvoker
|
||||
{
|
||||
public:
|
||||
typedef std::bool_constant<true> has_return;
|
||||
typedef BaseMethodInvoker<Result, Args...> base;
|
||||
|
||||
/**
|
||||
* A RefSafeResult handles return types that are references
|
||||
* (Which we cannot take a pointer to). This technically breaks
|
||||
* the contract of the return being "Result", but this is the type
|
||||
* the actual hook handler uses :/
|
||||
*/
|
||||
typedef typename ReferenceCarrier<Result>::type RefSafeResult;
|
||||
|
||||
|
||||
static void Invoke(IDelegate* delegate, RefSafeResult& result, Args... args)
|
||||
{
|
||||
result = delegate->Call(args...);
|
||||
}
|
||||
|
||||
static void Original(EmptyClass* self, typename base::EmptyDelegate mfp, RefSafeResult& result, Args... args)
|
||||
{
|
||||
result = (self->*mfp)(args...);
|
||||
}
|
||||
|
||||
template<typename Self, typename Mfp>
|
||||
static void OriginalRaised( Result (*Invoker)(Self, Mfp, Args...), Self self, Mfp mfp, RefSafeResult& result, Args... args )
|
||||
{
|
||||
result = Invoker(self, mfp, args...);
|
||||
}
|
||||
|
||||
static Result Dereference(const RefSafeResult* 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<ProtoInfo::CallConvention Convention, typename Result, typename... Args>
|
||||
struct HookHandlerImpl
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief The delegate type that SourceHook will invoke (user code)
|
||||
*/
|
||||
typedef typename fastdelegate::FastDelegate<Result, Args...> 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) {
|
||||
// Note: This is a safe cast because SourceHook will only call IsEqual() for delegates
|
||||
// of the same plugin/hookmangen. However, there is no tests enforcing this behavior (yet!)
|
||||
// this should never crash in the first place for differing delegates because the operator== should
|
||||
// only be using only fields in the FastDelegate<> base type, so no undefined reads?
|
||||
return _delegate == static_cast<CMyDelegateImpl *>(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<Result>::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<VoidSafeResult>::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<Result>) {
|
||||
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<detail::PrototypeBuilderFunctor, Args...>(&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<ISourceHook** SH, typename Invoker, typename InstType>
|
||||
static Result HookImplCore(InstType* Instance, void* self, Args... args)
|
||||
{
|
||||
using namespace ::SourceHook;
|
||||
|
||||
void *ourvfnptr = reinterpret_cast<void *>(
|
||||
*reinterpret_cast<void ***>(reinterpret_cast<char *>(self) + Instance->MFI.vtbloffs) + Instance->MFI.vtblindex);
|
||||
void *vfnptr_origentry;
|
||||
|
||||
META_RES status = MRES_IGNORED;
|
||||
META_RES prev_res;
|
||||
META_RES cur_res;
|
||||
|
||||
// TODO: fix MSVC warning C4700 uninitialized local variable used
|
||||
ResultType original_ret;
|
||||
ResultType override_ret;
|
||||
ResultType current_ret;
|
||||
|
||||
IMyDelegate *iter;
|
||||
IHookContext *context = (*SH)->SetupHookLoop(
|
||||
Instance->HI,
|
||||
ourvfnptr,
|
||||
reinterpret_cast<void *>(self),
|
||||
&vfnptr_origentry,
|
||||
&status,
|
||||
&prev_res,
|
||||
&cur_res,
|
||||
&original_ret,
|
||||
&override_ret);
|
||||
|
||||
// Call all pre-hooks
|
||||
prev_res = MRES_IGNORED;
|
||||
while ((iter = static_cast<IMyDelegate *>(context->GetNext()))) {
|
||||
cur_res = MRES_IGNORED;
|
||||
Invoker::Invoke(iter, current_ret, args...);
|
||||
prev_res = cur_res;
|
||||
|
||||
if (cur_res > status)
|
||||
status = cur_res;
|
||||
|
||||
if (cur_res >= MRES_OVERRIDE)
|
||||
*reinterpret_cast<ResultType *>(context->GetOverrideRetPtr()) = current_ret;
|
||||
}
|
||||
|
||||
// Call original method
|
||||
if (status != MRES_SUPERCEDE && context->ShouldCallOrig()) {
|
||||
typename Invoker::base::EmptyDelegate original;
|
||||
reinterpret_cast<void **>(&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 received.
|
||||
//
|
||||
// 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<typename InstType::UnloweredSelf*>(self);
|
||||
typename InstType::UnloweredDelegate known_mfp = reinterpret_cast<typename InstType::UnloweredDelegate>(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<IMyDelegate *>(context->GetNext()))) {
|
||||
cur_res = MRES_IGNORED;
|
||||
Invoker::Invoke(iter, current_ret, args...);
|
||||
prev_res = cur_res;
|
||||
|
||||
if (cur_res > status)
|
||||
status = cur_res;
|
||||
|
||||
if (cur_res >= MRES_OVERRIDE)
|
||||
*reinterpret_cast<ResultType *>(context->GetOverrideRetPtr()) = current_ret;
|
||||
}
|
||||
|
||||
const ResultType* result_ptr = reinterpret_cast<const ResultType *>((status >= MRES_OVERRIDE)
|
||||
? context->GetOverrideRetPtr()
|
||||
: context->GetOrigRetPtr());
|
||||
|
||||
(*SH)->EndContext(context);
|
||||
|
||||
return Invoker::Dereference(result_ptr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* You must specify the SourceHook pointer, interface, method pointer,
|
||||
* and prototype in the template arguments. Any derived class of the interface
|
||||
* can be hooked using this manager.
|
||||
*/
|
||||
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<Convention, Result, Args...> HookHandlerImpl;
|
||||
typedef typename HookHandlerImpl::Delegate Delegate;
|
||||
typedef typename HookHandlerImpl::IMyDelegate IMyDelegate;
|
||||
typedef typename HookHandlerImpl::CMyDelegateImpl CMyDelegateImpl;
|
||||
typedef typename HookHandlerImpl::ResultType HandlerResultType;
|
||||
|
||||
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<Result>::value,
|
||||
detail::VoidMethodInvoker<IMyDelegate, Args...>,
|
||||
detail::ReturningMethodInvoker<IMyDelegate, Result, Args...>
|
||||
>::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 decltype(Method) MethodType;
|
||||
|
||||
// IF YOU HAVE THIS ERROR:
|
||||
// Make sure the SECOND template argument to SourceHook::Hook and SourceHook::FmtHook
|
||||
// is a member function pointer for the interface you passed, such as:
|
||||
//
|
||||
// SourceHook::Hook<Interface, &Interface::Method, ...>
|
||||
// ^^^^^^^^^^^^^^^^^^
|
||||
// To resolve, ensure this second argument is a MEMBER function pointer to a VIRTUAL.
|
||||
// Does not work on statics/non-virtuals!
|
||||
static_assert( std::is_member_function_pointer<MethodType>::value,
|
||||
"You must specify a pointer to the hooked method (&Interface::Method) for the 'Method' argument!" );
|
||||
|
||||
// IF YOU HAVE THIS ERROR:
|
||||
// You are passing an Interface object that doesn't have a virtual table.
|
||||
// SourceHook can only hook virtual methods!
|
||||
//
|
||||
// To resolve, specify a type that actually has a virtual method.
|
||||
static_assert( std::is_polymorphic<Interface>::value,
|
||||
"Your interface is not polymorphic! Did you specify the wrong type?");
|
||||
|
||||
// IF YOU HAVE THIS ERROR:
|
||||
// Your arguments, interface, and/or return type are wrong!
|
||||
// This error occurs because the method type we expected (MemberMethod)
|
||||
// is not the same as the actual method that was passed to be hooked.
|
||||
//
|
||||
// Double check that all your arguments match up EXACTLY, as any deviation
|
||||
// can cause errors. Also make sure your Interface is correct :^)
|
||||
//
|
||||
// SourceHook::Hook<Interface, &Interface::Method, void, int&, const int*>
|
||||
// ┌───────────────────────────────────────┘ │ │
|
||||
// │ ┌───────┬─────────────────────────┴─────┘
|
||||
// V V V
|
||||
// virtual void Method(int &a, const int *b) = 0;
|
||||
//
|
||||
static_assert( std::is_same<MethodType, MemberMethod>::type::value,
|
||||
"Mismatched argument types" );
|
||||
|
||||
// uh oh, Parent is technically uninitialized here.
|
||||
// should find a workaround, this would be nice to have.
|
||||
//static_assert( std::is_base_of<HookCoreImpl, Parent>::type::value, "Mismatched hookman parent type! (INTERNAL ERROR - REPORT THIS AS A BUG)");
|
||||
|
||||
// Members
|
||||
::SourceHook::MemFuncInfo MFI;
|
||||
::SourceHook::IHookManagerInfo *HI;
|
||||
|
||||
HookHandlerImpl HookHandler;
|
||||
|
||||
// Singleton instance
|
||||
// Initialized below!
|
||||
static Parent Instance;
|
||||
|
||||
protected:
|
||||
HookCoreImpl(HookCoreImpl& other) = delete;
|
||||
|
||||
constexpr HookCoreImpl()
|
||||
// Build the ProtoInfo object
|
||||
: HookHandler(HookHandlerImpl())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public: // Public Interface
|
||||
|
||||
/**
|
||||
* @brief Add an instance of this hook to the specified interface
|
||||
*
|
||||
* @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
|
||||
* @param mode what objects the hook is invoked on - Hook_Normal for only the interface object we pass to ->Add().
|
||||
*/
|
||||
HookInstance Add(Interface* iface, bool post, Delegate handler, bool global = false)
|
||||
{
|
||||
using namespace ::SourceHook;
|
||||
MemFuncInfo mfi = {true, -1, 0, 0};
|
||||
GetFuncInfo(Method, mfi);
|
||||
|
||||
if (mfi.thisptroffs < 0 || !mfi.isVirtual)
|
||||
return {SH, false};
|
||||
|
||||
CMyDelegateImpl* delegate = new CMyDelegateImpl(handler);
|
||||
int id = (*SH)->AddHook(*PL, global ? ISourceHook::AddHookMode::Hook_VP : ISourceHook::AddHookMode::Hook_Normal, iface, mfi.thisptroffs, HookCoreImpl::HookManPubFunc, delegate, post);
|
||||
|
||||
return {SH, id};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove an existing hook handler from this interface
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
int Remove(Interface* iface, bool post, Delegate handler)
|
||||
{
|
||||
using namespace ::SourceHook;
|
||||
MemFuncInfo mfi = {true, -1, 0, 0};
|
||||
GetFuncInfo(Method, mfi);
|
||||
|
||||
// Temporary delegate for .IsEqual() comparison.
|
||||
CMyDelegateImpl temp(handler);
|
||||
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
|
||||
// TODO: Accept MFI from user code if they wish to do a manual hook
|
||||
GetFuncInfo<MemberMethod>(static_cast<MemberMethod>(Method), Instance.MFI);
|
||||
|
||||
if ((*SH)->GetIfaceVersion() != SH_IFACE_VERSION || (*SH)->GetImplVersion() < SH_IMPL_VERSION)
|
||||
return 1;
|
||||
|
||||
if (store)
|
||||
Instance.HI = hi;
|
||||
|
||||
if (hi) {
|
||||
// Build a memberfuncinfo for our hook processor.
|
||||
MemFuncInfo our_mfi = {true, -1, 0, 0};
|
||||
GetFuncInfo(&Parent::Hook, our_mfi);
|
||||
|
||||
static_assert( std::is_member_function_pointer< decltype(&Parent::Hook) >::value,
|
||||
"Internal Error - Parent::Hook is not a virtual!" );
|
||||
|
||||
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.HookHandler.Proto, us);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
static Result InvokeDelegates(void* self, Args... args)
|
||||
{
|
||||
return HookHandlerImpl::template HookImplCore<SH, Invoker, Parent>(&Instance, self, args...);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 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<ISourceHook** SH, Plugin* PL, typename Interface, auto Method, typename MemberMethod, ProtoInfo::CallConvention Convention, typename Parent, typename Result, typename... Args>
|
||||
Parent HookCoreImpl<SH, PL, Interface, Method, MemberMethod, Convention, Parent, Result, Args...>::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). In this case, they translate
|
||||
// (const char* fmt, ...) to ("%s", <formatted result>)
|
||||
//
|
||||
// Warning: Your HookManGen MUST take the exact args/protoinfo of every other
|
||||
// hookman (in other plugins) for the same method. If you have two hookmans
|
||||
// that pass different args to their delegates, and they end up hooking the same method,
|
||||
// they WILL crash because SourceHook will only pick one of the hookman to be the hook!
|
||||
// If you need to do something REALLY silly, you should be using HookManGen which allows
|
||||
// you to do some silly goose things like chuck varargs across C calls :^)
|
||||
//
|
||||
// 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 <Args..., const char* buffer> 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<ISourceHook** SH, Plugin* PL, typename Interface, auto Method, typename Result, typename... Args>
|
||||
struct HookImpl final : HookCoreImpl<
|
||||
SH, PL,
|
||||
Interface, Method, Result (Interface::*)(Args...), ProtoInfo::CallConv_ThisCall,
|
||||
HookImpl<SH, PL, Interface, Method, Result, Args...>,
|
||||
Result, Args...>
|
||||
{
|
||||
public:
|
||||
typedef Result (Interface::*UnloweredDelegate)(Args...);
|
||||
typedef Interface UnloweredSelf;
|
||||
|
||||
/**
|
||||
* @brief Hook handler virtual
|
||||
*/
|
||||
virtual Result Hook(Args... args) final
|
||||
{
|
||||
return HookImpl::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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @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<ISourceHook** SH, Plugin* PL, typename Interface, auto Method, typename Result, typename... Args>
|
||||
struct FmtHookImpl final : HookCoreImpl<
|
||||
SH, PL,
|
||||
Interface, Method, Result (Interface::*)(Args..., const char*, ...), ProtoInfo::CallConv_HasVafmt,
|
||||
FmtHookImpl<SH, PL, Interface, Method, Result, Args...>,
|
||||
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, ...) final
|
||||
{
|
||||
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 FmtHookImpl::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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit f559b1cc8f9e47d88e13bd46a220c32ec26af7b6
|
||||
Subproject commit 3e5ac8b96aa7e2d984e214dda7b95486aea6815b
|
||||
@ -40,4 +40,4 @@ msvc2015, take two
|
||||
"something witty" - Fishy!
|
||||
thisisawfuldotnetcom
|
||||
iluvwomen99
|
||||
Hi all, I'm new in here!!!!!!!!
|
||||
Hi all, I'm new in here!!!!!!!!!
|
||||
|
||||
Loading…
Reference in New Issue
Block a user