From bf3d98f77c3b60f580de0dbf2384bc19b892a00a Mon Sep 17 00:00:00 2001 From: Scott Ehlert Date: Wed, 21 Feb 2007 01:51:54 +0000 Subject: [PATCH] Added experimental user message API functions that don't error out on invalid indices Added user message list to 'meta game' command Terrible, ugly code... --HG-- extra : convert_revision : svn%3Ac2935e3e-5518-0410-8daf-afa5dab7d4e3/trunk%40338 --- sourcemm/CSmmAPI.cpp | 115 +++++++++++++++++++++++++++++++++++++-- sourcemm/CSmmAPI.h | 23 +++++++- sourcemm/GAMES.txt | 12 ++++ sourcemm/ISmmAPI.h | 29 +++++++++- sourcemm/concommands.cpp | 26 +++++++++ sourcemm/oslink.cpp | 8 +++ sourcemm/sourcemm.cpp | 24 +++++--- sourcemm/sourcemm.h | 3 + 8 files changed, 223 insertions(+), 17 deletions(-) create mode 100644 sourcemm/GAMES.txt diff --git a/sourcemm/CSmmAPI.cpp b/sourcemm/CSmmAPI.cpp index fde6982..1774032 100644 --- a/sourcemm/CSmmAPI.cpp +++ b/sourcemm/CSmmAPI.cpp @@ -27,10 +27,16 @@ CSmmAPI g_SmmAPI; CSmmAPI::CSmmAPI() { m_ConPrintf = NULL; - m_Cache = false; + 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; @@ -279,7 +285,7 @@ bool CSmmAPI::CacheCmds() //add the base offset, to the ip (which is the address+offset + 4 bytes for next instruction) m_ConPrintf = (CONPRINTF_FUNC)((unsigned long)m_ConPrintf + (unsigned long)(ptr + offs) + 4); - m_Cache = true; + m_CmdCache = true; return true; } @@ -291,9 +297,9 @@ bool CSmmAPI::CacheCmds() return false; } -bool CSmmAPI::CacheSuccessful() +bool CSmmAPI::CmdCacheSuccessful() { - return m_Cache; + return m_CmdCache; } void CSmmAPI::GetApiVersions(int &major, int &minor, int &plvers, int &plmin) @@ -498,3 +504,104 @@ 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 + #define MSGCLASS_SIGLEN 7 + #define MSGCLASS_SIG "\x8B\x0D\x2A\x2A\x2A\x2A\x56" + #define MSGCLASS_OFFS 2 +#elif defined OS_LINUX + #define MSGCLASS_SIGLEN 14 + #define MSGCLASS_SIG "\x53\x83\xEC\x2A\x8B\x5C\x2A\x2A\xA1\x2A\x2A\x2A\x2A\x89" + #define MSGCLASS_OFFS 9 +#endif + +/* This is the ugliest function in all of SourceMM */ +bool CSmmAPI::CacheUserMessages() +{ + SourceHook::MemFuncInfo info = {true, -1, 0, 0}; + SourceHook::GetFuncInfo(&IServerGameDLL::GetUserMessageInfo, info); + + // Get address of original GetUserMessageInfo() + char *vfunc = reinterpret_cast(g_GameDllPatch->GetOrigFunc(info.vtbloffs, info.vtblindex)); + + // If we can't get original function (GetOrigFunc bug?) + if (vfunc == NULL) + { + // This means there's no hook, so we must get it manually - Lovely code <:-( + char *adjustedptr = reinterpret_cast(g_GameDll.pGameDLL) + info.thisptroffs + info.vtbloffs; + char **vtable = *reinterpret_cast(adjustedptr); + + vfunc = vtable[info.vtblindex]; + } + + if (vcmp(vfunc, MSGCLASS_SIG, MSGCLASS_SIGLEN)) + { + // Get address of CUserMessages + char **userMsgClass = *reinterpret_cast(vfunc + MSGCLASS_OFFS); + + // Get address of CUserMessages::m_UserMessages + UserMsgDict *dict = reinterpret_cast(*userMsgClass); + + m_MsgCount = dict->Count(); + + // Cache messages in our CUtlDict + UserMessage *msg; + + for (int i = 0; i < m_MsgCount; i++) + { + msg = dict->Element(i); + m_UserMessages.Insert(msg->name, msg); + } + + return true; + } + + return false; +} + +bool CSmmAPI::MsgCacheSuccessful() +{ + return m_MsgCount > -1; +} + +int CSmmAPI::GetUserMessageCount() +{ + return m_MsgCount; +} + +int CSmmAPI::FindUserMessage(const char *name, int *size) +{ + int index = m_UserMessages.Find(name); + + if (size && index > -1) + { + UserMessage *msg = m_UserMessages.Element(index); + *size = msg->size; + } + + return index; +} + +const char *CSmmAPI::GetUserMessage(int index, int *size) +{ + if (m_MsgCount <= 0 || index < 0 || index >= m_MsgCount) + { + return NULL; + } + + UserMessage *msg = m_UserMessages.Element(index); + + if (size) + { + *size = msg->size; + } + + return msg->name; +} diff --git a/sourcemm/CSmmAPI.h b/sourcemm/CSmmAPI.h index 71d278e..7331fff 100644 --- a/sourcemm/CSmmAPI.h +++ b/sourcemm/CSmmAPI.h @@ -17,6 +17,15 @@ */ #include "ISmmAPI.h" +#include + +struct UserMessage +{ + int size; + const char *name; +}; + +typedef CUtlDict UserMsgDict; typedef void (*CONPRINTF_FUNC)(const char *, ...); @@ -26,6 +35,7 @@ namespace SourceMM { public: CSmmAPI(); + ~CSmmAPI(); public: void LogMsg(ISmmPlugin *pl, const char *msg, ...); public: @@ -41,10 +51,10 @@ namespace SourceMM void UnregisterConCmdBase(ISmmPlugin *plugin, ConCommandBase *pCommand); void ConPrint(const char *fmt); void ConPrintf(const char *fmt, ...); - bool CacheSuccessful(); + bool CmdCacheSuccessful(); bool RemotePrintingAvailable() { - return CacheSuccessful(); + return CmdCacheSuccessful(); } virtual void GetApiVersions(int &major, int &minor, int &plvers, int &plmin); virtual void GetShVersions(int &shvers, int &shimpl); @@ -58,6 +68,9 @@ namespace SourceMM void *VInterfaceMatch(CreateInterfaceFn fn, const char *iface, int min=-1); void EnableVSPListener(); int GetGameDLLVersion(); + int GetUserMessageCount(); + int FindUserMessage(const char *name, int *size=NULL); + const char *GetUserMessage(int index, int *size=NULL); public: bool CacheCmds(); void LoadAsVSP(); @@ -65,11 +78,15 @@ namespace SourceMM { return m_VSP; } + bool CacheUserMessages(); + bool MsgCacheSuccessful(); private: META_RES m_Res; CONPRINTF_FUNC m_ConPrintf; - bool m_Cache; + bool m_CmdCache; bool m_VSP; + int m_MsgCount; + UserMsgDict m_UserMessages; }; }; diff --git a/sourcemm/GAMES.txt b/sourcemm/GAMES.txt new file mode 100644 index 0000000..674962a --- /dev/null +++ b/sourcemm/GAMES.txt @@ -0,0 +1,12 @@ +Metamod: Source generally tries to support all Source engine based games. However, some API features +may only be supported by some games. This does not mean these features will not work on +games not listed here, but that games listed here are specifically known and tested to work. + +The only Source-based game completely unsupported by Metamod:Source is Dark Messiah. + +User Message API Functions - GetUserMessageCount(), FindUserMessage(), GetUserMessage() + - Counter-Strike: Source + - Day of Defeat: Source + - Half-Life 2: Deathmatch + - The Ship + - SourceForts \ No newline at end of file diff --git a/sourcemm/ISmmAPI.h b/sourcemm/ISmmAPI.h index c784569..dcc85e2 100644 --- a/sourcemm/ISmmAPI.h +++ b/sourcemm/ISmmAPI.h @@ -278,16 +278,41 @@ public: // Added in 1.4 (1:5) * @return Interface version of the loaded IServerGameDLL. */ virtual int GetGameDLLVersion() =0; + + /** + * @brief Returns the number of user messages in the GameDLL. + * + * @return Number of user messages, or -1 if SourceMM has failed to get user message list. + */ + virtual int GetUserMessageCount() =0; + + /** + * @brief Returns the index of the specified user message. + * + * @param name User message name. + * @param size Optional pointer to store size of user message. + * @return Message index, or -1 on failure. + */ + virtual int FindUserMessage(const char *name, int *size=NULL) =0; + + /** + * @brief Returns the name of the specified user message. + * + * @param index User message index. + * @param size Optional pointer to store size of user message. + * @return Message name, or NULL on failure. + */ + virtual const char *GetUserMessage(int index, int *size=NULL) =0; }; /** Version history - * 1.1.0 bumped API to 1:0. The breaking changes occured in sourcehook and the plugin API. + * 1.1.0 bumped API to 1:0. The breaking changes occurred in sourcehook and the plugin API. * 1.1.2 added API call for generating iface names. * 1.2 added API more helper functions and new SourceHook version. * 1.2.2 added API for printing to client console (with string formatting) * 1.3 added new interface search API - * 1.4 added VSP listener API + * 1.4 added VSP listener and user message API */ #endif //_INCLUDE_ISMM_API_H diff --git a/sourcemm/concommands.cpp b/sourcemm/concommands.cpp index 9f6d1ee..7315bea 100644 --- a/sourcemm/concommands.cpp +++ b/sourcemm/concommands.cpp @@ -156,6 +156,32 @@ CON_COMMAND(meta, "Metamod:Source Menu") CONMSG(" DLL Path: %s\n", g_BinPath.c_str()); CONMSG(" Interface: ServerGameDLL%03d\n", g_GameDllVersion); + // Display user messages + if (g_SmmAPI.MsgCacheSuccessful()) + { + const char *msgname; + int msgsize; + int msgcount = g_SmmAPI.GetUserMessageCount(); + + if (msgcount > 0) + { + CONMSG(" User Messages: %-32.31s %-5s %-5s\n", "Name", "Index", "Size"); + + 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"); + } + } else { + CONMSG(" User Messages: Failed to get list of user messages\n"); + } + return; } else if (strcmp(command, "refresh") == 0) { char full_path[255]; diff --git a/sourcemm/oslink.cpp b/sourcemm/oslink.cpp index b9d8fe6..cee64a3 100644 --- a/sourcemm/oslink.cpp +++ b/sourcemm/oslink.cpp @@ -36,6 +36,14 @@ int GetLastError() { return errno; } + +extern "C" void __cxa_guard_acquire(void) +{ +} + +extern "C" void __cxa_guard_release(void) +{ +} #endif bool GetFileOfAddress(void *pAddr, char *buffer, size_t maxlength) diff --git a/sourcemm/sourcemm.cpp b/sourcemm/sourcemm.cpp index 70932be..57b2469 100644 --- a/sourcemm/sourcemm.cpp +++ b/sourcemm/sourcemm.cpp @@ -32,6 +32,7 @@ SH_DECL_HOOK0_void(IServerGameDLL, LevelShutdown, SH_NOATTRIB, false); SH_DECL_HOOK6(IServerGameDLL, LevelInit, SH_NOATTRIB, false, bool, const char *, const char *, const char *, const char *, bool, bool); SH_DECL_HOOK1_void(IServerGameClients, ClientCommand, SH_NOATTRIB, 0, edict_t *); SH_DECL_HOOK0(IServerGameDLL, GameInit, SH_NOATTRIB, false, bool); + bool DLLInit(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn filesystemFactory, CGlobalVars *pGlobals); bool DLLInit_Post(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn filesystemFactory, CGlobalVars *pGlobals); void DLLShutdown_handler(); @@ -50,7 +51,7 @@ bool bInFirstLevel = true; bool gParsedGameInfo = false; bool bGameInit = false; SourceHook::List gamedll_list; -SourceHook::CallClass *dllExec; +SourceHook::CallClass *g_GameDllPatch; int g_GameDllVersion = 0; void ClearGamedllList(); @@ -145,12 +146,21 @@ bool DLLInit(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, //Initialize our console hooks ConCommandBaseMgr::OneTimeInit(static_cast(&g_SMConVarAccessor)); + g_GameDllPatch = SH_GET_CALLCLASS(g_GameDll.pGameDLL); + if (!g_SmmAPI.CacheCmds()) { LogMessage("[META] Warning: Failed to initialize Con_Printf. Defaulting to Msg()."); 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."); + } const char *pluginFile = g_Engine.icvar->GetCommandLineValue("mm_pluginsfile"); if (!pluginFile) @@ -165,8 +175,6 @@ bool DLLInit(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, bInFirstLevel = true; - dllExec = SH_GET_CALLCLASS(g_GameDll.pGameDLL); - RETURN_META_VALUE(MRES_IGNORED, true); } @@ -184,7 +192,7 @@ bool GameInit_handler() bGameInit = true; - return true; + RETURN_META_VALUE(MRES_IGNORED, true); } bool DLLInit_Post(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn filesystemFactory, CGlobalVars *pGlobals) @@ -524,10 +532,10 @@ void DLLShutdown_handler() g_SMConVarAccessor.MarkCommandsAsGameDLL(); g_SMConVarAccessor.UnregisterGameDLLCommands(); - SH_CALL(dllExec, &IServerGameDLL::DLLShutdown)(); + SH_CALL(g_GameDllPatch, &IServerGameDLL::DLLShutdown)(); - SH_RELEASE_CALLCLASS(dllExec); - dllExec = NULL; + SH_RELEASE_CALLCLASS(g_GameDllPatch); + g_GameDllPatch = NULL; g_SourceHook.CompleteShutdown(); @@ -724,7 +732,7 @@ void LevelShutdown_handler(void) bool LevelInit_handler(char const *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background) { - if (!g_SmmAPI.CacheSuccessful()) + if (!g_SmmAPI.CmdCacheSuccessful()) { LogMessage("[META] Warning: Failed to initialize Con_Printf. Defaulting to Msg()."); LogMessage("[META] Warning: Console messages will not be redirected to rcon console."); diff --git a/sourcemm/sourcemm.h b/sourcemm/sourcemm.h index c7163b2..854ae04 100644 --- a/sourcemm/sourcemm.h +++ b/sourcemm/sourcemm.h @@ -112,4 +112,7 @@ extern int g_GameDllVersion; extern bool bGameInit; +/** @brief Global CallClass for IServerGameDLL */ +extern SourceHook::CallClass *g_GameDllPatch; + #endif //_INCLUDE_SOURCEMM_H