Update linux dep script (no bug, r=NPOTB).
2 * vim: set ts=4 sw=4 tw=99 noet :
3 * =============================================================================
5 * Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved.
6 * =============================================================================
8 * This program is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, version 3.0, as published by the
10 * Free Software Foundation.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * You should have received a copy of the GNU General Public License along with
18 * this program. If not, see <http://www.gnu.org/licenses/>.
20 * As a special exception, AlliedModders LLC gives you permission to link the
21 * code of this program (as well as its derivative works) to "Half-Life 2," the
22 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
23 * by the Valve Corporation. You must obey the GNU General Public License in
24 * all respects for all other code used. Additionally, AlliedModders LLC grants
25 * this exception to all derivative works. AlliedModders LLC defines further
26 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
27 * or <http://www.sourcemod.net/license.php>.
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"
38 ConVarManager g_ConVarManager;
40 #if SOURCE_ENGINE >= SE_ORANGEBOX
41 SH_DECL_HOOK3_void(ICvar, CallGlobalChangeCallbacks, SH_NOATTRIB, false, ConVar *, const char *, float);
43 SH_DECL_HOOK2_void(ICvar, CallGlobalChangeCallback, SH_NOATTRIB, false, ConVar *, const char *);
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 *);
54 const ParamType CONVARCHANGE_PARAMS[] = {Param_Cell, Param_String, Param_String};
55 typedef List<const ConVar *> ConVarList;
56 NameHashSet<ConVarInfo *> convar_cache;
58 class ConVarReentrancyGuard
61 ConVarReentrancyGuard *up;
63 static ConVarReentrancyGuard *chain;
65 ConVarReentrancyGuard(ConVar *cvar)
66 : cvar(cvar), up(chain)
71 ~ConVarReentrancyGuard()
73 assert(chain == this);
77 static bool IsCvarInChain(ConVar *cvar)
79 ConVarReentrancyGuard *guard = chain;
82 if (guard->cvar == cvar)
90 ConVarReentrancyGuard *ConVarReentrancyGuard::chain = NULL;
92 ConVarManager::ConVarManager() : m_ConVarType(0), m_bIsDLLQueryHooked(false), m_bIsVSPQueryHooked(false)
96 ConVarManager::~ConVarManager()
100 void ConVarManager::OnSourceModStartup(bool late)
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;
109 /* Create the 'ConVar' handle type */
110 m_ConVarType = handlesys->CreateType("ConVar", this, 0, NULL, &sec, g_pCoreIdent, NULL);
113 void ConVarManager::OnSourceModAllInitialized()
116 * Episode 2 has this function by default, but the older versions do not.
118 #if SOURCE_ENGINE == SE_EPISODEONE
119 if (g_SMAPI->GetGameDLLVersion() >= 6)
121 SH_ADD_HOOK(IServerGameDLL, OnQueryCvarValueFinished, gamedll, SH_MEMBER(this, &ConVarManager::OnQueryCvarValueFinished), false);
122 m_bIsDLLQueryHooked = true;
126 #if SOURCE_ENGINE >= SE_ORANGEBOX
127 SH_ADD_HOOK(ICvar, CallGlobalChangeCallbacks, icvar, SH_STATIC(OnConVarChanged), false);
129 SH_ADD_HOOK(ICvar, CallGlobalChangeCallback, icvar, SH_STATIC(OnConVarChanged), false);
132 scripts->AddPluginsListener(this);
134 /* Add the 'convars' option to the 'sm' console command */
135 g_RootMenu.AddRootConsoleCommand("cvars", "View convars created by a plugin", this);
138 void ConVarManager::OnSourceModShutdown()
140 List<ConVarInfo *>::iterator iter = m_ConVars.begin();
141 HandleSecurity sec(NULL, g_pCoreIdent);
143 /* Iterate list of ConVarInfo structures, remove every one of them */
144 while (iter != m_ConVars.end())
146 ConVarInfo *pInfo = (*iter);
148 iter = m_ConVars.erase(iter);
150 handlesys->FreeHandle(pInfo->handle, &sec);
151 if (pInfo->pChangeForward != NULL)
153 forwardsys->ReleaseForward(pInfo->pChangeForward);
155 if (pInfo->sourceMod)
157 /* If we created it, we won't be tracking it, therefore it is
158 * safe to remove everything in one go.
160 META_UNREGCVAR(pInfo->pVar);
161 delete [] pInfo->pVar->GetName();
162 delete [] pInfo->pVar->GetHelpText();
163 delete [] pInfo->pVar->GetDefault();
168 /* If we didn't create it, we might be tracking it. Also,
169 * it could be unreadable.
171 UntrackConCommandBase(pInfo->pVar, this);
174 /* It's not safe to read the name here, so we simply delete the
175 * the info struct and clear the lookup cache at the end.
179 convar_cache.clear();
181 #if SOURCE_ENGINE != SE_DARKMESSIAH
183 if (m_bIsDLLQueryHooked)
185 SH_REMOVE_HOOK(IServerGameDLL, OnQueryCvarValueFinished, gamedll, SH_MEMBER(this, &ConVarManager::OnQueryCvarValueFinished), false);
186 m_bIsDLLQueryHooked = false;
188 else if (m_bIsVSPQueryHooked)
190 #if SOURCE_ENGINE != SE_DOTA
191 SH_REMOVE_HOOK(IServerPluginCallbacks, OnQueryCvarValueFinished, vsp_interface, SH_MEMBER(this, &ConVarManager::OnQueryCvarValueFinished), false);
193 m_bIsVSPQueryHooked = false;
197 #if SOURCE_ENGINE >= SE_ORANGEBOX
198 SH_REMOVE_HOOK(ICvar, CallGlobalChangeCallbacks, icvar, SH_STATIC(OnConVarChanged), false);
200 SH_REMOVE_HOOK(ICvar, CallGlobalChangeCallback, icvar, SH_STATIC(OnConVarChanged), false);
203 /* Remove the 'convars' option from the 'sm' console command */
204 g_RootMenu.RemoveRootConsoleCommand("cvars", this);
206 scripts->RemovePluginsListener(this);
208 /* Remove the 'ConVar' handle type */
209 handlesys->RemoveType(m_ConVarType, g_pCoreIdent);
213 * Orange Box will never use this.
215 void ConVarManager::OnSourceModVSPReceived()
218 * Don't bother if the DLL is already hooked.
220 if (m_bIsDLLQueryHooked)
225 /* For later MM:S versions, use the updated API, since it's cleaner. */
226 #if defined METAMOD_PLAPI_VERSION || PLAPI_VERSION >= 11
227 int engine = g_SMAPI->GetSourceEngineBuild();
228 if (engine == SOURCE_ENGINE_ORIGINAL || vsp_version < 2)
233 if (g_HL2.IsOriginalEngine() || vsp_version < 2)
239 #if SOURCE_ENGINE != SE_DARKMESSIAH && SOURCE_ENGINE != SE_DOTA
240 SH_ADD_HOOK(IServerPluginCallbacks, OnQueryCvarValueFinished, vsp_interface, SH_MEMBER(this, &ConVarManager::OnQueryCvarValueFinished), false);
241 m_bIsVSPQueryHooked = true;
245 bool convar_cache_lookup(const char *name, ConVarInfo **pVar)
247 return convar_cache.retrieve(name, pVar);
250 void ConVarManager::OnUnlinkConCommandBase(ConCommandBase *pBase, const char *name, bool is_read_safe)
252 /* Only check convars that have not been created by SourceMod's core */
254 if (!convar_cache_lookup(name, &pInfo))
259 HandleSecurity sec(NULL, g_pCoreIdent);
261 /* Remove it from our cache */
262 m_ConVars.remove(pInfo);
263 convar_cache.remove(name);
265 /* Now make sure no plugins are referring to this pointer */
266 IPluginIterator *pl_iter = scripts->GetPluginIterator();
267 while (pl_iter->MorePlugins())
269 IPlugin *pl = pl_iter->GetPlugin();
271 ConVarList *pConVarList;
272 if (pl->GetProperty("ConVarList", (void **)&pConVarList, true)
273 && pConVarList != NULL)
275 pConVarList->remove(pInfo->pVar);
278 pl_iter->NextPlugin();
282 handlesys->FreeHandle(pInfo->handle, &sec);
286 void ConVarManager::OnPluginUnloaded(IPlugin *plugin)
288 ConVarList *pConVarList;
289 List<ConVarQuery>::iterator iter;
291 /* If plugin has a convar list, free its memory */
292 if (plugin->GetProperty("ConVarList", (void **)&pConVarList, true))
297 const IPluginRuntime * pRuntime = plugin->GetRuntime();
299 /* Remove convar queries for this plugin that haven't returned results yet */
300 for (iter = m_ConVarQueries.begin(); iter != m_ConVarQueries.end();)
302 ConVarQuery &query = (*iter);
303 if (query.pCallback->GetParentRuntime() == pRuntime)
305 iter = m_ConVarQueries.erase(iter);
313 void ConVarManager::OnHandleDestroy(HandleType_t type, void *object)
317 bool ConVarManager::GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize)
319 *pSize = sizeof(ConVar) + sizeof(ConVarInfo);
323 void ConVarManager::OnRootConsoleCommand(const char *cmdname, const CCommand &command)
325 int argcount = command.ArgC();
328 bool wantReset = false;
330 /* Get plugin index that was passed */
331 const char *arg = command.Arg(2);
332 if (argcount >= 4 && strcmp(arg, "reset") == 0)
335 arg = command.Arg(3);
338 /* Get plugin object */
339 IPlugin *plugin = scripts->FindPluginByConsoleArg(arg);
343 g_RootMenu.ConsolePrint("[SM] Plugin \"%s\" was not found.", arg);
347 /* Get plugin name */
348 const sm_plugininfo_t *plinfo = plugin->GetPublicInfo();
349 const char *plname = IS_STR_FILLED(plinfo->name) ? plinfo->name : plugin->GetFilename();
351 ConVarList *pConVarList;
352 ConVarList::iterator iter;
354 /* If no convar list... */
355 if (!plugin->GetProperty("ConVarList", (void **)&pConVarList))
357 g_RootMenu.ConsolePrint("[SM] No convars found for: %s", plname);
363 g_RootMenu.ConsolePrint("[SM] Listing %d convars for: %s", pConVarList->size(), plname);
364 g_RootMenu.ConsolePrint(" %-32.31s %s", "[Name]", "[Value]");
367 /* Iterate convar list and display/reset each one */
368 for (iter = pConVarList->begin(); iter != pConVarList->end(); iter++)
370 /*const */ConVar *pConVar = const_cast<ConVar *>(*iter);
373 g_RootMenu.ConsolePrint(" %-32.31s %s", pConVar->GetName(), pConVar->GetString());
381 g_RootMenu.ConsolePrint("[SM] Reset %d convars for: %s", pConVarList->size(), plname);
387 /* Display usage of subcommand */
388 g_RootMenu.ConsolePrint("[SM] Usage: sm cvars [reset] <plugin #>");
391 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)
393 ConVar *pConVar = NULL;
394 ConVarInfo *pInfo = NULL;
397 /* Find out if the convar exists already */
398 pConVar = icvar->FindVar(name);
400 /* If the convar already exists... */
403 /* Add convar to plugin's list */
404 AddConVarToPluginList(pContext, pConVar);
406 /* First find out if we already have a handle to it */
407 if (convar_cache_lookup(name, &pInfo))
409 return pInfo->handle;
413 /* Create and initialize ConVarInfo structure */
414 pInfo = new ConVarInfo();
415 pInfo->sourceMod = false;
416 pInfo->pChangeForward = NULL;
417 pInfo->pVar = pConVar;
419 /* If we don't, then create a new handle from the convar */
420 hndl = handlesys->CreateHandle(m_ConVarType, pInfo, NULL, g_pCoreIdent, NULL);
421 if (hndl == BAD_HANDLE)
427 pInfo->handle = hndl;
429 /* Insert struct into caches */
430 m_ConVars.push_back(pInfo);
431 convar_cache.insert(name, pInfo);
432 TrackConCommandBase(pConVar, this);
438 /* Prevent creating a convar that has the same name as a console command */
439 if (FindCommand(name))
444 /* Create and initialize ConVarInfo structure */
445 pInfo = new ConVarInfo();
446 pInfo->handle = hndl;
447 pInfo->sourceMod = true;
448 pInfo->pChangeForward = NULL;
450 /* Create a handle from the new convar */
451 hndl = handlesys->CreateHandle(m_ConVarType, pInfo, NULL, g_pCoreIdent, NULL);
452 if (hndl == BAD_HANDLE)
458 pInfo->handle = hndl;
460 /* Since an existing convar (or concmd with the same name) was not found , now we can finally create it */
461 pConVar = new ConVar(sm_strdup(name), sm_strdup(defaultVal), flags, sm_strdup(description), hasMin, min, hasMax, max);
462 pInfo->pVar = pConVar;
464 /* Add convar to plugin's list */
465 AddConVarToPluginList(pContext, pConVar);
467 /* Insert struct into caches */
468 m_ConVars.push_back(pInfo);
469 convar_cache.insert(name, pInfo);
474 Handle_t ConVarManager::FindConVar(const char *name)
476 ConVar *pConVar = NULL;
480 /* Search for convar */
481 pConVar = icvar->FindVar(name);
483 /* If it doesn't exist, then return an invalid handle */
489 /* At this point, the convar exists. So, find out if we already have a handle */
490 if (convar_cache_lookup(name, &pInfo))
492 return pInfo->handle;
495 /* Create and initialize ConVarInfo structure */
496 pInfo = new ConVarInfo();
497 pInfo->sourceMod = false;
498 pInfo->pChangeForward = NULL;
499 pInfo->pVar = pConVar;
501 /* If we don't have a handle, then create a new one */
502 hndl = handlesys->CreateHandle(m_ConVarType, pInfo, NULL, g_pCoreIdent, NULL);
503 if (hndl == BAD_HANDLE)
509 pInfo->handle = hndl;
511 /* Insert struct into our caches */
512 m_ConVars.push_back(pInfo);
513 convar_cache.insert(name, pInfo);
514 TrackConCommandBase(pConVar, this);
519 void ConVarManager::AddConVarChangeListener(const char *name, IConVarChangeListener *pListener)
523 if (FindConVar(name) == BAD_HANDLE)
528 /* Find the convar in the lookup trie */
529 if (convar_cache_lookup(name, &pInfo))
531 pInfo->changeListeners.push_back(pListener);
535 void ConVarManager::RemoveConVarChangeListener(const char *name, IConVarChangeListener *pListener)
539 /* Find the convar in the lookup trie */
540 if (convar_cache_lookup(name, &pInfo))
542 pInfo->changeListeners.remove(pListener);
546 void ConVarManager::HookConVarChange(ConVar *pConVar, IPluginFunction *pFunction)
549 IChangeableForward *pForward;
551 /* Find the convar in the lookup trie */
552 if (convar_cache_lookup(pConVar->GetName(), &pInfo))
554 /* Get the forward */
555 pForward = pInfo->pChangeForward;
557 /* If forward does not exist, create it */
560 pForward = forwardsys->CreateForwardEx(NULL, ET_Ignore, 3, CONVARCHANGE_PARAMS);
561 pInfo->pChangeForward = pForward;
564 /* Add function to forward's list */
565 pForward->AddFunction(pFunction);
569 void ConVarManager::UnhookConVarChange(ConVar *pConVar, IPluginFunction *pFunction)
572 IChangeableForward *pForward;
573 IPluginContext *pContext = pFunction->GetParentContext();
575 /* Find the convar in the lookup trie */
576 if (convar_cache_lookup(pConVar->GetName(), &pInfo))
578 /* Get the forward */
579 pForward = pInfo->pChangeForward;
581 /* If the forward doesn't exist, we can't unhook anything */
584 pContext->ThrowNativeError("Convar \"%s\" has no active hook", pConVar->GetName());
588 /* Remove the function from the forward's list */
589 if (!pForward->RemoveFunction(pFunction))
591 pContext->ThrowNativeError("Invalid hook callback specified for convar \"%s\"", pConVar->GetName());
595 /* If the forward now has 0 functions in it... */
596 if (pForward->GetFunctionCount() == 0 &&
597 !ConVarReentrancyGuard::IsCvarInChain(pConVar))
599 /* Free this forward */
600 forwardsys->ReleaseForward(pForward);
601 pInfo->pChangeForward = NULL;
606 QueryCvarCookie_t ConVarManager::QueryClientConVar(edict_t *pPlayer, const char *name, IPluginFunction *pCallback, Handle_t hndl)
608 QueryCvarCookie_t cookie = 0;
610 #if SOURCE_ENGINE != SE_DARKMESSIAH
611 /* Call StartQueryCvarValue() in either the IVEngineServer or IServerPluginHelpers depending on situation */
612 if (m_bIsDLLQueryHooked)
614 #if SOURCE_ENGINE == SE_DOTA
615 cookie = engine->StartQueryCvarValue(CEntityIndex(IndexOfEdict(pPlayer)), name);
617 cookie = engine->StartQueryCvarValue(pPlayer, name);
620 #if SOURCE_ENGINE != SE_DOTA
621 else if (m_bIsVSPQueryHooked)
623 cookie = serverpluginhelpers->StartQueryCvarValue(pPlayer, name);
628 return InvalidQueryCvarCookie;
631 ConVarQuery query = {cookie, pCallback, (cell_t)hndl};
632 m_ConVarQueries.push_back(query);
638 void ConVarManager::AddConVarToPluginList(IPluginContext *pContext, const ConVar *pConVar)
640 ConVarList *pConVarList;
641 ConVarList::iterator iter;
642 bool inserted = false;
643 const char *orig = pConVar->GetName();
645 IPlugin *plugin = scripts->FindPluginByContext(pContext->GetContext());
647 /* Check plugin for an existing convar list */
648 if (!plugin->GetProperty("ConVarList", (void **)&pConVarList))
650 pConVarList = new ConVarList();
651 plugin->SetProperty("ConVarList", pConVarList);
653 else if (pConVarList->find(pConVar) != pConVarList->end())
655 /* If convar is already in list, then don't add it */
659 /* Insert convar into list which is sorted alphabetically */
660 for (iter = pConVarList->begin(); iter != pConVarList->end(); iter++)
662 if (strcmp(orig, (*iter)->GetName()) < 0)
664 pConVarList->insert(iter, pConVar);
672 pConVarList->push_back(pConVar);
676 #if SOURCE_ENGINE >= SE_ORANGEBOX
677 void ConVarManager::OnConVarChanged(ConVar *pConVar, const char *oldValue, float flOldValue)
679 void ConVarManager::OnConVarChanged(ConVar *pConVar, const char *oldValue)
682 /* If the values are the same, exit early in order to not trigger callbacks */
683 if (strcmp(pConVar->GetString(), oldValue) == 0)
690 /* Find the convar in the lookup trie */
691 if (!convar_cache_lookup(pConVar->GetName(), &pInfo))
696 IChangeableForward *pForward = pInfo->pChangeForward;
698 if (pInfo->changeListeners.size() != 0)
700 for (List<IConVarChangeListener *>::iterator i = pInfo->changeListeners.begin();
701 i != pInfo->changeListeners.end();
704 #if SOURCE_ENGINE >= SE_ORANGEBOX
705 (*i)->OnConVarChanged(pConVar, oldValue, flOldValue);
707 (*i)->OnConVarChanged(pConVar, oldValue, atof(oldValue));
712 if (pForward != NULL)
714 ConVarReentrancyGuard guard(pConVar);
716 /* Now call forwards in plugins that have hooked this */
717 pForward->PushCell(pInfo->handle);
718 pForward->PushString(oldValue);
719 pForward->PushString(pConVar->GetString());
720 pForward->Execute(NULL);
724 bool ConVarManager::IsQueryingSupported()
726 return (m_bIsDLLQueryHooked || m_bIsVSPQueryHooked);
729 #if SOURCE_ENGINE != SE_DARKMESSIAH
730 #if SOURCE_ENGINE == SE_DOTA
731 void ConVarManager::OnQueryCvarValueFinished(QueryCvarCookie_t cookie, CEntityIndex player, EQueryCvarValueStatus result, const char *cvarName, const char *cvarValue)
733 void ConVarManager::OnQueryCvarValueFinished(QueryCvarCookie_t cookie, edict_t *pPlayer, EQueryCvarValueStatus result, const char *cvarName, const char *cvarValue)
736 IPluginFunction *pCallback = NULL;
738 List<ConVarQuery>::iterator iter;
740 for (iter = m_ConVarQueries.begin(); iter != m_ConVarQueries.end(); iter++)
742 ConVarQuery &query = (*iter);
743 if (query.cookie == cookie)
745 pCallback = query.pCallback;
755 pCallback->PushCell(cookie);
756 #if SOURCE_ENGINE == SE_DOTA
757 pCallback->PushCell(player.Get());
759 pCallback->PushCell(IndexOfEdict(pPlayer));
761 pCallback->PushCell(result);
762 pCallback->PushString(cvarName);
764 if (result == eQueryCvarValueStatus_ValueIntact)
766 pCallback->PushString(cvarValue);
770 pCallback->PushString("\0");
773 pCallback->PushCell(value);
774 pCallback->Execute(&ret);
776 m_ConVarQueries.erase(iter);
781 HandleError ConVarManager::ReadConVarHandle(Handle_t hndl, ConVar **pVar)
786 if ((error = handlesys->ReadHandle(hndl, m_ConVarType, NULL, (void **)&pInfo)) != HandleError_None)