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