diff --git a/core/AMBuilder b/core/AMBuilder index 3c6746d6..4dd12bf6 100644 --- a/core/AMBuilder +++ b/core/AMBuilder @@ -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) diff --git a/core/ConCmdManager.h b/core/ConCmdManager.h index 456f7064..76c74717 100644 --- a/core/ConCmdManager.h +++ b/core/ConCmdManager.h @@ -159,3 +159,4 @@ private: extern ConCmdManager g_ConCmds; #endif // _INCLUDE_SOURCEMOD_CONCMDMANAGER_H_ + diff --git a/core/GameConfigs.cpp b/core/GameConfigs.cpp index cd7f540a..e32412fa 100644 --- a/core/GameConfigs.cpp +++ b/core/GameConfigs.cpp @@ -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= 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); } } diff --git a/core/Makefile b/core/Makefile index d2838883..4b0cb1ba 100644 --- a/core/Makefile +++ b/core/Makefile @@ -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 \ diff --git a/core/MemoryUtils.h b/core/MemoryUtils.h index 560d3a97..26713fc0 100644 --- a/core/MemoryUtils.h +++ b/core/MemoryUtils.h @@ -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); }; diff --git a/core/PlayerManager.cpp b/core/PlayerManager.cpp index 1215c675..7a21cbe0 100644 --- a/core/PlayerManager.cpp +++ b/core/PlayerManager.cpp @@ -48,6 +48,7 @@ #include "GameConfigs.h" #include "ExtensionSys.h" #include +#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); diff --git a/core/concmd_cleaner.cpp b/core/concmd_cleaner.cpp index f9ec8215..f54f1dd4 100644 --- a/core/concmd_cleaner.cpp +++ b/core/concmd_cleaner.cpp @@ -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 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::iterator iter = tracked_bases.begin(); + IConCommandLinkListener *listener = IConCommandLinkListener::head; + while (listener) + { + listener->OnUnlinkConCommand(pBase); + listener = listener->next; + } + if (pBase) { while (iter != tracked_bases.end()) diff --git a/core/concmd_cleaner.h b/core/concmd_cleaner.h index 0573ca12..330a984e 100644 --- a/core/concmd_cleaner.h +++ b/core/concmd_cleaner.h @@ -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_ diff --git a/core/msvc9/sourcemod_mm.vcproj b/core/msvc9/sourcemod_mm.vcproj index fb6feb9e..4e836306 100644 --- a/core/msvc9/sourcemod_mm.vcproj +++ b/core/msvc9/sourcemod_mm.vcproj @@ -142,6 +142,7 @@ + + @@ -1831,6 +1839,14 @@ RelativePath="..\ConCmdManager.h" > + + + + diff --git a/core/sm_stringutil.cpp b/core/sm_stringutil.cpp index 5400c5c4..137ff2a3 100644 --- a/core/sm_stringutil.cpp +++ b/core/sm_stringutil.cpp @@ -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; +} + diff --git a/core/sm_stringutil.h b/core/sm_stringutil.h index 745ec198..3b5ed50b 100644 --- a/core/sm_stringutil.h +++ b/core/sm_stringutil.h @@ -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_ + diff --git a/core/smn_console.cpp b/core/smn_console.cpp index 4dc9951a..84651002 100644 --- a/core/smn_console.cpp +++ b/core/smn_console.cpp @@ -43,6 +43,8 @@ #include #include #include +#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} }; diff --git a/gamedata/core.games/engine.ep1.txt b/gamedata/core.games/engine.ep1.txt index ce4def20..c58ec238 100644 --- a/gamedata/core.games/engine.ep1.txt +++ b/gamedata/core.games/engine.ep1.txt @@ -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" + } } } } + diff --git a/plugins/include/console.inc b/plugins/include/console.inc index 9d710e50..cc0d758a 100644 --- a/plugins/include/console.inc +++ b/plugins/include/console.inc @@ -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[]=""); +