Skip to content

Commit

Permalink
Hook->HookImpl, expose "public" hook template in PLUGIN_EXPOSE
Browse files Browse the repository at this point in the history
This allows us to specify the SH pointer and the Plugin ID pointer in the template, so when the plugin uses Hook<> it doesn't have to touch g_PLID or g_SHPtr.

Also added a little struct that wraps ISourceHook->xHookById methods.
  • Loading branch information
Mooshua committed Apr 24, 2024
1 parent e391dc5 commit d96b3ad
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 45 deletions.
4 changes: 3 additions & 1 deletion core/ISmmPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,9 @@ using namespace SourceMM;
extern SourceHook::ISourceHook *g_SHPtr; \
extern ISmmAPI *g_SMAPI; \
extern ISmmPlugin *g_PLAPI; \
extern PluginId g_PLID;
extern PluginId g_PLID; \
template<typename Interface, auto Method, typename Result, typename... Args> \
struct Hook : public ::SourceHook::HookImpl<&g_SHPtr, &g_PLID, Interface, Method, Result, Args...> {};

/**
* @brief This should be the first line in your Load callback.
Expand Down
2 changes: 2 additions & 0 deletions core/metamod_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,8 @@ extern PluginId g_PLID;
extern SourceHook::ISourceHook *g_SHPtr;
extern SourceMM::IMetamodSourceProvider *provider;
extern SourceMM::ISmmAPI *g_pMetamod;
template<typename Interface, auto Method, typename Result, typename... Args> \
struct Hook : public ::SourceHook::HookImpl<&g_SHPtr, &g_PLID, Interface, Method, Result, Args...> {};

#endif //_INCLUDE_METAMOD_SOURCE_SUPPORT_H_

24 changes: 12 additions & 12 deletions core/provider/source/provider_source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ static SourceProvider g_SourceProvider;

IMetamodSourceProvider* provider = &g_SourceProvider;

auto OnGameInit = SourceHook::Hook<&g_SHPtr, IServerGameDLL, &IServerGameDLL::GameInit, bool>::Make();
auto OnLevelInit = SourceHook::Hook<&g_SHPtr, IServerGameDLL, &IServerGameDLL::LevelInit, bool, const char*, const char*, const char*, const char*, bool, bool>::Make();
auto OnLevelShutdown = SourceHook::Hook<&g_SHPtr, IServerGameDLL, &IServerGameDLL::LevelShutdown, void>::Make();
auto OnGameInit = Hook<IServerGameDLL, &IServerGameDLL::GameInit, bool>::Make();
auto OnLevelInit = Hook<IServerGameDLL, &IServerGameDLL::LevelInit, bool, const char*, const char*, const char*, const char*, bool, bool>::Make();
auto OnLevelShutdown = Hook<IServerGameDLL, &IServerGameDLL::LevelShutdown, void>::Make();

#if SOURCE_ENGINE >= SE_ORANGEBOX
auto OnClientCommand = SourceHook::Hook<&g_SHPtr, IServerGameClients, &IServerGameClients::ClientCommand, void, edict_t*, const CCommand&>::Make();
auto OnClientCommand = Hook<IServerGameClients, &IServerGameClients::ClientCommand, void, edict_t*, const CCommand&>::Make();
#else
auto OnClientCommand = SourceHook::Hook<&g_SHPtr, IServerGameClients, &IServerGameClients::ClientCommand, void, edict_t*>::Make();
auto OnClientCommand = Hook<IServerGameClients, &IServerGameClients::ClientCommand, void, edict_t*>::Make();
#endif

void SourceProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory,
Expand Down Expand Up @@ -119,19 +119,19 @@ void SourceProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory,
#endif

if (gameclients) {
OnClientCommand->Add(g_PLID, gameclients, false, SH_MEMBER(this, &SourceProvider::Hook_ClientCommand));
OnClientCommand->Add(gameclients, false, SH_MEMBER(this, &SourceProvider::Hook_ClientCommand));
}

OnGameInit->Add(g_PLID, server, false, SH_MEMBER(this, &SourceProvider::Hook_GameInit));
OnLevelInit->Add(g_PLID, server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelInit));
OnLevelShutdown->Add(g_PLID, server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown));
OnGameInit->Add(server, false, SH_MEMBER(this, &SourceProvider::Hook_GameInit));
OnLevelInit->Add(server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelInit));
OnLevelShutdown->Add(server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown));
}

void SourceProvider::Notify_DLLShutdown_Pre()
{
OnGameInit->Remove(g_PLID, server, false, SH_MEMBER(this, &SourceProvider::Hook_GameInit));
OnLevelInit->Remove(g_PLID, server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelInit));
OnLevelShutdown->Remove(g_PLID, server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown));
OnGameInit->Remove(server, false, SH_MEMBER(this, &SourceProvider::Hook_GameInit));
OnLevelInit->Remove(server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelInit));
OnLevelShutdown->Remove(server, true, SH_MEMBER(this, &SourceProvider::Hook_LevelShutdown));

m_ConVarAccessor.RemoveMetamodCommands();

Expand Down
97 changes: 81 additions & 16 deletions core/sourcehook/generate/sourcehook.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,61 @@ namespace SourceHook
};
}

/**
* A reference to an active SourceHook
*/
struct HookInstance
{
public:
HookInstance(ISourceHook** sh, int hookid)
: SH(sh)
, _hookid(hookid)
{}
protected:
// The global pointer to the SourceHook API
ISourceHook** SH;

// The ID of this specific hook
int _hookid;

public:

/**
* @brief Returns true if the hook was successfully placed.
* @return
*/
bool Ok()
{
return _hookid != 0;
}

/**
* @brief Pause the hook, preventing it from being called until unpaused.
* @return
*/
bool Pause()
{
return (*SH)->PauseHookByID(_hookid);
}

/**
* @brief Unpause the hook if it is currently paused
* @return
*/
bool Unpause()
{
return (*SH)->UnpauseHookByID(_hookid);
}

/**
* @brief Remove the hook, permanently preventing it from being invoked.
* @return
*/
bool Remove()
{
return (*SH)->RemoveHookByID(_hookid);
}
};

/**
* @brief A hook manager, used to hook instances of a specific interface.
Expand All @@ -731,8 +786,8 @@ namespace SourceHook
* and prototype in the template arguments. Any derived class of the interface
* can be hooked using this manager.
*/
template<ISourceHook** SH, typename Interface, auto Method, typename Result, typename... Args>
struct Hook
template<ISourceHook** SH, Plugin* PL, typename Interface, auto Method, typename Result, typename... Args>
struct HookImpl
{
/**
* @brief The type we expect the template arg "Method" to be.
Expand All @@ -750,15 +805,15 @@ namespace SourceHook

// Singleton instance
// Initialized below!
static Hook Instance;
static HookImpl Instance;

private:
Hook(Hook& other) = delete;
HookImpl(HookImpl& other) = delete;

/**
* @brief Build the ProtoInfo for this hook
*/
constexpr Hook()
constexpr HookImpl()
{
// build protoinfo
Proto.numOfParams = sizeof...(Args);
Expand Down Expand Up @@ -791,11 +846,11 @@ namespace SourceHook
}

public:
static constexpr Hook* Make()
static constexpr HookImpl* Make()
{
Hook::Instance = Hook();
HookImpl::Instance = HookImpl();

return &Hook::Instance;
return &HookImpl::Instance;
}

public: // Public Interface
Expand All @@ -809,16 +864,18 @@ namespace SourceHook
* @param handler the handler that will be called in place of the original method
* @param mode the hook mode - Hook_Normal will only hook this instance, while others alter the global virtual table.
*/
int Add(Plugin id, Interface* iface, bool post, Delegate handler, ::SourceHook::ISourceHook::AddHookMode mode = ISourceHook::AddHookMode::Hook_Normal)
HookInstance Add(Interface* iface, bool post, Delegate handler, ::SourceHook::ISourceHook::AddHookMode mode = ISourceHook::AddHookMode::Hook_Normal)
{
using namespace ::SourceHook;
MemFuncInfo mfi = {true, -1, 0, 0};
GetFuncInfo(Method, mfi);
if (mfi.thisptroffs < 0 || !mfi.isVirtual)
return false;
return HookInstance(SH, 0);

CMyDelegateImpl* delegate = new CMyDelegateImpl(handler);
return (*SH)->AddHook(id, mode, iface, mfi.thisptroffs, Hook::HookManPubFunc, delegate, post);
int id = (*SH)->AddHook(*PL, mode, iface, mfi.thisptroffs, HookImpl::HookManPubFunc, delegate, post);

return HookInstance(SH, id);
}

/**
Expand All @@ -829,15 +886,15 @@ namespace SourceHook
* @param post true if this was a post hook, false otherwise (pre-hook)
* @param handler the handler that will be removed from this hook.
*/
int Remove(Plugin id, Interface* iface, bool post, Delegate handler)
int Remove(Interface* iface, bool post, Delegate handler)
{
using namespace ::SourceHook;
MemFuncInfo mfi = {true, -1, 0, 0};
GetFuncInfo(Method, mfi);

// Temporary delegate for .IsEqual() comparison.
CMyDelegateImpl temp(handler);
return (*SH)->RemoveHook(id, iface, mfi.thisptroffs, Hook::HookManPubFunc, &temp, post);
return (*SH)->RemoveHook(*PL, iface, mfi.thisptroffs, HookImpl::HookManPubFunc, &temp, post);
}

protected:
Expand All @@ -855,7 +912,7 @@ namespace SourceHook
if (hi) {
// Build a memberfuncinfo for our hook processor.
MemFuncInfo our_mfi = {true, -1, 0, 0};
GetFuncInfo(&Hook::Func, our_mfi);
GetFuncInfo(&HookImpl::Func, our_mfi);

void* us = reinterpret_cast<void **>(reinterpret_cast<char *>(&Instance) + our_mfi.vtbloffs)[our_mfi.vtblindex];
hi->SetInfo(SH_HOOKMAN_VERSION, Instance.MFI.vtbloffs, Instance.MFI.vtblindex, &Instance.Proto, us);
Expand Down Expand Up @@ -908,13 +965,21 @@ namespace SourceHook
Result
>::type VoidSafeResult;

/**
* @brief A return type that is C++-reference-safe.
*
* Prevents us from doing silly things with byref-passed values.
*/
typedef typename ReferenceCarrier<VoidSafeResult>::type ResultType;

/**
* @brief Hook handler virtual
*/
virtual Result Func(Args... args)
{
// Note to all ye who enter here:
// Do not, do NOT--DO NOT: touch "this" or any of our member variables.
// Hands off bucko. USE THE STATIC INSTANCE! (thisptr is undefined!!)
using namespace ::SourceHook;

void *ourvfnptr = reinterpret_cast<void *>(
Expand Down Expand Up @@ -994,8 +1059,8 @@ namespace SourceHook
// You're probably wondering what the hell this does.
// https://stackoverflow.com/questions/11709859/how-to-have-static-data-members-in-a-header-only-library/11711082#11711082
// Yes, I also happen to hate C++.
template<ISourceHook** SH, typename Interface, auto Method, typename Result, typename... Args>
Hook<SH, Interface, Method, Result, Args...> Hook<SH, Interface, Method, Result, Args...>::Instance;
template<ISourceHook** SH, Plugin* PL, typename Interface, auto Method, typename Result, typename... Args>
HookImpl<SH, PL, Interface, Method, Result, Args...> HookImpl<SH, PL, Interface, Method, Result, Args...>::Instance;

}

Expand Down
Loading

0 comments on commit d96b3ad

Please sign in to comment.