core/CoreConfig.cpp
author David Anderson <dvander@alliedmods.net>
Sun Aug 25 12:13:30 2013 -0700 (2013-08-25)
changeset 4070 4bd9fbf607d9
parent 3942 7aaa00382627
child 4110 f1f4eb1d9274
permissions -rw-r--r--
Switch ConVar, CoreConfig off KTrie (bug 5884 part 8, r=ds).
     1 /**
     2  * vim: set ts=4 sw=4 tw=99 noet :
     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 <ITextParsers.h>
    33 #include "CoreConfig.h"
    34 #include "sourcemod.h"
    35 #include "sourcemm_api.h"
    36 #include "sm_srvcmds.h"
    37 #include <sourcemod_version.h>
    38 #include "sm_stringutil.h"
    39 #include "LibrarySys.h"
    40 #include "Logger.h"
    41 #include "ForwardSys.h"
    42 #include "frame_hooks.h"
    43 #include "logic_bridge.h"
    44 
    45 #ifdef PLATFORM_WINDOWS
    46 ConVar sm_corecfgfile("sm_corecfgfile", "addons\\sourcemod\\configs\\core.cfg", 0, "SourceMod core configuration file");
    47 #elif defined PLATFORM_LINUX || defined PLATFORM_APPLE
    48 ConVar sm_corecfgfile("sm_corecfgfile", "addons/sourcemod/configs/core.cfg", 0, "SourceMod core configuration file");
    49 #endif
    50 
    51 IForward *g_pOnServerCfg = NULL;
    52 IForward *g_pOnConfigsExecuted = NULL;
    53 IForward *g_pOnAutoConfigsBuffered = NULL;
    54 CoreConfig g_CoreConfig;
    55 bool g_bConfigsExecd = false;
    56 bool g_bServerExecd = false;
    57 bool g_bGotServerStart = false;
    58 bool g_bGotTrigger = false;
    59 ConCommand *g_pExecPtr = NULL;
    60 ConVar *g_ServerCfgFile = NULL;
    61 
    62 void CheckAndFinalizeConfigs();
    63 
    64 #if SOURCE_ENGINE == SE_DOTA
    65 SH_DECL_EXTERN2_void(ConCommand, Dispatch, SH_NOATTRIB, false, const CCommandContext &, const CCommand &);
    66 void Hook_ExecDispatchPre(const CCommandContext &context, const CCommand &cmd)
    67 #elif SOURCE_ENGINE >= SE_ORANGEBOX
    68 SH_DECL_EXTERN1_void(ConCommand, Dispatch, SH_NOATTRIB, false, const CCommand &);
    69 void Hook_ExecDispatchPre(const CCommand &cmd)
    70 #elif SOURCE_ENGINE == SE_DARKMESSIAH
    71 SH_DECL_EXTERN0_void(ConCommand, Dispatch, SH_NOATTRIB, false);
    72 void Hook_ExecDispatchPre()
    73 #else
    74 # if SH_IMPL_VERSION >= 4
    75  extern int __SourceHook_FHAddConCommandDispatch(void *,bool,class fastdelegate::FastDelegate0<void>);
    76 # else
    77  extern bool __SourceHook_FHAddConCommandDispatch(void *,bool,class fastdelegate::FastDelegate0<void>);
    78 # endif
    79 extern bool __SourceHook_FHRemoveConCommandDispatch(void *,bool,class fastdelegate::FastDelegate0<void>);
    80 void Hook_ExecDispatchPre()
    81 #endif
    82 {
    83 #if SOURCE_ENGINE <= SE_DARKMESSIAH
    84 	CCommand cmd;
    85 #endif
    86 
    87 	const char *arg = cmd.Arg(1);
    88 
    89 	if (!g_bServerExecd && arg != NULL && strcmp(arg, g_ServerCfgFile->GetString()) == 0)
    90 	{
    91 		g_bGotTrigger = true;
    92 	}
    93 }
    94 
    95 #if SOURCE_ENGINE == SE_DOTA
    96 void Hook_ExecDispatchPost(const CCommandContext &context, const CCommand &cmd)
    97 #elif SOURCE_ENGINE >= SE_ORANGEBOX
    98 void Hook_ExecDispatchPost(const CCommand &cmd)
    99 #else
   100 void Hook_ExecDispatchPost()
   101 #endif
   102 {
   103 	if (g_bGotTrigger)
   104 	{
   105 		g_bGotTrigger = false;
   106 		g_bServerExecd = true;
   107 		CheckAndFinalizeConfigs();
   108 	}
   109 }
   110 
   111 void CheckAndFinalizeConfigs()
   112 {
   113 	if ((g_bServerExecd || g_ServerCfgFile == NULL) && g_bGotServerStart)
   114 	{
   115         g_PendingInternalPush = true;
   116 	}
   117 }
   118 
   119 void CoreConfig::OnSourceModAllInitialized()
   120 {
   121 	g_RootMenu.AddRootConsoleCommand("config", "Set core configuration options", this);
   122 	g_pOnServerCfg = g_Forwards.CreateForward("OnServerCfg", ET_Ignore, 0, NULL);
   123 	g_pOnConfigsExecuted = g_Forwards.CreateForward("OnConfigsExecuted", ET_Ignore, 0, NULL);
   124 	g_pOnAutoConfigsBuffered = g_Forwards.CreateForward("OnAutoConfigsBuffered", ET_Ignore, 0, NULL);
   125 }
   126 
   127 CoreConfig::CoreConfig() : m_Strings(512)
   128 {
   129 }
   130 
   131 CoreConfig::~CoreConfig()
   132 {
   133 }
   134 
   135 void CoreConfig::OnSourceModShutdown()
   136 {
   137 	g_RootMenu.RemoveRootConsoleCommand("config", this);
   138 	g_Forwards.ReleaseForward(g_pOnServerCfg);
   139 	g_Forwards.ReleaseForward(g_pOnConfigsExecuted);
   140 	g_Forwards.ReleaseForward(g_pOnAutoConfigsBuffered);
   141 
   142 	if (g_pExecPtr != NULL)
   143 	{
   144 		SH_REMOVE_HOOK(ConCommand, Dispatch, g_pExecPtr, SH_STATIC(Hook_ExecDispatchPre), false);
   145 		SH_REMOVE_HOOK(ConCommand, Dispatch, g_pExecPtr, SH_STATIC(Hook_ExecDispatchPost), true);
   146 		g_pExecPtr = NULL;
   147 	}
   148 }
   149 
   150 void CoreConfig::OnSourceModLevelChange(const char *mapName)
   151 {
   152 	static bool already_checked = false;
   153 
   154 	if (!already_checked)
   155 	{
   156 		if (engine->IsDedicatedServer())
   157 		{
   158 			g_ServerCfgFile = icvar->FindVar("servercfgfile");
   159 		}
   160 		else
   161 		{
   162 			g_ServerCfgFile = icvar->FindVar("lservercfgfile");
   163 		}
   164 
   165 		if (g_ServerCfgFile != NULL)
   166 		{
   167 			g_pExecPtr = FindCommand("exec");
   168 			if (g_pExecPtr != NULL)
   169 			{
   170 				SH_ADD_HOOK(ConCommand, Dispatch, g_pExecPtr, SH_STATIC(Hook_ExecDispatchPre), false);
   171 				SH_ADD_HOOK(ConCommand, Dispatch, g_pExecPtr, SH_STATIC(Hook_ExecDispatchPost), true);
   172 			}
   173 			else
   174 			{
   175 				g_ServerCfgFile = NULL;
   176 			}
   177 		}
   178 		already_checked = true;
   179 	}
   180 
   181 	g_bConfigsExecd = false;
   182 	g_bServerExecd = false;
   183 	g_bGotServerStart = false;
   184 	g_bGotTrigger = false;
   185 }
   186 
   187 void CoreConfig::OnRootConsoleCommand(const char *cmdname, const CCommand &command)
   188 {
   189 	int argcount = command.ArgC();
   190 	if (argcount >= 4)
   191 	{
   192 		const char *option = command.Arg(2);
   193 		const char *value = command.Arg(3);
   194 
   195 		char error[255];
   196 
   197 		ConfigResult res = SetConfigOption(option, value, ConfigSource_Console, error, sizeof(error));
   198 
   199 		if (res == ConfigResult_Reject)
   200 		{
   201 			g_RootMenu.ConsolePrint("[SM] Could not set config option \"%s\" to \"%s\". (%s)", option, value, error);
   202 		} else if (res == ConfigResult_Ignore) {
   203 			g_RootMenu.ConsolePrint("[SM] No such config option \"%s\" exists.", option);
   204 		} else {
   205 			g_RootMenu.ConsolePrint("[SM] Config option \"%s\" successfully set to \"%s\".", option, value);
   206 		}
   207 
   208 		return;
   209 	} else if (argcount >= 3) {
   210 		const char *option = command.Arg(2);
   211 		
   212 		const char *value = GetCoreConfigValue(option);
   213 		
   214 		if (value == NULL)
   215 		{
   216 			g_RootMenu.ConsolePrint("[SM] No such config option \"%s\" exists.", option);
   217 		} else {
   218 			g_RootMenu.ConsolePrint("[SM] Config option \"%s\" is set to \"%s\".", option, value);
   219 		}
   220 		
   221 		return;
   222 	}
   223 
   224 	g_RootMenu.ConsolePrint("[SM] Usage: sm config <option> [value]");
   225 }
   226 
   227 void CoreConfig::Initialize()
   228 {
   229 	SMCError err;
   230 	char filePath[PLATFORM_MAX_PATH];
   231 
   232 	/* Try to get command line value of core config convar */
   233 	const char *corecfg = icvar->GetCommandLineValue("sm_corecfgfile");
   234 
   235 	/* If sm_corecfgfile is on the command line, use that
   236 	 * If sm_corecfgfile isn't there, check sm_basepath on the command line and build the path off that
   237 	 * If sm_basepath isn't there, just use the default path for the cfg
   238 	 */
   239 	if (corecfg)
   240 	{
   241 		g_LibSys.PathFormat(filePath, sizeof(filePath), "%s/%s", g_SourceMod.GetGamePath(), corecfg);
   242 	}
   243 	else
   244 	{
   245 		const char *basepath = icvar->GetCommandLineValue("sm_basepath");
   246 
   247 		/* Format path to config file */
   248 		if (basepath)
   249 		{
   250 			g_LibSys.PathFormat(filePath, sizeof(filePath), "%s/%s/%s", g_SourceMod.GetGamePath(), basepath, "configs/core.cfg");
   251 		}
   252 		else
   253 		{
   254 			g_LibSys.PathFormat(filePath, sizeof(filePath), "%s/%s", g_SourceMod.GetGamePath(), sm_corecfgfile.GetDefault());
   255 		}
   256 	}
   257 
   258 	/* Reset cached key values */
   259 	m_KeyValues.clear();
   260 	m_Strings.Reset();
   261 
   262 	/* Parse config file */
   263 	if ((err=textparsers->ParseFile_SMC(filePath, this, NULL)) != SMCError_Okay)
   264 	{
   265  		/* :TODO: This won't actually log or print anything :( - So fix that somehow */
   266 		const char *error = textparsers->GetSMCErrorString(err);
   267 		g_Logger.LogFatal("[SM] Error encountered parsing core config file: %s", error ? error : "");
   268 	}
   269 }
   270 
   271 SMCResult CoreConfig::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
   272 {
   273 	char error[255];
   274 	ConfigResult err = SetConfigOption(key, value, ConfigSource_File, error, sizeof(error));
   275 
   276 	if (err == ConfigResult_Reject)
   277 	{
   278 		/* This is a fatal error */
   279 		g_Logger.LogFatal("Config error (key: %s) (value: %s) %s", key, value, error);
   280 	}
   281 
   282 	return SMCResult_Continue;
   283 }
   284 
   285 ConfigResult CoreConfig::SetConfigOption(const char *option, const char *value, ConfigSource source, char *error, size_t maxlength)
   286 {
   287 	ConfigResult result;
   288 
   289 	/* Notify! */
   290 	SMGlobalClass *pBase = SMGlobalClass::head;
   291 	while (pBase)
   292 	{
   293 		if ((result = pBase->OnSourceModConfigChanged(option, value, source, error, maxlength)) != ConfigResult_Ignore)
   294 		{
   295 			return result;
   296 		}
   297 		pBase = pBase->m_pGlobalClassNext;
   298 	}
   299 
   300 	m_KeyValues.replace(option, m_Strings.AddString(value));
   301 
   302 	return ConfigResult_Ignore;
   303 }
   304 
   305 const char *CoreConfig::GetCoreConfigValue(const char *key)
   306 {
   307 	int address;
   308 	if (!m_KeyValues.retrieve(key, &address))
   309 		return NULL;
   310 	return m_Strings.GetString(address);
   311 }
   312 
   313 bool SM_AreConfigsExecuted()
   314 {
   315 	return g_bConfigsExecd;
   316 }
   317 
   318 inline bool IsPathSepChar(char c)
   319 {
   320 #if defined PLATFORM_WINDOWS
   321 	return (c == '\\' || c == '/');
   322 #elif defined PLATFORM_LINUX || defined PLATFORM_POSIX
   323 	return (c == '/');
   324 #endif
   325 }
   326 
   327 bool SM_ExecuteConfig(IPlugin *pl, AutoConfig *cfg, bool can_create)
   328 {
   329 	bool will_create = false;
   330 	
   331 	/* See if we should be creating */
   332 	if (can_create && cfg->create)
   333 	{
   334 		will_create = true;
   335 		
   336 		/* If the folder does not exist, attempt to create it.  
   337 		 * We're awfully nice.
   338 		 */
   339 		const char *folder = cfg->folder.c_str();
   340 		char path[PLATFORM_MAX_PATH];
   341 		char build[PLATFORM_MAX_PATH];
   342 
   343 		g_SourceMod.BuildPath(Path_Game, path, sizeof(path), "cfg/%s", folder);
   344 
   345 		if (!g_LibSys.IsPathDirectory(path))
   346 		{
   347 			char *cur_ptr = path;
   348 			size_t len;
   349 			
   350 			g_LibSys.PathFormat(path, sizeof(path), "%s", folder);
   351 			len = g_SourceMod.BuildPath(Path_Game, build, sizeof(build), "cfg");
   352 
   353 			do
   354 			{
   355 				/* Find next suitable path */
   356 				char *next_ptr = cur_ptr;
   357 				while (*next_ptr != '\0')
   358 				{
   359 					if (IsPathSepChar(*next_ptr))
   360 					{
   361 						*next_ptr = '\0';
   362 						next_ptr++;
   363 						break;
   364 					}
   365 					next_ptr++;
   366 				}
   367 				if (*next_ptr == '\0')
   368 				{
   369 					next_ptr = NULL;
   370 				}
   371 				len += g_LibSys.PathFormat(&build[len], 
   372 					sizeof(build)-len,
   373 					"/%s",
   374 					cur_ptr);
   375 				if (!g_LibSys.CreateFolder(build))
   376 				{
   377 					break;
   378 				}
   379 				cur_ptr = next_ptr;
   380 			} while (cur_ptr);
   381 		}
   382 	}
   383 
   384 	/* Check if the file exists. */
   385 	char file[PLATFORM_MAX_PATH];
   386 	char local[PLATFORM_MAX_PATH];
   387 
   388 	if (cfg->folder.size())
   389 	{
   390 		g_LibSys.PathFormat(local, 
   391 			sizeof(local), 
   392 			"%s/%s.cfg",
   393 			cfg->folder.c_str(),
   394 			cfg->autocfg.c_str());
   395 	} else {
   396 		g_LibSys.PathFormat(local,
   397 			sizeof(local),
   398 			"%s.cfg",
   399 			cfg->autocfg.c_str());
   400 	}
   401 	
   402 	g_SourceMod.BuildPath(Path_Game, file, sizeof(file), "cfg/%s", local);
   403 
   404 	bool file_exists = g_LibSys.IsPathFile(file);
   405 	if (!file_exists && will_create)
   406 	{
   407 		List<const ConVar *> *convars = NULL;
   408 		if (pl->GetProperty("ConVarList", (void **)&convars, false) && convars)
   409 		{
   410 			/* Attempt to create it */
   411 			FILE *fp = fopen(file, "wt");
   412 			if (fp)
   413 			{
   414 				fprintf(fp, "// This file was auto-generated by SourceMod (v%s)\n", SM_VERSION_STRING);
   415 				fprintf(fp, "// ConVars for plugin \"%s\"\n", pl->GetFilename());
   416 				fprintf(fp, "\n\n");
   417 
   418 				List<const ConVar *>::iterator iter;
   419 				float x;
   420 				for (iter = convars->begin(); iter != convars->end(); iter++)
   421 				{
   422 					const ConVar *cvar = (*iter);
   423 
   424 					if ((cvar->GetFlags() & FCVAR_DONTRECORD) == FCVAR_DONTRECORD)
   425 					{
   426 						continue;
   427 					}
   428 
   429 					char descr[255];
   430 					char *dptr = descr;
   431 
   432 					/* Print comments until there is no more */
   433 					strncopy(descr, cvar->GetHelpText(), sizeof(descr));
   434 					while (*dptr != '\0')
   435 					{
   436 						/* Find the next line */
   437 						char *next_ptr = dptr;
   438 						while (*next_ptr != '\0')
   439 						{
   440 							if (*next_ptr == '\n')
   441 							{
   442 								*next_ptr = '\0';
   443 								next_ptr++;
   444 								break;
   445 							}
   446 							next_ptr++;
   447 						}
   448 						fprintf(fp, "// %s\n", dptr);
   449 						dptr = next_ptr;
   450 					}
   451 
   452 					fprintf(fp, "// -\n");
   453 					fprintf(fp, "// Default: \"%s\"\n", cvar->GetDefault());
   454 					if (cvar->GetMin(x))
   455 					{
   456 						fprintf(fp, "// Minimum: \"%02f\"\n", x);
   457 					}
   458 					if (cvar->GetMax(x))
   459 					{
   460 						fprintf(fp, "// Maximum: \"%02f\"\n", x);
   461 					}
   462 					fprintf(fp, "%s \"%s\"\n", cvar->GetName(), cvar->GetDefault());
   463 					fprintf(fp, "\n");
   464 				}
   465 				fprintf(fp, "\n");
   466 
   467 				file_exists = true;
   468 				can_create = false;
   469 				fclose(fp);
   470 			}
   471 			else
   472 			{
   473 				g_Logger.LogError("Failed to auto generate config for %s, make sure the directory has write permission.", pl->GetFilename());
   474 				return can_create;
   475 			}
   476 		}
   477 	}
   478 
   479 	if (file_exists)
   480 	{
   481 		char cmd[255];
   482 		UTIL_Format(cmd, sizeof(cmd), "exec %s\n", local);
   483 		engine->ServerCommand(cmd);
   484 	}
   485 
   486 	return can_create;
   487 }
   488 
   489 void SM_DoSingleExecFwds(IPluginContext *ctx)
   490 {
   491 	IPluginFunction *pf;
   492 
   493 	if ((pf = ctx->GetFunctionByName("OnServerCfg")) != NULL)
   494 	{
   495 		pf->Execute(NULL);
   496 	}
   497 
   498 	if ((pf = ctx->GetFunctionByName("OnConfigsExecuted")) != NULL)
   499 	{
   500 		pf->Execute(NULL);
   501 	}
   502 }
   503 
   504 void SM_ConfigsExecuted_Plugin(unsigned int serial)
   505 {
   506 	IPluginIterator *iter = scripts->GetPluginIterator();
   507 	while (iter->MorePlugins())
   508 	{
   509 		IPlugin *plugin = iter->GetPlugin();
   510 		if (plugin->GetSerial() == serial)
   511 		{
   512 			SM_DoSingleExecFwds(plugin->GetBaseContext());
   513 			break;
   514 		}
   515 		iter->NextPlugin();
   516 	}
   517 	iter->Release();
   518 }
   519 
   520 void SM_ExecuteForPlugin(IPluginContext *ctx)
   521 {
   522 	SMPlugin *plugin = scripts->FindPluginByContext(ctx->GetContext());
   523 
   524 	unsigned int num = plugin->GetConfigCount();
   525 	if (!num)
   526 	{
   527 		SM_DoSingleExecFwds(ctx);
   528 	}
   529 	else
   530 	{
   531 		bool can_create = true;
   532 		for (unsigned int i=0; i<num; i++)
   533 		{
   534 			can_create = SM_ExecuteConfig(plugin, plugin->GetConfig(i), can_create);
   535 		}
   536 		char cmd[255];
   537 		UTIL_Format(cmd, sizeof(cmd), "sm internal 2 %d\n", plugin->GetSerial());
   538 		engine->ServerCommand(cmd);
   539 	}
   540 }
   541 
   542 void SM_ExecuteAllConfigs()
   543 {
   544 	if (g_bGotServerStart)
   545 	{
   546 		return;
   547 	}
   548 
   549 	engine->ServerCommand("exec sourcemod/sourcemod.cfg\n");
   550 
   551 	AutoPluginList plugins(scripts);
   552 	for (size_t i = 0; i < plugins->size(); i++)
   553 	{
   554 		SMPlugin *plugin = plugins->at(i);
   555 		unsigned int num = plugin->GetConfigCount();
   556 		bool can_create = true;
   557 		for (unsigned int i=0; i<num; i++)
   558 		{
   559 			can_create = SM_ExecuteConfig(plugin, plugin->GetConfig(i), can_create);
   560 		}
   561 	}
   562 
   563 	g_bGotServerStart = true;
   564 	CheckAndFinalizeConfigs();
   565 }
   566 
   567 void SM_ConfigsExecuted_Global()
   568 {
   569 	if (g_bConfigsExecd)
   570 	{
   571 		return;
   572 	}
   573 
   574 	g_bConfigsExecd = true;
   575 
   576 	g_pOnServerCfg->Execute(NULL);
   577 	g_pOnConfigsExecuted->Execute(NULL);
   578 }
   579 
   580 void SM_InternalCmdTrigger()
   581 {
   582 	/* Order is important here.  We need to buffer things before we send the command out. */
   583 	g_pOnAutoConfigsBuffered->Execute(NULL);
   584 	engine->ServerCommand("sm internal 1\n");
   585     g_PendingInternalPush = false;
   586 }
   587