diff --git a/core/ConCmdManager.cpp b/core/ConCmdManager.cpp index 548569f6..cbccd673 100644 --- a/core/ConCmdManager.cpp +++ b/core/ConCmdManager.cpp @@ -36,20 +36,14 @@ #include "ChatTriggers.h" #include "logic_bridge.h" #include "sourcemod.h" +#include "provider.h" +#include "command_args.h" #include using namespace ke; ConCmdManager g_ConCmds; -#if SOURCE_ENGINE == SE_DOTA - SH_DECL_HOOK2_void(ConCommand, Dispatch, SH_NOATTRIB, false, const CCommandContext &, const CCommand &); -#elif SOURCE_ENGINE >= SE_ORANGEBOX - SH_DECL_HOOK1_void(ConCommand, Dispatch, SH_NOATTRIB, false, const CCommand &); -#else - SH_DECL_HOOK0_void(ConCommand, Dispatch, SH_NOATTRIB, false); -#endif - SH_DECL_HOOK1_void(IServerGameClients, SetCommandClient, SH_NOATTRIB, false, int); typedef ke::LinkedList PluginHookList; @@ -142,23 +136,13 @@ void ConCmdManager::OnPluginDestroyed(IPlugin *plugin) delete pList; } -#if SOURCE_ENGINE == SE_DOTA -void CommandCallback(const CCommandContext &context, const CCommand &command) +void CommandCallback(DISPATCH_ARGS) { -#elif SOURCE_ENGINE >= SE_ORANGEBOX -void CommandCallback(const CCommand &command) -{ -#else -void CommandCallback() -{ - CCommand command; -#endif + DISPATCH_PROLOGUE; + EngineArgs args(command); - g_HL2.PushCommandStack(&command); - - g_ConCmds.InternalDispatch(command); - - g_HL2.PopCommandStack(); + AutoEnterCommand autoEnterCommand(&args); + g_ConCmds.InternalDispatch(&args); } void ConCmdManager::SetCommandClient(int client) @@ -232,7 +216,7 @@ ResultType ConCmdManager::DispatchClientCommand(int client, const char *cmd, int return (ResultType)result; } -void ConCmdManager::InternalDispatch(const CCommand &command) +void ConCmdManager::InternalDispatch(const ICommandArgs *args) { int client = m_CmdClient; @@ -275,7 +259,7 @@ void ConCmdManager::InternalDispatch(const CCommand &command) return; cell_t result = Pl_Continue; - int args = command.ArgC() - 1; + int argc = args->ArgC() - 1; // On a listen server, sometimes the server host's client index can be set // as 0. So index 1 is passed to the command callback to correct this @@ -311,7 +295,7 @@ void ConCmdManager::InternalDispatch(const CCommand &command) hook->pf->PushCell(realClient); } - hook->pf->PushCell(args); + hook->pf->PushCell(argc); cell_t tempres = result; if (hook->pf->Execute(&tempres) == SP_ERROR_NONE) @@ -556,15 +540,12 @@ void ConCmdManager::RemoveConCmd(ConCmdInfo *info, const char *name, bool is_rea } else { - if (is_read_safe) - { - /* Remove the external hook */ - SH_REMOVE_HOOK(ConCommand, Dispatch, info->pCmd, SH_STATIC(CommandCallback), false); - } + // If it's not safe to read the pointer, we zap the SourceHook hook so it + // doesn't attempt to access the pointer's vtable. + if (!is_read_safe) + info->sh_hook->Zap(); if (untrack) - { UntrackConCommandBase(info->pCmd, this); - } } } @@ -623,7 +604,11 @@ ConCmdInfo *ConCmdManager::AddOrFindCommand(const char *name, const char *descri else { TrackConCommandBase(pCmd, this); - SH_ADD_HOOK(ConCommand, Dispatch, pCmd, SH_STATIC(CommandCallback), false); + CommandHook::Callback callback = [this] (const ICommandArgs *args) { + AutoEnterCommand autoEnterCommand(args); + this->InternalDispatch(args); + }; + pInfo->sh_hook = sCoreProviderImpl.AddCommandHook(pCmd, callback); } pInfo->pCmd = pCmd; diff --git a/core/ConCmdManager.h b/core/ConCmdManager.h index c17e8a95..def5ff52 100644 --- a/core/ConCmdManager.h +++ b/core/ConCmdManager.h @@ -40,6 +40,7 @@ #include #include #include "concmd_cleaner.h" +#include "GameHooks.h" #include #include #include @@ -105,6 +106,7 @@ struct ConCmdInfo ConCommand *pCmd; /**< Pointer to the command itself */ CmdHookList hooks; /**< Hook list */ FlagBits eflags; /**< Effective admin flags */ + ke::Ref sh_hook; /**< SourceHook hook, if any. */ }; typedef List ConCmdList; @@ -115,13 +117,7 @@ class ConCmdManager : public IPluginsListener, public IConCommandTracker { -#if SOURCE_ENGINE == SE_DOTA - friend void CommandCallback(const CCommandContext &context, const CCommand &command); -#elif SOURCE_ENGINE >= SE_ORANGEBOX - friend void CommandCallback(const CCommand &command); -#else - friend void CommandCallback(); -#endif + friend void CommandCallback(DISPATCH_ARGS); public: ConCmdManager(); ~ConCmdManager(); @@ -147,7 +143,7 @@ public: bool LookForSourceModCommand(const char *cmd); bool LookForCommandAdminFlags(const char *cmd, FlagBits *pFlags); private: - void InternalDispatch(const CCommand &command); + void InternalDispatch(const ICommandArgs *args); ResultType RunAdminCommand(ConCmdInfo *pInfo, int client, int args); ConCmdInfo *AddOrFindCommand(const char *name, const char *description, int flags); void SetCommandClient(int client); diff --git a/core/ConsoleDetours.cpp b/core/ConsoleDetours.cpp index 9d797393..035a1a06 100644 --- a/core/ConsoleDetours.cpp +++ b/core/ConsoleDetours.cpp @@ -50,6 +50,7 @@ #include "HalfLife2.h" #include "ConCommandBaseIterator.h" #include "logic_bridge.h" +#include "command_args.h" #include #include @@ -653,10 +654,10 @@ bool ConsoleDetours::RemoveListener(IPluginFunction *fun, const char *command) } } -cell_t ConsoleDetours::InternalDispatch(int client, const CCommand& args) +cell_t ConsoleDetours::InternalDispatch(int client, const ICommandArgs *args) { char name[255]; - const char *realname = args.Arg(0); + const char *realname = args->Arg(0); size_t len = strlen(realname); // Disallow command strings that are too long, for now. @@ -675,7 +676,7 @@ cell_t ConsoleDetours::InternalDispatch(int client, const CCommand& args) cell_t result = Pl_Continue; m_pForward->PushCell(client); m_pForward->PushString(name); - m_pForward->PushCell(args.ArgC() - 1); + m_pForward->PushCell(args->ArgC() - 1); m_pForward->Execute(&result, NULL); /* Don't let plugins block this. */ @@ -694,7 +695,7 @@ cell_t ConsoleDetours::InternalDispatch(int client, const CCommand& args) cell_t result2 = Pl_Continue; forward->PushCell(client); forward->PushString(name); - forward->PushCell(args.ArgC() - 1); + forward->PushCell(args->ArgC() - 1); forward->Execute(&result2, NULL); if (result2 > result) @@ -714,9 +715,12 @@ cell_t ConsoleDetours::Dispatch(ConCommand *pBase) { CCommand args; #endif - g_HL2.PushCommandStack(&args); - cell_t res = g_ConsoleDetours.InternalDispatch(g_ConCmds.GetCommandClient(), args); - g_HL2.PopCommandStack(); + EngineArgs cargs(args); + cell_t res; + { + AutoEnterCommand autoEnterCommand(&cargs); + res = g_ConsoleDetours.InternalDispatch(g_ConCmds.GetCommandClient(), &cargs); + } #if SH_IMPL_VERSION < 4 if (res >= Pl_Handled) diff --git a/core/ConsoleDetours.h b/core/ConsoleDetours.h index 3bf9ae74..424576a1 100644 --- a/core/ConsoleDetours.h +++ b/core/ConsoleDetours.h @@ -36,6 +36,10 @@ #include #include +namespace SourceMod { +class ICommandArgs; +} // namespace SourceMod + class ConsoleDetours : public SMGlobalClass, public IFeatureProvider @@ -54,7 +58,7 @@ public: bool AddListener(IPluginFunction *fun, const char *command); bool RemoveListener(IPluginFunction *fun, const char *command); private: - cell_t InternalDispatch(int client, const CCommand& args); + cell_t InternalDispatch(int client, const SourceMod::ICommandArgs *args); #if SOURCE_ENGINE >= SE_ORANGEBOX static cell_t Dispatch(ConCommand *pBase, const CCommand& args); #else diff --git a/core/GameHooks.cpp b/core/GameHooks.cpp index 90309070..0fa7019b 100644 --- a/core/GameHooks.cpp +++ b/core/GameHooks.cpp @@ -27,6 +27,7 @@ #include "GameHooks.h" #include "sourcemod.h" #include "ConVarManager.h" +#include "command_args.h" #if SOURCE_ENGINE >= SE_ORANGEBOX SH_DECL_HOOK3_void(ICvar, CallGlobalChangeCallbacks, SH_NOATTRIB, false, ConVar *, const char *, float); @@ -42,6 +43,14 @@ SH_DECL_HOOK5_void(IServerGameDLL, OnQueryCvarValueFinished, SH_NOATTRIB, 0, Que SH_DECL_HOOK5_void(IServerPluginCallbacks, OnQueryCvarValueFinished, SH_NOATTRIB, 0, QueryCvarCookie_t, edict_t *, EQueryCvarValueStatus, const char *, const char *); #endif +#if SOURCE_ENGINE == SE_DOTA +SH_DECL_HOOK2_void(ConCommand, Dispatch, SH_NOATTRIB, false, const CCommandContext &, const CCommand &); +#elif SOURCE_ENGINE >= SE_ORANGEBOX +SH_DECL_HOOK1_void(ConCommand, Dispatch, SH_NOATTRIB, false, const CCommand &); +#else +SH_DECL_HOOK0_void(ConCommand, Dispatch, SH_NOATTRIB, false); +#endif + GameHooks::GameHooks() : client_cvar_query_mode_(ClientCvarQueryMode::Unavailable) { @@ -128,3 +137,43 @@ void GameHooks::OnQueryCvarValueFinished(QueryCvarCookie_t cookie, edict_t *pPla g_ConVarManager.OnClientQueryFinished(cookie, client, result, cvarName, cvarValue); } #endif + +ke::PassRef +GameHooks::AddCommandHook(ConCommand *cmd, const CommandHook::Callback &callback) +{ + return new CommandHook(cmd, callback, false); +} + +ke::PassRef +GameHooks::AddPostCommandHook(ConCommand *cmd, const CommandHook::Callback &callback) +{ + return new CommandHook(cmd, callback, true); +} + +CommandHook::CommandHook(ConCommand *cmd, const Callback &callback, bool post) + : hook_id_(0), + callback_(callback) +{ + hook_id_ = SH_ADD_HOOK(ConCommand, Dispatch, cmd, SH_MEMBER(this, &CommandHook::Dispatch), post); +} + +CommandHook::~CommandHook() +{ + if (hook_id_) + SH_REMOVE_HOOK_ID(hook_id_); +} + +void CommandHook::Dispatch(DISPATCH_ARGS) +{ + DISPATCH_PROLOGUE; + EngineArgs args(command); + + AddRef(); + callback_(&args); + Release(); +} + +void CommandHook::Zap() +{ + hook_id_ = 0; +} diff --git a/core/GameHooks.h b/core/GameHooks.h index 0e5d9792..df28c282 100644 --- a/core/GameHooks.h +++ b/core/GameHooks.h @@ -32,18 +32,52 @@ #include #include #include +#include #include +#include class ConVar; +class CCommand; +struct CCommandContext; + +#if SOURCE_ENGINE == SE_DOTA +# define DISPATCH_ARGS const CCommandContext &context, const CCommand &command +# define DISPATCH_PROLOGUE +#elif SOURCE_ENGINE >= SE_ORANGEBOX +# define DISPATCH_ARGS const CCommand &command +# define DISPATCH_PROLOGUE +#else +# define DISPATCH_ARGS +# define DISPATCH_PROLOGUE CCommand command +#endif namespace SourceMod { +// Describes the mechanism in which client cvar queries are implemented. enum class ClientCvarQueryMode { Unavailable, DLL, VSP }; +class ICommandArgs; + +class CommandHook : public ke::Refcounted +{ +public: + typedef ke::Lambda Callback; + +public: + CommandHook(ConCommand *cmd, const Callback &callback, bool post); + ~CommandHook(); + void Dispatch(DISPATCH_ARGS); + void Zap(); + +private: + int hook_id_; + Callback callback_; +}; + class GameHooks { public: @@ -57,6 +91,10 @@ public: return client_cvar_query_mode_; } +public: + ke::PassRef AddCommandHook(ConCommand *cmd, const CommandHook::Callback &callback); + ke::PassRef AddPostCommandHook(ConCommand *cmd, const CommandHook::Callback &callback); + private: // Static callback that Valve's ConVar object executes when the convar's value changes. #if SOURCE_ENGINE >= SE_ORANGEBOX diff --git a/core/HalfLife2.cpp b/core/HalfLife2.cpp index 3a03697f..e280b7be 100644 --- a/core/HalfLife2.cpp +++ b/core/HalfLife2.cpp @@ -825,7 +825,7 @@ bool CHalfLife2::KVLoadFromFile(KeyValues *kv, IBaseFileSystem *filesystem, cons } } -void CHalfLife2::PushCommandStack(const CCommand *cmd) +void CHalfLife2::PushCommandStack(const ICommandArgs *cmd) { CachedCommandInfo info; @@ -837,7 +837,7 @@ void CHalfLife2::PushCommandStack(const CCommand *cmd) m_CommandStack.push(info); } -const CCommand *CHalfLife2::PeekCommandStack() +const ICommandArgs *CHalfLife2::PeekCommandStack() { if (m_CommandStack.empty()) { diff --git a/core/HalfLife2.h b/core/HalfLife2.h index 2e871d45..7756db49 100644 --- a/core/HalfLife2.h +++ b/core/HalfLife2.h @@ -49,7 +49,9 @@ #include #include -class CCommand; +namespace SourceMod { +class ICommandArgs; +} // namespace SourceMod using namespace SourceHook; using namespace SourceMod; @@ -100,7 +102,7 @@ struct DelayedFakeCliCmd struct CachedCommandInfo { - const CCommand *args; + const ICommandArgs *args; #if SOURCE_ENGINE <= SE_DARKMESSIAH char cmd[300]; #endif @@ -141,6 +143,7 @@ class CHalfLife2 : public SMGlobalClass, public IGameHelpers { + friend class AutoEnterCommand; public: CHalfLife2(); ~CHalfLife2(); @@ -190,9 +193,7 @@ public: void AddToFakeCliCmdQueue(int client, int userid, const char *cmd); void ProcessFakeCliCmdQueue(); public: - void PushCommandStack(const CCommand *cmd); - void PopCommandStack(); - const CCommand *PeekCommandStack(); + const ICommandArgs *PeekCommandStack(); const char *CurrentCommandName(); void AddDelayedKick(int client, int userid, const char *msg); void ProcessDelayedKicks(); @@ -200,6 +201,8 @@ public: bool IsOriginalEngine(); #endif private: + void PushCommandStack(const ICommandArgs *cmd); + void PopCommandStack(); DataTableInfo *_FindServerClass(const char *classname); private: void InitLogicalEntData(); @@ -224,4 +227,15 @@ extern CHalfLife2 g_HL2; bool IndexToAThings(cell_t, CBaseEntity **pEntData, edict_t **pEdictData); +class AutoEnterCommand +{ +public: + AutoEnterCommand(const ICommandArgs *args) { + g_HL2.PushCommandStack(args); + } + ~AutoEnterCommand() { + g_HL2.PopCommandStack(); + } +}; + #endif //_INCLUDE_SOURCEMOD_CHALFLIFE2_H_ diff --git a/core/PlayerManager.cpp b/core/PlayerManager.cpp index a0607ae6..3b88207c 100644 --- a/core/PlayerManager.cpp +++ b/core/PlayerManager.cpp @@ -47,6 +47,7 @@ #include "logic_bridge.h" #include #include "smn_keyvalues.h" +#include "command_args.h" #include #include #include @@ -1180,7 +1181,8 @@ void PlayerManager::OnClientCommand(edict_t *pEntity) RETURN_META(MRES_SUPERCEDE); } - g_HL2.PushCommandStack(&args); + EngineArgs cargs(args); + AutoEnterCommand autoEnterCommand(&cargs); int argcount = args.ArgC() - 1; const char *cmd = g_HL2.CurrentCommandName(); @@ -1199,10 +1201,9 @@ void PlayerManager::OnClientCommand(edict_t *pEntity) if (g_ConsoleDetours.IsEnabled()) { - cell_t res2 = g_ConsoleDetours.InternalDispatch(client, args); + cell_t res2 = g_ConsoleDetours.InternalDispatch(client, &cargs); if (res2 >= Pl_Stop) { - g_HL2.PopCommandStack(); RETURN_META(MRES_SUPERCEDE); } else if (res2 > res) @@ -1226,14 +1227,11 @@ void PlayerManager::OnClientCommand(edict_t *pEntity) if (res >= Pl_Stop) { - g_HL2.PopCommandStack(); RETURN_META(MRES_SUPERCEDE); } res = g_ConCmds.DispatchClientCommand(client, cmd, argcount, (ResultType)res); - g_HL2.PopCommandStack(); - if (res >= Pl_Handled) { RETURN_META(MRES_SUPERCEDE); diff --git a/core/logic_bridge.cpp b/core/logic_bridge.cpp index d1909411..e4b9076d 100644 --- a/core/logic_bridge.cpp +++ b/core/logic_bridge.cpp @@ -742,6 +742,18 @@ bool CoreProviderImpl::LoadBridge(char *error, size_t maxlength) return true; } +ke::PassRef +CoreProviderImpl::AddCommandHook(ConCommand *cmd, const CommandHook::Callback &callback) +{ + return hooks_.AddCommandHook(cmd, callback); +} + +ke::PassRef +CoreProviderImpl::AddPostCommandHook(ConCommand *cmd, const CommandHook::Callback &callback) +{ + return hooks_.AddPostCommandHook(cmd, callback); +} + void CoreProviderImpl::InitializeHooks() { hooks_.Start(); diff --git a/core/provider.h b/core/provider.h index 7ac3b35d..1bce731a 100644 --- a/core/provider.h +++ b/core/provider.h @@ -65,6 +65,9 @@ public: int QueryClientConVar(int client, const char *cvar) override; bool IsClientConVarQueryingSupported() override; + ke::PassRef AddCommandHook(ConCommand *cmd, const CommandHook::Callback &callback); + ke::PassRef AddPostCommandHook(ConCommand *cmd, const CommandHook::Callback &callback); + private: ke::Ref logic_; LogicInitFunction logic_init_; diff --git a/core/smn_console.cpp b/core/smn_console.cpp index 93be827e..4ebd9123 100644 --- a/core/smn_console.cpp +++ b/core/smn_console.cpp @@ -784,7 +784,7 @@ static cell_t sm_RegAdminCmd(IPluginContext *pContext, const cell_t *params) static cell_t sm_GetCmdArgs(IPluginContext *pContext, const cell_t *params) { - const CCommand *pCmd = g_HL2.PeekCommandStack(); + const ICommandArgs *pCmd = g_HL2.PeekCommandStack(); if (!pCmd) { @@ -796,7 +796,7 @@ static cell_t sm_GetCmdArgs(IPluginContext *pContext, const cell_t *params) static cell_t sm_GetCmdArg(IPluginContext *pContext, const cell_t *params) { - const CCommand *pCmd = g_HL2.PeekCommandStack(); + const ICommandArgs *pCmd = g_HL2.PeekCommandStack(); if (!pCmd) { @@ -814,7 +814,7 @@ static cell_t sm_GetCmdArg(IPluginContext *pContext, const cell_t *params) static cell_t sm_GetCmdArgString(IPluginContext *pContext, const cell_t *params) { - const CCommand *pCmd = g_HL2.PeekCommandStack(); + const ICommandArgs *pCmd = g_HL2.PeekCommandStack(); if (!pCmd) {