core/ConVarManager.cpp
author Nicholas Hastings <skamonkey@gmail.com>
Tue Nov 30 13:09:30 2010 -0500 (2010-11-30)
changeset 3162 d8f90b56529f
parent 2890 54f2a62d6198
child 3367 18eb63085ca4
permissions -rw-r--r--
Fixed BanClient always banning by IP address (bug 4626, r=fyren).
     1 /**
     2  * vim: set ts=4 sw=4 tw=99 noet :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2009 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     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.
    11  * 
    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
    15  * details.
    16  *
    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/>.
    19  *
    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>.
    28  */
    29 
    30 #include "ConVarManager.h"
    31 #include "HalfLife2.h"
    32 #include "PluginSys.h"
    33 #include "ForwardSys.h"
    34 #include "HandleSys.h"
    35 #include "sm_srvcmds.h"
    36 #include "sm_stringutil.h"
    37 #include <sh_vector.h>
    38 #include <sm_trie_tpl.h>
    39 
    40 ConVarManager g_ConVarManager;
    41 
    42 #if SOURCE_ENGINE <= SE_DARKMESSIAH
    43 #define CallGlobalChangeCallbacks	CallGlobalChangeCallback
    44 #endif
    45 
    46 #if SOURCE_ENGINE >= SE_ORANGEBOX
    47 SH_DECL_HOOK3_void(ICvar, CallGlobalChangeCallbacks, SH_NOATTRIB, false, ConVar *, const char *, float);
    48 #else
    49 SH_DECL_HOOK2_void(ICvar, CallGlobalChangeCallbacks, SH_NOATTRIB, false, ConVar *, const char *);
    50 #endif
    51 
    52 #if SOURCE_ENGINE != SE_DARKMESSIAH
    53 SH_DECL_HOOK5_void(IServerGameDLL, OnQueryCvarValueFinished, SH_NOATTRIB, 0, QueryCvarCookie_t, edict_t *, EQueryCvarValueStatus, const char *, const char *);
    54 SH_DECL_HOOK5_void(IServerPluginCallbacks, OnQueryCvarValueFinished, SH_NOATTRIB, 0, QueryCvarCookie_t, edict_t *, EQueryCvarValueStatus, const char *, const char *);
    55 #endif
    56 
    57 const ParamType CONVARCHANGE_PARAMS[] = {Param_Cell, Param_String, Param_String};
    58 typedef List<const ConVar *> ConVarList;
    59 KTrie<ConVarInfo *> convar_cache;
    60 
    61 class ConVarReentrancyGuard
    62 {
    63 	ConVar *cvar;
    64 	ConVarReentrancyGuard *up;
    65 public:
    66 	static ConVarReentrancyGuard *chain;
    67 
    68 	ConVarReentrancyGuard(ConVar *cvar)
    69 		: cvar(cvar), up(chain)
    70 	{
    71 		chain = this;
    72 	}
    73 
    74 	~ConVarReentrancyGuard()
    75 	{
    76 		assert(chain == this);
    77 		chain = up;
    78 	}
    79 
    80 	static bool IsCvarInChain(ConVar *cvar)
    81 	{
    82 		ConVarReentrancyGuard *guard = chain;
    83 		while (guard != NULL)
    84 		{
    85 			if (guard->cvar == cvar)
    86 				return true;
    87 			guard = guard->up;
    88 		}
    89 		return false;
    90 	}
    91 };
    92 
    93 ConVarReentrancyGuard *ConVarReentrancyGuard::chain = NULL;
    94 
    95 ConVarManager::ConVarManager() : m_ConVarType(0), m_bIsDLLQueryHooked(false), m_bIsVSPQueryHooked(false)
    96 {
    97 }
    98 
    99 ConVarManager::~ConVarManager()
   100 {
   101 }
   102 
   103 void ConVarManager::OnSourceModStartup(bool late)
   104 {
   105 	HandleAccess sec;
   106 
   107 	/* Set up access rights for the 'ConVar' handle type */
   108 	sec.access[HandleAccess_Read] = 0;
   109 	sec.access[HandleAccess_Delete] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
   110 	sec.access[HandleAccess_Clone] = HANDLE_RESTRICT_IDENTITY | HANDLE_RESTRICT_OWNER;
   111 
   112 	/* Create the 'ConVar' handle type */
   113 	m_ConVarType = g_HandleSys.CreateType("ConVar", this, 0, NULL, &sec, g_pCoreIdent, NULL);
   114 }
   115 
   116 void ConVarManager::OnSourceModAllInitialized()
   117 {
   118 	/**
   119 	 * Episode 2 has this function by default, but the older versions do not.
   120 	 */
   121 #if SOURCE_ENGINE == SE_EPISODEONE
   122 	if (g_SMAPI->GetGameDLLVersion() >= 6)
   123 	{
   124 		SH_ADD_HOOK_MEMFUNC(IServerGameDLL, OnQueryCvarValueFinished, gamedll, this, &ConVarManager::OnQueryCvarValueFinished, false);
   125 		m_bIsDLLQueryHooked = true;
   126 	}
   127 #endif
   128 
   129 	SH_ADD_HOOK_STATICFUNC(ICvar, CallGlobalChangeCallbacks, icvar, OnConVarChanged, false);
   130 
   131 	g_PluginSys.AddPluginsListener(this);
   132 
   133 	/* Add the 'convars' option to the 'sm' console command */
   134 	g_RootMenu.AddRootConsoleCommand("cvars", "View convars created by a plugin", this);
   135 }
   136 
   137 void ConVarManager::OnSourceModShutdown()
   138 {
   139 	List<ConVarInfo *>::iterator iter = m_ConVars.begin();
   140 	HandleSecurity sec(NULL, g_pCoreIdent);
   141 
   142 	/* Iterate list of ConVarInfo structures, remove every one of them */
   143 	while (iter != m_ConVars.end())
   144 	{
   145 		ConVarInfo *pInfo = (*iter);
   146 
   147 		iter = m_ConVars.erase(iter);
   148 
   149 		g_HandleSys.FreeHandle(pInfo->handle, &sec);
   150 		if (pInfo->pChangeForward != NULL)
   151 		{
   152 			g_Forwards.ReleaseForward(pInfo->pChangeForward);
   153 		}
   154 		if (pInfo->sourceMod)
   155 		{
   156 			/* If we created it, we won't be tracking it, therefore it is 
   157 			 * safe to remove everything in one go.
   158 			 */
   159 			META_UNREGCVAR(pInfo->pVar);
   160 			delete [] pInfo->pVar->GetName();
   161 			delete [] pInfo->pVar->GetHelpText();
   162 			delete [] pInfo->pVar->GetDefault();
   163 			delete pInfo->pVar;
   164 		}
   165 		else
   166 		{
   167 			/* If we didn't create it, we might be tracking it.  Also, 
   168 			 * it could be unreadable.
   169 			 */
   170 			UntrackConCommandBase(pInfo->pVar, this);
   171 		}
   172 
   173 		/* It's not safe to read the name here, so we simply delete the 
   174 		 * the info struct and clear the lookup cache at the end.
   175 		 */
   176 		delete pInfo;
   177 	}
   178 	convar_cache.clear();
   179 
   180 #if SOURCE_ENGINE != SE_DARKMESSIAH
   181 	/* Unhook things */
   182 	if (m_bIsDLLQueryHooked)
   183 	{
   184 		SH_REMOVE_HOOK_MEMFUNC(IServerGameDLL, OnQueryCvarValueFinished, gamedll, this, &ConVarManager::OnQueryCvarValueFinished, false);
   185 		m_bIsDLLQueryHooked = false;
   186 	}
   187 	else if (m_bIsVSPQueryHooked)
   188 	{
   189 		SH_REMOVE_HOOK_MEMFUNC(IServerPluginCallbacks, OnQueryCvarValueFinished, vsp_interface, this, &ConVarManager::OnQueryCvarValueFinished, false);
   190 		m_bIsVSPQueryHooked = false;
   191 	}
   192 #endif
   193 
   194 	SH_REMOVE_HOOK_STATICFUNC(ICvar, CallGlobalChangeCallbacks, icvar, OnConVarChanged, false);
   195 
   196 	/* Remove the 'convars' option from the 'sm' console command */
   197 	g_RootMenu.RemoveRootConsoleCommand("cvars", this);
   198 
   199 	g_PluginSys.RemovePluginsListener(this);
   200 
   201 	/* Remove the 'ConVar' handle type */
   202 	g_HandleSys.RemoveType(m_ConVarType, g_pCoreIdent);
   203 }
   204 
   205 /**
   206  * Orange Box will never use this.
   207  */
   208 void ConVarManager::OnSourceModVSPReceived()
   209 {
   210 	/**
   211 	 * Don't bother if the DLL is already hooked.
   212 	 */
   213 	if (m_bIsDLLQueryHooked)
   214 	{
   215 		return;
   216 	}
   217 
   218 	/* For later MM:S versions, use the updated API, since it's cleaner. */
   219 #if defined METAMOD_PLAPI_VERSION || PLAPI_VERSION >= 11
   220 	int engine = g_SMAPI->GetSourceEngineBuild();
   221 	if (engine == SOURCE_ENGINE_ORIGINAL || vsp_version < 2)
   222 	{
   223 		return;
   224 	}
   225 #else
   226 	if (g_HL2.IsOriginalEngine() || vsp_version < 2)
   227 	{
   228 		return;
   229 	}
   230 #endif
   231 
   232 #if SOURCE_ENGINE != SE_DARKMESSIAH
   233 	SH_ADD_HOOK_MEMFUNC(IServerPluginCallbacks, OnQueryCvarValueFinished, vsp_interface, this, &ConVarManager::OnQueryCvarValueFinished, false);
   234 	m_bIsVSPQueryHooked = true;
   235 #endif
   236 }
   237 
   238 bool convar_cache_lookup(const char *name, ConVarInfo **pVar)
   239 {
   240 	ConVarInfo **pLookup = convar_cache.retrieve(name);
   241 	if (pLookup != NULL)
   242 	{
   243 		*pVar = *pLookup;
   244 		return true;
   245 	}
   246 	else
   247 	{
   248 		return false;
   249 	}
   250 }
   251 
   252 void ConVarManager::OnUnlinkConCommandBase(ConCommandBase *pBase, const char *name, bool is_read_safe)
   253 {
   254 	/* Only check convars that have not been created by SourceMod's core */
   255 	ConVarInfo *pInfo;
   256 	if (!convar_cache_lookup(name, &pInfo))
   257 	{
   258 		return;
   259 	}
   260 
   261 	HandleSecurity sec(NULL, g_pCoreIdent);
   262 
   263 	/* Remove it from our cache */
   264 	m_ConVars.remove(pInfo);
   265 	convar_cache.remove(name);
   266 
   267 	/* Now make sure no plugins are referring to this pointer */
   268 	IPluginIterator *pl_iter = g_PluginSys.GetPluginIterator();
   269 	while (pl_iter->MorePlugins())
   270 	{
   271 		IPlugin *pl = pl_iter->GetPlugin();
   272 
   273 		ConVarList *pConVarList;
   274 		if (pl->GetProperty("ConVarList", (void **)&pConVarList, true) 
   275 			&& pConVarList != NULL)
   276 		{
   277 			pConVarList->remove(pInfo->pVar);
   278 		}
   279 
   280 		pl_iter->NextPlugin();
   281 	}
   282 
   283 	/* Free resources */
   284 	g_HandleSys.FreeHandle(pInfo->handle, &sec);
   285 	delete pInfo;
   286 }
   287 
   288 void ConVarManager::OnPluginUnloaded(IPlugin *plugin)
   289 {
   290 	ConVarList *pConVarList;
   291 	List<ConVarQuery>::iterator iter;
   292 
   293 	/* If plugin has a convar list, free its memory */
   294 	if (plugin->GetProperty("ConVarList", (void **)&pConVarList, true))
   295 	{
   296 		delete pConVarList;
   297 	}
   298 
   299 	/* Remove convar queries for this plugin that haven't returned results yet */
   300 	for (iter = m_ConVarQueries.begin(); iter != m_ConVarQueries.end(); iter++)
   301 	{
   302 		ConVarQuery &query = (*iter);
   303 		if (query.pCallback->GetParentRuntime() == plugin->GetRuntime())
   304 		{
   305 			m_ConVarQueries.erase(iter);
   306 		}
   307 	}
   308 }
   309 
   310 void ConVarManager::OnHandleDestroy(HandleType_t type, void *object)
   311 {
   312 }
   313 
   314 bool ConVarManager::GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize)
   315 {
   316 	*pSize = sizeof(ConVar) + sizeof(ConVarInfo);
   317 	return true;
   318 }
   319 
   320 void ConVarManager::OnRootConsoleCommand(const char *cmdname, const CCommand &command)
   321 {
   322 	int argcount = command.ArgC();
   323 	if (argcount >= 3)
   324 	{
   325 		/* Get plugin index that was passed */
   326 		const char *arg = command.Arg(2);
   327 		
   328 		/* Get plugin object */
   329 		CPlugin *plugin = g_PluginSys.FindPluginByConsoleArg(arg);
   330 
   331 		if (!plugin)
   332 		{
   333 			g_RootMenu.ConsolePrint("[SM] Plugin \"%s\" was not found.", arg);
   334 			return;
   335 		}
   336 
   337 		/* Get plugin name */
   338 		const sm_plugininfo_t *plinfo = plugin->GetPublicInfo();
   339 		const char *plname = IS_STR_FILLED(plinfo->name) ? plinfo->name : plugin->GetFilename();
   340 
   341 		ConVarList *pConVarList;
   342 		ConVarList::iterator iter;
   343 
   344 		/* If no convar list... */
   345 		if (!plugin->GetProperty("ConVarList", (void **)&pConVarList))
   346 		{
   347 			g_RootMenu.ConsolePrint("[SM] No convars found for: %s", plname);
   348 			return;
   349 		}
   350 
   351 		g_RootMenu.ConsolePrint("[SM] Listing %d convars for: %s", pConVarList->size(), plname);
   352 		g_RootMenu.ConsolePrint("  %-32.31s %s", "[Name]", "[Value]");
   353 
   354 		/* Iterate convar list and display each one */
   355 		for (iter = pConVarList->begin(); iter != pConVarList->end(); iter++)
   356 		{
   357 			const ConVar *pConVar = (*iter);
   358 			g_RootMenu.ConsolePrint("  %-32.31s %s", pConVar->GetName(), pConVar->GetString()); 
   359 		}
   360 
   361 		return;
   362 	}
   363 
   364 	/* Display usage of subcommand */
   365 	g_RootMenu.ConsolePrint("[SM] Usage: sm cvars <plugin #>");
   366 }
   367 
   368 Handle_t ConVarManager::CreateConVar(IPluginContext *pContext, const char *name, const char *defaultVal, const char *description, int flags, bool hasMin, float min, bool hasMax, float max)
   369 {
   370 	ConVar *pConVar = NULL;
   371 	ConVarInfo *pInfo = NULL;
   372 	Handle_t hndl = 0;
   373 
   374 	/* Find out if the convar exists already */
   375 	pConVar = icvar->FindVar(name);
   376 
   377 	/* If the convar already exists... */
   378 	if (pConVar)
   379 	{
   380 		/* Add convar to plugin's list */
   381 		AddConVarToPluginList(pContext, pConVar);
   382 
   383 		/* First find out if we already have a handle to it */
   384 		if (convar_cache_lookup(name, &pInfo))
   385 		{
   386 			return pInfo->handle;
   387 		}
   388 		else
   389 		{
   390 			/* Create and initialize ConVarInfo structure */
   391 			pInfo = new ConVarInfo();
   392 			pInfo->sourceMod = false;
   393 			pInfo->pChangeForward = NULL;
   394 			pInfo->pVar = pConVar;
   395 
   396 			/* If we don't, then create a new handle from the convar */
   397 			hndl = g_HandleSys.CreateHandle(m_ConVarType, pInfo, NULL, g_pCoreIdent, NULL);
   398 			if (hndl == BAD_HANDLE)
   399 			{
   400 				delete pInfo;
   401 				return BAD_HANDLE;
   402 			}
   403 
   404 			pInfo->handle = hndl;
   405 
   406 			/* Insert struct into caches */
   407 			m_ConVars.push_back(pInfo);
   408 			convar_cache.insert(name, pInfo);
   409 			TrackConCommandBase(pConVar, this);
   410 
   411 			return hndl;
   412 		}
   413 	}
   414 
   415 	/* Prevent creating a convar that has the same name as a console command */
   416 	if (FindCommand(name))
   417 	{
   418 		return BAD_HANDLE;
   419 	}
   420 
   421 	/* Create and initialize ConVarInfo structure */
   422 	pInfo = new ConVarInfo();
   423 	pInfo->handle = hndl;
   424 	pInfo->sourceMod = true;
   425 	pInfo->pChangeForward = NULL;
   426 
   427 	/* Create a handle from the new convar */
   428 	hndl = g_HandleSys.CreateHandle(m_ConVarType, pInfo, NULL, g_pCoreIdent, NULL);
   429 	if (hndl == BAD_HANDLE)
   430 	{
   431 		delete pInfo;
   432 		return BAD_HANDLE;
   433 	}
   434 
   435 	pInfo->handle = hndl;
   436 
   437 	/* Since an existing convar (or concmd with the same name) was not found , now we can finally create it */
   438 	pConVar = new ConVar(sm_strdup(name), sm_strdup(defaultVal), flags, sm_strdup(description), hasMin, min, hasMax, max);
   439 	pInfo->pVar = pConVar;
   440 
   441 	/* Add convar to plugin's list */
   442 	AddConVarToPluginList(pContext, pConVar);
   443 
   444 	/* Insert struct into caches */
   445 	m_ConVars.push_back(pInfo);
   446 	convar_cache.insert(name, pInfo);
   447 
   448 	return hndl;
   449 }
   450 
   451 Handle_t ConVarManager::FindConVar(const char *name)
   452 {
   453 	ConVar *pConVar = NULL;
   454 	ConVarInfo *pInfo;
   455 	Handle_t hndl;
   456 
   457 	/* Search for convar */
   458 	pConVar = icvar->FindVar(name);
   459 
   460 	/* If it doesn't exist, then return an invalid handle */
   461 	if (!pConVar)
   462 	{
   463 		return BAD_HANDLE;
   464 	}
   465 
   466 	/* At this point, the convar exists. So, find out if we already have a handle */
   467 	if (convar_cache_lookup(name, &pInfo))
   468 	{
   469 		return pInfo->handle;
   470 	}
   471 
   472 	/* Create and initialize ConVarInfo structure */
   473 	pInfo = new ConVarInfo();
   474 	pInfo->sourceMod = false;
   475 	pInfo->pChangeForward = NULL;
   476 	pInfo->pVar = pConVar;
   477 
   478 	/* If we don't have a handle, then create a new one */
   479 	hndl = g_HandleSys.CreateHandle(m_ConVarType, pInfo, NULL, g_pCoreIdent, NULL);
   480 	if (hndl == BAD_HANDLE)
   481 	{
   482 		delete pInfo;
   483 		return BAD_HANDLE;
   484 	}
   485 
   486 	pInfo->handle = hndl;
   487 
   488 	/* Insert struct into our caches */
   489 	m_ConVars.push_back(pInfo);
   490 	convar_cache.insert(name, pInfo);
   491 	TrackConCommandBase(pConVar, this);
   492 
   493 	return hndl;
   494 }
   495 
   496 void ConVarManager::AddConVarChangeListener(const char *name, IConVarChangeListener *pListener)
   497 {
   498 	ConVarInfo *pInfo;
   499 
   500 	if (FindConVar(name) == BAD_HANDLE)
   501 	{
   502 		return;
   503 	}
   504 
   505 	/* Find the convar in the lookup trie */
   506 	if (convar_cache_lookup(name, &pInfo))
   507 	{
   508 		pInfo->changeListeners.push_back(pListener);
   509 	}
   510 }
   511 
   512 void ConVarManager::RemoveConVarChangeListener(const char *name, IConVarChangeListener *pListener)
   513 {
   514 	ConVarInfo *pInfo;
   515 
   516 	/* Find the convar in the lookup trie */
   517 	if (convar_cache_lookup(name, &pInfo))
   518 	{
   519 		pInfo->changeListeners.remove(pListener);
   520 	}
   521 }
   522 
   523 void ConVarManager::HookConVarChange(ConVar *pConVar, IPluginFunction *pFunction)
   524 {
   525 	ConVarInfo *pInfo;
   526 	IChangeableForward *pForward;
   527 
   528 	/* Find the convar in the lookup trie */
   529 	if (convar_cache_lookup(pConVar->GetName(), &pInfo))
   530 	{
   531 		/* Get the forward */
   532 		pForward = pInfo->pChangeForward;
   533 
   534 		/* If forward does not exist, create it */
   535 		if (!pForward)
   536 		{
   537 			pForward = g_Forwards.CreateForwardEx(NULL, ET_Ignore, 3, CONVARCHANGE_PARAMS);
   538 			pInfo->pChangeForward = pForward;
   539 		}
   540 
   541 		/* Add function to forward's list */
   542 		pForward->AddFunction(pFunction);
   543 	}
   544 }
   545 
   546 void ConVarManager::UnhookConVarChange(ConVar *pConVar, IPluginFunction *pFunction)
   547 {
   548 	ConVarInfo *pInfo;
   549 	IChangeableForward *pForward;
   550 	IPluginContext *pContext = pFunction->GetParentContext();
   551 
   552 	/* Find the convar in the lookup trie */
   553 	if (convar_cache_lookup(pConVar->GetName(), &pInfo))
   554 	{
   555 		/* Get the forward */
   556 		pForward = pInfo->pChangeForward;
   557 
   558 		/* If the forward doesn't exist, we can't unhook anything */
   559 		if (!pForward)
   560 		{
   561 			pContext->ThrowNativeError("Convar \"%s\" has no active hook", pConVar->GetName());
   562 			return;
   563 		}
   564 
   565 		/* Remove the function from the forward's list */
   566 		if (!pForward->RemoveFunction(pFunction))
   567 		{
   568 			pContext->ThrowNativeError("Invalid hook callback specified for convar \"%s\"", pConVar->GetName());
   569 			return;
   570 		}
   571 
   572 		/* If the forward now has 0 functions in it... */
   573 		if (pForward->GetFunctionCount() == 0 &&
   574 			!ConVarReentrancyGuard::IsCvarInChain(pConVar))
   575 		{
   576 			/* Free this forward */
   577 			g_Forwards.ReleaseForward(pForward);
   578 			pInfo->pChangeForward = NULL;
   579 		}
   580 	}
   581 }
   582 
   583 QueryCvarCookie_t ConVarManager::QueryClientConVar(edict_t *pPlayer, const char *name, IPluginFunction *pCallback, Handle_t hndl)
   584 {
   585 	QueryCvarCookie_t cookie = 0;
   586 
   587 #if SOURCE_ENGINE != SE_DARKMESSIAH
   588 	/* Call StartQueryCvarValue() in either the IVEngineServer or IServerPluginHelpers depending on situation */
   589 	if (m_bIsDLLQueryHooked)
   590 	{
   591 		cookie = engine->StartQueryCvarValue(pPlayer, name);	
   592 	}
   593 	else if (m_bIsVSPQueryHooked)
   594 	{
   595 		cookie = serverpluginhelpers->StartQueryCvarValue(pPlayer, name);
   596 	}
   597 	else
   598 	{
   599 		return InvalidQueryCvarCookie;
   600 	}
   601 
   602 	ConVarQuery query = {cookie, pCallback, hndl};
   603 	m_ConVarQueries.push_back(query);
   604 #endif
   605 
   606 	return cookie;
   607 }
   608 
   609 void ConVarManager::AddConVarToPluginList(IPluginContext *pContext, const ConVar *pConVar)
   610 {
   611 	ConVarList *pConVarList;
   612 	ConVarList::iterator iter;
   613 	bool inserted = false;
   614 	const char *orig = pConVar->GetName();
   615 
   616 	IPlugin *plugin = g_PluginSys.FindPluginByContext(pContext->GetContext());
   617 
   618 	/* Check plugin for an existing convar list */
   619 	if (!plugin->GetProperty("ConVarList", (void **)&pConVarList))
   620 	{
   621 		pConVarList = new ConVarList();
   622 		plugin->SetProperty("ConVarList", pConVarList);
   623 	}
   624 	else if (pConVarList->find(pConVar) != pConVarList->end())
   625 	{
   626 		/* If convar is already in list, then don't add it */
   627 		return;
   628 	}
   629 
   630 	/* Insert convar into list which is sorted alphabetically */
   631 	for (iter = pConVarList->begin(); iter != pConVarList->end(); iter++)
   632 	{
   633 		if (strcmp(orig, (*iter)->GetName()) < 0)
   634 		{
   635 			pConVarList->insert(iter, pConVar);
   636 			inserted = true;
   637 			break;
   638 		}
   639 	}
   640 
   641 	if (!inserted)
   642 	{
   643 		pConVarList->push_back(pConVar);
   644 	}
   645 }
   646 
   647 #if SOURCE_ENGINE >= SE_ORANGEBOX
   648 void ConVarManager::OnConVarChanged(ConVar *pConVar, const char *oldValue, float flOldValue)
   649 #else
   650 void ConVarManager::OnConVarChanged(ConVar *pConVar, const char *oldValue)
   651 #endif
   652 {
   653 	/* If the values are the same, exit early in order to not trigger callbacks */
   654 	if (strcmp(pConVar->GetString(), oldValue) == 0)
   655 	{
   656 		return;
   657 	}
   658 
   659 	ConVarInfo *pInfo;
   660 
   661 	/* Find the convar in the lookup trie */
   662 	if (!convar_cache_lookup(pConVar->GetName(), &pInfo))
   663 	{
   664 		return;
   665 	}
   666 
   667 	IChangeableForward *pForward = pInfo->pChangeForward;
   668 
   669 	if (pInfo->changeListeners.size() != 0)
   670 	{
   671 		for (List<IConVarChangeListener *>::iterator i = pInfo->changeListeners.begin();
   672 			 i != pInfo->changeListeners.end();
   673 			 i++)
   674 		{
   675 #if SOURCE_ENGINE >= SE_ORANGEBOX
   676 			(*i)->OnConVarChanged(pConVar, oldValue, flOldValue);
   677 #else
   678 			(*i)->OnConVarChanged(pConVar, oldValue, atof(oldValue));
   679 #endif
   680 		}
   681 	}
   682 
   683 	if (pForward != NULL)
   684 	{
   685 		ConVarReentrancyGuard guard(pConVar);
   686 
   687 		/* Now call forwards in plugins that have hooked this */
   688 		pForward->PushCell(pInfo->handle);
   689 		pForward->PushString(oldValue);
   690 		pForward->PushString(pConVar->GetString());
   691 		pForward->Execute(NULL);
   692 	}
   693 }
   694 
   695 bool ConVarManager::IsQueryingSupported()
   696 {
   697 	return (m_bIsDLLQueryHooked || m_bIsVSPQueryHooked);
   698 }
   699 
   700 #if SOURCE_ENGINE != SE_DARKMESSIAH
   701 void ConVarManager::OnQueryCvarValueFinished(QueryCvarCookie_t cookie, edict_t *pPlayer, EQueryCvarValueStatus result, const char *cvarName, const char *cvarValue)
   702 {
   703 	IPluginFunction *pCallback = NULL;
   704 	cell_t value = 0;
   705 	List<ConVarQuery>::iterator iter;
   706 
   707 	for (iter = m_ConVarQueries.begin(); iter != m_ConVarQueries.end(); iter++)
   708 	{
   709 		ConVarQuery &query = (*iter);
   710 		if (query.cookie == cookie)
   711 		{
   712 			pCallback = query.pCallback;
   713 			value = query.value;
   714 			break;
   715 		}
   716 	}
   717 
   718 	if (pCallback)
   719 	{
   720 		cell_t ret;
   721 
   722 		pCallback->PushCell(cookie);
   723 		pCallback->PushCell(IndexOfEdict(pPlayer));
   724 		pCallback->PushCell(result);
   725 		pCallback->PushString(cvarName);
   726 
   727 		if (result == eQueryCvarValueStatus_ValueIntact)
   728 		{
   729 			pCallback->PushString(cvarValue);
   730 		}
   731 		else
   732 		{
   733 			pCallback->PushString("\0");
   734 		}
   735 
   736 		pCallback->PushCell(value);
   737 		pCallback->Execute(&ret);
   738 
   739 		m_ConVarQueries.erase(iter);
   740 	}
   741 }
   742 #endif
   743 
   744 HandleError ConVarManager::ReadConVarHandle(Handle_t hndl, ConVar **pVar)
   745 {
   746 	ConVarInfo *pInfo;
   747 	HandleError error;
   748 	
   749 	if ((error = g_HandleSys.ReadHandle(hndl, m_ConVarType, NULL, (void **)&pInfo)) != HandleError_None)
   750 	{
   751 		return error;
   752 	}
   753 
   754 	if (pVar)
   755 	{
   756 		*pVar = pInfo->pVar;
   757 	}
   758 
   759 	return error;
   760 }