From 67039543cd6a2e3f72d3d61992f9202dc5e707f9 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 6 Oct 2005 00:59:01 +0000 Subject: [PATCH] Initial import of new GameDLL loading code --HG-- extra : convert_revision : svn%3Ac2935e3e-5518-0410-8daf-afa5dab7d4e3/trunk%40123 --- sourcemm/CHLTVDirector.h | 41 --- sourcemm/CPlugin.cpp | 16 +- sourcemm/CPlugin.h | 3 + sourcemm/CServerGameClients.h | 77 ----- sourcemm/CServerGameDLL.h | 95 ------ sourcemm/CServerGameEnts.h | 45 --- sourcemm/IPluginManager.h | 36 +- sourcemm/ISmmPlugin.h | 94 ++++-- sourcemm/oslink.cpp | 22 ++ sourcemm/oslink.h | 11 + sourcemm/sourcemm.cpp | 596 ++++++++++++++++++++-------------- sourcemm/sourcemm.h | 12 +- sourcemm/sourcemm.vcproj | 15 - sourcemm/util.cpp | 144 +++++++- sourcemm/util.h | 11 +- 15 files changed, 656 insertions(+), 562 deletions(-) delete mode 100644 sourcemm/CHLTVDirector.h delete mode 100644 sourcemm/CServerGameClients.h delete mode 100644 sourcemm/CServerGameDLL.h delete mode 100644 sourcemm/CServerGameEnts.h diff --git a/sourcemm/CHLTVDirector.h b/sourcemm/CHLTVDirector.h deleted file mode 100644 index 59383c1..0000000 --- a/sourcemm/CHLTVDirector.h +++ /dev/null @@ -1,41 +0,0 @@ -/* ======== SourceMM ======== -* Copyright (C) 2004-2005 Metamod:Source Development Team -* No warranties of any kind -* -* License: zlib/libpng -* -* Author(s): David "BAILOPAN" Anderson -* ============================ -*/ - -#ifndef _INCLUDE_HLTVDIRECTOR_H -#define _INCLUDE_HLTVDIRECTOR_H - -/** - * @brief Dummy class for IHLTVDirector - * @file CHLTVDirector. - */ - -#include -#include - -class CHLTVDirector : IHLTVDirector -{ - IHLTVDirector *m_pOrig; -public: - CHLTVDirector() : m_pOrig(0) {} - void SetOrig(IHLTVDirector *pOrig) - { - m_pOrig = pOrig; - } - virtual bool IsActive() { return m_pOrig->IsActive(); } - virtual void SetHLTVServer(IHLTVServer *hltv) { m_pOrig->SetHLTVServer(hltv); } - virtual IHLTVServer *GetHLTVServer() { return m_pOrig->GetHLTVServer(); } - virtual int GetDirectorTick() { return m_pOrig->GetDirectorTick(); } - virtual int GetPVSEntity() { return m_pOrig->GetPVSEntity(); } - virtual Vector GetPVSOrigin() { return m_pOrig->GetPVSOrigin(); } - virtual float GetDelay() { return m_pOrig->GetDelay(); } - virtual const char**GetModEvents() { return m_pOrig->GetModEvents(); } -}; - -#endif //_INCLUDE_HLTVDIRECTOR_H diff --git a/sourcemm/CPlugin.cpp b/sourcemm/CPlugin.cpp index b02acfa..e015eed 100644 --- a/sourcemm/CPlugin.cpp +++ b/sourcemm/CPlugin.cpp @@ -260,7 +260,7 @@ CPluginManager::CPlugin *CPluginManager::_Load(const char *file, PluginId source snprintf(error, maxlen, "Plugin API %d is newer than internal version (%d)", api, PLAPI_VERSION); pl->m_Status = Pl_Error; } else { - if (pl->m_API->Load(pl->m_Id, static_cast(&g_SmmAPI), &(pl->fac_list), error, maxlen)) + if (pl->m_API->Load(pl->m_Id, static_cast(&g_SmmAPI), &(pl->fac_list), error, maxlen, m_AllLoaded)) { pl->m_Status = Pl_Running; if (m_AllLoaded) @@ -424,6 +424,20 @@ bool CPluginManager::Query(PluginId id, const char *&file, factories *&list, Pl_ return true; } +bool CPluginManager::QueryRunning(PluginId id, char *error, size_t maxlength) +{ + CPlugin *pl = FindById(id); + + if (!pl || !pl->m_API) + { + if (error) + snprintf(error, maxlength, "Plugin not valid"); + return false; + } + + return pl->m_API->QueryRunning(error, maxlength); +} + PluginIter CPluginManager::_begin() { return m_Plugins.begin(); diff --git a/sourcemm/CPlugin.h b/sourcemm/CPlugin.h index f5d4642..2057661 100644 --- a/sourcemm/CPlugin.h +++ b/sourcemm/CPlugin.h @@ -33,6 +33,8 @@ * 5: Bumped version for SourceHook V4 (2005-05-01) * 6: Added functions for console printing (2005-05-26) * MC 7: Changed template libraries (2005-08-11) + * New loading structure mechanism + * New SourceHook version */ #define PLAPI_MIN_VERSION 7 @@ -75,6 +77,7 @@ namespace SourceMM bool UnloadAll(); public: bool Query(PluginId id, const char *&file, factories *&list, Pl_Status &status, PluginId &source); + bool QueryRunning(PluginId id, char *error, size_t maxlength); void AddPluginCvar(ISmmPlugin *api, ConCommandBase *pCvar); void AddPluginCmd(ISmmPlugin *api, ConCommandBase *pCmd); diff --git a/sourcemm/CServerGameClients.h b/sourcemm/CServerGameClients.h deleted file mode 100644 index 5bb1424..0000000 --- a/sourcemm/CServerGameClients.h +++ /dev/null @@ -1,77 +0,0 @@ -/* ======== SourceMM ======== -* Copyright (C) 2004-2005 Metamod:Source Development Team -* No warranties of any kind -* -* License: zlib/libpng -* -* Author(s): David "BAILOPAN" Anderson -* ============================ -*/ - -#ifndef _INCLUDE_CSERVERGAMECLIENTS_H -#define _INCLUDE_CSERVERGAMECLIENTS_H - -/** - * @brief Dummy class for IServerGameClients - * @file CServerGameClients.h - */ - -#include - -class CServerGameClients : public IServerGameClients -{ - IServerGameClients *m_pOrig; -public: - CServerGameClients() : m_pOrig(0) {} - void SetOrig(IServerGameClients *pOrig) - { - m_pOrig = pOrig; - } - - virtual void GetPlayerLimits(int& minplayers, int& maxplayers, int &defaultMaxPlayers) const - { m_pOrig->GetPlayerLimits(minplayers, maxplayers, defaultMaxPlayers); } - - virtual bool ClientConnect(edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen) - { return m_pOrig->ClientConnect(pEntity, pszName, pszAddress, reject, maxrejectlen); } - - virtual void ClientActive(edict_t *pEntity, bool bLoadGame) - { m_pOrig->ClientActive(pEntity, bLoadGame); } - - virtual void ClientDisconnect(edict_t *pEntity) - { m_pOrig->ClientDisconnect(pEntity); } - - virtual void ClientPutInServer(edict_t *pEntity, char const *playername) - { m_pOrig->ClientPutInServer(pEntity, playername); } - - virtual void ClientCommand(edict_t *pEntity) - { m_pOrig->ClientCommand(pEntity); } - - virtual void SetCommandClient(int index) - { m_pOrig->SetCommandClient(index); } - - virtual void ClientSettingsChanged(edict_t *pEdict) - { m_pOrig->ClientSettingsChanged(pEdict); } - - virtual void ClientSetupVisibility(edict_t *pViewEntity, edict_t *pClient, unsigned char *pvs, int pvssize) - { m_pOrig->ClientSetupVisibility(pViewEntity, pClient, pvs, pvssize); } - - virtual float ProcessUsercmds(edict_t *player, bf_read *buf, int numcmds, int totalcmds, int dropped_packets, bool ignore, bool paused) - { return m_pOrig->ProcessUsercmds(player, buf, numcmds, totalcmds, dropped_packets, ignore, paused); } - - virtual void PostClientMessagesSent() - { m_pOrig->PostClientMessagesSent(); } - - virtual CPlayerState *GetPlayerState(edict_t *player) - { return m_pOrig->GetPlayerState(player); } - - virtual void ClientEarPosition(edict_t *pEntity, Vector *pEarOrigin) - { m_pOrig->ClientEarPosition(pEntity, pEarOrigin); } - - virtual int GetReplayDelay(edict_t *player) - { return m_pOrig->GetReplayDelay(player); } - - virtual void GetBugReportInfo(char *buf, int buflen) - { m_pOrig->GetBugReportInfo(buf, buflen); } -}; - -#endif //_INCLUDE_CSERVERGAMECLIENTS_H diff --git a/sourcemm/CServerGameDLL.h b/sourcemm/CServerGameDLL.h deleted file mode 100644 index 23b00bc..0000000 --- a/sourcemm/CServerGameDLL.h +++ /dev/null @@ -1,95 +0,0 @@ -/* ======== SourceMM ======== -* Copyright (C) 2004-2005 Metamod:Source Development Team -* No warranties of any kind -* -* License: zlib/libpng -* -* Author(s): David "BAILOPAN" Anderson -* ============================ -*/ - -#ifndef _INCLUDE_CSERVER_GAMEDLL_H -#define _INCLUDE_CSERVER_GAMEDLL_H - -/** - * @brief Defines wrapper class for gamedll interfaces - * @file CServerGameDLL.h - */ - -#include -#include "oslink.h" - -/** - * @brief Empty class. We only care about DLLInit. - */ -class CServerGameDLL : public IServerGameDLL -{ - IServerGameDLL *m_pOrig; - char m_GameDescBuffer[256]; -public: - CServerGameDLL() : m_pOrig(0) - { - strcpy(m_GameDescBuffer, "Metamod:Source"); - } - void SetOrig(IServerGameDLL *pOrig) - { - m_pOrig = pOrig; - snprintf(m_GameDescBuffer, 255, "%s", pOrig->GetGameDescription()); - } - - virtual bool DLLInit( CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn fileSystemFactory, CGlobalVars *pGlobals); - virtual bool GameInit( void ) - { return m_pOrig->GameInit(); } - virtual bool LevelInit( char const *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background ); - virtual void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) - { m_pOrig->ServerActivate(pEdictList, edictCount, clientMax); } - virtual void GameFrame( bool simulating ) - { m_pOrig->GameFrame(simulating); } - virtual void PreClientUpdate( bool simulating ) - { m_pOrig->PreClientUpdate(simulating); } - virtual void LevelShutdown( void ); - virtual void GameShutdown( void ) - { m_pOrig->GameShutdown(); } - virtual void DLLShutdown( void ); - virtual float GetTickInterval( void ) const - { return m_pOrig->GetTickInterval(); } - virtual ServerClass *GetAllServerClasses( void ) - { return m_pOrig->GetAllServerClasses(); } - virtual const char *GetGameDescription( void ); - virtual void CreateNetworkStringTables( void ) - { m_pOrig->CreateNetworkStringTables(); } - virtual CSaveRestoreData *SaveInit( int size ) - { return m_pOrig->SaveInit(size); } - virtual void SaveWriteFields( CSaveRestoreData *a1, const char *a2, void *a3, datamap_t *a4, typedescription_t *a5, int a6) - { m_pOrig->SaveWriteFields(a1, a2, a3, a4, a5, a6); } - virtual void SaveReadFields( CSaveRestoreData *a1, const char *a2, void *a3, datamap_t *a4, typedescription_t *a5, int a6) - { m_pOrig->SaveReadFields(a1, a2, a3, a4, a5, a6); } - virtual void SaveGlobalState( CSaveRestoreData *a1) - { m_pOrig->SaveGlobalState(a1); } - virtual void RestoreGlobalState( CSaveRestoreData *a1) - { m_pOrig->RestoreGlobalState(a1); } - virtual void PreSave( CSaveRestoreData *a1) - { m_pOrig->PreSave(a1); } - virtual void Save( CSaveRestoreData *a1) - { m_pOrig->Save(a1); } - virtual void GetSaveComment( char *comment, int maxlength ) - { m_pOrig->GetSaveComment(comment, maxlength); } - virtual void WriteSaveHeaders( CSaveRestoreData *a1) - { m_pOrig->WriteSaveHeaders(a1); } - virtual void ReadRestoreHeaders( CSaveRestoreData *a1) - { m_pOrig->ReadRestoreHeaders(a1); } - virtual void Restore( CSaveRestoreData *a1, bool a2) - { m_pOrig->Restore(a1, a2); } - virtual bool IsRestoring() - { return m_pOrig->IsRestoring(); } - virtual int CreateEntityTransitionList( CSaveRestoreData *a1, int a2) - { return m_pOrig->CreateEntityTransitionList(a1, a2); } - virtual void BuildAdjacentMapList( void ) - { m_pOrig->BuildAdjacentMapList(); } - virtual bool GetUserMessageInfo( int msg_type, char *name, int maxnamelength, int& size ) - { return m_pOrig->GetUserMessageInfo(msg_type, name, maxnamelength, size); } - virtual CStandardSendProxies* GetStandardSendProxies() - { return m_pOrig->GetStandardSendProxies(); } -}; - -#endif //_INCLUDE_CSERVER_GAMEDLL_H diff --git a/sourcemm/CServerGameEnts.h b/sourcemm/CServerGameEnts.h deleted file mode 100644 index adfe3f4..0000000 --- a/sourcemm/CServerGameEnts.h +++ /dev/null @@ -1,45 +0,0 @@ -/* ======== SourceMM ======== -* Copyright (C) 2004-2005 Metamod:Source Development Team -* No warranties of any kind -* -* License: zlib/libpng -* -* Author(s): David "BAILOPAN" Anderson -* ============================ -*/ - -#ifndef _INCLUDE_CSERVER_GAMEENTS_H -#define _INCLUDE_CSERVER_GAMEENTS_H - -/** - * @brief Dummy class for IServerGameEnts - * @file CServerGameEnts.h - */ - -#include - -class CServerGameEnts : public IServerGameEnts -{ - IServerGameEnts *m_pOrig; -public: - CServerGameEnts() : m_pOrig(0) {} - void SetOrig(IServerGameEnts *pOrig) - { - m_pOrig = pOrig; - } - - virtual void SetDebugEdictBase(edict_t *base) - { m_pOrig->SetDebugEdictBase(base); } - virtual void MarkEntitiesAsTouching(edict_t *e1, edict_t *e2) - { m_pOrig->MarkEntitiesAsTouching(e1, e2); } - virtual void FreeContainingEntity(edict_t *e) - { m_pOrig->FreeContainingEntity(e); } - virtual edict_t *BaseEntityToEdict(CBaseEntity *pEnt) - { return m_pOrig->BaseEntityToEdict(pEnt); } - virtual CBaseEntity *EdictToBaseEntity(edict_t *pEdict) - { return m_pOrig->EdictToBaseEntity(pEdict); } - virtual void CheckTransmit(CCheckTransmitInfo *pInfo, const unsigned short *pEdictIndices, int nEdicts) - { m_pOrig->CheckTransmit(pInfo, pEdictIndices, nEdicts); } -}; - -#endif //_INCLUDE_CSERVER_GAMEENTS_H diff --git a/sourcemm/IPluginManager.h b/sourcemm/IPluginManager.h index 584c573..bfe1d8c 100644 --- a/sourcemm/IPluginManager.h +++ b/sourcemm/IPluginManager.h @@ -18,6 +18,17 @@ #include "ISmmPlugin.h" +/** + * @brief Load sources + */ +enum +{ + Pl_BadLoad=0, + Pl_Console=-1, + Pl_File=-2, + Pl_MinId=1, +}; + /** * @brief Status of a plugin at runtime */ @@ -30,17 +41,6 @@ enum Pl_Status Pl_Running=0, }; -/** - * @brief Load sources - */ -enum -{ - Pl_BadLoad=0, - Pl_Console=-1, - Pl_File=-2, - Pl_MinId=1, -}; - typedef int PluginId; struct factories; @@ -48,7 +48,9 @@ class ISmmPluginManager { public: /** - * @brief Loads a plugin and returns its id + * @brief Loads a plugin and returns its id. If this is called before DLLInit(), + * then the plugin is considered to be "hot" - it might refuse its own load later! + * Also, a hot plugin might not have an error message. * * @param file String containing file name * @param source Specifies who loaded the plugin @@ -109,6 +111,16 @@ public: * @return True on success, false if not found */ virtual bool Query(PluginId id, const char *&file, factories *&list, Pl_Status &status, PluginId &source) =0; + + /** + * @brief Checks another plugin's QueryRunning() status. + * + * @param id Id of plugin + * @param error Message buffer + * @param maxlen Size of error buffer + * @return Status value + */ + virtual bool QueryRunning(PluginId id, char *error, size_t maxlength) =0; }; #endif //_INCLUDE_PLUGINMANAGER_H diff --git a/sourcemm/ISmmPlugin.h b/sourcemm/ISmmPlugin.h index 3e60729..511a4c6 100644 --- a/sourcemm/ISmmPlugin.h +++ b/sourcemm/ISmmPlugin.h @@ -38,42 +38,83 @@ class ISmmPlugin { public: virtual int GetApiVersion() { return PLAPI_VERSION; } + virtual ~ISmmPlugin() { } public: /** * @brief Called on plugin load. + * NOTE - As of API 7, this is called as DLLInit() executes - after the parameters are known, but before + * the original GameDLL function is called. Therefore, you cannot hook it, but you don't need to - + * Load() is basically your hook. + * NOTE - As of API 7, you can override factories before the engine and gamedll exchange them. + * However, take care to note that if your plugin is unloaded, and the gamedll/engine have cached + * an interface you've passed, something will definitely crash. Be careful. * - * @param id Internal id of plugin. Saved globally by PLUGIN_SAVEVARS() - * @param ismm External API for SourceMM. Saved globally by PLUGIN_SAVEVARS() - * @param list Contains a list of factories. Hook a factory call by setting one equal to your own function. + * @param id Internal id of plugin. Saved globally by PLUGIN_SAVEVARS() + * @param ismm External API for SourceMM. Saved globally by PLUGIN_SAVEVARS() + * @param list Contains a list of factories. Hook a factory call by setting one equal to your own function. + * @param late Set to true if your plugin was loaded late (not at server load). * @param error Error message buffer * @param maxlen Size of error message buffer - * @return True on success, return false to request no load. + * @return True if successful, return false to reject the load. */ - virtual bool Load(PluginId id, ISmmAPI *ismm, factories *list, char *error, size_t maxlen) =0; + virtual bool Load(PluginId id, ISmmAPI *ismm, factories *list, char *error, size_t maxlength, bool late) =0; - /** @brief Called on plugin unload. + /** + * @brief Called when your plugin is "queried". This is useful for rejecting a loaded + * state. For example, if your plugin wants to stop operating, it can simply return + * false and copy an error message. This will notify other plugins or MM:S of something + * bad that happened. NOTE - MM:S will not cache the return state, so if you return false, + * your plugin will not actually be paused or unloaded. This callback will be called when: + * - Another plugin requests it + * - Someone types "meta list", it will show up as "REFUSED" + * - When Metamod need to re-check the plugin's status + * - If the plugin does something like overload a factory, Metamod will make sure the Query() returns true + * before calling it. + * Also note that this query will only override Metamod when the plugin is running and not paused. * - * @param error Error message buffer - * @param maxlen Size of error message buffer - * @return True on success, return false to request no unload. + * @param error Buffer for error message. This can be NULL! + * @param maxlen Maximum length of error buffer. + * @return Status code - true for okay, false for badness. */ - virtual bool Unload(char *error, size_t maxlen) =0; + virtual bool QueryRunning(char *error, size_t maxlen) + { + return true; + } + + /** + * @brief Called on plugin unload. You can return false if you know your plugin + * is not capable of restoring critical states it modifies. + * + * @param error Error message buffer + * @param maxlen Size of error message buffer + * @return True on success, return false to request no unload. + */ + virtual bool Unload(char *error, size_t maxlen) + { + return true; + } /** @brief Called on plugin pause. * - * @param error Error message buffer - * @param maxlen Size of error message buffer - * @return True on success, return false to request no pause. + * @param error Error message buffer + * @param maxlen Size of error message buffer + * @return True on success, return false to request no pause. */ - virtual bool Pause(char *error, size_t maxlen) =0; + virtual bool Pause(char *error, size_t maxlen) + { + return true; + } /** @brief Called on plugin unpause. * - * @param error Error message buffer - * @param maxlen Size of error message buffer - * @return True on success, return false to request no unpause. + * @param error Error message buffer + * @param maxlen Size of error message buffer + * @return True on success, return false to request no unpause. */ - virtual bool Unpause(char *error, size_t maxlen) =0; + virtual bool Unpause(char *error, size_t maxlen) + { + return true; + } public: /** @brief Return author as string */ virtual const char *GetAuthor() =0; @@ -101,13 +142,16 @@ public: public: /** * @brief Called when all plugins have been loaded - API version 4 - * - * Ths is useful for knowing when it's safe to request things another plugin might have. + * + * This is useful for knowing when it's safe to request things another plugin might have. + * NOTE for API 7 - This is called after DLLInit(). */ - virtual void AllPluginsLoaded() =0; + virtual void AllPluginsLoaded() + { + } }; -#define PL_EXPOSURE CreateInterface +#define PL_EXPOSURE CreateInterface #define PL_EXPOSURE_C "CreateInterface" #define PLUGIN_EXPOSE(name, var) \ @@ -138,13 +182,13 @@ public: g_SMAPI->SetLastMetaReturn(mres); \ return value; -#define META_LOG g_SMAPI->LogMsg +#define META_LOG g_SMAPI->LogMsg #define META_REGCMD(name) g_SMAPI->RegisterConCmdBase(g_PLAPI, name##_command) #define META_REGCVAR(var) g_SMAPI->RegisterConCmdBase(g_PLAPI, var) #define META_UNREGCMD(name) g_SMAPI->UnregisterConCmdBase(g_PLAPI, name##_command) #define META_UNREGCVAR(var) g_SMAPI->UnregisterConCmdBase(g_PLAPI, var) -#define META_CONPRINT g_SMAPI->ConPrint -#define META_CONPRINTF g_SMAPI->ConPrintf +#define META_CONPRINT g_SMAPI->ConPrint +#define META_CONPRINTF g_SMAPI->ConPrintf //probably should use this up above someday #define CONCMD_VARNAME(name) name##_command diff --git a/sourcemm/oslink.cpp b/sourcemm/oslink.cpp index cd9978a..0ec65e0 100644 --- a/sourcemm/oslink.cpp +++ b/sourcemm/oslink.cpp @@ -36,3 +36,25 @@ int GetLastError() return errno; } #endif + +bool GetFileOfAddress(void *pAddr, char *buffer, size_t maxlength) +{ +#if defined WIN32 || defined _WIN32 + MEMORY_BASIC_INFORMATION mem; + if (!VirtualQuery((void *)pAddr, &mem, sizeof(mem))) + return false; + if (mem.AllocationBase == NULL) + return false; + HMODULE dll = (HMODULE)mem.AllocationBase; + GetModuleFileName(dll, (LPTSTR)buffer, maxlength); +#elif defined __linux__ + Dl_info info; + if (!dladdr((void *)pAddr, &info)) + return false; + if (!info.dli_fbase || !info.dli_fname) + return false; + const char *dllpath = info.dli_fname; + snprintf(buffer, maxlength, "%s", dllpath); +#endif + return true; +} diff --git a/sourcemm/oslink.h b/sourcemm/oslink.h index 4e5a62c..d6acffb 100644 --- a/sourcemm/oslink.h +++ b/sourcemm/oslink.h @@ -27,6 +27,10 @@ #define dlsym(x, s) GetProcAddress(x, s) #define dlclose(x) FreeLibrary(x) const char* dlerror(); + #define PATH_SEP_STR "\\" + #define PATH_SEP_CHAR '\\' + #define ALT_SEP_CHAR '/' + #define SERVER_DLL "server.dll" #elif defined __linux__ #define OS_LINUX #include @@ -35,6 +39,11 @@ #include #define dlmount(x) dlopen(x,RTLD_NOW) typedef void* HINSTANCE; + #define PATH_SEP_STR "/" + #define PATH_SEP_CHAR '/' + #define ALT_SEP_CHAR '\\' + #define stricmp strcasecmp + #define SERVER_DLL "server_i486.so" #endif #if defined __linux__ @@ -42,6 +51,8 @@ int GetLastError(); #endif +bool GetFileOfAddress(void *pAddr, char *buffer, size_t maxlength); + #if defined __WIN32__ || defined _WIN32 || defined WIN32 #define SMM_API extern "C" __declspec(dllexport) #elif defined __GNUC__ diff --git a/sourcemm/sourcemm.cpp b/sourcemm/sourcemm.cpp index 1b97ab8..9853f16 100644 --- a/sourcemm/sourcemm.cpp +++ b/sourcemm/sourcemm.cpp @@ -12,10 +12,6 @@ #include #include -#include "CServerGameDLL.h" -#include "CServerGameEnts.h" -#include "CServerGameClients.h" -#include "CHLTVDirector.h" #include "sourcemm.h" #include "concommands.h" #include "CSmmAPI.h" @@ -26,262 +22,389 @@ * @file sourcemm.cpp */ -CServerGameDLL g_TempGameDLL; -CServerGameEnts g_TempGameEnts; -CServerGameClients g_TempGameClients; -CHLTVDirector g_TempDirector; -GameDllInfo g_GameDll = {false, NULL, NULL}; -EngineInfo g_Engine = {NULL, NULL, NULL, NULL}; +SH_DECL_HOOK4(IServerGameDLL, DLLInit, SH_NOATTRIB, false, bool, CreateInterfaceFn, CreateInterfaceFn, CreateInterfaceFn, CGlobalVars *); +SH_DECL_HOOK0_void(IServerGameDLL, DLLShutdown, SH_NOATTRIB, false); +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); +bool DLLInit(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn filesystemFactory, CGlobalVars *pGlobals); +void DLLShutdown_handler(); +void LevelShutdown_handler(); +bool LevelInit_handler(char const *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background); + +GameDllInfo g_GameDll = {false, NULL, NULL, NULL}; +EngineInfo g_Engine; SourceHook::CSourceHookImpl g_SourceHook; -SourceHook::ISourceHook *g_SHPtr; +SourceHook::ISourceHook *g_SHPtr = &g_SourceHook; SourceHook::String g_ModPath; SourceHook::String g_BinPath; PluginId g_PLID = Pl_Console; //Technically, SourceMM is the "Console" plugin... :p bool bInFirstLevel = true; +bool gParsedGameInfo = false; +SourceHook::List gamedll_list; +SourceHook::CallClass *dllExec; + +void ClearGamedllList(); /////////////////////////////////// // Main code for HL2 Interaction // /////////////////////////////////// +//Initialize everything here +void InitMainStates() +{ + char full_path[260] = {0}; + GetFileOfAddress(g_GameDll.factory, full_path, sizeof(full_path)-1); + g_BinPath.assign(full_path); + + UTIL_PathFmt(full_path, sizeof(full_path)-1, "%s/%s", g_ModPath.c_str(), GetPluginsFile()); + + //Like metamod, reload plugins at the end of the map. + //This is so plugins can hook everything on load, BUT, new plugins will be reloaded + // if the server is shut down (silly, but rare case). + bInFirstLevel = true; + + SH_ADD_HOOK_STATICFUNC(IServerGameDLL, DLLInit, g_GameDll.pGameDLL, DLLInit, false); + SH_ADD_HOOK_STATICFUNC(IServerGameDLL, DLLShutdown, g_GameDll.pGameDLL, DLLShutdown_handler, false); + SH_ADD_HOOK_STATICFUNC(IServerGameDLL, LevelShutdown, g_GameDll.pGameDLL, LevelShutdown_handler, true); + SH_ADD_HOOK_STATICFUNC(IServerGameDLL, LevelInit, g_GameDll.pGameDLL, LevelInit_handler, true); +} + +bool DLLInit(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn filesystemFactory, CGlobalVars *pGlobals) +{ + g_Engine.engineFactory = engineFactory; + g_Engine.fileSystemFactory = filesystemFactory; + g_Engine.physicsFactory = physicsFactory; + g_Engine.pGlobals = pGlobals; + + g_Engine.engine = (IVEngineServer *)((engineFactory)(INTERFACEVERSION_VENGINESERVER, NULL)); + if (!g_Engine.engine) + { + Error("Could not find IVEngineServer! Metamod cannot load."); + return false; + } + g_Engine.icvar = (ICvar *)((engineFactory)(VENGINE_CVAR_INTERFACE_VERSION , NULL)); + if (!g_Engine.icvar) + { + Error("Could not find ICvar! Metamod cannot load."); + return false; + } + + g_Engine.loaded = true; + + //Initialize our console hooks + ConCommandBaseMgr::OneTimeInit(static_cast(&g_SMConVarAccessor)); + + 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."); + } + + char full_path[260]; + UTIL_PathFmt(full_path, sizeof(full_path)-1, "%s/%s", g_ModPath.c_str(), GetPluginsFile()); + + LoadPluginsFromFile(full_path); + + g_PluginMngr.SetAllLoaded(); + + bInFirstLevel = true; + + dllExec = SH_GET_CALLCLASS(g_GameDll.pGameDLL); + + RETURN_META_VALUE(MRES_IGNORED, true); +} + //This is where the magic happens SMM_API void *CreateInterface(const char *name, int *ret) { - if (!g_GameDll.loaded) + if (!gParsedGameInfo) { - //State of the Mod: - // Currently, HL2 Engine has loaded Metamod:Source - // It is now asking it to get an interface. We don't have one, - // so we're gonna try to give it a fake one to get the information we need. - // Then, the the interface will forward the calls to the original interface - - if (strcmp(name, INTERFACEVERSION_SERVERGAMEDLL) == 0) + gParsedGameInfo = true; + char curpath[260] = {0}; + char dllpath[260] = {0}; + getcwd(curpath, sizeof(curpath)-1); + if (!GetFileOfAddress((void *)CreateInterface, dllpath, sizeof(dllpath)-1)) { - //We're in. Give the server our fake class as bait. - if (ret) - *ret = IFACE_OK; - return static_cast(&g_TempGameDLL); - } else if (strcmp(name, INTERFACEVERSION_SERVERGAMEENTS) == 0) { - if (ret) - *ret = IFACE_OK; - - return static_cast(&g_TempGameEnts); - } else if (strcmp(name, INTERFACEVERSION_SERVERGAMECLIENTS) == 0) { - if (ret) - *ret = IFACE_OK; - - return static_cast(&g_TempGameClients); - } else if (strcmp(name, INTERFACEVERSION_HLTVDIRECTOR) == 0) { - if (ret) - *ret = IFACE_OK; - - return static_cast(&g_TempDirector); - } else { - if (ret) - *ret = IFACE_FAILED; - + Error("GetFileOfAddress() failed! Metamod cannot load."); return NULL; } - } else { - META_INTERFACE_MACRO(server, g_GameDll.factory); - } -} + SourceHook::String s_dllpath(dllpath); + //begin the heuristics for searching for the mod path (these are quite ugly). + //for OS compatibility purposes, we're going to do case insensitivity on windows. + size_t path_len = strlen(curpath); + size_t dll_len = strlen(dllpath); -bool CServerGameDLL::DLLInit(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn fileSystemFactory, CGlobalVars *pGlobals) -{ - if (m_pOrig) - return m_pOrig->DLLInit(engineFactory, physicsFactory, fileSystemFactory, pGlobals); + //strip the dll path off + //:TODO: with path stuff - in Linux, \ can exist in a file path as a non-seperator! + for (size_t i=dll_len-1; i>0; i--) + { + if (dllpath[i] == '\\' || dllpath[i] == '/') + { + if (i == dll_len-1) + break; + //save path by stripping off file name and ending terminator + dllpath[i] = '\0'; + dll_len = i; + break; + } + } + + //strip absolute path terminators if any! + if (curpath[path_len-1] == '/' || curpath[path_len-1] == '\\') + curpath[--path_len] = '\0'; + + //if the base path doesn't fit into the module path, something is wrong! + // ex: c:\games\srcds + // c:\gaben.dll + if (path_len > dll_len) + { + Error("Could not detect GameDLL path! Metamod cannot load[1]."); + return NULL; + } + + //we are now in such a position that the two dir names SHOULD MATCH! + typedef int (*STRNCMP)(const char *str1, const char *str2, size_t n); + STRNCMP cmp = +#ifdef WIN32 + strnicmp; +#else + strncmp; +#endif + //are they equal? + if ( ((cmp)(curpath, dllpath, path_len)) != 0 ) + { + //:TODO: In this case, we should read /proc/self/maps and find srcds! + Error("Could not detect GameDLL path! Metamod cannot load[2]."); + return NULL; + } + //this will skip past the dir and its separator char + char *ptr = &(dllpath[path_len+1]); + path_len = strlen(ptr); + for (size_t i=0; i::iterator iter; + GameDllInfo *pCheck; + bool found = false; + for (iter=gamedll_list.begin(); iter!=gamedll_list.end(); iter++) + { + pCheck = (*iter); + if (GetFileOfAddress(pCheck->factory, buffer, sizeof(buffer)-1)) + { + if (UTIL_PathCmp(temp_path, buffer)) + { + found = true; + break; + } + } + } + if (found) + continue; + fclose(fp); + HINSTANCE gamedll = dlmount(temp_path); + if (gamedll == NULL) + continue; + CreateInterfaceFn fn = (CreateInterfaceFn)dlsym(gamedll, "CreateInterface"); + if (fn == NULL) + { + dlclose(gamedll); + continue; + } + GameDllInfo *pInfo = new GameDllInfo; + pInfo->factory = fn; + pInfo->lib = gamedll; + pInfo->loaded = true; + pInfo->pGameDLL = NULL; + gamedll_list.push_back(pInfo); + } + } + } + fclose(fp); + } if (!g_GameDll.loaded) { - //Initialize SourceHook - g_SHPtr = static_cast(&g_SourceHook); + const char *str = "ServerGameDLL"; + size_t len = strlen(str); - //The gamedll isn't loaded yet. We need to find out where it's hiding. - IVEngineServer *ive = (IVEngineServer *)((engineFactory)(INTERFACEVERSION_VENGINESERVER, NULL)); - if (!ive) + if (strncmp(name, str, len) == 0) { - Error("Metamod:Source could not load %s", INTERFACEVERSION_VENGINESERVER); - return false; - } - - //Guess the file name - //We'll have better heurestics[sp?] later on - char mod_path[128], full_path[255]; - ive->GetGameDir(mod_path, sizeof(mod_path)-1); - g_ModPath.assign(mod_path); -#if defined WIN32 || defined _WIN32 - snprintf(full_path, sizeof(full_path)-1, "%s\\bin\\server.dll", mod_path); -#else - snprintf(full_path, sizeof(full_path)-1, "%s/bin/server_i486.so", mod_path); -#endif - - g_BinPath.assign(full_path); - - //See if the file even exists - FILE *fp = fopen(g_BinPath.c_str(), "r"); - if (!fp) - { - Error("Metamod:Source could not read %s", g_BinPath.c_str()); - return false; - } - fclose(fp); - fp = NULL; - - //Load the DLL - g_GameDll.lib = dlmount(g_BinPath.c_str()); - if (!g_GameDll.lib) - { - Error("Metamod:Source could not load GameDLL: %s", dlerror()); - return false; + //This is the interface we want! Right now we support versions 3 and 4. + int version = atoi(&(name[len])); + if (version < MIN_GAMEDLL_VERSION || version > MAX_GAMEDLL_VERSION) + { + Error("GameDLL version %d is not supported by Metamod!", version); + return NULL; + } + SourceHook::List::iterator iter; + GameDllInfo *pInfo = NULL; + void *ptr; + for (iter=gamedll_list.begin(); iter!=gamedll_list.end(); iter++) + { + pInfo = (*iter); + ptr = (pInfo->factory)(name, ret); + if (ptr) + { + //this is our gamedll. unload the others. + gamedll_list.erase(iter); + ClearGamedllList(); + pInfo->pGameDLL = static_cast(ptr); + g_GameDll = *pInfo; + delete pInfo; + break; + } + } + if (g_GameDll.loaded) + { + InitMainStates(); + } else { + if (ret) + *ret = IFACE_FAILED; + return NULL; + } } else { - //Find its factory - g_GameDll.factory = (CreateInterfaceFn)(dlsym(g_GameDll.lib, "CreateInterface")); - if (!g_GameDll.factory) - { - Error("Metamod:Source could not find an entry point in GameDLL: %s", g_BinPath.c_str()); - dlclose(g_GameDll.lib); - return false; - } - - //Find the new IServerGameDLL pointer - IServerGameDLL *serverDll; - serverDll = (IServerGameDLL *)((g_GameDll.factory)(INTERFACEVERSION_SERVERGAMEDLL, NULL)); - if (!serverDll) - { - Error("Metamod:Source could not find %s in GameDLL: %s", INTERFACEVERSION_SERVERGAMEDLL, g_BinPath.c_str()); - dlclose(g_GameDll.lib); - return false; - } - - //Set this information early in case our wrappers are called somehow - g_Engine.engineFactory = engineFactory; - g_Engine.icvar = (ICvar *)(g_Engine.engineFactory)(VENGINE_CVAR_INTERFACE_VERSION, NULL); - if (!g_Engine.icvar) - { - Error("Metamod:Source could not find %s in engine!", VENGINE_CVAR_INTERFACE_VERSION); - dlclose(g_GameDll.lib); - return false; - } - g_Engine.fileSystemFactory = fileSystemFactory; - g_Engine.pGlobals = pGlobals; - g_Engine.physicsFactory = physicsFactory; - g_Engine.engine = ive; - - //Attempt to load the GameDLL - // Note that nothing will be intercepting yet. - // This is the one and only call that plugins have no chance of seeing. - // Likewise, you won't be able to trick the Server DLL into loading random things. - // Luckily, because of SourceHook, this really isn't a problem - you can change - // the virtual interfaces in anything it requests. - if (!serverDll->DLLInit(EngineFactory, PhysicsFactory, FileSystemFactory, pGlobals)) - { - //For some reason, the GameDLL failed to load. - Error("Metamod:Source: GameDLL %s refused to load.", g_BinPath.c_str()); - dlclose(g_GameDll.lib); - return false; - } - - //Retrieve the pointers we'll need from the GameDLL - IServerGameEnts *serverEnts; - IServerGameClients *serverClients; - IHLTVDirector *serverHLTV; - - serverEnts = (IServerGameEnts *)((g_GameDll.factory)(INTERFACEVERSION_SERVERGAMEENTS, NULL)); - if (!serverEnts) - { - Error("Metamod:Source could not find %s in GameDLL: %s", INTERFACEVERSION_SERVERGAMEENTS, g_BinPath.c_str()); - dlclose(g_GameDll.lib); - return false; - } - - serverClients = (IServerGameClients *)((g_GameDll.factory)(INTERFACEVERSION_SERVERGAMECLIENTS, NULL)); - if (!serverClients) - { - Error("Metamod:Source could not find %s in GameDLL: %s", INTERFACEVERSION_SERVERGAMECLIENTS, g_BinPath.c_str()); - dlclose(g_GameDll.lib); - return false; - } - - serverHLTV = (IHLTVDirector *)((g_GameDll.factory)(INTERFACEVERSION_HLTVDIRECTOR, NULL)); - if (!serverHLTV) - { - Error("Metamod:Source could not find %s in GameDLL: %s", INTERFACEVERSION_HLTVDIRECTOR, g_BinPath.c_str()); - dlclose(g_GameDll.lib); - return false; - } - - // Now tell the global temp classes that they can call the original functions - g_TempDirector.SetOrig(serverHLTV); - g_TempGameClients.SetOrig(serverClients); - g_TempGameEnts.SetOrig(serverEnts); - g_TempGameDLL.SetOrig(serverDll); - - //Everything's done. - g_GameDll.loaded = true; - - //Initialize our console hooks - ConCommandBaseMgr::OneTimeInit(static_cast(&g_SMConVarAccessor)); - - 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."); - } - - //Now it's safe to load plugins. -#if defined WIN32 || defined _WIN32 - snprintf(full_path, sizeof(full_path)-1, "%s\\%s", g_ModPath.c_str(), GetPluginsFile()); -#else - snprintf(full_path, sizeof(full_path)-1, "%s/%s", g_ModPath.c_str(), GetPluginsFile()); -#endif - - LoadPluginsFromFile(full_path); - - //All plugins are now loaded. - g_PluginMngr.SetAllLoaded(); - - //Like metamod, reload plugins at the end of the map. - //This is so plugins can hook everything on load, BUT, new plugins will be reloaded - // if the server is shut down (silly, but rare case). - bInFirstLevel = true; - - return true; + //wtf do we do... + //:TODO: .. something a bit more intelligent? + Error("Engine requested unknown interface before GameDLL was known!"); + return NULL; } } - //Somehow, the function got here. This should be impossible. - Error("Metamod:Source fatal error - IServerGameDLL::DLLInit() called inappropriately"); + SetUnhandledExceptionFilter(NULL); - return false; + //if we got here, there's definitely a gamedll. + //META_INTERFACE_MACRO(server, g_GameDll.factory); + return (g_GameDll.factory)(name, ret); } -void Shutdown() +void ClearGamedllList() +{ + SourceHook::List::iterator iter; + + GameDllInfo *pInfo; + for (iter=gamedll_list.begin(); iter!=gamedll_list.end(); iter++) + { + pInfo = (*iter); + dlclose(pInfo->lib); + delete pInfo; + } + + gamedll_list.clear(); +} + +void DLLShutdown_handler() { //Unload plugins g_PluginMngr.UnloadAll(); - // Shutdown sourcehook now - g_SourceHook.CompleteShutdown(); - // Add the FCVAR_GAMEDLL flag to our cvars so the engine removes them properly g_SMConVarAccessor.MarkCommandsAsGameDLL(); -} - -void CServerGameDLL::DLLShutdown() -{ - Shutdown(); - - //Call the original function - m_pOrig->DLLShutdown(); - - //Unregister all commands marked as GameDLL now - //This prevents crashes when the engine tries to unregister them once we have already unloaded the gamedll - //(we used to unload the gamedll in a __attribute__((destructor)) function but I've had problems with that (crashes in dlclose) g_SMConVarAccessor.UnregisterGameDLLCommands(); + SH_CALL(dllExec, &IServerGameDLL::DLLShutdown)(); + + SH_RELEASE_CALLCLASS(dllExec); + dllExec = NULL; + + //right now this will crash when the function returns! + // :TODO: remove this warning once PM fixes it. + g_SourceHook.CompleteShutdown(); + if (g_GameDll.lib && g_GameDll.loaded) dlclose(g_GameDll.lib); memset(&g_GameDll, 0, sizeof(GameDllInfo)); + + RETURN_META(MRES_SUPERCEDE); } int LoadPluginsFromFile(const char *file) @@ -345,11 +468,7 @@ int LoadPluginsFromFile(const char *file) ext = ""; } //Format the new path -#if defined WIN32 || defined _WIN32 - snprintf(full_path, sizeof(full_path)-1, "%s\\%s%s", g_ModPath.c_str(), buffer, ext); -#else - snprintf(full_path, sizeof(full_path)-1, "%s/%s%s", g_ModPath.c_str(), buffer, ext); -#endif + UTIL_PathFmt(full_path, sizeof(full_path)-1, "%s/%s", g_ModPath.c_str(), buffer, ext); id = g_PluginMngr.Load(full_path, Pl_File, already, error, sizeof(error)-1); if (id < Pl_MinId || g_PluginMngr.FindById(id)->m_Status < Pl_Paused) { @@ -407,13 +526,12 @@ void LogMessage(const char *msg, ...) strcat(buffer, "\n"); va_end(ap); - g_Engine.engine->LogPrint(buffer); -} - -void CServerGameDLL::LevelShutdown(void) -{ - LevelShutdown_handler(); - m_pOrig->LevelShutdown(); + if (!g_Engine.engine) + { + fprintf(stdout, "%s", buffer); + } else { + g_Engine.engine->LogPrint(buffer); + } } void LevelShutdown_handler(void) @@ -432,7 +550,7 @@ void LevelShutdown_handler(void) } } -bool CServerGameDLL::LevelInit( char const *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background ) +bool LevelInit_handler(char const *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background) { if (!g_SmmAPI.CacheSuccessful()) { @@ -440,19 +558,7 @@ bool CServerGameDLL::LevelInit( char const *pMapName, char const *pMapEntities, LogMessage("[META] Warning: Console messages will not be redirected to rcon console."); } - return m_pOrig->LevelInit(pMapName, pMapEntities, pOldLevel, pLandmarkName, loadGame, background); -} - -const char *CServerGameDLL::GetGameDescription() -{ - if (m_pOrig) - { - const char *game = m_pOrig->GetGameDescription(); - if (game) - strcpy(m_GameDescBuffer, game); - } - - return m_GameDescBuffer; + RETURN_META_VALUE(MRES_IGNORED, false); } #if defined __GNUC__ && (__GNUC__ == 3) diff --git a/sourcemm/sourcemm.h b/sourcemm/sourcemm.h index 10df119..f6aa9e6 100644 --- a/sourcemm/sourcemm.h +++ b/sourcemm/sourcemm.h @@ -29,6 +29,9 @@ #define SOURCEMM_VERSION "1.10" #define SOURCEMM_DATE __DATE__ +#define MIN_GAMEDLL_VERSION 3 +#define MAX_GAMEDLL_VERSION 4 + /** * @brief Entry point for HL2 Engine */ @@ -55,11 +58,17 @@ struct GameDllInfo bool loaded; HINSTANCE lib; CreateInterfaceFn factory; + IServerGameDLL *pGameDLL; }; /** @brief Stores information about the HL2 Engine pointers */ struct EngineInfo { + EngineInfo() : loaded(false), + engineFactory(NULL), physicsFactory(NULL), fileSystemFactory(NULL), + pGlobals(NULL), icvar(NULL), engine(NULL) + { }; + bool loaded; CreateInterfaceFn engineFactory; CreateInterfaceFn physicsFactory; CreateInterfaceFn fileSystemFactory; @@ -89,7 +98,4 @@ extern SourceHook::ISourceHook *g_SHPtr; /** @brief We have our own internal plugin id... */ extern PluginId g_PLID; -/** @brief Hook for LevelShutdown */ -void LevelShutdown_handler(void); - #endif //_INCLUDE_SOURCEMM_H diff --git a/sourcemm/sourcemm.vcproj b/sourcemm/sourcemm.vcproj index 380f694..9e3272d 100644 --- a/sourcemm/sourcemm.vcproj +++ b/sourcemm/sourcemm.vcproj @@ -154,33 +154,18 @@ Name="Header Files" Filter="h;hpp;hxx;hm;inl;inc;xsd" UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"> - - - - - - - - - - diff --git a/sourcemm/util.cpp b/sourcemm/util.cpp index 20be759..e7f32d7 100644 --- a/sourcemm/util.cpp +++ b/sourcemm/util.cpp @@ -10,7 +10,10 @@ #include #include +#include +#include #include "util.h" +#include "oslink.h" /** * @brief Utility functons @@ -55,11 +58,43 @@ void UTIL_TrimLeft(char *buffer) i++; // If whitespace chars in buffer then adjust string so first non-whitespace char is at start of buffer + // :TODO: change this to not use memcpy()! if (i != buffer) memcpy(buffer, i, (strlen(i) + 1) * sizeof(char)); } } +//:TODO: this should skip string literals +void UTIL_TrimComments(char *buffer) +{ + int num_sc = 0; + size_t len = strlen(buffer); + if (buffer) + { + for (int i=(int)len-1; i>=0; i--) + { + if (buffer[i] == '/') + { + if (++num_sc >= 2 && i==0) + { + buffer[i] = '\0'; + return; + } + } else { + if (num_sc >= 2) + { + buffer[i] = '\0'; + return; + } + num_sc = 0; + } + //size_t won't go below 0, manually break out + if (i == 0) + break; + } + } +} + /* UTIL_TrimLeft * Removes whitespace characters from right side of string */ @@ -69,7 +104,7 @@ void UTIL_TrimRight(char *buffer) if (buffer) { // Loop through buffer backwards while replacing whitespace chars with null chars - for (unsigned int i = strlen(buffer) - 1; i >= 0; i--) + for (int i = (int)strlen(buffer) - 1; i >= 0; i--) { if (isspace(buffer[i])) buffer[i] = '\0'; @@ -79,3 +114,110 @@ void UTIL_TrimRight(char *buffer) } } +/* UTIL_KeySplit + * Breaks a string at the first space until it reaches a nonspace + */ +void UTIL_KeySplit(const char *str, char *buf1, size_t len1, char *buf2, size_t len2) +{ + size_t start; + size_t len = strlen(str); + + for (start=0; start= len1) + break; + buf1[c] = str[i]; + } + buf1[c] = '\0'; + + for (start=end; start= len2) + break; + buf2[c] = str[start]; + } + buf2[c] = '\0'; +} + +/** + * Formats a path name for an OS + */ +void UTIL_PathFmt(char *buffer, size_t len, const char *fmt, ...) +{ + va_list ap; + va_start(ap,fmt); + size_t mylen = vsnprintf(buffer, len, fmt, ap); + va_end(ap); + + for (size_t i=0; i