/** * vim: set ts=4 : * =============================================================== * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. * =============================================================== * * This file is not open source and may not be copied without explicit * written permission of AlliedModders LLC. This file may not be redistributed * in whole or significant part. * For information, see LICENSE.txt or http://www.sourcemod.net/license.php * * Version: $Id$ */ #include #include "ExtensionSys.h" #include "LibrarySys.h" #include "ShareSys.h" #include "Logger.h" #include "sourcemm_api.h" #include "PluginSys.h" #include "sm_srvcmds.h" #include "sm_stringutil.h" CExtensionManager g_Extensions; IdentityType_t g_ExtType; CExtension::CExtension(const char *filename, char *error, size_t maxlength) { m_File.assign(filename); m_pAPI = NULL; m_pIdentToken = NULL; m_PlId = 0; unload_code = 0; m_FullyLoaded = false; char path[PLATFORM_MAX_PATH]; g_SourceMod.BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "extensions/%s", filename); m_pLib = g_LibSys.OpenLibrary(path, error, maxlength); if (m_pLib == NULL) { return; } typedef IExtensionInterface *(*GETAPI)(); GETAPI pfnGetAPI = NULL; if ((pfnGetAPI=(GETAPI)m_pLib->GetSymbolAddress("GetSMExtAPI")) == NULL) { m_pLib->CloseLibrary(); m_pLib = NULL; snprintf(error, maxlength, "Unable to find extension entry point"); return; } m_pAPI = pfnGetAPI(); if (!m_pAPI || m_pAPI->GetExtensionVersion() > SMINTERFACE_EXTENSIONAPI_VERSION) { m_pLib->CloseLibrary(); m_pLib = NULL; snprintf(error, maxlength, "Extension version is too new to load (%d, max is %d)", m_pAPI->GetExtensionVersion(), SMINTERFACE_EXTENSIONAPI_VERSION); return; } if (m_pAPI->IsMetamodExtension()) { bool already; m_PlId = g_pMMPlugins->Load(path, g_PLID, already, error, maxlength); } m_pIdentToken = g_ShareSys.CreateIdentity(g_ExtType); if (!m_pAPI->OnExtensionLoad(this, &g_ShareSys, error, maxlength, !g_SourceMod.IsMapLoading())) { if (m_pAPI->IsMetamodExtension()) { if (m_PlId) { char dummy[255]; g_pMMPlugins->Unload(m_PlId, true, dummy, sizeof(dummy)); m_PlId = 0; } } m_pAPI = NULL; m_pLib->CloseLibrary(); m_pLib = NULL; g_ShareSys.DestroyIdentity(m_pIdentToken); m_pIdentToken = NULL; return; } else { /* Check if we're past load time */ if (!g_SourceMod.IsMapLoading()) { m_pAPI->OnExtensionsAllLoaded(); } } } CExtension::~CExtension() { if (m_pAPI) { m_pAPI->OnExtensionUnload(); if (m_PlId) { g_pMMPlugins->Unload(m_PlId, true, NULL, 0); } } if (m_pIdentToken) { g_ShareSys.DestroyIdentity(m_pIdentToken); } if (m_pLib) { m_pLib->CloseLibrary(); } } void CExtension::MarkAllLoaded() { assert(m_FullyLoaded == false); if (!m_FullyLoaded) { m_FullyLoaded = true; m_pAPI->OnExtensionsAllLoaded(); } } void CExtension::AddPlugin(IPlugin *pPlugin) { m_Plugins.push_back(pPlugin); } void CExtension::RemovePlugin(IPlugin *pPlugin) { m_Plugins.remove(pPlugin); } void CExtension::SetError(const char *error) { m_Error.assign(error); } IExtensionInterface *CExtension::GetAPI() { return m_pAPI; } const char *CExtension::GetFilename() { return m_File.c_str(); } IdentityToken_t *CExtension::GetIdentity() { return m_pIdentToken; } bool CExtension::IsLoaded() { return (m_pLib != NULL); } void CExtension::AddDependency(IfaceInfo *pInfo) { m_Deps.push_back(*pInfo); } ITERATOR *CExtension::FindFirstDependency(IExtension **pOwner, SMInterface **pInterface) { List::iterator iter = m_Deps.begin(); if (iter == m_Deps.end()) { return NULL; } if (pOwner) { *pOwner = (*iter).owner; } if (pInterface) { *pInterface = (*iter).iface; } List::iterator *pIter = new List::iterator(iter); return (ITERATOR *)pIter; } bool CExtension::FindNextDependency(ITERATOR *iter, IExtension **pOwner, SMInterface **pInterface) { List::iterator *pIter = (List::iterator *)iter; List::iterator _iter; if (_iter == m_Deps.end()) { return false; } _iter++; if (pOwner) { *pOwner = (*_iter).owner; } if (pInterface) { *pInterface = (*_iter).iface; } *pIter = _iter; if (_iter == m_Deps.end()) { return false; } return true; } void CExtension::FreeDependencyIterator(ITERATOR *iter) { List::iterator *pIter = (List::iterator *)iter; delete pIter; } void CExtension::AddInterface(SMInterface *pInterface) { m_Interfaces.push_back(pInterface); } bool CExtension::IsRunning(char *error, size_t maxlength) { if (!IsLoaded()) { if (error) { snprintf(error, maxlength, "%s", m_Error.c_str()); } return false; } return m_pAPI->QueryRunning(error, maxlength); } /********************* * EXTENSION MANAGER * *********************/ void CExtensionManager::OnSourceModAllInitialized() { g_ExtType = g_ShareSys.CreateIdentType("EXTENSION"); g_PluginSys.AddPluginsListener(this); g_RootMenu.AddRootConsoleCommand("exts", "Manage extensions", this); g_ShareSys.AddInterface(NULL, this); } void CExtensionManager::OnSourceModShutdown() { g_RootMenu.RemoveRootConsoleCommand("exts", this); g_ShareSys.DestroyIdentType(g_ExtType); } void CExtensionManager::TryAutoload() { char path[PLATFORM_MAX_PATH]; g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "extensions"); IDirectory *pDir = g_LibSys.OpenDirectory(path); if (!pDir) { return; } const char *lfile; size_t len; while (pDir->MoreFiles()) { if (pDir->IsEntryDirectory()) { pDir->NextEntry(); continue; } lfile = pDir->GetEntryName(); len = strlen(lfile); if (len <= 9) /* size of ".autoload" */ { pDir->NextEntry(); continue; } if (strcmp(&lfile[len - 9], ".autoload") != 0) { pDir->NextEntry(); continue; } char file[PLATFORM_MAX_PATH]; len = UTIL_Format(file, sizeof(file), "%s", lfile); strcpy(&file[len - 9], ".ext." PLATFORM_LIB_EXT); LoadAutoExtension(file); pDir->NextEntry(); } } IExtension *CExtensionManager::LoadAutoExtension(const char *path) { if (!strstr(path, "." PLATFORM_LIB_EXT)) { char newpath[PLATFORM_MAX_PATH]; snprintf(newpath, PLATFORM_MAX_PATH, "%s.%s", path, PLATFORM_LIB_EXT); return LoadAutoExtension(newpath); } IExtension *pAlready; if ((pAlready=FindExtensionByFile(path)) != NULL) { return pAlready; } char error[256]; CExtension *p = new CExtension(path, error, sizeof(error)); if (!p->IsLoaded()) { g_Logger.LogError("[SM] Unable to load extension \"%s\": %s", path, error); p->SetError(error); } m_Libs.push_back(p); return p; } IExtension *CExtensionManager::FindExtensionByFile(const char *file) { List::iterator iter; CExtension *pExt; if (!strstr(file, "." PLATFORM_LIB_EXT)) { char path[PLATFORM_MAX_PATH]; snprintf(path, PLATFORM_MAX_PATH, "%s.%s", file, PLATFORM_LIB_EXT); return FindExtensionByFile(path); } /* Make sure the file direction is right */ char path[PLATFORM_MAX_PATH]; g_LibSys.PathFormat(path, PLATFORM_MAX_PATH, "%s", file); for (iter=m_Libs.begin(); iter!=m_Libs.end(); iter++) { pExt = (*iter); if (strcmp(pExt->GetFilename(), path) == 0) { return pExt; } } return NULL; } IExtension *CExtensionManager::FindExtensionByName(const char *ext) { List::iterator iter; CExtension *pExt; IExtensionInterface *pAPI; const char *name; for (iter=m_Libs.begin(); iter!=m_Libs.end(); iter++) { pExt = (*iter); if (!pExt->IsLoaded()) { continue; } if ((pAPI = pExt->GetAPI()) == NULL) { continue; } name = pAPI->GetExtensionName(); if (!name) { continue; } if (strcmp(name, ext) == 0) { return pExt; } } return NULL; } IExtension *CExtensionManager::LoadExtension(const char *file, ExtensionLifetime lifetime, char *error, size_t maxlength) { IExtension *pAlready; if ((pAlready=FindExtensionByFile(file)) != NULL) { return pAlready; } CExtension *pExt = new CExtension(file, error, maxlength); /* :NOTE: lifetime is currently ignored */ if (!pExt->IsLoaded()) { delete pExt; return NULL; } /* :TODO: do QueryRunning check if the map is loaded */ m_Libs.push_back(pExt); return pExt; } void CExtensionManager::BindDependency(IExtension *pOwner, IfaceInfo *pInfo) { CExtension *pExt = (CExtension *)pOwner; pExt->AddDependency(pInfo); } void CExtensionManager::AddInterface(IExtension *pOwner, SMInterface *pInterface) { CExtension *pExt = (CExtension *)pOwner; pExt->AddInterface(pInterface); } void CExtensionManager::BindChildPlugin(IExtension *pParent, IPlugin *pPlugin) { CExtension *pExt = (CExtension *)pParent; pExt->AddPlugin(pPlugin); } void CExtensionManager::OnPluginDestroyed(IPlugin *plugin) { List::iterator iter; for (iter=m_Libs.begin(); iter!=m_Libs.end(); iter++) { (*iter)->RemovePlugin(plugin); } } CExtension *CExtensionManager::FindByOrder(unsigned int num) { if (num < 1 || num > m_Libs.size()) { return NULL; } List::iterator iter = m_Libs.begin(); while (iter != m_Libs.end()) { if (--num == 0) { return (*iter); } iter++; } return NULL; } void CExtensionManager::BindAllNativesToPlugin(IPlugin *pPlugin) { List::iterator iter; CExtension *pExt; IPluginContext *pContext = pPlugin->GetBaseContext(); for (iter=m_Libs.begin(); iter!=m_Libs.end(); iter++) { pExt = (*iter); if (!pExt->IsLoaded()) { continue; } if (!pExt->m_Natives.size()) { continue; } bool set = false; List::iterator n_iter; const sp_nativeinfo_t *natives; for (n_iter = pExt->m_Natives.begin(); n_iter != pExt->m_Natives.end(); n_iter++) { natives = (*n_iter); unsigned int i=0; while (natives[i].func != NULL && natives[i].name != NULL) { if (pContext->BindNative(&natives[i++]) == SP_ERROR_NONE) { set = true; } } } if (set && (pExt->m_Plugins.find(pPlugin) == pExt->m_Plugins.end())) { /* For now, mark this plugin as a requirement. Later we'll introduce native filtering. */ pExt->m_Plugins.push_back(pPlugin); } } } bool CExtensionManager::UnloadExtension(IExtension *_pExt) { if (!_pExt) { return false; } CExtension *pExt = (CExtension *)_pExt; if (m_Libs.find(pExt) == m_Libs.end()) { return false; } /* First remove us from internal lists */ g_ShareSys.RemoveInterfaces(_pExt); m_Libs.remove(pExt); List UnloadQueue; /* Handle dependencies */ if (pExt->IsLoaded()) { /* Unload any dependent plugins */ List::iterator p_iter = pExt->m_Plugins.begin(); while (p_iter != pExt->m_Plugins.end()) { /* We have to manually unlink ourselves here, since we're no longer being managed */ g_PluginSys.UnloadPlugin((*p_iter)); p_iter = pExt->m_Plugins.erase(p_iter); } /* Notify and/or unload all dependencies */ List::iterator c_iter; CExtension *pDep; IExtensionInterface *pAPI; for (c_iter = m_Libs.begin(); c_iter != m_Libs.end(); c_iter++) { pDep = (*c_iter); if ((pAPI=pDep->GetAPI()) == NULL) { continue; } if (pDep == pExt) { continue; } /* Now, get its dependency list */ bool dropped = false; List::iterator i_iter = pDep->m_Deps.begin(); while (i_iter != pDep->m_Deps.end()) { if ((*i_iter).owner == _pExt) { if (!dropped && !pAPI->QueryInterfaceDrop((*i_iter).iface)) { dropped = true; } pAPI->NotifyInterfaceDrop((*i_iter).iface); i_iter = pDep->m_Deps.erase(i_iter); } else { i_iter++; } } } } List::iterator iter; for (iter=UnloadQueue.begin(); iter!=UnloadQueue.end(); iter++) { /* NOTE: This is safe because the unload function backs out of anything not present */ UnloadExtension((*iter)); } return true; } void CExtensionManager::AddNatives(IExtension *pOwner, const sp_nativeinfo_t *natives) { CExtension *pExt = (CExtension *)pOwner; pExt->m_Natives.push_back(natives); } void CExtensionManager::MarkAllLoaded() { List::iterator iter; CExtension *pExt; for (iter=m_Libs.begin(); iter!=m_Libs.end(); iter++) { pExt = (*iter); if (!pExt->IsLoaded()) { continue; } if (pExt->m_FullyLoaded) { continue; } pExt->MarkAllLoaded(); } } void CExtensionManager::AddDependency(IExtension *pSource, const char *file, bool required, bool autoload) { /* :TODO: implement */ return; } void CExtensionManager::OnRootConsoleCommand(const char *cmd, unsigned int argcount) { if (argcount >= 3) { const char *cmd = g_RootMenu.GetArgument(2); if (strcmp(cmd, "list") == 0) { List::iterator iter; CExtension *pExt; unsigned int num = 1; switch (m_Libs.size()) { case 1: { g_RootMenu.ConsolePrint("[SM] Displaying 1 extension:"); break; } case 0: { g_RootMenu.ConsolePrint("[SM] No extensions are loaded."); break; } default: { g_RootMenu.ConsolePrint("[SM] Displaying %d extensions:", m_Libs.size()); break; } } for (iter=m_Libs.begin(); iter!=m_Libs.end(); iter++,num++) { pExt = (*iter); if (pExt->IsLoaded()) { char error[255]; if (!pExt->IsRunning(error, sizeof(error))) { g_RootMenu.ConsolePrint("[%02d] file \"%s\": %s", num, pExt->GetFilename(), error); } else { IExtensionInterface *pAPI = pExt->GetAPI(); const char *name = pAPI->GetExtensionName(); const char *version = pAPI->GetExtensionVerString(); const char *descr = pAPI->GetExtensionDescription(); g_RootMenu.ConsolePrint("[%02d] %s (%s): %s", num, name, version, descr); } } else { g_RootMenu.ConsolePrint("[%02d] file \"%s\": %s", num, pExt->GetFilename(), pExt->m_Error.c_str()); } } return; } else if (strcmp(cmd, "info") == 0) { if (argcount < 4) { g_RootMenu.ConsolePrint("[SM] Usage: sm info <#>"); return; } const char *sId = g_RootMenu.GetArgument(3); unsigned int id = atoi(sId); if (id <= 0) { g_RootMenu.ConsolePrint("[SM] Usage: sm info <#>"); return; } if (m_Libs.size() == 0) { g_RootMenu.ConsolePrint("[SM] No extensions are loaded."); return; } if (id > m_Libs.size()) { g_RootMenu.ConsolePrint("[SM] No extension was found with id %d.", id); return; } List::iterator iter = m_Libs.begin(); CExtension *pExt = NULL; while (iter != m_Libs.end()) { if (--id == 0) { pExt = (*iter); break; } iter++; } /* This should never happen */ if (!pExt) { g_RootMenu.ConsolePrint("[SM] No extension was found with id %d.", id); return; } if (!pExt->IsLoaded()) { g_RootMenu.ConsolePrint(" File: %s", pExt->GetFilename()); g_RootMenu.ConsolePrint(" Loaded: No (%s)", pExt->m_Error.c_str()); } else { char error[255]; if (!pExt->IsRunning(error, sizeof(error))) { g_RootMenu.ConsolePrint(" File: %s", pExt->GetFilename()); g_RootMenu.ConsolePrint(" Loaded: Yes"); g_RootMenu.ConsolePrint(" Running: No (%s)", error); } else { IExtensionInterface *pAPI = pExt->GetAPI(); g_RootMenu.ConsolePrint(" File: %s", pExt->GetFilename()); g_RootMenu.ConsolePrint(" Loaded: Yes (version %s)", pAPI->GetExtensionVerString()); g_RootMenu.ConsolePrint(" Name: %s (%s)", pAPI->GetExtensionName(), pAPI->GetExtensionDescription()); g_RootMenu.ConsolePrint(" Author: %s (%s)", pAPI->GetExtensionAuthor(), pAPI->GetExtensionURL()); g_RootMenu.ConsolePrint(" Binary info: API version %d (compiled %s)", pAPI->GetExtensionVersion(), pAPI->GetExtensionDateString()); g_RootMenu.ConsolePrint(" Metamod enabled: %s", pAPI->IsMetamodExtension() ? "yes" : "no"); } } return; } else if (strcmp(cmd, "unload") == 0) { if (argcount < 4) { g_RootMenu.ConsolePrint("[SM] Usage: sm unload <#> [code]"); return; } const char *arg = g_RootMenu.GetArgument(3); unsigned int num = atoi(arg); CExtension *pExt = FindByOrder(num); if (!pExt) { g_RootMenu.ConsolePrint("[SM] Extension number %d was not found.", num); return; } if (argcount > 4 && pExt->unload_code) { const char *unload = g_RootMenu.GetArgument(4); if (pExt->unload_code == (unsigned)atoi(unload)) { char filename[PLATFORM_MAX_PATH]; snprintf(filename, PLATFORM_MAX_PATH, "%s", pExt->GetFilename()); UnloadExtension(pExt); g_RootMenu.ConsolePrint("[SM] Extension %s is now unloaded.", filename); } else { g_RootMenu.ConsolePrint("[SM] Please try again, the correct unload code is \"%d\"", pExt->unload_code); } return; } if (!pExt->IsLoaded() || (!pExt->m_Deps.size() && !pExt->m_Plugins.size())) { char filename[PLATFORM_MAX_PATH]; snprintf(filename, PLATFORM_MAX_PATH, "%s", pExt->GetFilename()); UnloadExtension(pExt); g_RootMenu.ConsolePrint("[SM] Extension %s is now unloaded.", filename); return; } else { List plugins; if (pExt->m_Deps.size()) { g_RootMenu.ConsolePrint("[SM] Unloading %s will unload the following extensions: ", pExt->GetFilename()); List::iterator iter; CExtension *pOther; /* Get list of all extensions */ for (iter=m_Libs.begin(); iter!=m_Libs.end(); iter++) { List::iterator i_iter; pOther = (*iter); if (!pOther->IsLoaded() || pOther == pExt) { continue; } /* Get their dependencies */ for (i_iter=pOther->m_Deps.begin(); i_iter!=pOther->m_Deps.end(); i_iter++) { /* Is this dependency to us? */ if ((*i_iter).owner != pExt) { continue; } /* Will our dependent care? */ if (!pExt->GetAPI()->QueryInterfaceDrop((*i_iter).iface)) { g_RootMenu.ConsolePrint(" -> %s", pExt->GetFilename()); /* Add to plugin unload list */ List::iterator p_iter; for (p_iter=pOther->m_Plugins.begin(); p_iter!=pOther->m_Plugins.end(); p_iter++) { if (plugins.find((*p_iter)) == plugins.end()) { plugins.push_back((*p_iter)); } } } } } } if (pExt->m_Plugins.size()) { g_RootMenu.ConsolePrint("[SM] Unloading %s will unload the following plugins: ", pExt->GetFilename()); List::iterator iter; IPlugin *pPlugin; for (iter = pExt->m_Plugins.begin(); iter != pExt->m_Plugins.end(); iter++) { pPlugin = (*iter); if (plugins.find(pPlugin) == plugins.end()) { plugins.push_back(pPlugin); } } for (iter = plugins.begin(); iter != plugins.end(); iter++) { pPlugin = (*iter); g_RootMenu.ConsolePrint(" -> %s", pPlugin->GetFilename()); } } srand(static_cast(time(NULL))); pExt->unload_code = (rand() % 877) + 123; //123 to 999 g_RootMenu.ConsolePrint("[SM] To verify unloading %s, please use the following: ", pExt->GetFilename()); g_RootMenu.ConsolePrint("[SM] sm exts unload %d %d", num, pExt->unload_code); return; } } } g_RootMenu.ConsolePrint("SourceMod Extensions Menu:"); g_RootMenu.DrawGenericOption("info", "Extra extension information"); g_RootMenu.DrawGenericOption("list", "List extensions"); g_RootMenu.DrawGenericOption("unload", "Unload an extension"); }