Added generic command hooking mechanism, to replace Reg*Cmd which is intended for command creation (bug 4015, r=pred).

This commit is contained in:
David Anderson 2009-09-26 17:12:23 -04:00
parent 493a756aaa
commit d8474cfafa
14 changed files with 288 additions and 37 deletions

View File

@ -81,6 +81,7 @@ for i in SM.sdkInfo:
'smn_datapacks.cpp',
'smn_lang.cpp',
'sm_srvcmds.cpp',
'ConsoleDetours.cpp'
]
binary.AddSourceFiles('core', files)
SM.PostSetupHL2Job(extension, binary, i)

View File

@ -159,3 +159,4 @@ private:
extern ConCmdManager g_ConCmds;
#endif // _INCLUDE_SOURCEMOD_CONCMDMANAGER_H_

View File

@ -519,45 +519,18 @@ SMCResult CGameConfig::ReadSMC_LeavingSection(const SMCStates *states)
}
#endif
/* First, preprocess the signature */
char real_sig[511];
unsigned char real_sig[511];
size_t real_bytes;
size_t length;
real_bytes = 0;
length = strlen(s_TempSig.sig);
for (size_t i=0; i<length; i++)
{
if (real_bytes >= sizeof(real_sig))
{
break;
}
real_sig[real_bytes++] = s_TempSig.sig[i];
if (s_TempSig.sig[i] == '\\'
&& s_TempSig.sig[i+1] == 'x')
{
if (i + 3 >= length)
{
continue;
}
/* Get the hex part */
char s_byte[3];
int r_byte;
s_byte[0] = s_TempSig.sig[i+2];
s_byte[1] = s_TempSig.sig[i+3];
s_byte[2] = '\0';
/* Read it as an integer */
sscanf(s_byte, "%x", &r_byte);
/* Save the value */
real_sig[real_bytes-1] = r_byte;
/* Adjust index */
i += 3;
}
}
real_bytes = UTIL_DecodeHexString(real_sig, sizeof(real_sig), s_TempSig.sig);
if (real_bytes >= 1)
{
final_addr = g_MemUtils.FindPattern(addrInBase, real_sig, real_bytes);
final_addr = g_MemUtils.FindPattern(addrInBase, (char*)real_sig, real_bytes);
}
}

View File

@ -20,7 +20,7 @@ OBJECTS = AdminCache.cpp CDataPack.cpp ConCmdManager.cpp ConVarManager.cpp CoreC
sourcemm_api.cpp sourcemod.cpp MenuStyle_Base.cpp MenuStyle_Valve.cpp MenuManager.cpp \
MenuStyle_Radio.cpp ChatTriggers.cpp ADTFactory.cpp MenuVoting.cpp sm_crc32.cpp \
frame_hooks.cpp concmd_cleaner.cpp PhraseCollection.cpp NextMap.cpp \
NativeOwner.cpp logic_bridge.cpp
NativeOwner.cpp logic_bridge.cpp ConsoleDetours.cpp
OBJECTS += smn_admin.cpp smn_bitbuffer.cpp smn_console.cpp smn_core.cpp \
smn_datapacks.cpp smn_entities.cpp smn_events.cpp smn_fakenatives.cpp \
smn_filesystem.cpp smn_gameconfigs.cpp smn_halflife.cpp \

View File

@ -51,7 +51,7 @@ public: // SMGlobalClass
void OnSourceModAllInitialized();
public: // IMemoryUtils
void *FindPattern(const void *libPtr, const char *pattern, size_t len);
private:
public:
bool GetLibraryInfo(const void *libPtr, DynLibInfo &lib);
};

View File

@ -48,6 +48,7 @@
#include "GameConfigs.h"
#include "ExtensionSys.h"
#include <sourcemod_version.h>
#include "ConsoleDetours.h"
PlayerManager g_Players;
bool g_OnMapStarted = false;
@ -725,6 +726,20 @@ void PlayerManager::OnClientCommand(edict_t *pEntity)
}
}
if (g_ConsoleDetours.IsEnabled())
{
cell_t res2 = g_ConsoleDetours.InternalDispatch(client, args);
if (res2 >= Pl_Stop)
{
g_HL2.PopCommandStack();
RETURN_META(MRES_SUPERCEDE);
}
else if (res2 > res)
{
res = res2;
}
}
cell_t res2 = Pl_Continue;
m_clcommand->PushCell(client);
m_clcommand->PushCell(argcount);

View File

@ -39,6 +39,9 @@
#if SOURCE_ENGINE >= SE_ORANGEBOX
SH_DECL_HOOK1_void(ICvar, UnregisterConCommand, SH_NOATTRIB, 0, ConCommandBase *);
SH_DECL_HOOK1_void(ICvar, RegisterConCommand, SH_NOATTRIB, 0, ConCommandBase *);
#else
SH_DECL_HOOK1_void(ICvar, RegisterConCommandBase, SH_NOATTRIB, 0, ConCommandBase *);
#endif
using namespace SourceHook;
@ -51,29 +54,55 @@ struct ConCommandInfo
};
List<ConCommandInfo *> tracked_bases;
IConCommandLinkListener *IConCommandLinkListener::head = NULL;
ConCommandBase *FindConCommandBase(const char *name);
class ConCommandCleaner : public SMGlobalClass
{
public:
#if SOURCE_ENGINE >= SE_ORANGEBOX
void OnSourceModAllInitialized()
{
#if SOURCE_ENGINE >= SE_ORANGEBOX
SH_ADD_HOOK_MEMFUNC(ICvar, UnregisterConCommand, icvar, this, &ConCommandCleaner::UnlinkConCommandBase, false);
SH_ADD_HOOK_MEMFUNC(ICvar, RegisterConCommand, icvar, this, &ConCommandCleaner::LinkConCommandBase, false);
#else
SH_ADD_HOOK_MEMFUNC(ICvar, RegisterConCommandBase, icvar, this, &ConCommandCleaner::LinkConCommandBase, false);
#endif
}
void OnSourceModShutdown()
{
#if SOURCE_ENGINE >= SE_ORANGEBOX
SH_REMOVE_HOOK_MEMFUNC(ICvar, UnregisterConCommand, icvar, this, &ConCommandCleaner::UnlinkConCommandBase, false);
}
SH_REMOVE_HOOK_MEMFUNC(ICvar, RegisterConCommand, icvar, this, &ConCommandCleaner::LinkConCommandBase, false);
#else
SH_REMOVE_HOOK_MEMFUNC(ICvar, RegisterConCommandBase, icvar, this, &ConCommandCleaner::LinkConCommandBase, false);
#endif
}
void LinkConCommandBase(ConCommandBase *pBase)
{
IConCommandLinkListener *listener = IConCommandLinkListener::head;
while (listener)
{
listener->OnLinkConCommand(pBase);
listener = listener->next;
}
}
void UnlinkConCommandBase(ConCommandBase *pBase)
{
ConCommandInfo *pInfo;
List<ConCommandInfo *>::iterator iter = tracked_bases.begin();
IConCommandLinkListener *listener = IConCommandLinkListener::head;
while (listener)
{
listener->OnUnlinkConCommand(pBase);
listener = listener->next;
}
if (pBase)
{
while (iter != tracked_bases.end())

View File

@ -42,4 +42,26 @@ void TrackConCommandBase(ConCommandBase *pBase, IConCommandTracker *me);
void UntrackConCommandBase(ConCommandBase *pBase, IConCommandTracker *me);
void Global_OnUnlinkConCommandBase(ConCommandBase *pBase);
class IConCommandLinkListener
{
friend class ConCommandCleaner;
static IConCommandLinkListener *head;
IConCommandLinkListener *next;
public:
IConCommandLinkListener()
{
next = head;
head = this;
}
virtual void OnLinkConCommand(ConCommandBase *pBase)
{
}
virtual void OnUnlinkConCommand(ConCommandBase *pBase)
{
}
};
#endif //_INCLUDE_CONCMD_TRACKER_H_

View File

@ -142,6 +142,7 @@
<Tool
Name="VCResourceCompilerTool"
PreprocessorDefinitions="BINARY_NAME=\&quot;$(TargetFileName)\&quot;"
AdditionalIncludeDirectories="..\..\public"
/>
<Tool
Name="VCPreLinkEventTool"
@ -389,6 +390,7 @@
<Tool
Name="VCResourceCompilerTool"
PreprocessorDefinitions="BINARY_NAME=\&quot;$(TargetFileName)\&quot;"
AdditionalIncludeDirectories="..\..\public"
/>
<Tool
Name="VCPreLinkEventTool"
@ -1130,6 +1132,7 @@
<Tool
Name="VCResourceCompilerTool"
PreprocessorDefinitions="BINARY_NAME=\&quot;$(TargetFileName)\&quot;"
AdditionalIncludeDirectories="..\..\public"
/>
<Tool
Name="VCPreLinkEventTool"
@ -1377,6 +1380,7 @@
<Tool
Name="VCResourceCompilerTool"
PreprocessorDefinitions="BINARY_NAME=\&quot;$(TargetFileName)\&quot;"
AdditionalIncludeDirectories="..\..\public"
/>
<Tool
Name="VCPreLinkEventTool"
@ -1533,6 +1537,10 @@
RelativePath="..\ConCmdManager.cpp"
>
</File>
<File
RelativePath="..\ConsoleDetours.cpp"
>
</File>
<File
RelativePath="..\ConVarManager.cpp"
>
@ -1831,6 +1839,14 @@
RelativePath="..\ConCmdManager.h"
>
</File>
<File
RelativePath="..\ConCommandBaseIterator.h"
>
</File>
<File
RelativePath="..\ConsoleDetours.h"
>
</File>
<File
RelativePath="..\ConVarManager.h"
>

View File

@ -1580,3 +1580,50 @@ char *UTIL_TrimWhitespace(char *str, size_t &len)
return str;
}
size_t UTIL_DecodeHexString(unsigned char *buffer, size_t maxlength, const char *hexstr)
{
size_t written = 0;
size_t length = strlen(hexstr);
for (size_t i = 0; i < length; i++)
{
if (written >= maxlength)
break;
buffer[written++] = hexstr[i];
if (hexstr[i] == '\\' && hexstr[i + 1] == 'x')
{
if (i + 3 >= length)
continue;
/* Get the hex part. */
char s_byte[3];
int r_byte;
s_byte[0] = hexstr[i + 2];
s_byte[1] = hexstr[i + 3];
s_byte[2] = '\0';
/* Read it as an integer */
sscanf(s_byte, "%x", &r_byte);
/* Save the value */
buffer[written - 1] = r_byte;
/* Adjust index */
i += 3;
}
}
return written;
}
char *UTIL_ToLowerCase(const char *str)
{
size_t len = strlen(str);
char *buffer = new char[len + 1];
for (size_t i = 0; i < len; i++)
{
if (str[i] >= 'A' && str[i] <= 'Z')
buffer[i] = tolower(str[i]);
else
buffer[i] = str[i];
}
buffer[len] = '\0';
return buffer;
}

View File

@ -60,5 +60,8 @@ char *sm_strdup(const char *str);
unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace, bool caseSensitive = true);
char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t searchLen, const char *replace, size_t replaceLen, bool caseSensitive = true);
char *UTIL_TrimWhitespace(char *str, size_t &len);
size_t UTIL_DecodeHexString(unsigned char *buffer, size_t maxlength, const char *hexstr);
char *UTIL_ToLowerCase(const char *str);
#endif // _INCLUDE_SOURCEMOD_STRINGUTIL_H_

View File

@ -43,6 +43,8 @@
#include <inetchannel.h>
#include <bitbuf.h>
#include <sm_trie_tpl.h>
#include "Logger.h"
#include "ConsoleDetours.h"
#if SOURCE_ENGINE == SE_LEFT4DEAD
#define NET_SETCONVAR 6
@ -668,7 +670,7 @@ static cell_t sm_RegServerCmd(IPluginContext *pContext, const cell_t *params)
if (strcasecmp(name, "sm") == 0)
{
return pContext->ThrowNativeError("Cannot override \"sm\" command");
return pContext->ThrowNativeError("Cannot register \"sm\" command");
}
pContext->LocalToString(params[3], &help);
@ -696,7 +698,7 @@ static cell_t sm_RegConsoleCmd(IPluginContext *pContext, const cell_t *params)
if (strcasecmp(name, "sm") == 0)
{
return pContext->ThrowNativeError("Cannot override \"sm\" command");
return pContext->ThrowNativeError("Cannot register \"sm\" command");
}
pContext->LocalToString(params[3], &help);
@ -727,7 +729,7 @@ static cell_t sm_RegAdminCmd(IPluginContext *pContext, const cell_t *params)
if (strcasecmp(name, "sm") == 0)
{
return pContext->ThrowNativeError("Cannot override \"sm\" command");
return pContext->ThrowNativeError("Cannot register \"sm\" command");
}
pContext->LocalToString(params[4], &help);
@ -1332,6 +1334,46 @@ static cell_t RemoveServerTag(IPluginContext *pContext, const cell_t *params)
return 0;
}
static cell_t AddCommandListener(IPluginContext *pContext, const cell_t *params)
{
char *name;
IPluginFunction *pFunction;
pContext->LocalToString(params[2], &name);
if (strcasecmp(name, "sm") == 0)
{
g_Logger.LogError("Request to register \"sm\" command denied.");
return 0;
}
pFunction = pContext->GetFunctionById(params[1]);
if (!pFunction)
return pContext->ThrowNativeError("Invalid function id (%X)", params[1]);
if (!g_ConsoleDetours.AddListener(pFunction, name[0] == '\0' ? NULL : name))
return pContext->ThrowNativeError("This game does not support command listeners");
return 1;
}
static cell_t RemoveCommandListener(IPluginContext *pContext, const cell_t *params)
{
char *name;
IPluginFunction *pFunction;
pContext->LocalToString(params[2], &name);
pFunction = pContext->GetFunctionById(params[1]);
if (!pFunction)
return pContext->ThrowNativeError("Invalid function id (%X)", params[1]);
if (!g_ConsoleDetours.RemoveListener(pFunction, name[0] == '\0' ? NULL : name))
return pContext->ThrowNativeError("No matching callback was registered");
return 1;
}
REGISTER_NATIVES(consoleNatives)
{
{"CreateConVar", sm_CreateConVar},
@ -1381,5 +1423,7 @@ REGISTER_NATIVES(consoleNatives)
{"SendConVarValue", SendConVarValue},
{"AddServerTag", AddServerTag},
{"RemoveServerTag", RemoveServerTag},
{"AddCommandListener", AddCommandListener},
{"RemoveCommandListener", RemoveCommandListener},
{NULL, NULL}
};

View File

@ -44,6 +44,41 @@
"linux" "4"
}
}
"Keys"
{
/* Windows */
"CES_Patch_Windows" "\xFF\x52\x2C\x5B\x5F\x8B\xC6"
"CES_Offset_Windows" "530"
"CES_Save_Windows" "3"
"CES_Reg_Windows" "1"
"CGC_Patch_Windows" "\xFF\x50\x2C\x5F\xB0\x01"
"CGC_Offset_Windows" "190"
"CGC_Save_Windows" "3"
"CGC_Reg_Windows" "1"
/* Linux i486 */
"CES_Patch_Linux_486" "\x89\x1C\x24\xFF\x52\x30"
"CES_Offset_Linux_486" "901"
"CES_Reg_Linux_486" "3"
"CGC_Patch_Linux_486" "\xFF\x57\x30\xBA\x01\x00\x00"
"CGC_Offset_Linux_486" "391"
/* Linux i686 */
"CES_Patch_Linux_686" "\x89\x1C\x24\xFF\x52\x30"
"CES_Offset_Linux_686" "901"
"CES_Reg_Linux_686" "3"
"CGC_Patch_Linux_686" "\xFF\x57\x30\xBA\x01\x00\x00"
"CGC_Offset_Linux_686" "391"
/* Linux AMD */
"CES_Patch_Linux_AMD" "\xFF\x52\x30\x89\xDA\x83\xC4\x10"
"CES_Offset_Linux_AMD" "916"
"CES_Save_Linux_AMD" "3"
"CGC_Patch_Linux_AMD" "\x89\x1C\x24\xFF\x52\x30"
"CGC_Offset_Linux_AMD" "380"
"CGC_Reg_Linux_AMD" "3"
}
"Signatures"
{
@ -52,11 +87,27 @@
"library" "server"
"windows" "\xE8\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\xB9\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\xE8"
}
"gEntList"
{
"library" "server"
"linux" "@gEntList"
}
"Cmd_ExecuteString"
{
"library" "engine"
"linux" "@_Z17Cmd_ExecuteStringPKc12cmd_source_t"
"windows" "\x8B\x4C\x24\x04\x8B\x44\x24\x08\x51\xA3\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\x83\xC4\x04\x83\x3D"
}
"CGameClient::ExecuteString"
{
"library" "engine"
"linux" "@_ZN11CGameClient20ExecuteStringCommandEPKc"
"windows" "\x56\x8B\x74\x24\x08\x57\x56\x8B\xF9\xE8\x2A\x2A\x2A\x2A\x84\xC0\x0F\x85\xC4\x00\x00\x00\x56\x8D"
}
}
}
}

View File

@ -835,3 +835,52 @@ native AddServerTag(const String:tag[]);
* @noreturn
*/
native RemoveServerTag(const String:tag[]);
/**
* Callback for command listeners. This is invoked whenever any command
* reaches the server, from the server console itself or a player.
* Returning Plugin_Handled or Plugin_Stop will prevent the original,
* baseline code from running.
*
* -- TEXT BELOW IS IMPLEMENTATION, AND NOT GUARANTEED --
* Even if returning Plugin_Handled or Plugin_Stop, some callbacks will still
* trigger. These are:
* * C++ command dispatch hooks from Metamod:Source plugins
* * Reg*Cmd() hooks that did not create new commands.
*
* @param client Client, or 0 for server. Client will be connected but
* not necessarily in game.
* @param command Command name, lower case. To get name as typed, use
* GetCmdArg() and specify argument 0.
* @param argc Argument count.
* @return Action to take (see extended notes above).
*/
functag public Action:CommandListener(client, const String:command[], argc);
/**
* Adds a callback that will fire when a command is sent to the server.
*
* Registering commands is designed to create a new command as part of the UI,
* whereas this is a lightweight hook on a command string, existing or not.
* Using Reg*Cmd to intercept is in poor practice, as it physically creates a
* new command and can slow down dispatch in general.
*
* @param callback Callback.
* @param command Command, or if not specified, a global listener.
* The command is case insensitive.
* @return True if this feature is available on the current game,
* false otherwise.
*/
native bool:AddCommandListener(CommandListener:callback, const String:command[]="");
/**
* Removes a previously added command listener, in reverse order of being added.
*
* @param callback Callback.
* @param command Command, or if not specified, a global listener.
* The command is case insensitive.
* @error Callback has no active listeners.
*/
native RemoveCommandListener(CommandListener:callback, const String:command[]="");