2 * vim: set ts=4 sw=4 tw=99 noet :
3 * =============================================================================
5 * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
6 * =============================================================================
8 * This program is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, version 3.0, as published by the
10 * Free Software Foundation.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * You should have received a copy of the GNU General Public License along with
18 * this program. If not, see <http://www.gnu.org/licenses/>.
20 * As a special exception, AlliedModders LLC gives you permission to link the
21 * code of this program (as well as its derivative works) to "Half-Life 2," the
22 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
23 * by the Valve Corporation. You must obey the GNU General Public License in
24 * all respects for all other code used. Additionally, AlliedModders LLC grants
25 * this exception to all derivative works. AlliedModders LLC defines further
26 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
27 * or <http://www.sourcemod.net/license.php>.
34 #include "PluginSys.h"
36 #include <ILibrarySys.h>
37 #include <ISourceMod.h>
38 #include <IHandleSys.h>
39 #include <IForwardSys.h>
40 #include <IPlayerHelpers.h>
41 #include "ExtensionSys.h"
42 #include "GameConfigs.h"
43 #include "common_logic.h"
44 #include "Translator.h"
46 CPluginManager g_PluginSys;
47 HandleType_t g_PluginType = 0;
48 IdentityType_t g_PluginIdent = 0;
50 CPlugin::CPlugin(const char *file)
52 static int MySerial = 0;
54 m_type = PluginType_Private;
55 m_status = Plugin_Uncompiled;
56 m_bSilentlyFailed = false;
57 m_serial = ++MySerial;
59 m_errormsg[sizeof(m_errormsg) - 1] = '\0';
60 smcore.Format(m_filename, sizeof(m_filename), "%s", file);
63 m_FakeNativesMissing = false;
64 m_LibraryMissing = false;
65 m_bGotAllLoaded = false;
66 m_pPhrases = g_Translator.CreatePhraseCollection();
67 m_MaxClientsVar = NULL;
75 sec.pOwner = g_PluginSys.GetIdentity();
76 sec.pIdentity = sec.pOwner;
78 handlesys->FreeHandle(m_handle, &sec);
79 g_ShareSys.DestroyIdentity(m_ident);
82 if (m_pRuntime != NULL)
88 for (size_t i=0; i<m_configs.size(); i++)
93 if (m_pPhrases != NULL)
95 m_pPhrases->Destroy();
100 void CPlugin::InitIdentity()
104 m_ident = g_ShareSys.CreateIdentity(g_PluginIdent, this);
105 m_handle = handlesys->CreateHandle(g_PluginType, this, g_PluginSys.GetIdentity(), g_PluginSys.GetIdentity(), NULL);
106 m_pRuntime->GetDefaultContext()->SetKey(1, m_ident);
107 m_pRuntime->GetDefaultContext()->SetKey(2, (IPlugin *)this);
111 unsigned int CPlugin::CalcMemUsage()
113 unsigned int base_size =
115 + sizeof(IdentityToken_t)
116 + (m_configs.size() * (sizeof(AutoConfig *) + sizeof(AutoConfig)))
117 + m_Props.mem_usage();
119 for (unsigned int i = 0; i < m_configs.size(); i++)
121 base_size += m_configs[i]->autocfg.size();
122 base_size += m_configs[i]->folder.size();
125 for (List<String>::iterator i = m_Libraries.begin();
126 i != m_Libraries.end();
129 base_size += (*i).size();
132 for (List<String>::iterator i = m_RequiredLibs.begin();
133 i != m_RequiredLibs.end();
136 base_size += (*i).size();
142 Handle_t CPlugin::GetMyHandle()
147 CPlugin *CPlugin::CreatePlugin(const char *file, char *error, size_t maxlength)
149 char fullpath[PLATFORM_MAX_PATH];
150 g_pSM->BuildPath(Path_SM, fullpath, sizeof(fullpath), "plugins/%s", file);
151 FILE *fp = fopen(fullpath, "rb");
153 CPlugin *pPlugin = new CPlugin(file);
159 smcore.Format(error, maxlength, "Unable to open file");
161 pPlugin->m_status = Plugin_BadLoad;
170 bool CPlugin::GetProperty(const char *prop, void **ptr, bool remove/* =false */)
172 StringHashMap<void *>::Result r = m_Props.find(prop);
185 bool CPlugin::SetProperty(const char *prop, void *ptr)
187 return m_Props.insert(prop, ptr);
190 IPluginRuntime *CPlugin::GetRuntime()
195 void CPlugin::SetErrorState(PluginStatus status, const char *error_fmt, ...)
197 PluginStatus old_status = m_status;
200 if (old_status == Plugin_Running)
202 /* Tell everyone we're now paused */
203 g_PluginSys._SetPauseState(this, true);
207 va_start(ap, error_fmt);
208 smcore.FormatArgs(m_errormsg, sizeof(m_errormsg), error_fmt, ap);
211 if (m_pRuntime != NULL)
213 m_pRuntime->SetPauseState(true);
217 bool CPlugin::UpdateInfo()
219 /* Now grab the info */
221 IPluginContext *base = GetBaseContext();
222 int err = base->FindPubvarByName("myinfo", &idx);
224 memset(&m_info, 0, sizeof(m_info));
226 if (err == SP_ERROR_NONE)
228 struct sm_plugininfo_c_t
236 sm_plugininfo_c_t *cinfo;
239 base->GetPubvarAddrs(idx, &local_addr, (cell_t **)&cinfo);
240 base->LocalToString(cinfo->name, (char **)&m_info.name);
241 base->LocalToString(cinfo->description, (char **)&m_info.description);
242 base->LocalToString(cinfo->author, (char **)&m_info.author);
243 base->LocalToString(cinfo->url, (char **)&m_info.url);
244 base->LocalToString(cinfo->version, (char **)&m_info.version);
247 m_info.author = m_info.author ? m_info.author : "";
248 m_info.description = m_info.description ? m_info.description : "";
249 m_info.name = m_info.name ? m_info.name : "";
250 m_info.url = m_info.url ? m_info.url : "";
251 m_info.version = m_info.version ? m_info.version : "";
253 if ((err = base->FindPubvarByName("__version", &idx)) == SP_ERROR_NONE)
255 struct __version_info
262 __version_info *info;
264 const char *pDate, *pTime, *pFileVers;
269 base->GetPubvarAddrs(idx, &local_addr, (cell_t **)&info);
270 m_FileVersion = info->version;
271 if (m_FileVersion >= 4)
273 base->LocalToString(info->date, (char **)&pDate);
274 base->LocalToString(info->time, (char **)&pTime);
275 smcore.Format(m_DateTime, sizeof(m_DateTime), "%s %s", pDate, pTime);
277 if (m_FileVersion > 5)
279 base->LocalToString(info->filevers, (char **)&pFileVers);
280 SetErrorState(Plugin_Failed, "Newer SourceMod required (%s or higher)", pFileVers);
289 if ((err = base->FindPubvarByName("MaxClients", &idx)) == SP_ERROR_NONE)
291 base->GetPubvarByIndex(idx, &m_MaxClientsVar);
297 void CPlugin::SyncMaxClients(int max_clients)
299 if (m_MaxClientsVar == NULL)
304 *m_MaxClientsVar->offs = max_clients;
307 void CPlugin::Call_OnPluginStart()
309 if (m_status != Plugin_Loaded)
314 m_status = Plugin_Running;
316 SyncMaxClients(playerhelpers->GetMaxClients());
319 IPluginFunction *pFunction = m_pRuntime->GetFunctionByName("OnPluginStart");
326 if ((err=pFunction->Execute(&result)) != SP_ERROR_NONE)
328 SetErrorState(Plugin_Error, "Error detected in plugin startup (see error logs)");
332 void CPlugin::Call_OnPluginEnd()
334 if (m_status > Plugin_Paused)
340 IPluginFunction *pFunction = m_pRuntime->GetFunctionByName("OnPluginEnd");
346 pFunction->Execute(&result);
349 void CPlugin::Call_OnAllPluginsLoaded()
351 if (m_status > Plugin_Paused)
361 m_bGotAllLoaded = true;
364 IPluginFunction *pFunction = m_pRuntime->GetFunctionByName("OnAllPluginsLoaded");
365 if (pFunction != NULL)
367 pFunction->Execute(&result);
370 if (smcore.IsMapRunning())
372 if ((pFunction = m_pRuntime->GetFunctionByName("OnMapStart")) != NULL)
374 pFunction->Execute(NULL);
378 if (smcore.AreConfigsExecuted())
380 smcore.ExecuteConfigs(GetBaseContext());
384 APLRes CPlugin::Call_AskPluginLoad(char *error, size_t maxlength)
386 if (m_status != Plugin_Created)
388 return APLRes_Failure;
391 m_status = Plugin_Loaded;
395 bool haveNewAPL = false;
396 IPluginFunction *pFunction = m_pRuntime->GetFunctionByName("AskPluginLoad2");
402 else if (!(pFunction = m_pRuntime->GetFunctionByName("AskPluginLoad")))
404 return APLRes_Success;
407 pFunction->PushCell(m_handle);
408 pFunction->PushCell(g_PluginSys.IsLateLoadTime() ? 1 : 0);
409 pFunction->PushStringEx(error, maxlength, 0, SM_PARAM_COPYBACK);
410 pFunction->PushCell(maxlength);
411 if ((err=pFunction->Execute(&result)) != SP_ERROR_NONE)
413 return APLRes_Failure;
418 return (APLRes)result;
422 return APLRes_Success;
426 return APLRes_Failure;
430 void *CPlugin::GetPluginStructure()
435 IPluginContext *CPlugin::GetBaseContext()
437 if (m_pRuntime == NULL)
442 return m_pRuntime->GetDefaultContext();
445 sp_context_t *CPlugin::GetContext()
450 const char *CPlugin::GetFilename()
455 PluginType CPlugin::GetType()
460 const sm_plugininfo_t *CPlugin::GetPublicInfo()
462 if (GetStatus() >= Plugin_Created)
469 unsigned int CPlugin::GetSerial()
474 PluginStatus CPlugin::GetStatus()
479 bool CPlugin::IsSilentlyFailed()
481 return m_bSilentlyFailed;
484 bool CPlugin::IsDebugging()
486 if (m_pRuntime == NULL)
494 void CPlugin::SetSilentlyFailed(bool sf)
496 m_bSilentlyFailed = sf;
499 void CPlugin::LibraryActions(LibraryAction action)
501 List<String>::iterator iter;
502 for (iter = m_Libraries.begin();
503 iter != m_Libraries.end();
506 g_PluginSys.OnLibraryAction((*iter).c_str(), action);
510 bool CPlugin::SetPauseState(bool paused)
512 if (paused && GetStatus() != Plugin_Running)
515 } else if (!paused && GetStatus() != Plugin_Paused) {
521 LibraryActions(LibraryAction_Removed);
524 IPluginFunction *pFunction = m_pRuntime->GetFunctionByName("OnPluginPauseChange");
528 pFunction->PushCell(paused ? 1 : 0);
529 pFunction->Execute(&result);
534 m_status = Plugin_Paused;
535 m_pRuntime->SetPauseState(true);
537 m_status = Plugin_Running;
538 m_pRuntime->SetPauseState(false);
541 g_PluginSys._SetPauseState(this, paused);
545 LibraryActions(LibraryAction_Added);
551 IdentityToken_t *CPlugin::GetIdentity()
556 bool CPlugin::IsRunnable()
558 return (m_status <= Plugin_Paused) ? true : false;
561 time_t CPlugin::GetFileTimeStamp()
563 char path[PLATFORM_MAX_PATH];
564 g_pSM->BuildPath(Path_SM, path, sizeof(path), "plugins/%s", m_filename);
565 #ifdef PLATFORM_WINDOWS
567 if (_stat(path, &s) != 0)
568 #elif defined PLATFORM_POSIX
570 if (stat(path, &s) != 0)
581 time_t CPlugin::GetTimeStamp()
586 void CPlugin::SetTimeStamp(time_t t)
591 IPhraseCollection *CPlugin::GetPhrases()
596 void CPlugin::DependencyDropped(CPlugin *pOwner)
603 List<String>::iterator reqlib_iter;
604 List<String>::iterator lib_iter;
605 for (lib_iter=pOwner->m_Libraries.begin(); lib_iter!=pOwner->m_Libraries.end(); lib_iter++)
607 for (reqlib_iter=m_RequiredLibs.begin(); reqlib_iter!=m_RequiredLibs.end(); reqlib_iter++)
609 if ((*reqlib_iter) == (*lib_iter))
611 m_LibraryMissing = true;
616 List<NativeEntry *>::iterator iter;
617 NativeEntry *pNative;
620 unsigned int unbound = 0;
622 for (iter = pOwner->m_Natives.begin();
623 iter != pOwner->m_Natives.end();
627 /* Find this native! */
628 if (m_pRuntime->FindNativeByName(pNative->name(), &idx) != SP_ERROR_NONE)
633 m_pRuntime->GetNativeByIndex(idx, &native);
635 native->status = SP_NATIVE_UNBOUND;
641 m_FakeNativesMissing = true;
644 /* :IDEA: in the future, add native trapping? */
645 if (m_FakeNativesMissing || m_LibraryMissing)
647 SetErrorState(Plugin_Error, "Depends on plugin: %s", pOwner->GetFilename());
651 size_t CPlugin::GetConfigCount()
653 return (unsigned int)m_configs.size();
656 AutoConfig *CPlugin::GetConfig(size_t i)
658 if (i >= GetConfigCount())
666 void CPlugin::AddConfig(bool autoCreate, const char *cfg, const char *folder)
668 /* Do a check for duplicates to prevent double-execution */
669 for (size_t i = 0; i < m_configs.size(); i++)
671 if (m_configs[i]->autocfg.compare(cfg) == 0
672 && m_configs[i]->folder.compare(folder) == 0
673 && m_configs[i]->create == autoCreate)
679 AutoConfig *c = new AutoConfig;
683 c->create = autoCreate;
685 m_configs.push_back(c);
688 void CPlugin::DropEverything()
691 List<WeakNative>::iterator wk_iter;
693 /* Tell everyone that depends on us that we're about to drop */
694 for (List<CPlugin *>::iterator iter = m_Dependents.begin();
695 iter != m_Dependents.end();
698 pOther = static_cast<CPlugin *>(*iter);
699 pOther->DependencyDropped(this);
702 /* Note: we don't care about things we depend on.
703 * The reason is that extensions have their own cleanup
704 * code for plugins. Although the "right" design would be
705 * to centralize that here, i'm omitting it for now. Thus,
706 * the code below to walk the plugins list will suffice.
709 /* Other plugins could be holding weak references that were
710 * added by us. We need to clean all of those up now.
712 for (List<CPlugin *>::iterator iter = g_PluginSys.m_plugins.begin();
713 iter != g_PluginSys.m_plugins.end();
716 (*iter)->ToNativeOwner()->DropRefsTo(this);
719 /* Proceed with the rest of the necessities. */
720 CNativeOwner::DropEverything();
723 bool CPlugin::AddFakeNative(IPluginFunction *pFunc, const char *name, SPVM_FAKENATIVE_FUNC func)
727 if ((pEntry = g_ShareSys.AddFakeNative(pFunc, name, func)) == NULL)
732 m_Natives.push_back(pEntry);
741 CPluginManager::CPluginIterator::CPluginIterator(List<CPlugin *> *_mylist)
747 IPlugin *CPluginManager::CPluginIterator::GetPlugin()
752 bool CPluginManager::CPluginIterator::MorePlugins()
754 return (current != mylist->end());
757 void CPluginManager::CPluginIterator::NextPlugin()
762 void CPluginManager::CPluginIterator::Release()
764 g_PluginSys.ReleaseIterator(this);
767 CPluginManager::CPluginIterator::~CPluginIterator()
771 void CPluginManager::CPluginIterator::Reset()
773 current = mylist->begin();
780 CPluginManager::CPluginManager()
782 m_AllPluginsLoaded = false;
784 m_LoadingLocked = false;
786 m_bBlockBadPlugins = true;
789 CPluginManager::~CPluginManager()
791 CStack<CPluginManager::CPluginIterator *>::iterator iter;
792 for (iter=m_iters.begin(); iter!=m_iters.end(); iter++)
799 void CPluginManager::Shutdown()
801 List<CPlugin *>::iterator iter;
803 while ((iter = m_plugins.begin()) != m_plugins.end())
809 void CPluginManager::LoadAll(const char *config_path, const char *plugins_path)
811 LoadAll_FirstPass(config_path, plugins_path);
812 g_Extensions.MarkAllLoaded();
813 LoadAll_SecondPass();
814 g_Extensions.MarkAllLoaded();
818 void CPluginManager::LoadAll_FirstPass(const char *config, const char *basedir)
820 /* First read in the database of plugin settings */
821 m_AllPluginsLoaded = false;
822 LoadPluginsFromDir(basedir, NULL);
825 void CPluginManager::LoadPluginsFromDir(const char *basedir, const char *localpath)
827 char base_path[PLATFORM_MAX_PATH];
829 /* Form the current path to start reading from */
830 if (localpath == NULL)
832 libsys->PathFormat(base_path, sizeof(base_path), "%s", basedir);
834 libsys->PathFormat(base_path, sizeof(base_path), "%s/%s", basedir, localpath);
837 IDirectory *dir = libsys->OpenDirectory(base_path);
842 libsys->GetPlatformError(error, sizeof(error));
843 smcore.LogError("[SM] Failure reading from plugins path: %s", localpath);
844 smcore.LogError("[SM] Platform returned error: %s", error);
848 while (dir->MoreFiles())
850 if (dir->IsEntryDirectory()
851 && (strcmp(dir->GetEntryName(), ".") != 0)
852 && (strcmp(dir->GetEntryName(), "..") != 0)
853 && (strcmp(dir->GetEntryName(), "disabled") != 0)
854 && (strcmp(dir->GetEntryName(), "optional") != 0))
856 char new_local[PLATFORM_MAX_PATH];
857 if (localpath == NULL)
859 /* If no path yet, don't add a former slash */
860 smcore.Format(new_local, sizeof(new_local), "%s", dir->GetEntryName());
862 libsys->PathFormat(new_local, sizeof(new_local), "%s/%s", localpath, dir->GetEntryName());
864 LoadPluginsFromDir(basedir, new_local);
865 } else if (dir->IsEntryFile()) {
866 const char *name = dir->GetEntryName();
867 size_t len = strlen(name);
869 && strcmp(&name[len-4], ".smx") == 0)
871 /* If the filename matches, load the plugin */
872 char plugin[PLATFORM_MAX_PATH];
873 if (localpath == NULL)
875 smcore.Format(plugin, sizeof(plugin), "%s", name);
877 libsys->PathFormat(plugin, sizeof(plugin), "%s/%s", localpath, name);
879 LoadAutoPlugin(plugin);
884 libsys->CloseDirectory(dir);
887 LoadRes CPluginManager::_LoadPlugin(CPlugin **aResult, const char *path, bool debug, PluginType type, char error[], size_t maxlength)
890 return LoadRes_NeverLoad;
895 * Does this plugin already exist?
898 if (m_LoadLookup.retrieve(path, &pPlugin))
900 /* Check to see if we should try reloading it */
901 if (pPlugin->GetStatus() == Plugin_BadLoad
902 || pPlugin->GetStatus() == Plugin_Error
903 || pPlugin->GetStatus() == Plugin_Failed)
905 UnloadPlugin(pPlugin);
911 return LoadRes_AlreadyLoaded;
915 pPlugin = CPlugin::CreatePlugin(path, error, maxlength);
916 assert(pPlugin != NULL);
918 pPlugin->m_type = PluginType_MapUpdated;
920 ICompilation *co = NULL;
922 if (pPlugin->m_status == Plugin_Uncompiled)
924 co = g_pSourcePawn2->StartCompilation();
927 /* Do the actual compiling */
930 char fullpath[PLATFORM_MAX_PATH];
931 g_pSM->BuildPath(Path_SM, fullpath, sizeof(fullpath), "plugins/%s", pPlugin->m_filename);
933 pPlugin->m_pRuntime = g_pSourcePawn2->LoadPlugin(co, fullpath, &err);
934 if (pPlugin->m_pRuntime == NULL)
940 "Unable to load plugin (error %d: %s)",
942 g_pSourcePawn2->GetErrorString(err));
944 pPlugin->m_status = Plugin_BadLoad;
948 if (pPlugin->UpdateInfo())
950 pPlugin->m_status = Plugin_Created;
956 smcore.Format(error, maxlength, "%s", pPlugin->m_errormsg);
962 if (pPlugin->GetStatus() == Plugin_Created)
964 unsigned char *pCodeHash = pPlugin->m_pRuntime->GetCodeHash();
966 char codeHashBuf[40];
967 smcore.Format(codeHashBuf, 40, "plugin_");
968 for (int i = 0; i < 16; i++)
969 smcore.Format(codeHashBuf + 7 + (i * 2), 3, "%02x", pCodeHash[i]);
971 const char *bulletinUrl = g_pGameConf->GetKeyValue(codeHashBuf);
972 if (bulletinUrl != NULL)
974 if (m_bBlockBadPlugins)
978 if (bulletinUrl[0] != '\0')
980 smcore.Format(error, maxlength, "Known malware detected and blocked. See %s for more info", bulletinUrl);
982 smcore.Format(error, maxlength, "Possible malware or illegal plugin detected and blocked");
985 pPlugin->m_status = Plugin_BadLoad;
987 if (bulletinUrl[0] != '\0')
989 smcore.Log("%s: Known malware detected. See %s for more info, blocking disabled in core.cfg", pPlugin->GetFilename(), bulletinUrl);
991 smcore.Log("%s: Possible malware or illegal plugin detected, blocking disabled in core.cfg", pPlugin->GetFilename());
997 LoadRes loadFailure = LoadRes_Failure;
999 if (pPlugin->GetStatus() == Plugin_Created)
1001 /* First native pass - add anything from Core */
1002 g_ShareSys.BindNativesToPlugin(pPlugin, true);
1003 pPlugin->InitIdentity();
1004 APLRes result = pPlugin->Call_AskPluginLoad(error, maxlength);
1007 case APLRes_Success:
1008 /* Autoload any modules */
1009 LoadOrRequireExtensions(pPlugin, 1, error, maxlength);
1011 case APLRes_Failure:
1012 pPlugin->SetErrorState(Plugin_Failed, "%s", error);
1013 loadFailure = LoadRes_Failure;
1015 case APLRes_SilentFailure:
1016 pPlugin->SetErrorState(Plugin_Failed, "%s", error);
1017 loadFailure = LoadRes_SilentFailure;
1018 pPlugin->SetSilentlyFailed(true);
1025 /* Save the time stamp */
1026 time_t t = pPlugin->GetFileTimeStamp();
1027 pPlugin->SetTimeStamp(t);
1032 return (pPlugin->GetStatus() == Plugin_Loaded) ? LoadRes_Successful : loadFailure;
1035 IPlugin *CPluginManager::LoadPlugin(const char *path, bool debug, PluginType type, char error[], size_t maxlength, bool *wasloaded)
1041 if ((res=_LoadPlugin(&pl, path, true, type, error, maxlength)) == LoadRes_Failure)
1047 if (res == LoadRes_AlreadyLoaded)
1053 if (res == LoadRes_NeverLoad)
1057 if (m_LoadingLocked)
1059 smcore.Format(error, maxlength, "There is a global plugin loading lock in effect");
1063 smcore.Format(error, maxlength, "This plugin is blocked from loading (see plugin_settings.cfg)");
1071 /* Run second pass if we need to */
1072 if (IsLateLoadTime() && pl->GetStatus() == Plugin_Loaded)
1074 if (!RunSecondPass(pl, error, maxlength))
1079 pl->Call_OnAllPluginsLoaded();
1085 void CPluginManager::LoadAutoPlugin(const char *plugin)
1089 char error[255] = "Unknown error";
1091 if ((res=_LoadPlugin(&pl, plugin, false, PluginType_MapUpdated, error, sizeof(error))) == LoadRes_Failure)
1093 smcore.LogError("[SM] Failed to load plugin \"%s\": %s.", plugin, error);
1095 pl->GetStatus() <= Plugin_Created ? Plugin_BadLoad : pl->GetStatus(),
1100 if (res == LoadRes_Successful || res == LoadRes_Failure || res == LoadRes_SilentFailure)
1106 void CPluginManager::AddPlugin(CPlugin *pPlugin)
1108 List<IPluginsListener *>::iterator iter;
1109 IPluginsListener *pListener;
1111 for (iter=m_listeners.begin(); iter!=m_listeners.end(); iter++)
1113 pListener = (*iter);
1114 pListener->OnPluginCreated(pPlugin);
1117 m_plugins.push_back(pPlugin);
1118 m_LoadLookup.insert(pPlugin->m_filename, pPlugin);
1121 void CPluginManager::LoadAll_SecondPass()
1123 List<CPlugin *>::iterator iter;
1127 for (iter=m_plugins.begin(); iter!=m_plugins.end(); iter++)
1130 if (pPlugin->GetStatus() == Plugin_Loaded)
1133 if (!RunSecondPass(pPlugin, error, sizeof(error)))
1135 smcore.LogError("[SM] Unable to load plugin \"%s\": %s", pPlugin->GetFilename(), error);
1136 pPlugin->SetErrorState(Plugin_Failed, "%s", error);
1141 m_AllPluginsLoaded = true;
1144 bool CPluginManager::FindOrRequirePluginDeps(CPlugin *pPlugin, char *error, size_t maxlength)
1153 IPluginContext *pBase = pPlugin->GetBaseContext();
1154 uint32_t num = pBase->GetPubVarsNum();
1155 sp_pubvar_t *pubvar;
1157 char pathfile[PLATFORM_MAX_PATH];
1159 for (uint32_t i=0; i<num; i++)
1161 if (pBase->GetPubvarByIndex(i, &pubvar) != SP_ERROR_NONE)
1165 if (strncmp(pubvar->name, "__pl_", 5) == 0)
1167 pl = (_pl *)pubvar->offs;
1168 if (pBase->LocalToString(pl->file, &file) != SP_ERROR_NONE)
1172 if (pBase->LocalToString(pl->name, &name) != SP_ERROR_NONE)
1176 libsys->GetFileFromPath(pathfile, sizeof(pathfile), pPlugin->GetFilename());
1177 if (strcmp(pathfile, file) == 0)
1181 if (pl->required == false)
1183 IPluginFunction *pFunc;
1185 smcore.Format(buffer, sizeof(buffer), "__pl_%s_SetNTVOptional", &pubvar->name[5]);
1186 if ((pFunc=pBase->GetFunctionByName(buffer)))
1189 pFunc->Execute(&res);
1190 if (pPlugin->GetBaseContext()->GetLastNativeError() != SP_ERROR_NONE)
1194 smcore.Format(error, maxlength, "Fatal error during initializing plugin load");
1202 /* Check that we aren't registering the same library twice */
1203 if (pPlugin->m_RequiredLibs.find(name) == pPlugin->m_RequiredLibs.end())
1205 pPlugin->m_RequiredLibs.push_back(name);
1211 List<CPlugin *>::iterator iter;
1214 for (iter=m_plugins.begin(); iter!=m_plugins.end(); iter++)
1217 if (pl->m_Libraries.find(name) != pl->m_Libraries.end())
1227 smcore.Format(error, maxlength, "Could not find required plugin \"%s\"", name);
1238 bool CPluginManager::LoadOrRequireExtensions(CPlugin *pPlugin, unsigned int pass, char *error, size_t maxlength)
1240 /* Find any extensions this plugin needs */
1249 IPluginContext *pBase = pPlugin->GetBaseContext();
1250 uint32_t num = pBase->GetPubVarsNum();
1251 sp_pubvar_t *pubvar;
1253 char path[PLATFORM_MAX_PATH];
1255 for (uint32_t i=0; i<num; i++)
1257 if (pBase->GetPubvarByIndex(i, &pubvar) != SP_ERROR_NONE)
1261 if (strncmp(pubvar->name, "__ext_", 6) == 0)
1263 ext = (_ext *)pubvar->offs;
1264 if (pBase->LocalToString(ext->file, &file) != SP_ERROR_NONE)
1268 if (pBase->LocalToString(ext->name, &name) != SP_ERROR_NONE)
1274 /* Attempt to auto-load if necessary */
1277 libsys->PathFormat(path, PLATFORM_MAX_PATH, "%s", file);
1278 bool bErrorOnMissing = ext->required ? true : false;
1279 g_Extensions.LoadAutoExtension(path, bErrorOnMissing);
1284 /* Is this required? */
1287 libsys->PathFormat(path, PLATFORM_MAX_PATH, "%s", file);
1288 if ((pExt = g_Extensions.FindExtensionByFile(path)) == NULL)
1290 pExt = g_Extensions.FindExtensionByName(name);
1292 /* :TODO: should we bind to unloaded extensions?
1293 * Currently the extension manager will ignore this.
1295 if (!pExt || !pExt->IsRunning(NULL, 0))
1299 smcore.Format(error, maxlength, "Required extension \"%s\" file(\"%s\") not running", name, file);
1305 g_Extensions.BindChildPlugin(pExt, pPlugin);
1310 IPluginFunction *pFunc;
1312 smcore.Format(buffer, sizeof(buffer), "__ext_%s_SetNTVOptional", &pubvar->name[6]);
1314 if ((pFunc = pBase->GetFunctionByName(buffer)) != NULL)
1317 pFunc->Execute(&res);
1318 if (pPlugin->GetBaseContext()->GetLastNativeError() != SP_ERROR_NONE)
1322 smcore.Format(error, maxlength, "Fatal error during plugin initialization (ext req)");
1335 bool CPluginManager::RunSecondPass(CPlugin *pPlugin, char *error, size_t maxlength)
1337 /* Second pass for extension requirements */
1338 if (!LoadOrRequireExtensions(pPlugin, 2, error, maxlength))
1343 if (!FindOrRequirePluginDeps(pPlugin, error, maxlength))
1348 /* Run another binding pass */
1349 g_ShareSys.BindNativesToPlugin(pPlugin, false);
1351 /* Find any unbound natives. Right now, these are not allowed. */
1352 IPluginContext *pContext = pPlugin->GetBaseContext();
1353 uint32_t num = pContext->GetNativesNum();
1354 sp_native_t *native;
1355 for (unsigned int i=0; i<num; i++)
1357 if (pContext->GetNativeByIndex(i, &native) != SP_ERROR_NONE)
1361 if (native->status == SP_NATIVE_UNBOUND
1362 && native->name[0] != '@'
1363 && !(native->flags & SP_NTVFLAG_OPTIONAL))
1367 smcore.Format(error, maxlength, "Native \"%s\" was not found", native->name);
1373 /* Finish by telling all listeners */
1374 List<IPluginsListener *>::iterator iter;
1375 IPluginsListener *pListener;
1376 for (iter=m_listeners.begin(); iter!=m_listeners.end(); iter++)
1378 pListener = (*iter);
1379 pListener->OnPluginLoaded(pPlugin);
1382 /* Tell this plugin to finish initializing itself */
1383 pPlugin->Call_OnPluginStart();
1385 /* Now, if we have fake natives, go through all plugins that might need rebinding */
1386 if (pPlugin->GetStatus() <= Plugin_Paused && pPlugin->m_Natives.size())
1388 List<CPlugin *>::iterator pl_iter;
1390 for (pl_iter = m_plugins.begin();
1391 pl_iter != m_plugins.end();
1394 pOther = (*pl_iter);
1395 if ((pOther->GetStatus() == Plugin_Error
1396 && (pOther->m_FakeNativesMissing || pOther->m_LibraryMissing))
1397 || pOther->m_FakeNativesMissing)
1399 TryRefreshDependencies(pOther);
1401 else if ((pOther->GetStatus() == Plugin_Running
1402 || pOther->GetStatus() == Plugin_Paused)
1403 && pOther != pPlugin)
1405 List<NativeEntry *>::iterator nv_iter;
1406 for (nv_iter = pPlugin->m_Natives.begin();
1407 nv_iter != pPlugin->m_Natives.end();
1410 g_ShareSys.BindNativeToPlugin(pOther, (*nv_iter));
1416 /* Go through our libraries and tell other plugins they're added */
1417 List<String>::iterator s_iter;
1418 for (s_iter = pPlugin->m_Libraries.begin();
1419 s_iter != pPlugin->m_Libraries.end();
1422 OnLibraryAction((*s_iter).c_str(), LibraryAction_Added);
1425 /* :TODO: optimize? does this even matter? */
1426 pPlugin->GetPhrases()->AddPhraseFile("core.phrases");
1431 void CPluginManager::TryRefreshDependencies(CPlugin *pPlugin)
1433 assert(pPlugin->GetBaseContext() != NULL);
1435 g_ShareSys.BindNativesToPlugin(pPlugin, false);
1437 List<String>::iterator lib_iter;
1438 List<String>::iterator req_iter;
1439 List<CPlugin *>::iterator pl_iter;
1441 for (req_iter=pPlugin->m_RequiredLibs.begin(); req_iter!=pPlugin->m_RequiredLibs.end(); req_iter++)
1444 for (pl_iter=m_plugins.begin(); pl_iter!=m_plugins.end(); pl_iter++)
1447 for (lib_iter=pl->m_Libraries.begin(); lib_iter!=pl->m_Libraries.end(); lib_iter++)
1449 if ((*req_iter) == (*lib_iter))
1457 pPlugin->SetErrorState(Plugin_Error, "Library not found: %s", (*req_iter).c_str());
1462 /* Find any unbound natives
1463 * Right now, these are not allowed
1465 IPluginContext *pContext = pPlugin->GetBaseContext();
1466 uint32_t num = pContext->GetNativesNum();
1467 sp_native_t *native;
1468 for (unsigned int i=0; i<num; i++)
1470 if (pContext->GetNativeByIndex(i, &native) != SP_ERROR_NONE)
1474 if (native->status == SP_NATIVE_UNBOUND
1475 && native->name[0] != '@'
1476 && !(native->flags & SP_NTVFLAG_OPTIONAL))
1478 pPlugin->SetErrorState(Plugin_Error, "Native not found: %s", native->name);
1483 if (pPlugin->GetStatus() == Plugin_Error)
1485 /* If we got here, all natives are okay again! */
1486 pPlugin->m_status = Plugin_Running;
1487 if (pPlugin->m_pRuntime->IsPaused())
1489 pPlugin->m_pRuntime->SetPauseState(false);
1490 _SetPauseState(pPlugin, false);
1495 bool CPluginManager::UnloadPlugin(IPlugin *plugin)
1497 CPlugin *pPlugin = (CPlugin *)plugin;
1499 /* This prevents removal during insertion or anything else weird */
1500 if (m_plugins.find(pPlugin) == m_plugins.end())
1505 IPluginContext *pContext = plugin->GetBaseContext();
1506 if (pContext != NULL && pContext->IsInExec())
1509 smcore.Format(buffer, sizeof(buffer), "sm plugins unload %s\n", plugin->GetFilename());
1510 engine->ServerCommand(buffer);
1514 /* Remove us from the lookup table and linked list */
1515 m_plugins.remove(pPlugin);
1516 m_LoadLookup.remove(pPlugin->m_filename);
1518 /* Go through our libraries and tell other plugins they're gone */
1519 List<String>::iterator s_iter;
1520 for (s_iter = pPlugin->m_Libraries.begin();
1521 s_iter != pPlugin->m_Libraries.end();
1524 OnLibraryAction((*s_iter).c_str(), LibraryAction_Removed);
1527 List<IPluginsListener *>::iterator iter;
1528 IPluginsListener *pListener;
1530 if (pPlugin->GetStatus() <= Plugin_Error)
1533 pPlugin->Call_OnPluginEnd();
1535 /* Notify listeners of unloading */
1536 for (iter=m_listeners.begin(); iter!=m_listeners.end(); iter++)
1538 pListener = (*iter);
1539 pListener->OnPluginUnloaded(pPlugin);
1543 pPlugin->DropEverything();
1545 for (iter=m_listeners.begin(); iter!=m_listeners.end(); iter++)
1547 /* Notify listeners of destruction */
1548 pListener = (*iter);
1549 pListener->OnPluginDestroyed(pPlugin);
1552 /* Tell the plugin to delete itself */
1558 IPlugin *CPluginManager::FindPluginByContext(const sp_context_t *ctx)
1561 IPluginContext *pContext;
1563 pContext = reinterpret_cast<IPluginContext *>(const_cast<sp_context_t *>(ctx));
1565 if (pContext->GetKey(2, (void **)&pPlugin))
1573 CPlugin *CPluginManager::GetPluginByCtx(const sp_context_t *ctx)
1575 return (CPlugin *)FindPluginByContext(ctx);
1578 unsigned int CPluginManager::GetPluginCount()
1580 return m_plugins.size();
1583 void CPluginManager::AddPluginsListener(IPluginsListener *listener)
1585 m_listeners.push_back(listener);
1588 void CPluginManager::RemovePluginsListener(IPluginsListener *listener)
1590 m_listeners.remove(listener);
1593 IPluginIterator *CPluginManager::GetPluginIterator()
1595 if (m_iters.empty())
1597 return new CPluginIterator(&m_plugins);
1599 CPluginIterator *iter = m_iters.front();
1606 void CPluginManager::ReleaseIterator(CPluginIterator *iter)
1611 bool CPluginManager::TestAliasMatch(const char *alias, const char *localpath)
1613 /* As an optimization, we do not call strlen, but compute the length in the first pass */
1614 size_t alias_len = 0;
1615 size_t local_len = 0;
1617 const char *ptr = alias;
1618 unsigned int alias_explicit_paths = 0;
1619 unsigned int alias_path_end = 0;
1620 while (*ptr != '\0')
1622 if (*ptr == '\\' || *ptr == '/')
1624 alias_explicit_paths++;
1625 alias_path_end = alias_len;
1631 if (alias_explicit_paths && alias_path_end == alias_len - 1)
1633 /* Trailing slash is totally invalid here */
1638 unsigned int local_explicit_paths = 0;
1639 unsigned int local_path_end = 0;
1640 while (*ptr != '\0')
1642 if (*ptr == '\\' || *ptr == '/')
1644 local_explicit_paths++;
1645 local_path_end = local_len;
1651 /* If the alias has more explicit paths than the real path,
1652 * no match will be possible.
1654 if (alias_explicit_paths > local_explicit_paths)
1659 if (alias_explicit_paths)
1661 /* We need to find if the paths match now. For example, these should all match:
1663 * csdm optional/csdm
1664 * csdm/ban optional/crab/csdm/ban
1666 const char *aliasptr = alias;
1667 const char *localptr = localpath;
1671 if (*aliasptr != *localptr)
1673 /* We have to knock one path off */
1674 local_explicit_paths--;
1675 if (alias_explicit_paths > local_explicit_paths)
1677 /* Skip out if we're gonna have an impossible match */
1680 /* Eat up localptr tokens until we get a result */
1681 while (((localptr - localpath) < (int)local_path_end)
1683 && *localptr != '\\')
1687 /* Check if we hit the end of our searchable area.
1688 * This probably isn't possible because of the path
1689 * count check, but it's a good idea anyway.
1691 if ((localptr - localpath) >= (int)local_path_end)
1695 /* Consume the slash token */
1698 /* Reset the alias pointer so we can continue consuming */
1704 * This is safe because if localptr terminates early, aliasptr will too
1708 /* We should never reach the end of the string because of this check. */
1709 bool aliasend = (aliasptr - alias) > (int)alias_path_end;
1710 bool localend = (localptr - localpath) > (int)local_path_end;
1711 if (aliasend || localend)
1713 if (aliasend && localend)
1715 /* we matched, and we can break out now */
1719 /* Otherwise, we've hit the end somehow and rest won't match up. Break out. */
1724 /* If we got here, it's safe to compare the next two tokens */
1725 if (*localptr != *aliasptr)
1736 /* If we got here, it's time to compare filenames */
1737 const char *aliasptr = alias;
1738 const char *localptr = localpath;
1740 if (alias_explicit_paths)
1742 aliasptr = &alias[alias_path_end + 1];
1745 if (local_explicit_paths)
1747 localptr = &localpath[local_path_end + 1];
1752 if (*aliasptr == '*')
1754 /* First, see if this is the last character */
1755 if ((unsigned)(aliasptr - alias) == alias_len - 1)
1757 /* If so, there's no need to match anything else */
1760 /* Otherwise, we need to search for an appropriate matching sequence in local.
1761 * Note that we only need to search up to the next asterisk.
1765 const char *local_orig = localptr;
1769 while (*aliasptr != '\0' && *aliasptr != '*')
1771 /* Since aliasptr is never '\0', localptr hitting the end will fail */
1772 if (*aliasptr != *localptr)
1782 /* If we didn't get a match, we need to advance the search stream.
1783 * This will let us skip tokens while still searching for another match.
1785 localptr = ++local_orig;
1786 /* Make sure we don't go out of bounds */
1787 if (*localptr == '\0')
1798 /* If we got a match, move on to the next token */
1801 } else if (*aliasptr == '\0') {
1802 if (*localptr == '\0'
1804 strcmp(localptr, ".smx") == 0)
1810 } else if (*aliasptr != *localptr) {
1820 bool CPluginManager::IsLateLoadTime() const
1822 return (m_AllPluginsLoaded || !smcore.IsMapLoading());
1825 void CPluginManager::OnSourceModAllInitialized()
1827 m_MyIdent = g_ShareSys.CreateCoreIdentity();
1830 handlesys->InitAccessDefaults(NULL, &sec);
1832 sec.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY;
1833 sec.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY;
1835 g_PluginType = handlesys->CreateType("Plugin", this, 0, NULL, &sec, m_MyIdent, NULL);
1836 g_PluginIdent = g_ShareSys.CreateIdentType("PLUGIN");
1838 rootmenu->AddRootConsoleCommand("plugins", "Manage Plugins", this);
1840 g_ShareSys.AddInterface(NULL, GetOldAPI());
1842 m_pOnLibraryAdded = forwardsys->CreateForward("OnLibraryAdded", ET_Ignore, 1, NULL, Param_String);
1843 m_pOnLibraryRemoved = forwardsys->CreateForward("OnLibraryRemoved", ET_Ignore, 1, NULL, Param_String);
1846 void CPluginManager::OnSourceModShutdown()
1848 rootmenu->RemoveRootConsoleCommand("plugins", this);
1852 handlesys->RemoveType(g_PluginType, m_MyIdent);
1853 g_ShareSys.DestroyIdentType(g_PluginIdent);
1854 g_ShareSys.DestroyIdentity(m_MyIdent);
1856 forwardsys->ReleaseForward(m_pOnLibraryAdded);
1857 forwardsys->ReleaseForward(m_pOnLibraryRemoved);
1860 ConfigResult CPluginManager::OnSourceModConfigChanged(const char *key,
1862 ConfigSource source,
1866 if (strcmp(key, "BlockBadPlugins") == 0) {
1867 if (strcasecmp(value, "yes") == 0)
1869 m_bBlockBadPlugins = true;
1870 } else if (strcasecmp(value, "no") == 0) {
1871 m_bBlockBadPlugins = false;
1873 smcore.Format(error, maxlength, "Invalid value: must be \"yes\" or \"no\"");
1874 return ConfigResult_Reject;
1876 return ConfigResult_Accept;
1878 return ConfigResult_Ignore;
1881 void CPluginManager::OnHandleDestroy(HandleType_t type, void *object)
1883 /* We don't care about the internal object, actually */
1886 bool CPluginManager::GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize)
1888 *pSize = ((CPlugin *)object)->CalcMemUsage();
1892 IPlugin *CPluginManager::PluginFromHandle(Handle_t handle, HandleError *err)
1900 sec.pIdentity = m_MyIdent;
1902 if ((_err=handlesys->ReadHandle(handle, g_PluginType, &sec, (void **)&pPlugin)) != HandleError_None)
1915 CPlugin *CPluginManager::GetPluginByOrder(int num)
1917 if (num < 1 || num > (int)GetPluginCount())
1925 SourceHook::List<CPlugin *>::iterator iter;
1926 for (iter = m_plugins.begin(); iter != m_plugins.end() && id < num; iter++, id++) {}
1933 const char *CPluginManager::GetStatusText(PluginStatus st)
1937 case Plugin_Running:
1943 case Plugin_Uncompiled:
1944 return "Uncompiled";
1945 case Plugin_BadLoad:
1955 static inline bool IS_STR_FILLED(const char *text)
1957 return text[0] != '\0';
1960 void CPluginManager::OnRootConsoleCommand(const char *cmdname, const CCommand &command)
1962 int argcount = smcore.Argc(command);
1965 const char *cmd = smcore.Arg(command, 2);
1966 if (strcmp(cmd, "list") == 0)
1969 unsigned int id = 1;
1970 int plnum = GetPluginCount();
1974 rootmenu->ConsolePrint("[SM] No plugins loaded");
1979 rootmenu->ConsolePrint("[SM] Listing %d plugin%s:", GetPluginCount(), (plnum > 1) ? "s" : "");
1983 SourceHook::List<CPlugin *>::iterator iter;
1984 SourceHook::List<CPlugin *> m_FailList;
1986 for (iter = m_plugins.begin(); iter != m_plugins.end(); iter++, id++)
1989 assert(pl->GetStatus() != Plugin_Created);
1991 const sm_plugininfo_t *info = pl->GetPublicInfo();
1992 if (pl->GetStatus() != Plugin_Running && !pl->IsSilentlyFailed())
1994 len += smcore.Format(buffer, sizeof(buffer), " %02d <%s>", id, GetStatusText(pl->GetStatus()));
1996 if (pl->GetStatus() <= Plugin_Error)
1998 /* Plugin has failed to load. */
1999 m_FailList.push_back(pl);
2004 len += smcore.Format(buffer, sizeof(buffer), " %02d", id);
2006 if (pl->GetStatus() < Plugin_Created)
2008 if (pl->IsSilentlyFailed())
2009 len += smcore.Format(&buffer[len], sizeof(buffer)-len, " Disabled:");
2010 len += smcore.Format(&buffer[len], sizeof(buffer)-len, " \"%s\"", (IS_STR_FILLED(info->name)) ? info->name : pl->GetFilename());
2011 if (IS_STR_FILLED(info->version))
2013 len += smcore.Format(&buffer[len], sizeof(buffer)-len, " (%s)", info->version);
2015 if (IS_STR_FILLED(info->author))
2017 smcore.Format(&buffer[len], sizeof(buffer)-len, " by %s", info->author);
2022 smcore.Format(&buffer[len], sizeof(buffer)-len, " %s", pl->m_filename);
2024 rootmenu->ConsolePrint("%s", buffer);
2027 if (!m_FailList.empty())
2029 rootmenu->ConsolePrint("Load Errors:");
2031 SourceHook::List<CPlugin *>::iterator _iter;
2035 for (_iter=m_FailList.begin(); _iter!=m_FailList.end(); _iter++)
2037 pl = (CPlugin *)*_iter;
2038 rootmenu->ConsolePrint("%s: %s",(IS_STR_FILLED(pl->GetPublicInfo()->name)) ? pl->GetPublicInfo()->name : pl->GetFilename(), pl->m_errormsg);
2044 else if (strcmp(cmd, "load") == 0)
2048 rootmenu->ConsolePrint("[SM] Usage: sm plugins load <file>");
2054 const char *filename = smcore.Arg(command, 3);
2056 char pluginfile[256];
2057 const char *ext = libsys->GetFileExtension(filename) ? "" : ".smx";
2058 g_pSM->BuildPath(Path_None, pluginfile, sizeof(pluginfile), "%s%s", filename, ext);
2060 IPlugin *pl = LoadPlugin(pluginfile, false, PluginType_MapUpdated, error, sizeof(error), &wasloaded);
2064 rootmenu->ConsolePrint("[SM] Plugin %s is already loaded.", pluginfile);
2070 rootmenu->ConsolePrint("[SM] Loaded plugin %s successfully.", pluginfile);
2074 rootmenu->ConsolePrint("[SM] Plugin %s failed to load: %s.", pluginfile, error);
2079 else if (strcmp(cmd, "unload") == 0)
2083 rootmenu->ConsolePrint("[SM] Usage: sm plugins unload <#|file>");
2089 const char *arg = smcore.Arg(command, 3);
2090 int id = strtol(arg, &end, 10);
2094 pl = GetPluginByOrder(id);
2097 rootmenu->ConsolePrint("[SM] Plugin index %d not found.", id);
2103 char pluginfile[256];
2104 const char *ext = libsys->GetFileExtension(arg) ? "" : ".smx";
2105 g_pSM->BuildPath(Path_None, pluginfile, sizeof(pluginfile), "%s%s", arg, ext);
2107 if (!m_LoadLookup.retrieve(pluginfile, &pl))
2109 rootmenu->ConsolePrint("[SM] Plugin %s is not loaded.", pluginfile);
2114 char name[PLATFORM_MAX_PATH];
2116 if (pl->GetStatus() < Plugin_Created)
2118 const sm_plugininfo_t *info = pl->GetPublicInfo();
2119 smcore.Format(name, sizeof(name), (IS_STR_FILLED(info->name)) ? info->name : pl->GetFilename());
2123 smcore.Format(name, sizeof(name), "%s", pl->GetFilename());
2126 if (UnloadPlugin(pl))
2128 rootmenu->ConsolePrint("[SM] Plugin %s unloaded successfully.", name);
2132 rootmenu->ConsolePrint("[SM] Failed to unload plugin %s.", name);
2137 else if (strcmp(cmd, "unload_all") == 0)
2139 g_PluginSys.UnloadAll();
2140 rootmenu->ConsolePrint("[SM] All plugins have been unloaded.");
2143 else if (strcmp(cmd, "load_lock") == 0)
2145 if (m_LoadingLocked)
2147 rootmenu->ConsolePrint("[SM] There is already a loading lock in effect.");
2151 m_LoadingLocked = true;
2152 rootmenu->ConsolePrint("[SM] Loading is now locked; no plugins will be loaded or re-loaded.");
2156 else if (strcmp(cmd, "load_unlock") == 0)
2158 if (m_LoadingLocked)
2160 m_LoadingLocked = false;
2161 rootmenu->ConsolePrint("[SM] The loading lock is no longer in effect.");
2165 rootmenu->ConsolePrint("[SM] There was no loading lock in effect.");
2169 else if (strcmp(cmd, "info") == 0)
2173 rootmenu->ConsolePrint("[SM] Usage: sm plugins info <#>");
2179 const char *arg = smcore.Arg(command, 3);
2180 int id = strtol(arg, &end, 10);
2184 pl = GetPluginByOrder(id);
2187 rootmenu->ConsolePrint("[SM] Plugin index %d not found.", id);
2193 char pluginfile[256];
2194 const char *ext = libsys->GetFileExtension(arg) ? "" : ".smx";
2195 g_pSM->BuildPath(Path_None, pluginfile, sizeof(pluginfile), "%s%s", arg, ext);
2197 if (!m_LoadLookup.retrieve(pluginfile, &pl))
2199 rootmenu->ConsolePrint("[SM] Plugin %s is not loaded.", pluginfile);
2204 const sm_plugininfo_t *info = pl->GetPublicInfo();
2206 rootmenu->ConsolePrint(" Filename: %s", pl->GetFilename());
2207 if (pl->GetStatus() <= Plugin_Error)
2209 if (IS_STR_FILLED(info->name))
2211 if (IS_STR_FILLED(info->description))
2213 rootmenu->ConsolePrint(" Title: %s (%s)", info->name, info->description);
2215 rootmenu->ConsolePrint(" Title: %s", info->name);
2218 if (IS_STR_FILLED(info->author))
2220 rootmenu->ConsolePrint(" Author: %s", info->author);
2222 if (IS_STR_FILLED(info->version))
2224 rootmenu->ConsolePrint(" Version: %s", info->version);
2226 if (IS_STR_FILLED(info->url))
2228 rootmenu->ConsolePrint(" URL: %s", info->url);
2230 if (pl->GetStatus() == Plugin_Error)
2232 rootmenu->ConsolePrint(" Error: %s", pl->m_errormsg);
2236 if (pl->GetStatus() == Plugin_Running)
2238 rootmenu->ConsolePrint(" Status: running");
2242 rootmenu->ConsolePrint(" Status: not running");
2245 const char *typestr = "";
2246 switch (pl->GetType())
2248 case PluginType_MapUpdated:
2249 typestr = "Map Change if Updated";
2251 case PluginType_MapOnly:
2252 typestr = "Map Change";
2254 case PluginType_Private:
2255 case PluginType_Global:
2260 rootmenu->ConsolePrint(" Reloads: %s", typestr);
2262 if (pl->m_FileVersion >= 3)
2264 rootmenu->ConsolePrint(" Timestamp: %s", pl->m_DateTime);
2267 unsigned char *pCodeHash = pl->m_pRuntime->GetCodeHash();
2268 unsigned char *pDataHash = pl->m_pRuntime->GetDataHash();
2270 char combinedHash[33];
2271 for (int i = 0; i < 16; i++)
2272 smcore.Format(combinedHash + (i * 2), 3, "%02x", pCodeHash[i] ^ pDataHash[i]);
2274 rootmenu->ConsolePrint(" Hash: %s", combinedHash);
2278 rootmenu->ConsolePrint(" Load error: %s", pl->m_errormsg);
2279 if (pl->GetStatus() < Plugin_Created)
2281 rootmenu->ConsolePrint(" File info: (title \"%s\") (version \"%s\")",
2282 info->name ? info->name : "<none>",
2283 info->version ? info->version : "<none>");
2284 if (IS_STR_FILLED(info->url))
2286 rootmenu->ConsolePrint(" File URL: %s", info->url);
2293 else if (strcmp(cmd, "refresh") == 0)
2295 smcore.DoGlobalPluginLoads();
2296 rootmenu->ConsolePrint("[SM] The plugin list has been refreshed and reloaded.");
2299 else if (strcmp(cmd, "reload") == 0)
2303 rootmenu->ConsolePrint("[SM] Usage: sm plugins reload <#|file>");
2309 const char *arg = smcore.Arg(command, 3);
2310 int id = strtol(arg, &end, 10);
2314 pl = GetPluginByOrder(id);
2317 rootmenu->ConsolePrint("[SM] Plugin index %d not found.", id);
2323 char pluginfile[256];
2324 const char *ext = libsys->GetFileExtension(arg) ? "" : ".smx";
2325 g_pSM->BuildPath(Path_None, pluginfile, sizeof(pluginfile), "%s%s", arg, ext);
2327 if (!m_LoadLookup.retrieve(pluginfile, &pl))
2329 rootmenu->ConsolePrint("[SM] Plugin %s is not loaded.", pluginfile);
2334 char name[PLATFORM_MAX_PATH];
2335 const sm_plugininfo_t *info = pl->GetPublicInfo();
2336 if (pl->GetStatus() <= Plugin_Paused)
2337 strcpy(name, (IS_STR_FILLED(info->name)) ? info->name : pl->GetFilename());
2339 strcpy(name, pl->GetFilename());
2341 if (ReloadPlugin(pl))
2343 rootmenu->ConsolePrint("[SM] Plugin %s reloaded successfully.", name);
2347 rootmenu->ConsolePrint("[SM] Failed to reload plugin %s.", name);
2354 /* Draw the main menu */
2355 rootmenu->ConsolePrint("SourceMod Plugins Menu:");
2356 rootmenu->DrawGenericOption("info", "Information about a plugin");
2357 rootmenu->DrawGenericOption("list", "Show loaded plugins");
2358 rootmenu->DrawGenericOption("load", "Load a plugin");
2359 rootmenu->DrawGenericOption("load_lock", "Prevents any more plugins from being loaded");
2360 rootmenu->DrawGenericOption("load_unlock", "Re-enables plugin loading");
2361 rootmenu->DrawGenericOption("refresh", "Reloads/refreshes all plugins in the plugins folder");
2362 rootmenu->DrawGenericOption("reload", "Reloads a plugin");
2363 rootmenu->DrawGenericOption("unload", "Unload a plugin");
2364 rootmenu->DrawGenericOption("unload_all", "Unloads all plugins");
2367 bool CPluginManager::ReloadPlugin(CPlugin *pl)
2369 List<CPlugin *>::iterator iter;
2370 char filename[PLATFORM_MAX_PATH];
2376 strcpy(filename, pl->m_filename);
2377 ptype = pl->GetType();
2379 for (iter=m_plugins.begin(); iter!=m_plugins.end(); iter++, id++)
2387 if (!UnloadPlugin(pl))
2391 if (!(newpl=LoadPlugin(filename, true, ptype, NULL, 0, &wasloaded)))
2396 for (iter=m_plugins.begin(); iter!=m_plugins.end(); iter++)
2398 if ((*iter) == (CPlugin *)newpl)
2400 m_plugins.erase(iter);
2406 for (i=1, iter=m_plugins.begin(); iter!=m_plugins.end() && i<id; iter++, i++)
2409 m_plugins.insert(iter, (CPlugin *)newpl);
2414 void CPluginManager::RefreshAll()
2416 /* If we're in a load lock, just skip this whole bit. */
2417 if (m_LoadingLocked)
2422 List<CPlugin *>::iterator iter;
2423 List<CPlugin *> tmp_list = m_plugins;
2427 for (iter=tmp_list.begin(); iter!=tmp_list.end(); iter++)
2430 if (pl->GetType() == PluginType_MapOnly)
2432 UnloadPlugin((IPlugin *)pl);
2434 else if (pl->GetType() == PluginType_MapUpdated)
2436 t = pl->GetFileTimeStamp();
2437 if (!t || t > pl->GetTimeStamp())
2439 pl->SetTimeStamp(t);
2440 UnloadPlugin((IPlugin *)pl);
2446 void CPluginManager::_SetPauseState(CPlugin *pl, bool paused)
2448 List<IPluginsListener *>::iterator iter;
2449 IPluginsListener *pListener;
2450 for (iter=m_listeners.begin(); iter!=m_listeners.end(); iter++)
2452 pListener = (*iter);
2453 pListener->OnPluginPauseChange(pl, paused);
2457 void CPluginManager::AddFunctionsToForward(const char *name, IChangeableForward *pForward)
2459 List<CPlugin *>::iterator iter;
2461 IPluginFunction *pFunction;
2463 for (iter = m_plugins.begin(); iter != m_plugins.end(); iter++)
2467 if (pPlugin->GetStatus() <= Plugin_Paused)
2469 pFunction = pPlugin->GetBaseContext()->GetFunctionByName(name);
2473 pForward->AddFunction(pFunction);
2479 CPlugin *CPluginManager::GetPluginFromIdentity(IdentityToken_t *pToken)
2481 if (pToken->type != g_PluginIdent)
2486 return (CPlugin *)(pToken->ptr);
2489 void CPluginManager::OnLibraryAction(const char *lib, LibraryAction action)
2493 case LibraryAction_Removed:
2494 m_pOnLibraryRemoved->PushString(lib);
2495 m_pOnLibraryRemoved->Execute(NULL);
2497 case LibraryAction_Added:
2498 m_pOnLibraryAdded->PushString(lib);
2499 m_pOnLibraryAdded->Execute(NULL);
2504 bool CPluginManager::LibraryExists(const char *lib)
2506 List<CPlugin *>::iterator iter;
2508 for (iter=m_plugins.begin();
2509 iter!=m_plugins.end();
2512 CPlugin *pl = (*iter);
2513 if (pl->GetStatus() != Plugin_Running)
2517 List<String>::iterator s_iter;
2518 for (s_iter = pl->m_Libraries.begin();
2519 s_iter != pl->m_Libraries.end();
2522 if ((*s_iter).compare(lib) == 0)
2532 void CPluginManager::AllPluginsLoaded()
2534 List<CPlugin *>::iterator iter;
2537 for (iter=m_plugins.begin(); iter!=m_plugins.end(); iter++)
2540 pl->Call_OnAllPluginsLoaded();
2544 void CPluginManager::UnloadAll()
2546 List<CPlugin *>::iterator iter;
2547 while ( (iter = m_plugins.begin()) != m_plugins.end() )
2549 UnloadPlugin((*iter));
2553 int CPluginManager::GetOrderOfPlugin(IPlugin *pl)
2556 List<CPlugin *>::iterator iter;
2558 for (iter = m_plugins.begin(); iter != m_plugins.end(); iter++, id++)
2569 SMPlugin *CPluginManager::FindPluginByConsoleArg(const char *arg)
2575 id = strtol(arg, &end, 10);
2579 pl = GetPluginByOrder(id);
2587 char pluginfile[256];
2588 const char *ext = libsys->GetFileExtension(arg) ? "" : ".smx";
2589 smcore.Format(pluginfile, sizeof(pluginfile), "%s%s", arg, ext);
2591 if (!m_LoadLookup.retrieve(pluginfile, &pl))
2598 void CPluginManager::OnSourceModMaxPlayersChanged(int newvalue)
2600 SyncMaxClients(newvalue);
2603 void CPluginManager::SyncMaxClients(int max_clients)
2605 List<CPlugin *>::iterator iter;
2607 for (iter = m_plugins.begin(); iter != m_plugins.end(); iter++)
2609 (*iter)->SyncMaxClients(max_clients);
2613 const CVector<SMPlugin *> *CPluginManager::ListPlugins()
2615 CVector<SMPlugin *> *list = new CVector<SMPlugin *>();
2617 for (List<CPlugin *>::iterator iter = m_plugins.begin(); iter != m_plugins.end(); iter++)
2618 list->push_back((*iter));
2623 void CPluginManager::FreePluginList(const CVector<SMPlugin *> *list)
2625 delete const_cast<CVector<SMPlugin *> *>(list);
2628 class OldPluginAPI : public IPluginManager
2631 IPlugin *LoadPlugin(const char *path,
2638 return g_PluginSys.LoadPlugin(path, debug, type, error, maxlength, wasloaded);
2641 bool UnloadPlugin(IPlugin *plugin)
2643 return g_PluginSys.UnloadPlugin(plugin);
2646 IPlugin *FindPluginByContext(const sp_context_t *ctx)
2648 return g_PluginSys.FindPluginByContext(ctx);
2651 unsigned int GetPluginCount()
2653 return g_PluginSys.GetPluginCount();
2656 IPluginIterator *GetPluginIterator()
2658 return g_PluginSys.GetPluginIterator();
2661 void AddPluginsListener(IPluginsListener *listener)
2663 g_PluginSys.AddPluginsListener(listener);
2666 void RemovePluginsListener(IPluginsListener *listener)
2668 g_PluginSys.RemovePluginsListener(listener);
2671 IPlugin *PluginFromHandle(Handle_t handle, HandleError *err)
2673 return g_PluginSys.PluginFromHandle(handle, err);
2677 static OldPluginAPI sOldPluginAPI;
2679 IPluginManager *CPluginManager::GetOldAPI()
2681 return &sOldPluginAPI;