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