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