core/systems/PluginSys.cpp
author David Anderson <dvander@alliedmods.net>
Wed Jan 24 23:43:31 2007 +0000 (2007-01-24)
changeset 336 3f4073018d76
parent 333 b668cbd49c35
child 340 c39c68f4874c
permissions -rw-r--r--
Added initial admin system natives
Added a few API changes to the admin system
Exposed more interfaces
     1 #include <stdio.h>
     2 #include "PluginSys.h"
     3 #include "ShareSys.h"
     4 #include "LibrarySys.h"
     5 #include "HandleSys.h"
     6 #include "sourcemm_api.h"
     7 #include "sourcemod.h"
     8 #include "CTextParsers.h"
     9 #include "CLogger.h"
    10 #include "ExtensionSys.h"
    11 #include "sm_srvcmds.h"
    12 #include "sm_stringutil.h"
    13 
    14 CPluginManager g_PluginSys;
    15 HandleType_t g_PluginType = 0;
    16 IdentityType_t g_PluginIdent = 0;
    17 
    18 CPlugin::CPlugin(const char *file)
    19 {
    20 	static int MySerial = 0;
    21 
    22 	m_type = PluginType_Private;
    23 	m_status = Plugin_Uncompiled;
    24 	m_serial = ++MySerial;
    25 	m_plugin = NULL;
    26 	m_errormsg[256] = '\0';
    27 	snprintf(m_filename, sizeof(m_filename), "%s", file);
    28 	m_handle = 0;
    29 	m_ident = NULL;
    30 }
    31 
    32 CPlugin::~CPlugin()
    33 {
    34 	if (m_handle)
    35 	{
    36 		HandleSecurity sec;
    37 		sec.pOwner = g_PluginSys.GetIdentity();
    38 		sec.pIdentity = sec.pOwner;
    39 
    40 		g_HandleSys.FreeHandle(m_handle, &sec);
    41 		g_ShareSys.DestroyIdentity(m_ident);
    42 	}
    43 
    44 	if (m_ctx.base)
    45 	{
    46 		delete m_ctx.base;
    47 		m_ctx.base = NULL;
    48 	}
    49 	if (m_ctx.ctx)
    50 	{
    51 		m_ctx.vm->FreeContext(m_ctx.ctx);
    52 		m_ctx.ctx = NULL;
    53 	}
    54 	if (m_ctx.co)
    55 	{
    56 		m_ctx.vm->AbortCompilation(m_ctx.co);
    57 		m_ctx.co = NULL;
    58 	}
    59 
    60 	if (m_plugin)
    61 	{
    62 		g_pSourcePawn->FreeFromMemory(m_plugin);
    63 		m_plugin = NULL;
    64 	}
    65 }
    66 
    67 void CPlugin::InitIdentity()
    68 {
    69 	if (!m_handle)
    70 	{
    71 		m_ident = g_ShareSys.CreateIdentity(g_PluginIdent);
    72 		m_handle = g_HandleSys.CreateHandle(g_PluginType, this, g_PluginSys.GetIdentity(), g_PluginSys.GetIdentity(), NULL);
    73 		m_ctx.base->SetIdentity(m_ident);
    74 	}
    75 }
    76 
    77 CPlugin *CPlugin::CreatePlugin(const char *file, char *error, size_t maxlength)
    78 {
    79 	char fullpath[PLATFORM_MAX_PATH+1];
    80 	g_SourceMod.BuildPath(Path_SM, fullpath, sizeof(fullpath), "plugins/%s", file);
    81 	FILE *fp = fopen(fullpath, "rb");
    82 
    83 	CPlugin *pPlugin = new CPlugin(file);
    84 
    85 	if (!fp)
    86 	{
    87 		if (error)
    88 		{
    89 			snprintf(error, maxlength, "Unable to open file");
    90 		}
    91 		pPlugin->m_status = Plugin_BadLoad;
    92 		return pPlugin;
    93 	}
    94 
    95 	int err;
    96 	sp_plugin_t *pl = g_pSourcePawn->LoadFromFilePointer(fp, &err);
    97 	if (pl == NULL)
    98 	{
    99 		fclose(fp);
   100 		if (error)
   101 		{
   102 			snprintf(error, maxlength, "Error %d while parsing plugin", err);
   103 		}
   104 		pPlugin->m_status = Plugin_BadLoad;
   105 		return pPlugin;
   106 	}
   107 
   108 	fclose(fp);
   109 
   110 	pPlugin->m_plugin = pl;
   111 
   112 	return pPlugin;
   113 }
   114 
   115 ICompilation *CPlugin::StartMyCompile(IVirtualMachine *vm)
   116 {
   117 	if (!m_plugin)
   118 	{
   119 		return NULL;
   120 	}
   121 
   122 	/* :NOTICE: We will eventually need to change these natives
   123 	 * for swapping in new contexts
   124 	 */
   125 	if (m_ctx.co || m_ctx.ctx)
   126 	{
   127 		return NULL;
   128 	}
   129 
   130 	m_status = Plugin_Uncompiled;
   131 
   132 	m_ctx.vm = vm;
   133 	m_ctx.co = vm->StartCompilation(m_plugin);
   134 
   135 	return m_ctx.co;
   136 }
   137 
   138 void CPlugin::CancelMyCompile()
   139 {
   140 	if (!m_ctx.co)
   141 	{
   142 		return;
   143 	}
   144 
   145 	m_ctx.vm->AbortCompilation(m_ctx.co);
   146 	m_ctx.co = NULL;
   147 	m_ctx.vm = NULL;
   148 }
   149 
   150 bool CPlugin::FinishMyCompile(char *error, size_t maxlength)
   151 {
   152 	if (!m_ctx.co)
   153 	{
   154 		return false;
   155 	}
   156 
   157 	int err;
   158 	m_ctx.ctx = m_ctx.vm->CompileToContext(m_ctx.co, &err);
   159 	if (!m_ctx.ctx)
   160 	{
   161 		memset(&m_ctx, 0, sizeof(m_ctx));
   162 		if (error)
   163 		{
   164 			snprintf(error, maxlength, "Failed to compile (error %d)", err);
   165 		}
   166 		return false;
   167 	}
   168 
   169 	m_ctx.base = new BaseContext(m_ctx.ctx);
   170 	m_ctx.base->SetRunnable(false);
   171 	m_ctx.ctx->user[SM_CONTEXTVAR_MYSELF] = (void *)this;
   172 
   173 	m_status = Plugin_Created;
   174 	m_ctx.co = NULL;
   175 
   176 	UpdateInfo();
   177 
   178 	return true;
   179 }
   180 
   181 void CPlugin::SetErrorState(PluginStatus status, const char *error_fmt, ...)
   182 {
   183 	m_status = status;
   184 
   185 	va_list ap;
   186 	va_start(ap, error_fmt);
   187 	vsnprintf(m_errormsg, sizeof(m_errormsg), error_fmt, ap);
   188 	va_end(ap);
   189 
   190 	if (m_ctx.base)
   191 	{
   192 		m_ctx.base->SetRunnable(false);
   193 	}
   194 }
   195 
   196 void CPlugin::UpdateInfo()
   197 {
   198 	/* Now grab the info */
   199 	uint32_t idx;
   200 	IPluginContext *base = GetBaseContext();
   201 	int err = base->FindPubvarByName("myinfo", &idx);
   202 
   203 	if (err == SP_ERROR_NONE)
   204 	{
   205 		struct sm_plugininfo_c_t
   206 		{
   207 			cell_t name;
   208 			cell_t description;
   209 			cell_t author;
   210 			cell_t version;
   211 			cell_t url;
   212 		};
   213 		sm_plugininfo_c_t *cinfo;
   214 		cell_t local_addr;
   215 
   216 		base->GetPubvarAddrs(idx, &local_addr, (cell_t **)&cinfo);
   217 		base->LocalToString(cinfo->name, (char **)&m_info.name);
   218 		base->LocalToString(cinfo->description, (char **)&m_info.description);
   219 		base->LocalToString(cinfo->author, (char **)&m_info.author);
   220 		base->LocalToString(cinfo->url, (char **)&m_info.url);
   221 		base->LocalToString(cinfo->version, (char **)&m_info.version);
   222 	}
   223 
   224 	m_info.author = m_info.author ? m_info.author : "";
   225 	m_info.description = m_info.description ? m_info.description : "";
   226 	m_info.name = m_info.name ? m_info.name : "";
   227 	m_info.url = m_info.url ? m_info.url : "";
   228 	m_info.version = m_info.version ? m_info.version : "";
   229 }
   230 
   231 void CPlugin::Call_OnPluginInit()
   232 {
   233 	if (m_status != Plugin_Loaded)
   234 	{
   235 		return;
   236 	}
   237 
   238 	m_status = Plugin_Running;
   239 
   240 	cell_t result;
   241 	IPluginFunction *pFunction = m_ctx.base->GetFunctionByName("OnPluginInit");
   242 	if (!pFunction)
   243 	{
   244 		return;
   245 	}
   246 
   247 	pFunction->PushCell(m_handle);
   248 	pFunction->Execute(&result);
   249 }
   250 
   251 void CPlugin::Call_OnPluginUnload()
   252 {
   253 	if (m_status < Plugin_Paused)
   254 	{
   255 		return;
   256 	}
   257 
   258 	cell_t result;
   259 	IPluginFunction *pFunction = m_ctx.base->GetFunctionByName("OnPluginUnload");
   260 	if (!pFunction)
   261 	{
   262 		return;
   263 	}
   264 
   265 	pFunction->Execute(&result);
   266 }
   267 
   268 bool CPlugin::Call_AskPluginLoad(char *error, size_t maxlength)
   269 {
   270 	if (m_status != Plugin_Created)
   271 	{
   272 		return false;
   273 	}
   274 
   275 	m_status = Plugin_Loaded;
   276 	m_ctx.base->SetRunnable(true);
   277 
   278 	int err;
   279 	cell_t result;
   280 	IPluginFunction *pFunction = m_ctx.base->GetFunctionByName("AskPluginLoad");
   281 
   282 	if (!pFunction)
   283 	{
   284 		return true;
   285 	}
   286 
   287 	pFunction->PushCell(m_handle);
   288 	pFunction->PushCell(g_PluginSys.IsLateLoadTime() ? 1 : 0);
   289 	pFunction->PushStringEx(error, maxlength, 0, SM_PARAM_COPYBACK);
   290 	pFunction->PushCell(maxlength);
   291 	if ((err=pFunction->Execute(&result)) != SP_ERROR_NONE)
   292 	{
   293 		return false;
   294 	}
   295 
   296 	if (!result || m_status != Plugin_Loaded)
   297 	{
   298 		return false;
   299 	}
   300 
   301 	return true;
   302 }
   303 
   304 const sp_plugin_t *CPlugin::GetPluginStructure() const
   305 {
   306 	return m_plugin;
   307 }
   308 
   309 IPluginContext *CPlugin::GetBaseContext() const
   310 {
   311 	return m_ctx.base;
   312 }
   313 
   314 sp_context_t *CPlugin::GetContext() const
   315 {
   316 	return m_ctx.ctx;
   317 }
   318 
   319 const char *CPlugin::GetFilename() const
   320 {
   321 	return m_filename;
   322 }
   323 
   324 PluginType CPlugin::GetType() const
   325 {
   326 	return m_type;
   327 }
   328 
   329 const sm_plugininfo_t *CPlugin::GetPublicInfo() const
   330 {
   331 	return &m_info;
   332 }
   333 
   334 unsigned int CPlugin::GetSerial() const
   335 {
   336 	return m_serial;
   337 }
   338 
   339 PluginStatus CPlugin::GetStatus() const
   340 {
   341 	return m_status;
   342 }
   343 
   344 bool CPlugin::IsDebugging() const
   345 {
   346 	if (!m_ctx.ctx)
   347 	{
   348 		return false;
   349 	}
   350 
   351 	return ((m_ctx.ctx->flags & SP_FLAG_DEBUG) == SP_FLAG_DEBUG);
   352 }
   353 
   354 bool CPlugin::SetPauseState(bool paused)
   355 {
   356 	if (paused && GetStatus() != Plugin_Paused)
   357 	{
   358 		return false;
   359 	} else if (!paused && GetStatus() != Plugin_Running) {
   360 		return false;
   361 	}
   362 
   363 	m_status = (paused) ? Plugin_Paused : Plugin_Running;
   364 
   365 	IPluginFunction *pFunction = m_ctx.base->GetFunctionByName("OnPluginPauseChange");
   366 	if (pFunction)
   367 	{
   368 		cell_t result;
   369 		pFunction->PushCell(paused ? 1 : 0);
   370 		pFunction->Execute(&result);
   371 	}
   372 
   373 	return true;
   374 }
   375 
   376 IdentityToken_t *CPlugin::GetIdentity() const
   377 {
   378 	return m_ident;
   379 }
   380 
   381 bool CPlugin::ToggleDebugMode(bool debug, char *error, size_t maxlength)
   382 {
   383 	int err;
   384 
   385 	if (!IsRunnable())
   386 	{
   387 		if (error)
   388 		{
   389 			snprintf(error, maxlength, "Plugin is not runnable.");
   390 		}
   391 		return false;
   392 	}
   393 
   394 	if (debug && IsDebugging())
   395 	{
   396 		if (error)
   397 		{
   398 			snprintf(error, maxlength, "Plugin is already in debug mode.");
   399 		}
   400 		return false;
   401 	} else if (!debug && !IsDebugging()) {
   402 		if (error)
   403 		{
   404 			snprintf(error, maxlength, "Plugins is already in production mode.");
   405 		}
   406 		return false;
   407 	}
   408 
   409 	ICompilation *co = g_pVM->StartCompilation(m_ctx.ctx->plugin);
   410 	if (!g_pVM->SetCompilationOption(co, "debug", (debug) ? "1" : "0"))
   411 	{
   412 		if (error)
   413 		{
   414 			snprintf(error, maxlength, "Failed to change plugin mode (JIT failure).");
   415 		}
   416 		return false;
   417 	}
   418 
   419 	sp_context_t *new_ctx = g_pVM->CompileToContext(co, &err);
   420 
   421 	if (new_ctx)
   422 	{
   423 		memcpy(new_ctx->memory, m_ctx.ctx->memory, m_ctx.ctx->mem_size);
   424 		new_ctx->hp = m_ctx.ctx->hp;
   425 		new_ctx->sp = m_ctx.ctx->sp;
   426 		new_ctx->frm = m_ctx.ctx->frm;
   427 		new_ctx->dbreak = m_ctx.ctx->dbreak;
   428 		new_ctx->context = m_ctx.ctx->context;
   429 		memcpy(new_ctx->user, m_ctx.ctx->user, sizeof(m_ctx.ctx->user));
   430 
   431 		g_pVM->FreeContext(m_ctx.ctx);
   432 		m_ctx.ctx = new_ctx;
   433 		m_ctx.base->SetContext(new_ctx);
   434 
   435 		UpdateInfo();
   436 	} else {
   437 		if (error)
   438 		{
   439 			snprintf(error, maxlength, "Failed to recompile plugin (JIT error %d).", err);
   440 		}
   441 		return false;
   442 	}
   443 
   444 	return true;
   445 }
   446 
   447 bool CPlugin::IsRunnable() const
   448 {
   449 	return (m_status <= Plugin_Paused) ? true : false;
   450 }
   451 
   452 time_t CPlugin::GetFileTimeStamp()
   453 {
   454 	char path[PLATFORM_MAX_PATH+1];
   455 	g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "plugins/%s", m_filename);
   456 #ifdef PLATFORM_WINDOWS
   457 	struct _stat s;
   458 	if (_stat(path, &s) != 0)
   459 #else if defined PLATFORM_POSIX
   460 	struct stat s;
   461 	if (stat(path, &s) != 0)
   462 #endif
   463 	{
   464 		g_Logger.LogError("Could not obtain plugin time stamp, error: %d", errno);
   465 		return 0;
   466 	} else {
   467 		return s.st_mtime;
   468 	}
   469 }
   470 
   471 time_t CPlugin::GetTimeStamp() const
   472 {
   473 	return m_LastAccess;
   474 }
   475 
   476 void CPlugin::SetTimeStamp(time_t t)
   477 {
   478 	m_LastAccess = t;
   479 }
   480 
   481 /*******************
   482  * PLUGIN ITERATOR *
   483  *******************/
   484 
   485 CPluginManager::CPluginIterator::CPluginIterator(List<CPlugin *> *_mylist)
   486 {
   487 	mylist = _mylist;
   488 	Reset();
   489 }
   490 
   491 IPlugin *CPluginManager::CPluginIterator::GetPlugin()
   492 {
   493 	return (*current);
   494 }
   495 
   496 bool CPluginManager::CPluginIterator::MorePlugins()
   497 {
   498 	return (current != mylist->end());
   499 }
   500 
   501 void CPluginManager::CPluginIterator::NextPlugin()
   502 {
   503 	current++;
   504 }
   505 
   506 void CPluginManager::CPluginIterator::Release()
   507 {
   508 	g_PluginSys.ReleaseIterator(this);
   509 }
   510 
   511 CPluginManager::CPluginIterator::~CPluginIterator()
   512 {
   513 }
   514 
   515 void CPluginManager::CPluginIterator::Reset()
   516 {
   517 	current = mylist->begin();
   518 }
   519 
   520 /******************
   521  * PLUGIN MANAGER *
   522  ******************/
   523 
   524 CPluginManager::CPluginManager()
   525 {
   526 	m_LoadLookup = sm_trie_create();
   527 	m_AllPluginsLoaded = false;
   528 	m_MyIdent = NULL;
   529 }
   530 
   531 CPluginManager::~CPluginManager()
   532 {
   533 	/* :NOTICE: 
   534 	 * Ignore the fact that there might be plugins in the cache.
   535 	 * This usually means that Core is not being unloaded properly, and everything
   536 	 * will crash anyway.  YAY
   537 	 */
   538 	sm_trie_destroy(m_LoadLookup);
   539 }
   540 
   541 
   542 void CPluginManager::LoadAll_FirstPass(const char *config, const char *basedir)
   543 {
   544 	/* First read in the database of plugin settings */
   545 	SMCParseError err;
   546 	unsigned int line, col;
   547 	m_AllPluginsLoaded = false;
   548 	if ((err=g_TextParser.ParseFile_SMC(config, &m_PluginInfo, &line, &col)) != SMCParse_Okay)
   549 	{
   550 		g_Logger.LogError("[SM] Encountered fatal error parsing file \"%s\"", config);
   551 		const char *err_msg = g_TextParser.GetSMCErrorString(err);
   552 		if (err_msg)
   553 		{
   554 			g_Logger.LogError("[SM] Parse error encountered: \"%s\"", err_msg);
   555 		}
   556 	}
   557 
   558 	LoadPluginsFromDir(basedir, NULL);
   559 }
   560 
   561 void CPluginManager::LoadPluginsFromDir(const char *basedir, const char *localpath)
   562 {
   563 	char base_path[PLATFORM_MAX_PATH+1];
   564 
   565 	/* Form the current path to start reading from */
   566 	if (localpath == NULL)
   567 	{
   568 		g_LibSys.PathFormat(base_path, sizeof(base_path), "%s", basedir);
   569 	} else {
   570 		g_LibSys.PathFormat(base_path, sizeof(base_path), "%s/%s", basedir, localpath);
   571 	}
   572 
   573 	IDirectory *dir = g_LibSys.OpenDirectory(base_path);
   574 
   575 	if (!dir)
   576 	{
   577 		char error[256];
   578 		g_LibSys.GetPlatformError(error, sizeof(error));
   579 		g_Logger.LogError("[SM] Failure reading from plugins path: %s", localpath);
   580 		g_Logger.LogError("[SM] Platform returned error: %s", error);
   581 		return;
   582 	}
   583 
   584 	while (dir->MoreFiles())
   585 	{
   586 		if (dir->IsEntryDirectory() 
   587 			&& (strcmp(dir->GetEntryName(), ".") != 0)
   588 			&& (strcmp(dir->GetEntryName(), "..") != 0)
   589 			&& (strcmp(dir->GetEntryName(), "disabled") != 0)
   590 			&& (strcmp(dir->GetEntryName(), "optional") != 0))
   591 		{
   592 			char new_local[PLATFORM_MAX_PATH+1];
   593 			if (localpath == NULL)
   594 			{
   595 				/* If no path yet, don't add a former slash */
   596 				snprintf(new_local, sizeof(new_local), "%s", dir->GetEntryName());
   597 			} else {
   598 				g_LibSys.PathFormat(new_local, sizeof(new_local), "%s/%s", localpath, dir->GetEntryName());
   599 			}
   600 			LoadPluginsFromDir(basedir, new_local);
   601 		} else if (dir->IsEntryFile()) {
   602 			const char *name = dir->GetEntryName();
   603 			size_t len = strlen(name);
   604 			if (len >= 4
   605 				&& strcmp(&name[len-4], ".smx") == 0)
   606 			{
   607 				/* If the filename matches, load the plugin */
   608 				char plugin[PLATFORM_MAX_PATH+1];
   609 				if (localpath == NULL)
   610 				{
   611 					snprintf(plugin, sizeof(plugin), "%s", name);
   612 				} else {
   613 					g_LibSys.PathFormat(plugin, sizeof(plugin), "%s/%s", localpath, name);
   614 				}
   615 				LoadAutoPlugin(plugin);
   616 			}
   617 		}
   618 		dir->NextEntry();
   619 	}
   620 	g_LibSys.CloseDirectory(dir);
   621 }
   622 
   623 //well i have discovered that gabe newell is very fat, so i wrote this comment now
   624 bool CPluginManager::_LoadPlugin(CPlugin **_plugin, const char *path, bool debug, PluginType type, char error[], size_t err_max)
   625 {
   626 	/**
   627 	 * Does this plugin already exist?
   628 	 */
   629 	CPlugin *pPlugin;
   630 	if (sm_trie_retrieve(m_LoadLookup, path, (void **)&pPlugin))
   631 	{
   632 		/* First check the type */
   633 		PluginType type = pPlugin->GetType();
   634 		if (type == PluginType_Private
   635 			|| type == PluginType_Global)
   636 		{
   637 			return true;
   638 		}
   639 		/* Check to see if we should try reloading it */
   640 		if (pPlugin->GetStatus() == Plugin_BadLoad
   641 			|| pPlugin->GetStatus() == Plugin_Error
   642 			|| pPlugin->GetStatus() == Plugin_Failed)
   643 		{
   644 			UnloadPlugin(pPlugin);
   645 		}
   646 	}
   647 
   648 	pPlugin = CPlugin::CreatePlugin(path, error, err_max);
   649 
   650 	assert(pPlugin != NULL);
   651 
   652 	pPlugin->m_type = PluginType_MapUpdated;
   653 
   654 	ICompilation *co = NULL;
   655 
   656 	if (pPlugin->m_status == Plugin_Uncompiled)
   657 	{
   658 		co = pPlugin->StartMyCompile(g_pVM);
   659 	}
   660 
   661 	PluginSettings *pset;
   662 	unsigned int setcount = m_PluginInfo.GetSettingsNum();
   663 	for (unsigned int i=0; i<setcount; i++)
   664 	{
   665 		if ((pset=m_PluginInfo.GetSettingsIfMatch(i, path)) == NULL)
   666 		{
   667 			continue;
   668 		}
   669 		pPlugin->m_type = pset->type_val;
   670 		if (co)
   671 		{
   672 			for (unsigned int j=0; j<pset->opts_num; j++)
   673 			{
   674 				const char *key, *val;
   675 				m_PluginInfo.GetOptionsForPlugin(pset, j, &key, &val);
   676 				if (!key || !val)
   677 				{
   678 					continue;
   679 				}
   680 				if (!g_pVM->SetCompilationOption(co, key, val))
   681 				{
   682 					if (error)
   683 					{
   684 						snprintf(error, err_max, "Unable to set JIT option (key \"%s\") (value \"%s\")", key, val);
   685 					}
   686 					pPlugin->CancelMyCompile();
   687 					co = NULL;
   688 					break;
   689 				}
   690 			}
   691 		}
   692 	}
   693 
   694 	/* Do the actual compiling */
   695 	if (co)
   696 	{
   697 		pPlugin->FinishMyCompile(NULL, 0);
   698 		co = NULL;
   699 	}
   700 
   701 	/* Get the status */
   702 	if (pPlugin->GetStatus() == Plugin_Created)
   703 	{
   704 		AddCoreNativesToPlugin(pPlugin);
   705 		pPlugin->InitIdentity();
   706 		if (pPlugin->Call_AskPluginLoad(error, err_max))
   707 		{
   708 			/* Autoload any modules */
   709 			LoadOrRequireExtensions(pPlugin, 1, error, err_max);
   710 		}
   711 	}
   712 
   713 	/* Save the time stamp */
   714 	time_t t = pPlugin->GetFileTimeStamp();
   715 	pPlugin->SetTimeStamp(t);
   716 
   717 	if (_plugin)
   718 	{
   719 		*_plugin = pPlugin;
   720 	}
   721 
   722 	return (pPlugin->GetStatus() == Plugin_Loaded);
   723 }
   724 
   725 IPlugin *CPluginManager::LoadPlugin(const char *path, bool debug, PluginType type, char error[], size_t err_max)
   726 {
   727 	CPlugin *pl;
   728 
   729 	if (!_LoadPlugin(&pl, path, debug, type, error, err_max))
   730 	{
   731 		delete pl;
   732 		return NULL;
   733 	}
   734 
   735 	AddPlugin(pl);
   736 
   737 	/* Run second pass if we need to */
   738 	if (IsLateLoadTime() && pl->GetStatus() == Plugin_Loaded)
   739 	{
   740 		if (!RunSecondPass(pl, error, err_max))
   741 		{
   742 			UnloadPlugin(pl);
   743 			return NULL;
   744 		}
   745 	}
   746 
   747 	return pl;
   748 }
   749 
   750 void CPluginManager::LoadAutoPlugin(const char *plugin)
   751 {
   752 	CPlugin *pl;
   753 	char error[255] = "Unknown error";
   754 
   755 	if (!_LoadPlugin(&pl, plugin, false, PluginType_MapUpdated, error, sizeof(error)))
   756 	{
   757 		g_Logger.LogError("[SM] Failed to load plugin \"%s\": %s", plugin, error);
   758 		pl->SetErrorState(Plugin_Failed, "%s", error);
   759 	}
   760 
   761 	AddPlugin(pl);
   762 }
   763 
   764 void CPluginManager::AddPlugin(CPlugin *pPlugin)
   765 {
   766 	List<IPluginsListener *>::iterator iter;
   767 	IPluginsListener *pListener;
   768 
   769 	for (iter=m_listeners.begin(); iter!=m_listeners.end(); iter++)
   770 	{
   771 		pListener = (*iter);
   772 		pListener->OnPluginCreated(pPlugin);
   773 	}
   774 
   775 	m_plugins.push_back(pPlugin);
   776 	sm_trie_insert(m_LoadLookup, pPlugin->m_filename, pPlugin);
   777 }
   778 
   779 void CPluginManager::LoadAll_SecondPass()
   780 {
   781 	List<CPlugin *>::iterator iter;
   782 	CPlugin *pPlugin;
   783 
   784 	char error[256];
   785 	for (iter=m_plugins.begin(); iter!=m_plugins.end(); iter++)
   786 	{
   787 		pPlugin = (*iter);
   788 		if (pPlugin->GetStatus() == Plugin_Loaded)
   789 		{
   790 			error[0] = '\0';
   791 			if (!RunSecondPass(pPlugin, error, sizeof(error)))
   792 			{
   793 				g_Logger.LogError("[SM] Unable to load plugin \"%s\": %s", pPlugin->GetFilename(), error);
   794 				pPlugin->SetErrorState(Plugin_Failed, "%s", error);
   795 			}
   796 		}
   797 	}
   798 
   799 	m_AllPluginsLoaded = true;
   800 }
   801 
   802 bool CPluginManager::LoadOrRequireExtensions(CPlugin *pPlugin, unsigned int pass, char *error, size_t maxlength)
   803 {
   804 	/* Find any extensions this plugin needs */
   805 	struct _ext
   806 	{
   807 		cell_t name;
   808 		cell_t file;
   809 		cell_t autoload;
   810 		cell_t required;
   811 	} *ext;
   812 
   813 	IPluginContext *pBase = pPlugin->GetBaseContext();
   814 	uint32_t num = pBase->GetPubVarsNum();
   815 	sp_pubvar_t *pubvar;
   816 	IExtension *pExt;
   817 	char path[PLATFORM_MAX_PATH+1];
   818 	char *file, *name;
   819 	for (uint32_t i=0; i<num; i++)
   820 	{
   821 		if (pBase->GetPubvarByIndex(i, &pubvar) != SP_ERROR_NONE)
   822 		{
   823 			continue;
   824 		}
   825 		if (strncmp(pubvar->name, "__ext_", 6) == 0)
   826 		{
   827 			ext = (_ext *)pubvar->offs;
   828 			if (!ext->required && !ext->autoload)
   829 			{
   830 				continue;
   831 			}
   832 			if (pBase->LocalToString(ext->file, &file) != SP_ERROR_NONE)
   833 			{
   834 				continue;
   835 			}
   836 			if (pBase->LocalToString(ext->name, &name) != SP_ERROR_NONE)
   837 			{
   838 				continue;
   839 			}
   840 			if (pass == 1)
   841 			{
   842 				/* Attempt to auto-load if necessary */
   843 				if (ext->autoload)
   844 				{
   845 					g_LibSys.PathFormat(path, PLATFORM_MAX_PATH, "%s", file);
   846 					g_Extensions.LoadAutoExtension(path);
   847 				}
   848 			} else if (pass == 2) {
   849 				/* Is this required? */
   850 				if (ext->required)
   851 				{
   852 					g_LibSys.PathFormat(path, PLATFORM_MAX_PATH, "%s", file);
   853 					if ((pExt = g_Extensions.FindExtensionByFile(path)) == NULL)
   854 					{
   855 						pExt = g_Extensions.FindExtensionByName(name);
   856 					}
   857 					/* :TODO: should we bind to unloaded extensions?
   858 					 * Currently the extension manager will ignore this.
   859 					 */
   860 					if (!pExt || !pExt->IsRunning(NULL, 0))
   861 					{
   862 						if (error)
   863 						{
   864 							snprintf(error, maxlength, "Required extension \"%s\" file(\"%s\") not running", name, file);
   865 						}
   866 						return false;
   867 					} else {
   868 						g_Extensions.BindChildPlugin(pExt, pPlugin);
   869 					}
   870 				}
   871 			}
   872 		}
   873 	}
   874 
   875 	return true;
   876 }
   877 
   878 bool CPluginManager::RunSecondPass(CPlugin *pPlugin, char *error, size_t maxlength)
   879 {
   880 	/* Second pass for extension requirements */
   881 	if (!LoadOrRequireExtensions(pPlugin, 2, error, maxlength))
   882 	{
   883 		return false;
   884 	}
   885 
   886 	/* Bind all extra natives */
   887 	g_Extensions.BindAllNativesToPlugin(pPlugin);
   888 
   889 	/* Find any unbound natives
   890 	 * Right now, these are not allowed
   891 	 */
   892 	IPluginContext *pContext = pPlugin->GetBaseContext();
   893 	uint32_t num = pContext->GetNativesNum();
   894 	sp_native_t *native;
   895 	for (unsigned int i=0; i<num; i++)
   896 	{
   897 		if (pContext->GetNativeByIndex(i, &native) != SP_ERROR_NONE)
   898 		{
   899 			break;
   900 		}
   901 		if (native->status == SP_NATIVE_UNBOUND)
   902 		{
   903 			if (error)
   904 			{
   905 				snprintf(error, maxlength, "Native \"%s\" was not found.", native->name);
   906 			}
   907 			return false;
   908 		}
   909 	}
   910 
   911 	/* Finish by telling all listeners */
   912 	List<IPluginsListener *>::iterator iter;
   913 	IPluginsListener *pListener;
   914 	for (iter=m_listeners.begin(); iter!=m_listeners.end(); iter++)
   915 	{
   916 		pListener = (*iter);
   917 		pListener->OnPluginLoaded(pPlugin);
   918 	}
   919 	
   920 	/* Tell this plugin to finish initializing itself */
   921 	pPlugin->Call_OnPluginInit();
   922 
   923 	return true;
   924 }
   925 
   926 void CPluginManager::AddCoreNativesToPlugin(CPlugin *pPlugin)
   927 {
   928 	List<sp_nativeinfo_t *>::iterator iter;
   929 
   930 	for (iter=m_natives.begin(); iter!=m_natives.end(); iter++)
   931 	{
   932 		sp_nativeinfo_t *natives = (*iter);
   933 		IPluginContext *ctx = pPlugin->GetBaseContext();
   934 		unsigned int i=0;
   935 		/* Attempt to bind every native! */
   936 		while (natives[i].func != NULL)
   937 		{
   938 			ctx->BindNative(&natives[i++]);
   939 		}
   940 	}
   941 }
   942 
   943 bool CPluginManager::UnloadPlugin(IPlugin *plugin)
   944 {
   945 	CPlugin *pPlugin = (CPlugin *)plugin;
   946 
   947 	/* This prevents removal during insertion or anything else weird */
   948 	if (m_plugins.find(pPlugin) == m_plugins.end())
   949 	{
   950 		return false;
   951 	}
   952 
   953 	List<IPluginsListener *>::iterator iter;
   954 	IPluginsListener *pListener;
   955 
   956 	if (pPlugin->GetStatus() <= Plugin_Error)
   957 	{
   958 		/* Notify listeners of unloading */
   959 		for (iter=m_listeners.begin(); iter!=m_listeners.end(); iter++)
   960 		{
   961 			pListener = (*iter);
   962 			pListener->OnPluginUnloaded(pPlugin);
   963 		}
   964 		/* Notify plugin */
   965 		pPlugin->Call_OnPluginUnload();
   966 	}
   967 
   968 	for (iter=m_listeners.begin(); iter!=m_listeners.end(); iter++)
   969 	{
   970 		/* Notify listeners of destruction */
   971 		pListener = (*iter);
   972 		pListener->OnPluginDestroyed(pPlugin);
   973 	}
   974 
   975 	/* Remove us from the lookup table and linked list */
   976 	m_plugins.remove(pPlugin);
   977 	sm_trie_delete(m_LoadLookup, pPlugin->m_filename);
   978 	
   979 	/* Tell the plugin to delete itself */
   980 	delete pPlugin;
   981 
   982 	return true;
   983 }
   984 
   985 IPlugin *CPluginManager::FindPluginByContext(const sp_context_t *ctx)
   986 {
   987 	IPlugin *pl = (IPlugin *)ctx->user[SM_CONTEXTVAR_MYSELF];
   988 	return pl;
   989 }
   990 
   991 unsigned int CPluginManager::GetPluginCount()
   992 {
   993 	return m_plugins.size();
   994 }
   995 
   996 void CPluginManager::AddPluginsListener(IPluginsListener *listener)
   997 {
   998 	m_listeners.push_back(listener);
   999 }
  1000 
  1001 void CPluginManager::RemovePluginsListener(IPluginsListener *listener)
  1002 {
  1003 	m_listeners.remove(listener);
  1004 }
  1005 
  1006 IPluginIterator *CPluginManager::GetPluginIterator()
  1007 {
  1008 	if (m_iters.empty())
  1009 	{
  1010 		return new CPluginIterator(&m_plugins);
  1011 	} else {
  1012 		CPluginIterator *iter = m_iters.front();
  1013 		m_iters.pop();
  1014 		iter->Reset();
  1015 		return iter;
  1016 	}
  1017 }
  1018 
  1019 void CPluginManager::ReleaseIterator(CPluginIterator *iter)
  1020 {
  1021 	m_iters.push(iter);
  1022 }
  1023 
  1024 bool CPluginManager::TestAliasMatch(const char *alias, const char *localpath)
  1025 {
  1026 	/* As an optimization, we do not call strlen, but compute the length in the first pass */
  1027 	size_t alias_len = 0;
  1028 	size_t local_len = 0;
  1029 
  1030 	const char *ptr = alias;
  1031 	unsigned int alias_explicit_paths = 0;
  1032 	unsigned int alias_path_end = 0;
  1033 	while (*ptr != '\0')
  1034 	{
  1035 		if (*ptr == '\\' || *ptr == '/')
  1036 		{
  1037 			alias_explicit_paths++;
  1038 			alias_path_end = alias_len;
  1039 		}
  1040 		alias_len++;
  1041 		ptr++;
  1042 	}
  1043 
  1044 	if (alias_explicit_paths && alias_path_end == alias_len - 1)
  1045 	{
  1046 		/* Trailing slash is totally invalid here */
  1047 		return false;
  1048 	}
  1049 
  1050 	ptr = localpath;
  1051 	unsigned int local_explicit_paths = 0;
  1052 	unsigned int local_path_end = 0;
  1053 	while (*ptr != '\0')
  1054 	{
  1055 		if (*ptr == '\\' || *ptr == '/')
  1056 		{
  1057 			local_explicit_paths++;
  1058 			local_path_end = local_len;
  1059 		}
  1060 		local_len++;
  1061 		ptr++;
  1062 	}
  1063 
  1064 	/* If the alias has more explicit paths than the real path,
  1065 	 * no match will be possible.
  1066 	 */
  1067 	if (alias_explicit_paths > local_explicit_paths)
  1068 	{
  1069 		return false;
  1070 	}
  1071 
  1072 	if (alias_explicit_paths)
  1073 	{
  1074 		/* We need to find if the paths match now.  For example, these should all match:
  1075 		 * csdm     csdm
  1076 		 * csdm     optional/csdm
  1077 		 * csdm/ban optional/crab/csdm/ban
  1078 		*/
  1079 		const char *aliasptr = alias;
  1080 		const char *localptr = localpath;
  1081 		bool match = true;
  1082 		do
  1083 		{
  1084 			if (*aliasptr != *localptr)
  1085 			{
  1086 				/* We have to knock one path off */
  1087 				local_explicit_paths--;
  1088 				if (alias_explicit_paths > local_explicit_paths)
  1089 				{
  1090 					/* Skip out if we're gonna have an impossible match */
  1091 					return false;
  1092 				}
  1093 				/* Eat up localptr tokens until we get a result */
  1094 				while (((localptr - localpath) < (int)local_path_end)
  1095 					&& *localptr != '/'
  1096 					&& *localptr != '\\')
  1097 				{
  1098 					localptr++;
  1099 				}
  1100 				/* Check if we hit the end of our searchable area.
  1101 				 * This probably isn't possible because of the path 
  1102 				 * count check, but it's a good idea anyway.
  1103 				 */
  1104 				if ((localptr - localpath) >= (int)local_path_end)
  1105 				{
  1106 					return false;
  1107 				} else {
  1108 					/* Consume the slash token */
  1109 					localptr++;
  1110 				}
  1111 				/* Reset the alias pointer so we can continue consuming */
  1112 				aliasptr = alias;
  1113 				match = false;
  1114 				continue;
  1115 			}
  1116 			/* Note:
  1117 			 * This is safe because if localptr terminates early, aliasptr will too
  1118 			 */
  1119 			do
  1120 			{
  1121 				/* We should never reach the end of the string because of this check. */
  1122 				bool aliasend = (aliasptr - alias) > (int)alias_path_end;
  1123 				bool localend = (localptr - localpath) > (int)local_path_end;
  1124 				if (aliasend || localend)
  1125 				{
  1126 					if (aliasend && localend)
  1127 					{
  1128 						/* we matched, and we can break out now */
  1129 						match = true;
  1130 						break;
  1131 					}
  1132 					/* Otherwise, we've hit the end somehow and rest won't match up.  Break out. */
  1133 					match = false;
  1134 					break;
  1135 				}
  1136 
  1137 				/* If we got here, it's safe to compare the next two tokens */
  1138 				if (*localptr != *aliasptr)
  1139 				{
  1140 					match = false;
  1141 					break;
  1142 				}
  1143 				localptr++;
  1144 				aliasptr++;
  1145 			} while (true);
  1146 		} while (!match);
  1147 	}
  1148 
  1149 	/* If we got here, it's time to compare filenames */
  1150 	const char *aliasptr = alias;
  1151 	const char *localptr = localpath;
  1152 
  1153 	if (alias_explicit_paths)
  1154 	{
  1155 		aliasptr = &alias[alias_path_end + 1];
  1156 	}
  1157 
  1158 	if (local_explicit_paths)
  1159 	{
  1160 		localptr = &localpath[local_path_end + 1];
  1161 	}
  1162 
  1163 	while (true)
  1164 	{
  1165 		if (*aliasptr == '*')
  1166 		{
  1167 			/* First, see if this is the last character */
  1168 			if (aliasptr - alias == alias_len - 1)
  1169 			{
  1170 				/* If so, there's no need to match anything else */
  1171 				return true;
  1172 			}
  1173 			/* Otherwise, we need to search for an appropriate matching sequence in local.
  1174 			 * Note that we only need to search up to the next asterisk.
  1175 			 */
  1176 			aliasptr++;
  1177 			bool match = true;
  1178 			const char *local_orig = localptr;
  1179 			do
  1180 			{
  1181 				match = true;
  1182 				while (*aliasptr != '\0' && *aliasptr != '*')
  1183 				{
  1184 					/* Since aliasptr is never '\0', localptr hitting the end will fail */
  1185 					if (*aliasptr != *localptr)
  1186 					{
  1187 						match = false;
  1188 						break;
  1189 					}
  1190 					aliasptr++;
  1191 					localptr++;
  1192 				}
  1193 				if (!match)
  1194 				{
  1195 					/* If we didn't get a match, we need to advance the search stream.
  1196 					 * This will let us skip tokens while still searching for another match.
  1197 					 */
  1198 					localptr = ++local_orig;
  1199 					/* Make sure we don't go out of bounds */
  1200 					if (*localptr == '\0')
  1201 					{
  1202 						break;
  1203 					}
  1204 				}
  1205 			} while (!match);
  1206 
  1207 			if (!match)
  1208 			{
  1209 				return false;
  1210 			} else {
  1211 				/* If we got a match, move on to the next token */
  1212 				continue;
  1213 			}
  1214 		} else if (*aliasptr == '\0') {
  1215 			if (*localptr == '\0'
  1216 				||
  1217 				strcmp(localptr, ".smx") == 0)
  1218 			{
  1219 				return true;
  1220 			} else {
  1221 				return false;
  1222 			}
  1223 		} else if (*aliasptr != *localptr) {
  1224 			return false;
  1225 		}
  1226 		aliasptr++;
  1227 		localptr++;
  1228 	}
  1229 
  1230 	return true;
  1231 }
  1232 
  1233 bool CPluginManager::IsLateLoadTime() const
  1234 {
  1235 	return (m_AllPluginsLoaded || !g_SourceMod.IsMapLoading());
  1236 }
  1237 
  1238 void CPluginManager::OnSourceModAllInitialized()
  1239 {
  1240 	m_MyIdent = g_ShareSys.CreateCoreIdentity();
  1241 
  1242 	HandleAccess sec;
  1243 	g_HandleSys.InitAccessDefaults(NULL, &sec);
  1244 
  1245 	sec.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY;
  1246 	sec.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY;
  1247 
  1248 	g_PluginType = g_HandleSys.CreateType("Plugin", this, 0, NULL, &sec, m_MyIdent, NULL);
  1249 	g_PluginIdent = g_ShareSys.CreateIdentType("PLUGIN");
  1250 
  1251 	g_RootMenu.AddRootConsoleCommand("plugins", "Manage Plugins", this);
  1252 
  1253 	g_ShareSys.AddInterface(NULL, this);
  1254 }
  1255 
  1256 void CPluginManager::OnSourceModShutdown()
  1257 {
  1258 	g_RootMenu.RemoveRootConsoleCommand("plugins", this);
  1259 
  1260 	List<CPlugin *>::iterator iter;
  1261 	while ( (iter = m_plugins.begin()) != m_plugins.end() )
  1262 	{
  1263 		UnloadPlugin((*iter));
  1264 	}
  1265 
  1266 	g_HandleSys.RemoveType(g_PluginType, m_MyIdent);
  1267 	g_ShareSys.DestroyIdentType(g_PluginIdent);
  1268 	g_ShareSys.DestroyIdentity(m_MyIdent);
  1269 }
  1270 
  1271 void CPluginManager::OnHandleDestroy(HandleType_t type, void *object)
  1272 {
  1273 	/* We don't care about the internal object, actually */
  1274 }
  1275 
  1276 void CPluginManager::RegisterNativesFromCore(sp_nativeinfo_t *natives)
  1277 {
  1278 	m_natives.push_back(natives);
  1279 }
  1280 
  1281 IPlugin *CPluginManager::PluginFromHandle(Handle_t handle, HandleError *err)
  1282 {
  1283 	IPlugin *pPlugin;
  1284 	HandleError _err;
  1285 
  1286 	HandleSecurity sec;
  1287 
  1288 	sec.pOwner = NULL;
  1289 	sec.pIdentity = m_MyIdent;
  1290 
  1291 	if ((_err=g_HandleSys.ReadHandle(handle, g_PluginType, &sec, (void **)&pPlugin)) != HandleError_None)
  1292 	{
  1293 		pPlugin = NULL;
  1294 	}
  1295 
  1296 	if (err)
  1297 	{
  1298 		*err = _err;
  1299 	}
  1300 
  1301 	return pPlugin;
  1302 }
  1303 
  1304 CPlugin *CPluginManager::GetPluginByOrder(int num)
  1305 {
  1306 	if (num < 1 || num > (int)GetPluginCount())
  1307 	{
  1308 		return NULL;
  1309 	}
  1310 
  1311 	CPlugin *pl;
  1312 	int id = 1;
  1313 
  1314 	IPluginIterator *iter = GetPluginIterator();
  1315 	for (; iter->MorePlugins() && id<num; iter->NextPlugin(), id++) {}
  1316 
  1317 	pl = (CPlugin *)(iter->GetPlugin());
  1318 	iter->Release();
  1319 
  1320 	return pl;
  1321 }
  1322 
  1323 const char *CPluginManager::GetStatusText(PluginStatus st)
  1324 {
  1325 	switch (st)
  1326 	{
  1327 	case Plugin_Running:
  1328 		return "Running";
  1329 	case Plugin_Paused:
  1330 		return "Paused";
  1331 	case Plugin_Error:
  1332 		return "Error";
  1333 	case Plugin_Uncompiled:
  1334 		return "Uncompiled";
  1335 	case Plugin_BadLoad:
  1336 		return "Bad Load";
  1337 	case Plugin_Failed:
  1338 		return "Failed";
  1339 	default:
  1340 		assert(false);
  1341 		return "-";
  1342 	}
  1343 }
  1344 
  1345 #define IS_STR_FILLED(var) (var[0] != '\0')
  1346 void CPluginManager::OnRootConsoleCommand(const char *command, unsigned int argcount)
  1347 {
  1348 	if (argcount >= 3)
  1349 	{
  1350 		const char *cmd = g_RootMenu.GetArgument(2);
  1351 		if (strcmp(cmd, "list") == 0)
  1352 		{
  1353 			char buffer[256];
  1354 			unsigned int id = 1;
  1355 			int plnum = GetPluginCount();
  1356 
  1357 			if (!plnum)
  1358 			{
  1359 				g_RootMenu.ConsolePrint("[SM] No plugins loaded");
  1360 				return;
  1361 			} else {
  1362 				g_RootMenu.ConsolePrint("[SM] Displaying %d plugin%s:", GetPluginCount(), (plnum > 1) ? "s" : "");
  1363 			}
  1364 
  1365 			IPluginIterator *iter = GetPluginIterator();
  1366 			for (; iter->MorePlugins(); iter->NextPlugin(), id++)
  1367 			{
  1368 				IPlugin *pl = iter->GetPlugin();
  1369 				assert(pl->GetStatus() != Plugin_Created);
  1370 				int len = 0;
  1371 				const sm_plugininfo_t *info = pl->GetPublicInfo();
  1372 
  1373 				len += UTIL_Format(buffer, sizeof(buffer), "  %02d <%s>", id, GetStatusText(pl->GetStatus()));
  1374 				len += UTIL_Format(&buffer[len], sizeof(buffer)-len, " \"%s\"", (IS_STR_FILLED(info->name)) ? info->name : pl->GetFilename());
  1375 				if (IS_STR_FILLED(info->version))
  1376 				{
  1377 					len += UTIL_Format(&buffer[len], sizeof(buffer)-len, " (%s)", info->version);
  1378 				}
  1379 				if (IS_STR_FILLED(info->author))
  1380 				{
  1381 					UTIL_Format(&buffer[len], sizeof(buffer)-len, " by %s", info->author);
  1382 				}
  1383 				g_RootMenu.ConsolePrint("%s", buffer);
  1384 			}
  1385 
  1386 			iter->Release();
  1387 			return;
  1388 		} else if (strcmp(cmd, "load") == 0) {
  1389 			if (argcount < 4)
  1390 			{
  1391 				g_RootMenu.ConsolePrint("[SM] Usage: sm plugins load <file>");
  1392 				return;
  1393 			}
  1394 
  1395 			char error[128];
  1396 			const char *filename = g_RootMenu.GetArgument(3);
  1397 			IPlugin *pl = LoadPlugin(filename, false, PluginType_MapUpdated, error, sizeof(error));
  1398 
  1399 			if (pl)
  1400 			{
  1401 				g_RootMenu.ConsolePrint("[SM] Loaded plugin %s successfully.", filename);
  1402 			} else {
  1403 				g_RootMenu.ConsolePrint("[SM] Plugin %s failed to load: %s.", filename, error);
  1404 			}
  1405 
  1406 			return;
  1407 		} else if (strcmp(cmd, "unload") == 0) {
  1408 			if (argcount < 4)
  1409 			{
  1410 				g_RootMenu.ConsolePrint("[SM] Usage: sm plugins unload <#>");
  1411 				return;
  1412 			}
  1413 
  1414 			int id = 1;
  1415 			int num = atoi(g_RootMenu.GetArgument(3));
  1416 			if (num < 1 || num > (int)GetPluginCount())
  1417 			{
  1418 				g_RootMenu.ConsolePrint("[SM] Plugin index %d not found.", num);
  1419 				return;
  1420 			}
  1421 
  1422 			IPluginIterator *iter = GetPluginIterator();
  1423 			for (; iter->MorePlugins() && id<num; iter->NextPlugin(), id++)
  1424 				;
  1425 			IPlugin *pl = iter->GetPlugin();
  1426 
  1427 			char name[PLATFORM_MAX_PATH+1];
  1428 			const sm_plugininfo_t *info = pl->GetPublicInfo();
  1429 			strcpy(name, (IS_STR_FILLED(info->name)) ? info->name : pl->GetFilename());
  1430 
  1431 			if (UnloadPlugin(pl))
  1432 			{
  1433 				g_RootMenu.ConsolePrint("[SM] Plugin %s unloaded successfully.", name);
  1434 			} else {
  1435 				g_RootMenu.ConsolePrint("[SM] Failed to unload plugin %s.", name);
  1436 			}
  1437 
  1438 			iter->Release();
  1439 			return;
  1440 		} else if (strcmp(cmd, "info") == 0) {
  1441 			if (argcount < 4)
  1442 			{
  1443 				g_RootMenu.ConsolePrint("[SM] Usage: sm plugins info <#>");
  1444 				return;
  1445 			}
  1446 
  1447 			int id = 1;
  1448 			int num = atoi(g_RootMenu.GetArgument(3));
  1449 			if (num < 1 || num > (int)GetPluginCount())
  1450 			{
  1451 				g_RootMenu.ConsolePrint("[SM] Plugin index not found.");
  1452 				return;
  1453 			}
  1454 
  1455 			CPlugin *pl = GetPluginByOrder(num);
  1456 			const sm_plugininfo_t *info = pl->GetPublicInfo();
  1457 
  1458 			g_RootMenu.ConsolePrint("  Filename: %s", pl->GetFilename());
  1459 			if (pl->GetStatus() <= Plugin_Error)
  1460 			{
  1461 				if (IS_STR_FILLED(info->name))
  1462 				{
  1463 					g_RootMenu.ConsolePrint("  Title: %s", info->name);
  1464 				}
  1465 				if (IS_STR_FILLED(info->version))
  1466 				{
  1467 					g_RootMenu.ConsolePrint("  Version: %s", info->version);
  1468 				}
  1469 				if (IS_STR_FILLED(info->url))
  1470 				{
  1471 					g_RootMenu.ConsolePrint("  URL: %s", info->url);
  1472 				}
  1473 				if (IS_STR_FILLED(info->author))
  1474 				{
  1475 					g_RootMenu.ConsolePrint("  Author: %s", info->author);
  1476 				}
  1477 				if (IS_STR_FILLED(info->version))
  1478 				{
  1479 					g_RootMenu.ConsolePrint("  Version: %s", info->version);
  1480 				}
  1481 				if (IS_STR_FILLED(info->description))
  1482 				{
  1483 					g_RootMenu.ConsolePrint("  Description: %s", info->description);
  1484 				}	
  1485 				g_RootMenu.ConsolePrint("  Debugging: %s", pl->IsDebugging() ? "yes" : "no");
  1486 				g_RootMenu.ConsolePrint("  Paused: %s", pl->GetStatus() == Plugin_Running ? "yes" : "no");
  1487 			} else {
  1488 				g_RootMenu.ConsolePrint("  Load error: %s", pl->m_errormsg);
  1489 				g_RootMenu.ConsolePrint("  File info: (title \"%s\") (version \"%s\")", 
  1490 										info->name ? info->name : "<none>",
  1491 										info->version ? info->version : "<none>");
  1492 				if (IS_STR_FILLED(info->url))
  1493 				{
  1494 					g_RootMenu.ConsolePrint("  File URL: %s", info->url);
  1495 				}
  1496 			}
  1497 
  1498 			return;
  1499 		} else if (strcmp(cmd, "debug") == 0) {
  1500 			if (argcount < 5)
  1501 			{
  1502 				g_RootMenu.ConsolePrint("[SM] Usage: sm plugins debug <#> [on|off]");
  1503 				return;
  1504 			}
  1505 
  1506 			int num = atoi(g_RootMenu.GetArgument(3));
  1507 			if (num < 1 || num > (int)GetPluginCount())
  1508 			{
  1509 				g_RootMenu.ConsolePrint("[SM] Plugin index not found.");
  1510 				return;
  1511 			}
  1512 
  1513 			int res;
  1514 			const char *mode = g_RootMenu.GetArgument(4);
  1515 			if ((res=strcmp("on", mode)) && strcmp("off", mode))
  1516 			{
  1517 				g_RootMenu.ConsolePrint("[SM] The only possible options are \"on\" and \"off.\"");
  1518 				return;
  1519 			}
  1520 
  1521 			bool debug;
  1522 			if (!res)
  1523 			{
  1524 				debug = true;
  1525 			} else {
  1526 				debug = false;
  1527 			}
  1528 
  1529 			CPlugin *pl = GetPluginByOrder(num);
  1530 			if (debug && pl->IsDebugging())
  1531 			{
  1532 				g_RootMenu.ConsolePrint("[SM] This plugin is already in debug mode.");
  1533 				return;
  1534 			} else if (!debug && !pl->IsDebugging()) {
  1535 				g_RootMenu.ConsolePrint("[SM] Debug mode is already disabled in this plugin.");
  1536 				return;
  1537 			}
  1538 
  1539 			char error[256];
  1540 			if (pl->ToggleDebugMode(debug, error, sizeof(error)))
  1541 			{
  1542 				g_RootMenu.ConsolePrint("[SM] Successfully toggled debug mode on plugin %s.", pl->GetFilename());
  1543 				return;
  1544 			} else {
  1545 				g_RootMenu.ConsolePrint("[SM] Could not toggle debug mode on plugin %s.", pl->GetFilename());
  1546 				g_RootMenu.ConsolePrint("[SM] Plugin returned error: %s", error);
  1547 				return;
  1548 			}
  1549 		}
  1550 	}
  1551 
  1552 	/* Draw the main menu */
  1553 	g_RootMenu.ConsolePrint("SourceMod Plugins Menu:");
  1554 	g_RootMenu.DrawGenericOption("list", "Show loaded plugins");
  1555 	g_RootMenu.DrawGenericOption("load", "Load a plugin");
  1556 	g_RootMenu.DrawGenericOption("unload", "Unload a plugin");
  1557 	g_RootMenu.DrawGenericOption("info", "Information about a plugin");
  1558 	g_RootMenu.DrawGenericOption("debug", "Toggle debug mode on a plugin");
  1559 }
  1560 
  1561 void CPluginManager::ReloadOrUnloadPlugins()
  1562 {
  1563 	List<CPlugin *>::iterator iter;
  1564 	List<CPlugin *> tmp_list = m_plugins;
  1565 	CPlugin *pl;
  1566 	time_t t;
  1567 
  1568 	for (iter=tmp_list.begin(); iter!=tmp_list.end(); iter++)
  1569 	{
  1570 		pl = (*iter);
  1571 		if (pl->GetType() ==  PluginType_MapOnly)
  1572 		{
  1573 			UnloadPlugin((IPlugin *)pl);
  1574 		} else if (pl->GetType() == PluginType_MapUpdated) {
  1575 			t=pl->GetFileTimeStamp();
  1576 			if (!t)
  1577 			{
  1578 				continue;
  1579 			}
  1580 			if (t > pl->GetTimeStamp())
  1581 			{
  1582 				pl->SetTimeStamp(t);
  1583 				UnloadPlugin((IPlugin *)pl);
  1584 			}
  1585 		}
  1586 	}
  1587 }