From 1f1d6e57f9658c084b78ebd447b668b1b006b092 Mon Sep 17 00:00:00 2001 From: Scott Ehlert Date: Thu, 10 Dec 2009 00:59:50 -0600 Subject: [PATCH] Rewrote user message caching code to always use IServerGameDLL::GetUserMessageInfo (bug 4117, r=dvander). Games where the above function can crash are handled by detouring tier0's Error() function and jumping back to our own code. So in theory this should work on all games now. --- core-legacy/CSmmAPI.cpp | 203 +++++++++++----------------- core-legacy/CSmmAPI.h | 17 +-- core-legacy/concommands.cpp | 29 ++-- core-legacy/oslink.h | 4 +- core-legacy/sourcemm.cpp | 9 +- core/metamod_console.cpp | 29 ++-- core/metamod_oslink.h | 8 +- core/provider/provider_ep2.cpp | 237 +++++++++------------------------ 8 files changed, 174 insertions(+), 362 deletions(-) diff --git a/core-legacy/CSmmAPI.cpp b/core-legacy/CSmmAPI.cpp index 5e64b8a..97c1cb9 100644 --- a/core-legacy/CSmmAPI.cpp +++ b/core-legacy/CSmmAPI.cpp @@ -14,6 +14,8 @@ #include "concommands.h" #include "CPlugin.h" #include "util.h" +#include "sh_memory.h" +#include /** * @brief Implementation of main API interface @@ -21,22 +23,32 @@ */ using namespace SourceMM; +using namespace SourceHook; + +struct UsrMsgInfo +{ + UsrMsgInfo() + { + } + UsrMsgInfo(int s, const char *t) : size(s), name(t) + { + } + int size; + String name; +}; CSmmAPI g_SmmAPI; +static CVector usermsgs_list; +static jmp_buf usermsg_end; + CSmmAPI::CSmmAPI() { m_ConPrintf = NULL; m_CmdCache = false; - m_MsgCount = -1; m_VSP = false; } -CSmmAPI::~CSmmAPI() -{ - m_UserMessages.RemoveAll(); -} - void CSmmAPI::LogMsg(ISmmPlugin *pl, const char *msg, ...) { va_list ap; @@ -491,149 +503,96 @@ int CSmmAPI::GetGameDLLVersion() return g_GameDllVersion; } -////////////////////////////////////////////////////////////////////// -// EVEN MORE HACKS HERE! YOU HAVE BEEN WARNED! // -// Signatures necessary in finding the pointer to the CUtlDict that // -// stores user message information. // -// IServerGameDLL::GetUserMessageInfo() normally crashes with bad // -// message indices. This is our answer to it. Yuck! <:-( // -////////////////////////////////////////////////////////////////////// -#ifdef OS_WIN32 - /* General Windows sig */ - #define MSGCLASS_SIGLEN 7 - #define MSGCLASS_SIG "\x8B\x0D\x2A\x2A\x2A\x2A\x56" - #define MSGCLASS_OFFS 2 - - /* Dystopia Wimdows hack */ - #define MSGCLASS2_SIGLEN 16 - #define MSGCLASS2_SIG "\x56\x8B\x74\x24\x2A\x85\xF6\x7C\x2A\x3B\x35\x2A\x2A\x2A\x2A\x7D" - #define MSGCLASS2_OFFS 11 - - /* Windows frame pointer sig */ - #define MSGCLASS3_SIGLEN 18 - #define MSGCLASS3_SIG "\x55\x8B\xEC\x51\x89\x2A\x2A\x8B\x2A\x2A\x50\x8B\x0D\x2A\x2A\x2A\x2A\xE8" - #define MSGCLASS3_OFFS 13 -#elif defined OS_LINUX - /* No frame pointer sig */ - #define MSGCLASS_SIGLEN 14 - #define MSGCLASS_SIG "\x53\x83\xEC\x2A\x8B\x2A\x2A\x2A\xA1\x2A\x2A\x2A\x2A\x89" - #define MSGCLASS_OFFS 9 - - /* Frame pointer sig */ - #define MSGCLASS2_SIGLEN 16 - #define MSGCLASS2_SIG "\x55\x89\xE5\x53\x83\xEC\x2A\x8B\x2A\x2A\xA1\x2A\x2A\x2A\x2A\x89" - #define MSGCLASS2_OFFS 11 -#endif - -/* This is the ugliest function in all of SourceMM */ -/* :TODO: Make this prettier */ -bool CSmmAPI::CacheUserMessages() +/* This only gets called if IServerGameDLL::GetUserMessageInfo() triggers it */ +void Detour_Error(const tchar *pMsg, ...) { - UserMsgDict *dict = NULL; - char *vfunc = UTIL_GetOrigFunction(&IServerGameDLL::GetUserMessageInfo, g_GameDll.pGameDLL); - - if (!vfunc) - { - return false; - } - - if (UTIL_VerifySignature(vfunc, MSGCLASS_SIG, MSGCLASS_SIGLEN)) - { - /* Get address of CUserMessages instance */ - char **userMsgClass = *reinterpret_cast(vfunc + MSGCLASS_OFFS); - - /* Get address of CUserMessages::m_UserMessages */ - dict = reinterpret_cast(*userMsgClass); - } else if (UTIL_VerifySignature(vfunc, MSGCLASS2_SIG, MSGCLASS2_SIGLEN)) { - #ifdef OS_WIN32 - /* If we get here, the code is possibly inlined like in Dystopia */ - - /* Get the address of the CUtlRBTree */ - char *rbtree = *reinterpret_cast(vfunc + MSGCLASS2_OFFS); - - /* CUtlDict should be 8 bytes before the CUtlRBTree (hacktacular!) */ - dict = reinterpret_cast(rbtree - 8); - #elif defined OS_LINUX - /* Get address of CUserMessages instance */ - char **userMsgClass = *reinterpret_cast(vfunc + MSGCLASS2_OFFS); - - /* Get address of CUserMessages::m_UserMessages */ - dict = reinterpret_cast(*userMsgClass); - #endif - #ifdef OS_WIN32 - } else if (UTIL_VerifySignature(vfunc, MSGCLASS3_SIG, MSGCLASS3_SIGLEN)) { - /* Get address of CUserMessages instance */ - char **userMsgClass = *reinterpret_cast(vfunc + MSGCLASS3_OFFS); - - /* Get address of CUserMessages::m_UserMessages */ - dict = reinterpret_cast(*userMsgClass); - #endif - } - - if (dict) - { - m_MsgCount = dict->Count(); - - /* Ensure that count is within bounds of an unsigned byte, because that's what engine supports */ - if (m_MsgCount < 0 || m_MsgCount > 255) - { - m_MsgCount = -1; - return false; - } - - UserMessage *msg; - - /* Cache messages in our CUtlDict */ - for (int i = 0; i < m_MsgCount; i++) - { - msg = dict->Element(i); - m_UserMessages.Insert(msg->name, msg); - } - - return true; - } - - return false; + /* Jump back to setjmp() in CacheUserMessages() */ + longjmp(usermsg_end, 1); } -bool CSmmAPI::MsgCacheSuccessful() +/* IServerGameDLL::GetUserMessageInfo() crashes on games based on the old engine and + * early Orange Box. This is because Error() from tier0 gets called when a bad index is + * passed. This is all due to a bug in CUtlRBTree::IsValidIndex(). + * + * So we detour Error() to fix this. Our detour then jumps back into CacheUserMessages() + * to a point before GetUserMessageInfo() is called. The detour is then removed and we + * exit. + */ +void CSmmAPI::CacheUserMessages() { - return m_MsgCount > -1; + int q, size; + char buffer[256]; + unsigned char *target, *detour; + unsigned char orig_bytes[5]; + + target = (unsigned char *)&Error; + detour = (unsigned char *)&Detour_Error; + + /* Save bytes from target function */ + memcpy(orig_bytes, target, sizeof(orig_bytes)); + + /* Patch in relative jump to our Error() detour */ + SetMemAccess(target, sizeof(orig_bytes), SH_MEM_READ|SH_MEM_WRITE|SH_MEM_EXEC); + target[0] = IA32_JMP_IMM32; + *(int32_t *)&target[1] = (int32_t)(detour - (target + 5)); + + /* This is where longjmp() will end up */ + if (setjmp(usermsg_end)) + { + /* Restore bytes and memory protection */ + memcpy(target, orig_bytes, sizeof(orig_bytes)); + SetMemAccess(target, sizeof(orig_bytes), SH_MEM_READ|SH_MEM_EXEC); + return; + } + + q = 0; + + /* If GetUserMessageInfo() calls Error(), we should end up in our detour */ + while (g_GameDll.pGameDLL->GetUserMessageInfo(q, buffer, sizeof(buffer), size)) + { + usermsgs_list.push_back(UsrMsgInfo(size, buffer)); + q++; + } + + /* Jump back to setjmp() */ + longjmp(usermsg_end, 1); } int CSmmAPI::GetUserMessageCount() { - return m_MsgCount; + return (int)usermsgs_list.size(); } int CSmmAPI::FindUserMessage(const char *name, int *size) { - int index = m_UserMessages.Find(name); - - if (size && index > -1) + for (size_t i = 0; i < usermsgs_list.size(); i++) { - UserMessage *msg = m_UserMessages.Element(index); - *size = msg->size; + if (usermsgs_list[i].name.compare(name) == 0) + { + if (size) + { + *size = usermsgs_list[i].size; + } + return (int)i; + } } - return index; + return -1; } const char *CSmmAPI::GetUserMessage(int index, int *size) { - if (m_MsgCount <= 0 || index < 0 || index >= m_MsgCount) + if (index < 0 || index >= (int)usermsgs_list.size()) { return NULL; } - UserMessage *msg = m_UserMessages.Element(index); - if (size) { - *size = msg->size; + *size = usermsgs_list[index].size; } - return msg->name; + return usermsgs_list[index].name.c_str(); } IServerPluginCallbacks *CSmmAPI::GetVSPInfo(int *pVersion) diff --git a/core-legacy/CSmmAPI.h b/core-legacy/CSmmAPI.h index 95cb6df..76489f4 100644 --- a/core-legacy/CSmmAPI.h +++ b/core-legacy/CSmmAPI.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== - * Copyright (C) 2004-2008 Metamod:Source Development Team + * Copyright (C) 2004-2009 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -17,15 +17,6 @@ */ #include "ISmmAPI.h" -#include - -struct UserMessage -{ - int size; - const char *name; -}; - -typedef CUtlDict UserMsgDict; typedef void (*CONPRINTF_FUNC)(const char *, ...); @@ -35,7 +26,6 @@ namespace SourceMM { public: CSmmAPI(); - ~CSmmAPI(); public: void LogMsg(ISmmPlugin *pl, const char *msg, ...); public: @@ -80,15 +70,12 @@ namespace SourceMM { return m_VSP; } - bool CacheUserMessages(); - bool MsgCacheSuccessful(); + void CacheUserMessages(); private: META_RES m_Res; CONPRINTF_FUNC m_ConPrintf; bool m_CmdCache; bool m_VSP; - int m_MsgCount; - UserMsgDict m_UserMessages; }; }; diff --git a/core-legacy/concommands.cpp b/core-legacy/concommands.cpp index 4f18550..282c789 100644 --- a/core-legacy/concommands.cpp +++ b/core-legacy/concommands.cpp @@ -176,29 +176,24 @@ CON_COMMAND(meta, "Metamod:Source Menu") } // Display user messages - if (g_SmmAPI.MsgCacheSuccessful()) + const char *msgname; + int msgsize; + int msgcount = g_SmmAPI.GetUserMessageCount(); + + if (msgcount > 0) { - const char *msgname; - int msgsize; - int msgcount = g_SmmAPI.GetUserMessageCount(); + CONMSG(" User Messages: %-32.31s %-5s %-5s\n", "Name", "Index", "Size"); - if (msgcount > 0) + for (int i = 0; i < msgcount; i++) { - CONMSG(" User Messages: %-32.31s %-5s %-5s\n", "Name", "Index", "Size"); + msgname = g_SmmAPI.GetUserMessage(i, &msgsize); - for (int i = 0; i < msgcount; i++) - { - msgname = g_SmmAPI.GetUserMessage(i, &msgsize); - - CONMSG(" %-32.31s %-5d %-5d\n", msgname, i, msgsize); - } - - CONMSG(" %d user message%s in total\n", msgcount, (msgcount > 1) ? "s" : ""); - } else { - CONMSG(" User Messages: None\n"); + CONMSG(" %-32.31s %-5d %-5d\n", msgname, i, msgsize); } + + CONMSG(" %d user message%s in total\n", msgcount, (msgcount > 1) ? "s" : ""); } else { - CONMSG(" User Messages: Failed to get list of user messages\n"); + CONMSG(" User Messages: None\n"); } return; diff --git a/core-legacy/oslink.h b/core-legacy/oslink.h index a21ea4d..56126a9 100644 --- a/core-legacy/oslink.h +++ b/core-legacy/oslink.h @@ -1,5 +1,5 @@ /* ======== SourceMM ======== - * Copyright (C) 2004-2008 Metamod:Source Development Team + * Copyright (C) 2004-2009 Metamod:Source Development Team * No warranties of any kind * * License: zlib/libpng @@ -75,6 +75,8 @@ bool GetFileOfAddress(void *pAddr, char *buffer, size_t maxlength); #if defined __WIN32__ || defined _WIN32 || defined WIN32 typedef __int64 int64_t; typedef unsigned __int64 uint64_t; + typedef __int32 int32_t; + typedef unsigned __int32 uint32_t; #elif defined __GNUC__ #include #if !__GLIBC_HAVE_LONG_LONG diff --git a/core-legacy/sourcemm.cpp b/core-legacy/sourcemm.cpp index 2c28b0f..2bc07c6 100644 --- a/core-legacy/sourcemm.cpp +++ b/core-legacy/sourcemm.cpp @@ -165,14 +165,7 @@ bool StartupMetamod(CreateInterfaceFn engineFactory, bool bWaitForGameInit) LogMessage("[META] Warning: Console messages will not be redirected to rcon console."); } - if (!g_SmmAPI.CacheUserMessages()) - { - /* Don't know of a mod that has stripped out user messages completely, - * but perhaps should do something different here? - */ - LogMessage("[META] Warning: Failed to get list of user messages."); - LogMessage("[META] Warning: The 'meta game' command will not display user messages."); - } + g_SmmAPI.CacheUserMessages(); baseFs = (IFileSystem *)((engineFactory)(FILESYSTEM_INTERFACE_VERSION, NULL)); if (baseFs == NULL) diff --git a/core/metamod_console.cpp b/core/metamod_console.cpp index 1a98f4d..ab35b0b 100644 --- a/core/metamod_console.cpp +++ b/core/metamod_console.cpp @@ -126,33 +126,26 @@ bool Command_Meta(IMetamodSourceCommandInfo *info) #endif // Display user messages + const char *msgname; + int msgsize; int messages = g_Metamod.GetUserMessageCount(); - if (messages != -1) + + if (messages > 0) { - const char *msgname; - int msgsize; + CONMSG(" User Messages: %-32.31s %-5s %-5s\n", "Name", "Index", "Size"); - if (messages > 0) + for (int i = 0; i < messages; i++) { - CONMSG(" User Messages: %-32.31s %-5s %-5s\n", "Name", "Index", "Size"); + msgname = g_Metamod.GetUserMessage(i, &msgsize); - for (int i = 0; i < messages; i++) - { - msgname = g_Metamod.GetUserMessage(i, &msgsize); - - CONMSG(" %-32.31s %-5d %-5d\n", msgname, i, msgsize); - } - - CONMSG(" %d user message%s in total\n", messages, (messages > 1) ? "s" : ""); - } - else - { - CONMSG(" User Messages: None\n"); + CONMSG(" %-32.31s %-5d %-5d\n", msgname, i, msgsize); } + + CONMSG(" %d user message%s in total\n", messages, (messages > 1) ? "s" : ""); } else { - CONMSG(" User Messages: Failed to get list of user messages\n"); + CONMSG(" User Messages: None\n"); } return true; diff --git a/core/metamod_oslink.h b/core/metamod_oslink.h index 3899faa..eb1b11a 100644 --- a/core/metamod_oslink.h +++ b/core/metamod_oslink.h @@ -1,8 +1,8 @@ /** - * vim: set ts=4 : + * vim: set ts=4 sw=4 tw=99 noet : * ====================================================== * Metamod:Source - * Copyright (C) 2004-2008 AlliedModders LLC and authors. + * Copyright (C) 2004-2009 AlliedModders LLC and authors. * All rights reserved. * ====================================================== * @@ -21,8 +21,6 @@ * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. - * - * Version: $Id$ */ #ifndef _INCLUDE_OSLINK_H @@ -95,6 +93,8 @@ bool GetFileOfAddress(void *pAddr, char *buffer, size_t maxlength); #if defined __WIN32__ || defined _WIN32 || defined WIN32 typedef __int64 int64_t; typedef unsigned __int64 uint64_t; + typedef __int32 int32_t; + typedef unsigned __int32 uint32_t; #elif defined __GNUC__ #include #if !__GLIBC_HAVE_LONG_LONG diff --git a/core/provider/provider_ep2.cpp b/core/provider/provider_ep2.cpp index 50611d2..5a1e282 100644 --- a/core/provider/provider_ep2.cpp +++ b/core/provider/provider_ep2.cpp @@ -24,12 +24,12 @@ */ #include +#include #include "../metamod_oslink.h" #include #include #include #include -#include #include #include #include "../metamod_util.h" @@ -60,7 +60,8 @@ DLL_IMPORT ICommandLine *CommandLine(); #endif /* Functions */ -bool CacheUserMessages(); +void CacheUserMessages(); +void Detour_Error(const tchar *pMsg, ...); #if SOURCE_ENGINE >= SE_ORANGEBOX void ClientCommand(edict_t *pEdict, const CCommand &args); void LocalCommand_Meta(const CCommand &args); @@ -71,10 +72,10 @@ void LocalCommand_Meta(); void _ServerCommand(); /* Variables */ -static bool usermsgs_extracted = false; -static CVector usermsgs_list; static BaseProvider g_Ep1Provider; static List conbases_unreg; +static CVector usermsgs_list; +static jmp_buf usermsg_end; ICvar *icvar = NULL; IFileSystem *baseFs = NULL; @@ -90,22 +91,6 @@ SH_DECL_HOOK2_void(IServerGameClients, ClientCommand, SH_NOATTRIB, 0, edict_t *, SH_DECL_HOOK1_void(IServerGameClients, ClientCommand, SH_NOATTRIB, 0, edict_t *); #endif -bool AssumeUserMessages() -{ - int q, size; - char buffer[256]; - - q = 0; - - while (server->GetUserMessageInfo(q, buffer, sizeof(buffer), size)) - { - usermsgs_list.push_back(UsrMsgInfo(size, buffer)); - q++; - } - - return true; -} - void BaseProvider::ConsolePrint(const char *str) { #if SOURCE_ENGINE >= SE_ORANGEBOX @@ -155,10 +140,7 @@ void BaseProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory, g_SMConVarAccessor.RegisterConCommandBase(&meta_local_cmd); - if ((usermsgs_extracted = CacheUserMessages()) == false) - { - usermsgs_extracted = AssumeUserMessages(); - } + CacheUserMessages(); #if SOURCE_ENGINE == SE_DARKMESSIAH if (!g_SMConVarAccessor.InitConCommandBaseList()) @@ -334,11 +316,6 @@ void BaseProvider::UnregisterConCommandBase(ConCommandBase *pCommand) int BaseProvider::GetUserMessageCount() { - if (!usermsgs_extracted) - { - return -1; - } - return (int)usermsgs_list.size(); } @@ -361,7 +338,7 @@ int BaseProvider::FindUserMessage(const char *name, int *size) const char *BaseProvider::GetUserMessage(int index, int *size) { - if (!usermsgs_extracted || index < 0 || index >= (int)usermsgs_list.size()) + if (index < 0 || index >= (int)usermsgs_list.size()) { return NULL; } @@ -534,153 +511,59 @@ void ClientCommand(edict_t *pEdict) RETURN_META(MRES_IGNORED); } -////////////////////////////////////////////////////////////////////// -// EVEN MORE HACKS HERE! YOU HAVE BEEN WARNED! // -// Signatures necessary in finding the pointer to the CUtlDict that // -// stores user message information. // -// IServerGameDLL::GetUserMessageInfo() normally crashes with bad // -// message indices. This is our answer to it. Yuck! <:-( // -////////////////////////////////////////////////////////////////////// -#ifdef OS_WIN32 - /* General Windows sig */ - #define MSGCLASS_SIGLEN 7 - #define MSGCLASS_SIG "\x8B\x0D\x2A\x2A\x2A\x2A\x56" - #define MSGCLASS_OFFS 2 - - /* Dystopia Windows hack */ - #define MSGCLASS2_SIGLEN 16 - #define MSGCLASS2_SIG "\x56\x8B\x74\x24\x2A\x85\xF6\x7C\x2A\x3B\x35\x2A\x2A\x2A\x2A\x7D" - #define MSGCLASS2_OFFS 11 - - /* Windows frame pointer sig */ - #define MSGCLASS3_SIGLEN 18 - #define MSGCLASS3_SIG "\x55\x8B\xEC\x51\x89\x2A\x2A\x8B\x2A\x2A\x50\x8B\x0D\x2A\x2A\x2A\x2A\xE8" - #define MSGCLASS3_OFFS 13 -#elif defined OS_LINUX - /* No frame pointer sig */ - #define MSGCLASS_SIGLEN 14 - #define MSGCLASS_SIG "\x53\x83\xEC\x2A\x8B\x2A\x2A\x2A\xA1\x2A\x2A\x2A\x2A\x89" - #define MSGCLASS_OFFS 9 - - /* Frame pointer sig */ - #define MSGCLASS2_SIGLEN 16 - #define MSGCLASS2_SIG "\x55\x89\xE5\x53\x83\xEC\x2A\x8B\x2A\x2A\xA1\x2A\x2A\x2A\x2A\x89" - #define MSGCLASS2_OFFS 11 -#endif - -struct UserMessage +/* This only gets called if IServerGameDLL::GetUserMessageInfo() triggers it */ +void Detour_Error(const tchar *pMsg, ...) { - int size; - const char *name; -}; - -typedef CUtlDict UserMsgDict; - -/* This is the ugliest function in all of MM:S */ -bool CacheUserMessages() -{ - UserMsgDict *dict = NULL; - - /* Get address of original GetUserMessageInfo() */ - char *vfunc = (char *)SH_GET_ORIG_VFNPTR_ENTRY(server, &IServerGameDLL::GetUserMessageInfo); - - /* Oh dear, we have a relative jump on our hands - * PVK II on Windows made me do this, but I suppose it doesn't hurt to check this on Linux too... - */ - if (*vfunc == '\xE9') - { - /* Get address from displacement... - * - * Add 5 because it's relative to next instruction: - * Opcode <1 byte> + 32-bit displacement <4 bytes> - */ - vfunc += *reinterpret_cast(vfunc + 1) + 5; - } - - if (UTIL_VerifySignature(vfunc, MSGCLASS_SIG, MSGCLASS_SIGLEN)) - { - /* Get address of CUserMessages instance */ - char **userMsgClass = *reinterpret_cast(vfunc + MSGCLASS_OFFS); - - /* Get address of CUserMessages::m_UserMessages */ - dict = reinterpret_cast(*userMsgClass); - } - else if (UTIL_VerifySignature(vfunc, MSGCLASS2_SIG, MSGCLASS2_SIGLEN)) - { - #ifdef OS_WIN32 - /* If we get here, the code is possibly inlined like in Dystopia */ - - /* Get the address of the CUtlRBTree */ - char *rbtree = *reinterpret_cast(vfunc + MSGCLASS2_OFFS); - - /* CUtlDict should be 8 bytes before the CUtlRBTree (hacktacular!) */ - dict = reinterpret_cast(rbtree - 8); - #elif defined OS_LINUX - /* Get address of CUserMessages instance */ - char **userMsgClass = *reinterpret_cast(vfunc + MSGCLASS2_OFFS); - - /* Get address of CUserMessages::m_UserMessages */ - dict = reinterpret_cast(*userMsgClass); - #endif - #ifdef OS_WIN32 - } - else if (UTIL_VerifySignature(vfunc, MSGCLASS3_SIG, MSGCLASS3_SIGLEN)) - { - /* Get address of CUserMessages instance */ - char **userMsgClass = *reinterpret_cast(vfunc + MSGCLASS3_OFFS); - - /* Get address of CUserMessages::m_UserMessages */ - dict = reinterpret_cast(*userMsgClass); - #endif - } - - #if !defined OS_WIN32 - if (dict == NULL) - { - char path[255]; - if (GetFileOfAddress(vfunc, path, sizeof(path))) - { - void *handle = dlopen(path, RTLD_NOW); - if (handle != NULL) - { - void *addr = dlsym(handle, "usermessages"); - if (addr == NULL) - { - dlclose(handle); - return false; - } - dict = (UserMsgDict *)*(void **)addr; - dlclose(handle); - } - } - } - #endif - - if (dict != NULL) - { - int msg_count = dict->Count(); - - /* Ensure that count is within bounds of an unsigned byte, because that's what engine supports */ - if (msg_count < 0 || msg_count > 255) - { - return false; - } - - UserMessage *msg; - UsrMsgInfo u_msg; - - /* Cache messages in our CUtlDict */ - for (int i = 0; i < msg_count; i++) - { - msg = dict->Element(i); - u_msg.name = msg->name; - u_msg.size = msg->size; - usermsgs_list.push_back(u_msg); - } - - return true; - } - - return false; + /* Jump back to setjmp() in CacheUserMessages() */ + longjmp(usermsg_end, 1); } +#define IA32_JMP_IMM32 0xE9 + +/* IServerGameDLL::GetUserMessageInfo() crashes on games based on the old engine and + * early Orange Box. This is because Error() from tier0 gets called when a bad index is + * passed. This is all due to a bug in CUtlRBTree::IsValidIndex(). + * + * So we detour Error() to fix this. Our detour then jumps back into CacheUserMessages() + * to a point before GetUserMessageInfo() is called. The detour is then removed and we + * exit. + */ +void CacheUserMessages() +{ + int q, size; + char buffer[256]; + unsigned char *target, *detour; + unsigned char orig_bytes[5]; + + target = (unsigned char *)&Error; + detour = (unsigned char *)&Detour_Error; + + /* Save bytes from target function */ + memcpy(orig_bytes, target, sizeof(orig_bytes)); + + /* Patch in relative jump to our Error() detour */ + SetMemAccess(target, sizeof(orig_bytes), SH_MEM_READ|SH_MEM_WRITE|SH_MEM_EXEC); + target[0] = IA32_JMP_IMM32; + *(int32_t *)&target[1] = (int32_t)(detour - (target + 5)); + + /* This is where longjmp() will end up */ + if (setjmp(usermsg_end)) + { + /* Restore bytes and memory protection */ + memcpy(target, orig_bytes, sizeof(orig_bytes)); + SetMemAccess(target, sizeof(orig_bytes), SH_MEM_READ|SH_MEM_EXEC); + return; + } + + q = 0; + + /* If GetUserMessageInfo() calls Error(), we should end up in our detour */ + while (server->GetUserMessageInfo(q, buffer, sizeof(buffer), size)) + { + usermsgs_list.push_back(UsrMsgInfo(size, buffer)); + q++; + } + + /* Jump back to setjmp() */ + longjmp(usermsg_end, 1); +}