core/CoreConfig.cpp
author Scott Ehlert <ds@alliedmods.net>
Wed Feb 18 02:19:22 2009 -0600 (2009-02-18)
changeset 2583 d3b2571c378b
parent 2479 44e8e76d4e53
child 2584 6972eb067f48
permissions -rw-r--r--
Added support for Dark Messiah engine and game. (no bug, r=me).

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