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