2 * ===============================================================
3 * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
4 * ===============================================================
6 * This file is not open source and may not be copied without explicit
7 * written permission of AlliedModders LLC. This file may not be redistributed
8 * in whole or significant part.
9 * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
14 #include "ExtensionSys.h"
15 #include "LibrarySys.h"
18 #include "sourcemm_api.h"
19 #include "PluginSys.h"
20 #include "sm_srvcmds.h"
23 CExtensionManager g_Extensions;
24 IdentityType_t g_ExtType;
26 CExtension::CExtension(const char *filename, char *error, size_t err_max)
28 m_File.assign(filename);
33 m_FullyLoaded = false;
35 char path[PLATFORM_MAX_PATH+1];
36 g_SourceMod.BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "extensions/%s", filename);
38 m_pLib = g_LibSys.OpenLibrary(path, error, err_max);
45 typedef IExtensionInterface *(*GETAPI)();
46 GETAPI pfnGetAPI = NULL;
48 if ((pfnGetAPI=(GETAPI)m_pLib->GetSymbolAddress("GetSMExtAPI")) == NULL)
50 m_pLib->CloseLibrary();
52 snprintf(error, err_max, "Unable to find extension entry point");
57 if (!m_pAPI || m_pAPI->GetExtensionVersion() > SMINTERFACE_EXTENSIONAPI_VERSION)
59 m_pLib->CloseLibrary();
61 snprintf(error, err_max, "Extension version is too new to load (%d, max is %d)", m_pAPI->GetExtensionVersion(), SMINTERFACE_EXTENSIONAPI_VERSION);
65 if (m_pAPI->IsMetamodExtension())
68 m_PlId = g_pMMPlugins->Load(path, g_PLID, already, error, err_max);
71 m_pIdentToken = g_ShareSys.CreateIdentity(g_ExtType);
73 if (!m_pAPI->OnExtensionLoad(this, &g_ShareSys, error, err_max, !g_SourceMod.IsMapLoading()))
75 if (m_pAPI->IsMetamodExtension())
80 g_pMMPlugins->Unload(m_PlId, true, dummy, sizeof(dummy));
85 m_pLib->CloseLibrary();
87 g_ShareSys.DestroyIdentity(m_pIdentToken);
91 /* Check if we're past load time */
92 if (!g_SourceMod.IsMapLoading())
94 m_pAPI->OnExtensionsAllLoaded();
99 CExtension::~CExtension()
103 m_pAPI->OnExtensionUnload();
106 g_pMMPlugins->Unload(m_PlId, true, NULL, 0);
112 g_ShareSys.DestroyIdentity(m_pIdentToken);
117 m_pLib->CloseLibrary();
121 void CExtension::MarkAllLoaded()
123 assert(m_FullyLoaded == false);
126 m_FullyLoaded = true;
127 m_pAPI->OnExtensionsAllLoaded();
131 void CExtension::AddPlugin(IPlugin *pPlugin)
133 m_Plugins.push_back(pPlugin);
136 void CExtension::RemovePlugin(IPlugin *pPlugin)
138 m_Plugins.remove(pPlugin);
141 void CExtension::SetError(const char *error)
143 m_Error.assign(error);
146 IExtensionInterface *CExtension::GetAPI()
151 const char *CExtension::GetFilename()
153 return m_File.c_str();
156 IdentityToken_t *CExtension::GetIdentity()
158 return m_pIdentToken;
161 bool CExtension::IsLoaded()
163 return (m_pLib != NULL);
166 void CExtension::AddDependency(IfaceInfo *pInfo)
168 m_Deps.push_back(*pInfo);
171 ITERATOR *CExtension::FindFirstDependency(IExtension **pOwner, SMInterface **pInterface)
173 List<IfaceInfo>::iterator iter = m_Deps.begin();
175 if (iter == m_Deps.end())
182 *pOwner = (*iter).owner;
186 *pInterface = (*iter).iface;
189 List<IfaceInfo>::iterator *pIter = new List<IfaceInfo>::iterator(iter);
191 return (ITERATOR *)pIter;
194 bool CExtension::FindNextDependency(ITERATOR *iter, IExtension **pOwner, SMInterface **pInterface)
196 List<IfaceInfo>::iterator *pIter = (List<IfaceInfo>::iterator *)iter;
197 List<IfaceInfo>::iterator _iter;
199 if (_iter == m_Deps.end())
208 *pOwner = (*_iter).owner;
212 *pInterface = (*_iter).iface;
217 if (_iter == m_Deps.end())
225 void CExtension::FreeDependencyIterator(ITERATOR *iter)
227 List<IfaceInfo>::iterator *pIter = (List<IfaceInfo>::iterator *)iter;
232 void CExtension::AddInterface(SMInterface *pInterface)
234 m_Interfaces.push_back(pInterface);
237 bool CExtension::IsRunning(char *error, size_t maxlength)
243 snprintf(error, maxlength, "%s", m_Error.c_str());
248 return m_pAPI->QueryRunning(error, maxlength);
251 /*********************
252 * EXTENSION MANAGER *
253 *********************/
255 void CExtensionManager::OnSourceModAllInitialized()
257 g_ExtType = g_ShareSys.CreateIdentType("EXTENSION");
258 g_PluginSys.AddPluginsListener(this);
259 g_RootMenu.AddRootConsoleCommand("exts", "Manage extensions", this);
260 g_ShareSys.AddInterface(NULL, this);
263 void CExtensionManager::OnSourceModShutdown()
265 g_RootMenu.RemoveRootConsoleCommand("exts", this);
266 g_PluginSys.RemovePluginsListener(this);
267 g_ShareSys.DestroyIdentType(g_ExtType);
270 IExtension *CExtensionManager::LoadAutoExtension(const char *path)
272 if (!strstr(path, "." PLATFORM_LIB_EXT))
274 char newpath[PLATFORM_MAX_PATH+1];
275 snprintf(newpath, PLATFORM_MAX_PATH, "%s.%s", path, PLATFORM_LIB_EXT);
276 return LoadAutoExtension(newpath);
279 IExtension *pAlready;
280 if ((pAlready=FindExtensionByFile(path)) != NULL)
286 CExtension *p = new CExtension(path, error, sizeof(error));
290 g_Logger.LogError("[SM] Unable to load extension \"%s\": %s", path, error);
299 IExtension *CExtensionManager::FindExtensionByFile(const char *file)
301 List<CExtension *>::iterator iter;
304 if (!strstr(file, "." PLATFORM_LIB_EXT))
306 char path[PLATFORM_MAX_PATH];
307 snprintf(path, PLATFORM_MAX_PATH, "%s.%s", file, PLATFORM_LIB_EXT);
308 return FindExtensionByFile(path);
311 /* Make sure the file direction is right */
312 char path[PLATFORM_MAX_PATH+1];
313 g_LibSys.PathFormat(path, PLATFORM_MAX_PATH, "%s", file);
315 for (iter=m_Libs.begin(); iter!=m_Libs.end(); iter++)
318 if (strcmp(pExt->GetFilename(), path) == 0)
327 IExtension *CExtensionManager::FindExtensionByName(const char *ext)
329 List<CExtension *>::iterator iter;
331 IExtensionInterface *pAPI;
334 for (iter=m_Libs.begin(); iter!=m_Libs.end(); iter++)
337 if (!pExt->IsLoaded())
341 if ((pAPI = pExt->GetAPI()) == NULL)
345 name = pAPI->GetExtensionName();
350 if (strcmp(name, ext) == 0)
359 IExtension *CExtensionManager::LoadExtension(const char *file, ExtensionLifetime lifetime, char *error, size_t err_max)
361 IExtension *pAlready;
362 if ((pAlready=FindExtensionByFile(file)) != NULL)
367 CExtension *pExt = new CExtension(file, error, err_max);
369 /* :NOTE: lifetime is currently ignored */
371 if (!pExt->IsLoaded())
377 /* :TODO: do QueryRunning check if the map is loaded */
379 m_Libs.push_back(pExt);
384 void CExtensionManager::BindDependency(IExtension *pOwner, IfaceInfo *pInfo)
386 CExtension *pExt = (CExtension *)pOwner;
388 pExt->AddDependency(pInfo);
391 void CExtensionManager::AddInterface(IExtension *pOwner, SMInterface *pInterface)
393 CExtension *pExt = (CExtension *)pOwner;
395 pExt->AddInterface(pInterface);
398 void CExtensionManager::BindChildPlugin(IExtension *pParent, IPlugin *pPlugin)
400 CExtension *pExt = (CExtension *)pParent;
402 pExt->AddPlugin(pPlugin);
405 void CExtensionManager::OnPluginDestroyed(IPlugin *plugin)
407 List<CExtension *>::iterator iter;
409 for (iter=m_Libs.begin(); iter!=m_Libs.end(); iter++)
411 (*iter)->RemovePlugin(plugin);
415 CExtension *CExtensionManager::FindByOrder(unsigned int num)
417 if (num < 1 || num > m_Libs.size())
422 List<CExtension *>::iterator iter = m_Libs.begin();
424 while (iter != m_Libs.end())
436 void CExtensionManager::BindAllNativesToPlugin(IPlugin *pPlugin)
438 List<CExtension *>::iterator iter;
440 IPluginContext *pContext = pPlugin->GetBaseContext();
441 for (iter=m_Libs.begin(); iter!=m_Libs.end(); iter++)
444 if (!pExt->IsLoaded())
448 if (!pExt->m_Natives.size())
453 List<const sp_nativeinfo_t *>::iterator n_iter;
454 const sp_nativeinfo_t *natives;
455 for (n_iter = pExt->m_Natives.begin();
456 n_iter != pExt->m_Natives.end();
461 while (natives[i].func != NULL && natives[i].name != NULL)
463 if (pContext->BindNative(&natives[i++]) == SP_ERROR_NONE)
469 if (set && (pExt->m_Plugins.find(pPlugin) == pExt->m_Plugins.end()))
471 /* For now, mark this plugin as a requirement. Later we'll introduce native filtering. */
472 pExt->m_Plugins.push_back(pPlugin);
477 bool CExtensionManager::UnloadExtension(IExtension *_pExt)
484 CExtension *pExt = (CExtension *)_pExt;
486 if (m_Libs.find(pExt) == m_Libs.end())
491 /* First remove us from internal lists */
492 g_ShareSys.RemoveInterfaces(_pExt);
495 List<CExtension *> UnloadQueue;
497 /* Handle dependencies */
498 if (pExt->IsLoaded())
500 /* Unload any dependent plugins */
501 List<IPlugin *>::iterator p_iter = pExt->m_Plugins.begin();
502 while (p_iter != pExt->m_Plugins.end())
504 /* We have to manually unlink ourselves here, since we're no longer being managed */
505 g_PluginSys.UnloadPlugin((*p_iter));
506 p_iter = pExt->m_Plugins.erase(p_iter);
509 /* Notify and/or unload all dependencies */
510 List<CExtension *>::iterator c_iter;
512 IExtensionInterface *pAPI;
513 for (c_iter = m_Libs.begin(); c_iter != m_Libs.end(); c_iter++)
516 if ((pAPI=pDep->GetAPI()) == NULL)
524 /* Now, get its dependency list */
525 bool dropped = false;
526 List<IfaceInfo>::iterator i_iter = pDep->m_Deps.begin();
527 while (i_iter != pDep->m_Deps.end())
529 if ((*i_iter).owner == _pExt)
531 if (!dropped && !pAPI->QueryInterfaceDrop((*i_iter).iface))
535 pAPI->NotifyInterfaceDrop((*i_iter).iface);
536 i_iter = pDep->m_Deps.erase(i_iter);
544 List<CExtension *>::iterator iter;
545 for (iter=UnloadQueue.begin(); iter!=UnloadQueue.end(); iter++)
547 /* NOTE: This is safe because the unload function backs out of anything not present */
548 UnloadExtension((*iter));
554 void CExtensionManager::AddNatives(IExtension *pOwner, const sp_nativeinfo_t *natives)
556 CExtension *pExt = (CExtension *)pOwner;
558 pExt->m_Natives.push_back(natives);
561 void CExtensionManager::MarkAllLoaded()
563 List<CExtension *>::iterator iter;
566 for (iter=m_Libs.begin(); iter!=m_Libs.end(); iter++)
569 if (!pExt->IsLoaded())
573 if (pExt->m_FullyLoaded)
577 pExt->MarkAllLoaded();
581 void CExtensionManager::AddDependency(IExtension *pSource, const char *file, bool required, bool autoload)
583 /* :TODO: implement */
587 void CExtensionManager::OnRootConsoleCommand(const char *cmd, unsigned int argcount)
591 const char *cmd = g_RootMenu.GetArgument(2);
592 if (strcmp(cmd, "list") == 0)
594 List<CExtension *>::iterator iter;
596 unsigned int num = 1;
597 switch (m_Libs.size())
601 g_RootMenu.ConsolePrint("[SM] Displaying 1 extension:");
606 g_RootMenu.ConsolePrint("[SM] No extensions are loaded.");
611 g_RootMenu.ConsolePrint("[SM] Displaying %d extensions:", m_Libs.size());
615 for (iter=m_Libs.begin(); iter!=m_Libs.end(); iter++,num++)
618 if (pExt->IsLoaded())
621 if (!pExt->IsRunning(error, sizeof(error)))
623 g_RootMenu.ConsolePrint("[%02d] <FAILED> file \"%s\": %s", num, pExt->GetFilename(), error);
625 IExtensionInterface *pAPI = pExt->GetAPI();
626 const char *name = pAPI->GetExtensionName();
627 const char *version = pAPI->GetExtensionVerString();
628 const char *descr = pAPI->GetExtensionDescription();
629 g_RootMenu.ConsolePrint("[%02d] %s (%s): %s", num, name, version, descr);
632 g_RootMenu.ConsolePrint("[%02d] <FAILED> file \"%s\": %s", num, pExt->GetFilename(), pExt->m_Error.c_str());
636 } else if (strcmp(cmd, "info") == 0) {
639 g_RootMenu.ConsolePrint("[SM] Usage: sm info <#>");
643 const char *sId = g_RootMenu.GetArgument(3);
644 unsigned int id = atoi(sId);
647 g_RootMenu.ConsolePrint("[SM] Usage: sm info <#>");
651 if (m_Libs.size() == 0)
653 g_RootMenu.ConsolePrint("[SM] No extensions are loaded.");
657 if (id > m_Libs.size())
659 g_RootMenu.ConsolePrint("[SM] No extension was found with id %d.", id);
663 List<CExtension *>::iterator iter = m_Libs.begin();
664 CExtension *pExt = NULL;
665 while (iter != m_Libs.end())
674 /* This should never happen */
677 g_RootMenu.ConsolePrint("[SM] No extension was found with id %d.", id);
681 if (!pExt->IsLoaded())
683 g_RootMenu.ConsolePrint(" File: %s", pExt->GetFilename());
684 g_RootMenu.ConsolePrint(" Loaded: No (%s)", pExt->m_Error.c_str());
687 if (!pExt->IsRunning(error, sizeof(error)))
689 g_RootMenu.ConsolePrint(" File: %s", pExt->GetFilename());
690 g_RootMenu.ConsolePrint(" Loaded: Yes");
691 g_RootMenu.ConsolePrint(" Running: No (%s)", error);
693 IExtensionInterface *pAPI = pExt->GetAPI();
694 g_RootMenu.ConsolePrint(" File: %s", pExt->GetFilename());
695 g_RootMenu.ConsolePrint(" Loaded: Yes (version %s)", pAPI->GetExtensionVerString());
696 g_RootMenu.ConsolePrint(" Name: %s (%s)", pAPI->GetExtensionName(), pAPI->GetExtensionDescription());
697 g_RootMenu.ConsolePrint(" Author: %s (%s)", pAPI->GetExtensionAuthor(), pAPI->GetExtensionURL());
698 g_RootMenu.ConsolePrint(" Binary info: API version %d (compiled %s)", pAPI->GetExtensionVersion(), pAPI->GetExtensionDateString());
699 g_RootMenu.ConsolePrint(" Metamod enabled: %s", pAPI->IsMetamodExtension() ? "yes" : "no");
703 } else if (strcmp(cmd, "unload") == 0) {
706 g_RootMenu.ConsolePrint("[SM] Usage: sm unload <#> [code]");
710 const char *arg = g_RootMenu.GetArgument(3);
711 unsigned int num = atoi(arg);
712 CExtension *pExt = FindByOrder(num);
716 g_RootMenu.ConsolePrint("[SM] Extension number %d was not found.", num);
720 if (argcount > 4 && pExt->unload_code)
722 const char *unload = g_RootMenu.GetArgument(4);
723 if (pExt->unload_code == (unsigned)atoi(unload))
725 char filename[PLATFORM_MAX_PATH+1];
726 snprintf(filename, PLATFORM_MAX_PATH, "%s", pExt->GetFilename());
727 UnloadExtension(pExt);
728 g_RootMenu.ConsolePrint("[SM] Extension %s is now unloaded.", filename);
730 g_RootMenu.ConsolePrint("[SM] Please try again, the correct unload code is \"%d\"", pExt->unload_code);
735 if (!pExt->IsLoaded()
736 || (!pExt->m_Deps.size() && !pExt->m_Plugins.size()))
738 char filename[PLATFORM_MAX_PATH+1];
739 snprintf(filename, PLATFORM_MAX_PATH, "%s", pExt->GetFilename());
740 UnloadExtension(pExt);
741 g_RootMenu.ConsolePrint("[SM] Extension %s is now unloaded.", filename);
744 List<IPlugin *> plugins;
745 if (pExt->m_Deps.size())
747 g_RootMenu.ConsolePrint("[SM] Unloading %s will unload the following extensions: ", pExt->GetFilename());
748 List<CExtension *>::iterator iter;
750 /* Get list of all extensions */
751 for (iter=m_Libs.begin(); iter!=m_Libs.end(); iter++)
753 List<IfaceInfo>::iterator i_iter;
755 if (!pOther->IsLoaded() || pOther == pExt)
759 /* Get their dependencies */
760 for (i_iter=pOther->m_Deps.begin();
761 i_iter!=pOther->m_Deps.end();
764 /* Is this dependency to us? */
765 if ((*i_iter).owner != pExt)
769 /* Will our dependent care? */
770 if (!pExt->GetAPI()->QueryInterfaceDrop((*i_iter).iface))
772 g_RootMenu.ConsolePrint(" -> %s", pExt->GetFilename());
773 /* Add to plugin unload list */
774 List<IPlugin *>::iterator p_iter;
775 for (p_iter=pOther->m_Plugins.begin();
776 p_iter!=pOther->m_Plugins.end();
779 if (plugins.find((*p_iter)) == plugins.end())
781 plugins.push_back((*p_iter));
788 if (pExt->m_Plugins.size())
790 g_RootMenu.ConsolePrint("[SM] Unloading %s will unload the following plugins: ", pExt->GetFilename());
791 List<IPlugin *>::iterator iter;
793 for (iter = pExt->m_Plugins.begin(); iter != pExt->m_Plugins.end(); iter++)
796 if (plugins.find(pPlugin) == plugins.end())
798 plugins.push_back(pPlugin);
801 for (iter = plugins.begin(); iter != plugins.end(); iter++)
804 g_RootMenu.ConsolePrint(" -> %s", pPlugin->GetFilename());
807 srand(static_cast<int>(time(NULL)));
808 pExt->unload_code = (rand() % 877) + 123; //123 to 999
809 g_RootMenu.ConsolePrint("[SM] To verify unloading %s, please use the following: ", pExt->GetFilename());
810 g_RootMenu.ConsolePrint("[SM] sm exts unload %d %d", num, pExt->unload_code);
817 g_RootMenu.ConsolePrint("SourceMod Extensions Menu:");
818 g_RootMenu.DrawGenericOption("info", "Extra extension information");
819 g_RootMenu.DrawGenericOption("list", "List extensions");
820 g_RootMenu.DrawGenericOption("unload", "Unload an extension");