/** * vim: set ts=4 sw=4 tw=99 noet : * ====================================================== * Metamod:Source * Copyright (C) 2004-2009 AlliedModders LLC and authors. * All rights reserved. * ====================================================== * * This software is provided 'as-is', without any express or implied warranty. * In no event will the authors be held liable for any damages arising from * the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software in a * product, an acknowledgment in the product documentation would be * appreciated but is not required. * 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. */ #include #include "metamod_oslink.h" #include "metamod.h" #include "metamod_plugins.h" #include "metamod_util.h" /** * @brief Implements functions from CPlugin.h * @file CPlugin.cpp */ using namespace SourceMM; #define ITER_PLEVENT(evn, plid) \ CPluginManager::CPlugin *_Xpl; \ SourceHook::List::iterator event; \ IMetamodListener *api; \ for (PluginIter iter = g_PluginMngr._begin(); iter != g_PluginMngr._end(); iter++) { \ _Xpl = (*iter); \ if (_Xpl->m_Id == plid) \ continue; \ for (event=_Xpl->m_Events.begin(); event!=_Xpl->m_Events.end(); event++) { \ api = (*event); \ api->evn(plid); \ } \ } CPluginManager g_PluginMngr; MetamodVersionInfo GlobVersionInfo = { METAMOD_API_MAJOR, METAMOD_API_MINOR, SH_IFACE_VERSION, SH_IMPL_VERSION, PLAPI_MIN_VERSION, METAMOD_PLAPI_VERSION, SOURCE_ENGINE_UNKNOWN, NULL }; CPluginManager::CPluginManager() { m_LastId = Pl_MinId; m_AllLoaded = false; } CPluginManager::~CPluginManager() { SourceHook::List::iterator iter; for (iter = m_Aliases.begin(); iter != m_Aliases.end(); iter++) { delete (*iter); } m_Aliases.clear(); } const char *CPluginManager::LookupAlias(const char *alias) { SourceHook::List::iterator iter; CNameAlias *p; for (iter = m_Aliases.begin(); iter != m_Aliases.end(); iter++) { p = (*iter); if (p->alias.compare(alias) == 0) { return p->value.c_str(); } } return NULL; } SourceHook::List::iterator CPluginManager::_alias_begin() { return m_Aliases.begin(); } SourceHook::List::iterator CPluginManager::_alias_end() { return m_Aliases.end(); } void CPluginManager::SetAlias(const char *alias, const char *value) { SourceHook::List::iterator iter; CNameAlias *p; for (iter = m_Aliases.begin(); iter != m_Aliases.end(); iter++) { p = (*iter); if (p->alias.compare(alias) == 0) { if (value[0] == '\0') { iter = m_Aliases.erase(iter); return; } else { p->value.assign(value); return; } } } if (value[0] != '\0') { p = new CNameAlias; p->alias.assign(alias); p->value.assign(value); m_Aliases.push_back(p); } } CPluginManager::CPlugin::CPlugin() : m_Id(0), m_Source(0), m_API(NULL), m_Lib(NULL), m_UnloadFn(NULL) { } PluginId CPluginManager::Load(const char *file, PluginId source, bool &already, char *error, size_t maxlen) { already = false; //Check if we're about to reload an old plugin PluginIter i = m_Plugins.begin(); while (i != m_Plugins.end()) { if ((*i) && UTIL_PathCmp(file, (*i)->m_File.c_str())) { if ((*i)->m_Status < Pl_Paused) { //Attempt to load the plugin again already = true; i = m_Plugins.erase(i); continue; } else { //No need to load it already = true; return (*i)->m_Id; } } i++; } CPlugin *pl = _Load(file, source, error, maxlen); if (!pl) { return Pl_BadLoad; } ITER_PLEVENT(OnPluginLoad, pl->m_Id); return pl->m_Id; } CPluginManager::CPlugin *CPluginManager::FindById(PluginId id) { PluginIter i; for (i=m_Plugins.begin(); i!=m_Plugins.end(); i++) { if ( (*i)->m_Id == id )\ { return (*i); } } return NULL; } void CPluginManager::SetAllLoaded() { if (m_AllLoaded) { return; } m_AllLoaded = true; PluginIter i; for (i=m_Plugins.begin(); i!=m_Plugins.end(); i++) { if ( (*i) && (*i)->m_Status == Pl_Running && (*i)->m_API ) { //API 4 is when we added this callback //Min version is now 5, so we ignore this check //if ( (*i)->m_API->GetApiVersion() >= 004 ) (*i)->m_API->AllPluginsLoaded(); } } } bool CPluginManager::Pause(PluginId id, char *error, size_t maxlen) { CPlugin *pl = FindById(id); if (!pl) { UTIL_Format(error, maxlen, "Plugin id not found"); return false; } bool ret; if ((ret=_Pause(pl, error, maxlen)) == true) { ITER_PLEVENT(OnPluginPause, pl->m_Id); } return ret; } bool CPluginManager::Unpause(PluginId id, char *error, size_t maxlen) { CPlugin *pl = FindById(id); if (!pl) { UTIL_Format(error, maxlen, "Plugin id not found"); return false; } bool ret; if ( (ret=_Unpause(pl, error, maxlen)) == true ) { ITER_PLEVENT(OnPluginUnpause, pl->m_Id); } return ret; } bool CPluginManager::Unload(PluginId id, bool force, char *error, size_t maxlen) { CPlugin *pl = FindById(id); if (!pl) { UTIL_Format(error, maxlen, "Plugin %d not found", id); return false; } bool ret; PluginId old_id = pl->m_Id; if ( (ret=_Unload(pl, force, error, maxlen)) == true ) { ITER_PLEVENT(OnPluginUnload, old_id); } return ret; } bool CPluginManager::Retry(PluginId id, char *error, size_t len) { PluginIter i; char buffer[64]; for (i=m_Plugins.begin(); i!=m_Plugins.end(); i++) { if ( (*i) && (*i)->m_Id == id ) { if ( (*i)->m_Status >= Pl_Paused) { UTIL_Format(error, len, "Plugin %d is already running.", id); return false; } CPlugin *pl = _Load((*i)->m_File.c_str(), Pl_Console, error, len); if (!pl) { return false; } if (pl->m_Status >= Pl_Paused) { //Now it gets crazy... unload the original copy. _Unload( (*i), true, buffer, sizeof(buffer)-1 ); //Set the new copy's id pl->m_Id = id; //We just wasted an id... reclaim it m_LastId--; return true; } else { //don't really care about the buffer here _Unload(pl, true, buffer, sizeof(buffer)-1); //We just wasted an id... reclaim it m_LastId--; return false; } } } UTIL_Format(error, len, "Plugin %d not found,", id); return false; } CPluginManager::CPlugin *CPluginManager::FindByAPI(ISmmPlugin *api) { PluginIter i; //don't find bad plugins! if (!api) { return NULL; } for (i=m_Plugins.begin(); i!=m_Plugins.end(); i++) { if ((*i)->m_API == api) { return (*i); } } return NULL; } int CPluginManager::GetPluginCount() { return (int)m_Plugins.size(); } const char *CPluginManager::GetStatusText(CPlugin *pl) { switch (pl->m_Status) { case Pl_NotFound: return "NOFILE"; case Pl_Error: return "ERROR"; case Pl_Refused: return "FAILED"; case Pl_Paused: return "PAUSED"; case Pl_Running: { if (pl->m_API && pl->m_API->QueryRunning(NULL, 0)) { return "STOPPED"; } else { return "RUNNING"; } } default: return "-"; } } struct Unloader : public SourceHook::Impl::UnloadListener { CPluginManager::CPlugin *plugin_; bool destroy_; Unloader(CPluginManager::CPlugin *plugin, bool destroy) : plugin_(plugin), destroy_(destroy) { } void ReadyToUnload(SourceHook::Plugin plug) { if (plugin_->m_UnloadFn != NULL) plugin_->m_UnloadFn(); dlclose(plugin_->m_Lib); if (destroy_) { delete plugin_; } else { plugin_->m_Lib = NULL; plugin_->m_API = NULL; } delete this; } }; CPluginManager::CPlugin *CPluginManager::_Load(const char *file, PluginId source, char *error, size_t maxlen) { FILE *fp; CPlugin *pl; pl = new CPlugin(); *error = '\0'; //Add plugin to list pl->m_Id = m_LastId; pl->m_File.assign(file); m_Plugins.push_back(pl); m_LastId++; //Check if the file even exists fp = fopen(file, "r"); if (!fp) { if (error) { UTIL_Format(error, maxlen, "File not found: %s", file); } pl->m_Status = Pl_NotFound; } if (fp) { fclose(fp); fp = NULL; //Load the file pl->m_Lib = dlmount(file); if (!pl->m_Lib) { if (error) { UTIL_Format(error, maxlen, "%s", dlerror()); } pl->m_Status = Pl_Error; } else { pl->m_API = NULL; /** * First, try the new "advanced" interface */ METAMOD_FN_LOAD fnLoad = (METAMOD_FN_LOAD)dlsym(pl->m_Lib, "CreateInterface_MMS"); if (fnLoad != NULL) { if (GlobVersionInfo.source_engine == SOURCE_ENGINE_UNKNOWN) { GlobVersionInfo.source_engine = g_Metamod.GetSourceEngineBuild(); } if (GlobVersionInfo.game_dir == NULL) { GlobVersionInfo.game_dir = strrchr(g_Metamod.GetBaseDir(), PATH_SEP_CHAR) + 1; } /* Build path information */ char file_path[256]; size_t len = g_Metamod.PathFormat(file_path, sizeof(file_path), "%s", file); for (size_t i = len - 1; i < len; i--) { if (_IsPathSepChar(file_path[i])) { file_path[i] = '\0'; break; } } MetamodLoaderInfo info; info.pl_file = file; info.pl_path = file_path; pl->m_API = fnLoad(&GlobVersionInfo, &info); #if SOURCE_ENGINE == SE_CSS || SOURCE_ENGINE == SE_HL2DM || SOURCE_ENGINE == SE_DODS || SOURCE_ENGINE == SE_TF2 /* For plugin compat - try loading again using original OB if OB-Valve has failed */ if (pl->m_API == NULL) { GlobVersionInfo.source_engine = SOURCE_ENGINE_ORANGEBOXVALVE_DEPRECATED; pl->m_API = fnLoad(&GlobVersionInfo, &info); if (pl->m_API == NULL) { GlobVersionInfo.source_engine = SOURCE_ENGINE_ORANGEBOX; pl->m_API = fnLoad(&GlobVersionInfo, &info); } GlobVersionInfo.source_engine = g_Metamod.GetSourceEngineBuild(); } #endif pl->m_UnloadFn = (METAMOD_FN_UNLOAD)dlsym(pl->m_Lib, "UnloadInterface_MMS"); } /** * If we didn't get anything, try the normal/simple interface. */ if (pl->m_API == NULL) { CreateInterfaceFn pfn = (CreateInterfaceFn)(dlsym(pl->m_Lib, PL_EXPOSURE_C)); if (!pfn) { if (error) { UTIL_Format(error, maxlen, "Function %s not found", PL_EXPOSURE_C); } pl->m_Status = Pl_Error; } else { pl->m_API = static_cast((pfn)(METAMOD_PLAPI_NAME, NULL)); if (!pl->m_API) { if (error) { UTIL_Format(error, maxlen, "Failed to get API"); } pl->m_Status = Pl_Error; } } } if (pl->m_API != NULL) { int api = pl->m_API->GetApiVersion(); if (api < PLAPI_MIN_VERSION) { if (error) { if (api == 13) { UTIL_Format(error, maxlen, "Plugin uses experimental Metamod build, probably 1.6.x (%d < %d)", api, PLAPI_MIN_VERSION); } else if (api <= 12 && api >= 7) { UTIL_Format(error, maxlen, "Older Metamod version required, probably 1.4.x (%d < %d)", api, PLAPI_MIN_VERSION); } else { UTIL_Format(error, maxlen, "Older Metamod version required, probably 1.0 (%d < %d)", api, PLAPI_MIN_VERSION); } } pl->m_Status = Pl_Error; } else if (api > METAMOD_PLAPI_VERSION) { if (error) { UTIL_Format(error, maxlen, "Plugin requires newer Metamod version (%d > %d)", api, METAMOD_PLAPI_VERSION); } pl->m_Status = Pl_Error; } else { if (pl->m_API->Load(pl->m_Id, &g_Metamod, error, maxlen, m_AllLoaded)) { pl->m_Status = Pl_Running; if (m_AllLoaded) { pl->m_API->AllPluginsLoaded(); } } else { pl->m_Status = Pl_Refused; } } } } } if (pl->m_Lib && (pl->m_Status < Pl_Paused)) { pl->m_Events.clear(); UnregAllConCmds(pl); g_SourceHook.UnloadPlugin(pl->m_Id, new Unloader(pl, false)); } return pl; } bool CPluginManager::_Unload(CPluginManager::CPlugin *pl, bool force, char *error, size_t maxlen) { if (error) { *error = '\0'; } if (pl->m_API && pl->m_Lib) { //Note, we'll always tell the plugin it will be unloading... if (pl->m_API->Unload(error, maxlen) || force) { pl->m_Events.clear(); UnregAllConCmds(pl); //Remove the plugin from the list PluginIter i; for (i=m_Plugins.begin(); i!=m_Plugins.end(); i++) { if ( (*i)->m_Id == pl->m_Id ) { i = m_Plugins.erase(i); break; } } //Make sure to detach it from sourcehook! g_SourceHook.UnloadPlugin(pl->m_Id, new Unloader(pl, true)); return true; } } else { //The plugin is not valid, and let's just remove it from the list anyway PluginIter i; for (i=m_Plugins.begin(); i!=m_Plugins.end(); i++) { if ( (*i)->m_Id == pl->m_Id ) { i = m_Plugins.erase(i); break; } } delete pl; return true; } return false; } bool CPluginManager::_Pause(CPluginManager::CPlugin *pl, char *error, size_t maxlen) { if (error) { *error = '\0'; } if (pl->m_Status != Pl_Running || !pl->m_API) { if (error) { UTIL_Format(error, maxlen, "Plugin cannot be paused"); } } else { if (pl->m_API->Pause(error, maxlen)) { g_SourceHook.PausePlugin(pl->m_Id); pl->m_Status = Pl_Paused; return true; } } return false; } bool CPluginManager::_Unpause(CPluginManager::CPlugin *pl, char *error, size_t maxlen) { if (error) { *error = '\0'; } if (pl->m_Status != Pl_Paused || !pl->m_API) { if (error) { UTIL_Format(error, maxlen, "Plugin cannot be unpaused"); } } else { if (pl->m_API->Unpause(error, maxlen)) { g_SourceHook.UnpausePlugin(pl->m_Id); pl->m_Status = Pl_Running; return true; } } return false; } bool CPluginManager::UnloadAll() { PluginIter i; char error[128]; bool status = true; while ((i = m_Plugins.begin()) != m_Plugins.end()) { if ( !_Unload( (*i), true, error, sizeof(error)) ) { status = false; } } return status; } bool CPluginManager::Query(PluginId id, const char **file, Pl_Status *status, PluginId *source) { CPlugin *pl = FindById(id); if (!pl) { return false; } if (file != NULL) { *file = pl->m_File.c_str(); } if (status != NULL) { *status = pl->m_Status; } if (source != NULL) { *source = pl->m_Source; } return true; } bool CPluginManager::QueryRunning(PluginId id, char *error, size_t maxlength) { CPlugin *pl = FindById(id); if (!pl || !pl->m_API) { if (error) { UTIL_Format(error, maxlength, "Plugin not valid"); } return false; } return pl->m_API->QueryRunning(error, maxlength); } bool CPluginManager::QueryHandle(PluginId id, void **handle) { CPlugin *pl = FindById(id); if (!pl) { return false; } if (handle) { *handle = static_cast(pl->m_Lib); } return true; } PluginIter CPluginManager::_begin() { return m_Plugins.begin(); } PluginIter CPluginManager::_end() { return m_Plugins.end(); } void CPluginManager::AddPluginCvar(ISmmPlugin *api, ConCommandBase *pCvar) { CPlugin *pl = FindByAPI(api); if (!pl) { return; } pl->m_Cvars.push_back(pCvar); } void CPluginManager::AddPluginCmd(ISmmPlugin *api, ConCommandBase *pCmd) { CPlugin *pl = FindByAPI(api); if (!pl) { return; } pl->m_Cmds.push_back(pCmd); } void CPluginManager::RemovePluginCvar(ISmmPlugin *api, ConCommandBase *pCvar) { CPlugin *pl = FindByAPI(api); if (!pl) { return; } pl->m_Cvars.remove(pCvar); } void CPluginManager::RemovePluginCmd(ISmmPlugin *api, ConCommandBase *pCmd) { CPlugin *pl = FindByAPI(api); if (!pl) { return; } pl->m_Cmds.remove(pCmd); } void CPluginManager::UnregAllConCmds(CPlugin *pl) { SourceHook::List::iterator i; /* :TODO: */ for (i=pl->m_Cvars.begin(); i!=pl->m_Cvars.end(); i++) { g_Metamod.UnregisterConCommandBase(pl->m_Id, (*i) ); } pl->m_Cvars.clear(); for (i=pl->m_Cmds.begin(); i!=pl->m_Cmds.end(); i++) { g_Metamod.UnregisterConCommandBase(pl->m_Id, (*i) ); } pl->m_Cmds.clear(); }