core/logic/GameConfigs.cpp
author Scott Ehlert <ds@alliedmods.net>
Sun Feb 09 22:50:20 2014 -0600 (2014-02-09)
changeset 4241 46fdb8cd9883
parent 4236 843313c70517
child 4242 95bc548f5857
permissions -rw-r--r--
Use == instead of is operator for checking string equality in AMBuild scripts (bug 6043, r=dvander).
     1 /**
     2  * vim: set ts=4 sw=4 tw=99 noet :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2009 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 
    30 #include "common_logic.h"
    31 #include <string.h>
    32 #include <stdlib.h>
    33 #include <sh_list.h>
    34 #include <sh_string.h>
    35 #include "GameConfigs.h"
    36 #include "stringutil.h"
    37 #include <IGameHelpers.h>
    38 #include <ILibrarySys.h>
    39 #include <IHandleSys.h>
    40 #include <IMemoryUtils.h>
    41 #include <ISourceMod.h>
    42 #include "common_logic.h"
    43 #include "sm_crc32.h"
    44 #include "MemoryUtils.h"
    45 
    46 #if defined PLATFORM_POSIX
    47 #include <dlfcn.h>
    48 #endif
    49 
    50 using namespace SourceHook;
    51 
    52 GameConfigManager g_GameConfigs;
    53 IGameConfig *g_pGameConf = NULL;
    54 static char g_Game[256];
    55 static char g_GameDesc[256] = {'!', '\0'};
    56 static char g_GameName[256] = {'$', '\0'};
    57 static const char *g_pParseEngine = NULL;
    58 
    59 #define PSTATE_NONE						0
    60 #define PSTATE_GAMES					1
    61 #define PSTATE_GAMEDEFS					2
    62 #define PSTATE_GAMEDEFS_OFFSETS			3
    63 #define PSTATE_GAMEDEFS_OFFSETS_OFFSET	4
    64 #define PSTATE_GAMEDEFS_KEYS			5
    65 #define PSTATE_GAMEDEFS_SUPPORTED		6
    66 #define PSTATE_GAMEDEFS_SIGNATURES		7
    67 #define PSTATE_GAMEDEFS_SIGNATURES_SIG	8
    68 #define PSTATE_GAMEDEFS_CRC				9
    69 #define PSTATE_GAMEDEFS_CRC_BINARY		10
    70 #define PSTATE_GAMEDEFS_CUSTOM			11
    71 #define PSTATE_GAMEDEFS_ADDRESSES		12
    72 #define PSTATE_GAMEDEFS_ADDRESSES_ADDRESS	13
    73 #define PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ	14
    74 
    75 #if defined PLATFORM_WINDOWS
    76 #define PLATFORM_NAME				"windows"
    77 #define PLATFORM_SERVER_BINARY		"server.dll"
    78 #elif defined PLATFORM_LINUX
    79 #define PLATFORM_NAME				"linux"
    80 #define PLATFORM_SERVER_BINARY		"server_i486.so"
    81 #elif defined PLATFORM_APPLE
    82 #define PLATFORM_NAME               "mac"
    83 #define PLATFORM_SERVER_BINARY      "server.dylib"
    84 #endif
    85 
    86 struct TempSigInfo
    87 {
    88 	void Reset()
    89 	{
    90 		library[0] = '\0';
    91 		sig[0] = '\0';
    92 	}
    93 	char sig[1024];
    94 	char library[64];
    95 } s_TempSig;
    96 unsigned int s_ServerBinCRC;
    97 bool s_ServerBinCRC_Ok = false;
    98 
    99 static bool DoesGameMatch(const char *value)
   100 {
   101 #if defined PLATFORM_WINDOWS
   102 	if (strcasecmp(value, g_Game) == 0 ||
   103 #else
   104 	if (strcmp(value, g_Game) == 0 ||
   105 #endif
   106 		strcmp(value, g_GameDesc) == 0 ||
   107 		strcmp(value, g_GameName) == 0)
   108 	{
   109 		return true;
   110 	}
   111 	return false;
   112 }
   113 
   114 static bool DoesEngineMatch(const char *value)
   115 {
   116 	return strcmp(value, g_pParseEngine) == 0;
   117 }
   118 
   119 CGameConfig::CGameConfig(const char *file, const char *engine)
   120 {
   121 	strncopy(m_File, file, sizeof(m_File));
   122 
   123 	m_CustomLevel = 0;
   124 	m_CustomHandler = NULL;
   125 
   126 	if (!engine)
   127 		m_pEngine = smcore.GetSourceEngineName();
   128 	else
   129 		m_pEngine = engine;
   130 
   131 	if (strcmp(m_pEngine, "css") == 0 || strcmp(m_pEngine, "dods") == 0 || strcmp(m_pEngine, "hl2dm") == 0 || strcmp(m_pEngine, "tf2") == 0)
   132 		this->SetBaseEngine("orangebox_valve");
   133 	else if (strcmp(m_pEngine, "nucleardawn") == 0)
   134 		this->SetBaseEngine("left4dead2");
   135 	else
   136 		this->SetBaseEngine(NULL);
   137 }
   138 
   139 CGameConfig::~CGameConfig()
   140 {
   141 	g_GameConfigs.RemoveCachedConfig(this);
   142 }
   143 
   144 SMCResult CGameConfig::ReadSMC_NewSection(const SMCStates *states, const char *name)
   145 {
   146 	if (m_IgnoreLevel)
   147 	{
   148 		m_IgnoreLevel++;
   149 		return SMCResult_Continue;
   150 	}
   151 
   152 	switch (m_ParseState)
   153 	{
   154 	case PSTATE_NONE:
   155 		{
   156 			if (strcmp(name, "Games") == 0)
   157 			{
   158 				m_ParseState = PSTATE_GAMES;
   159 			} else {
   160 				m_IgnoreLevel++;
   161 			}
   162 			break;
   163 		}
   164 	case PSTATE_GAMES:
   165 		{
   166 			if (strcmp(name, "*") == 0 ||
   167 				strcmp(name, "#default") == 0 ||
   168 				DoesGameMatch(name))
   169 			{
   170 				bShouldBeReadingDefault = true;
   171 				m_ParseState = PSTATE_GAMEDEFS;
   172 				strncopy(m_Game, name, sizeof(m_Game));
   173 			} else {
   174 				m_IgnoreLevel++;
   175 			}
   176 			break;
   177 		}
   178 	case PSTATE_GAMEDEFS:
   179 		{
   180 			if (strcmp(name, "Offsets") == 0)
   181 			{
   182 				m_ParseState = PSTATE_GAMEDEFS_OFFSETS;
   183 			}
   184 			else if (strcmp(name, "Keys") == 0)
   185 			{
   186 				m_ParseState = PSTATE_GAMEDEFS_KEYS;
   187 			}
   188 			else if ((strcmp(name, "#supported") == 0) && (strcmp(m_Game, "#default") == 0))
   189 			{
   190 				m_ParseState = PSTATE_GAMEDEFS_SUPPORTED;
   191 				/* Ignore this section unless we get a game. */
   192 				bShouldBeReadingDefault = false;
   193 				had_game = false;
   194 				matched_game = false;
   195 				had_engine = false;
   196 				matched_engine = false;
   197 			}
   198 			else if (strcmp(name, "Signatures") == 0)
   199 			{
   200 				m_ParseState = PSTATE_GAMEDEFS_SIGNATURES;
   201 			}
   202 			else if (strcmp(name, "CRC") == 0)
   203 			{
   204 				m_ParseState = PSTATE_GAMEDEFS_CRC;
   205 				bShouldBeReadingDefault = false;
   206 			}
   207 			else if (strcmp(name, "Addresses") == 0)
   208 			{
   209 				m_ParseState = PSTATE_GAMEDEFS_ADDRESSES;
   210 			}
   211 			else
   212 			{
   213 				if (g_GameConfigs.m_customHandlers.retrieve(name, &m_CustomHandler))
   214 				{
   215 					m_CustomLevel = 0;
   216 					m_ParseState = PSTATE_GAMEDEFS_CUSTOM;
   217 					m_CustomHandler->ReadSMC_ParseStart();
   218 					break;
   219 				}
   220 
   221 				m_IgnoreLevel++;
   222 			}
   223 			break;
   224 		}
   225 	case PSTATE_GAMEDEFS_OFFSETS:
   226 		{
   227 			m_Prop[0] = '\0';
   228 			m_Class[0] = '\0';
   229 			strncopy(m_offset, name, sizeof(m_offset));
   230 			m_ParseState = PSTATE_GAMEDEFS_OFFSETS_OFFSET;
   231 			break;
   232 		}
   233 	case PSTATE_GAMEDEFS_SIGNATURES:
   234 		{
   235 			strncopy(m_offset, name, sizeof(m_offset));
   236 			s_TempSig.Reset();
   237 			m_ParseState = PSTATE_GAMEDEFS_SIGNATURES_SIG;
   238 			break;
   239 		}
   240 	case PSTATE_GAMEDEFS_CRC:
   241 		{
   242 			char error[255];
   243 			error[0] = '\0';
   244 			if (strcmp(name, "server") != 0)
   245 			{
   246 				smcore.Format(error, sizeof(error), "Unrecognized library \"%s\"", name);
   247 			} 
   248 			else if (!s_ServerBinCRC_Ok)
   249 			{
   250 				FILE *fp;
   251 				char path[PLATFORM_MAX_PATH];
   252 
   253 				g_pSM->BuildPath(Path_Game, path, sizeof(path), "bin/" PLATFORM_SERVER_BINARY);
   254 				if ((fp = fopen(path, "rb")) == NULL)
   255 				{
   256 					smcore.Format(error, sizeof(error), "Could not open binary: %s", path);
   257 				} else {
   258 					size_t size;
   259 					void *buffer;
   260 
   261 					fseek(fp, 0, SEEK_END);
   262 					size = ftell(fp);
   263 					fseek(fp, 0, SEEK_SET);
   264 
   265 					buffer = malloc(size);
   266 					fread(buffer, size, 1, fp);
   267 					s_ServerBinCRC = UTIL_CRC32(buffer, size);
   268 					free(buffer);
   269 					s_ServerBinCRC_Ok = true;
   270 					fclose(fp);
   271 				}
   272 			}
   273 			if (error[0] != '\0')
   274 			{
   275 				m_IgnoreLevel = 1;
   276 				smcore.LogError("[SM] Error while parsing CRC section for \"%s\" (%s):", m_Game, m_CurFile);
   277 				smcore.LogError("[SM] %s", error);
   278 			} else {
   279 				m_ParseState = PSTATE_GAMEDEFS_CRC_BINARY;
   280 			}
   281 			break;
   282 		}
   283 	case PSTATE_GAMEDEFS_CUSTOM:
   284 		{
   285 			m_CustomLevel++;
   286 			return m_CustomHandler->ReadSMC_NewSection(states, name);
   287 			break;
   288 		}
   289 	case PSTATE_GAMEDEFS_ADDRESSES:
   290 		{
   291 			m_Address[0] = '\0';
   292 			m_AddressSignature[0] = '\0';
   293 			m_AddressReadCount = 0;
   294 
   295 			strncopy(m_Address, name, sizeof(m_Address));
   296 			m_ParseState = PSTATE_GAMEDEFS_ADDRESSES_ADDRESS;
   297 
   298 			break;
   299 		}
   300 	case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS:
   301 		{
   302 			if (strcmp(name, PLATFORM_NAME) == 0)
   303 			{
   304 				m_ParseState = PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ;
   305 			}
   306 			else
   307 			{
   308 				if (strcmp(name, "linux") != 0 && strcmp(name, "windows") != 0 && strcmp(name, "mac") != 0)
   309 				{
   310 					smcore.LogError("[SM] Error while parsing Address section for \"%s\" (%s):", m_Address, m_CurFile);
   311 					smcore.LogError("[SM] Unrecognized platform \"%s\"", name);
   312 				}
   313 				m_IgnoreLevel = 1;
   314 			}
   315 			break;
   316 		}
   317 	/* No sub-sections allowed:
   318 	 case PSTATE_GAMEDEFS_OFFSETS_OFFSET:
   319 	 case PSTATE_GAMEDEFS_KEYS:
   320 	 case PSTATE_GAMEDEFS_SUPPORTED:
   321 	 case PSTATE_GAMEDEFS_SIGNATURES_SIG:
   322 	 case PSTATE_GAMEDEFS_CRC_BINARY:
   323 	 case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ:
   324 	 */
   325 	default:
   326 		{
   327 			/* If we don't know what we got, start ignoring */
   328 			m_IgnoreLevel++;
   329 			break;
   330 		}
   331 	}
   332 
   333 	return SMCResult_Continue;
   334 }
   335 
   336 SMCResult CGameConfig::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
   337 {
   338 	if (m_IgnoreLevel)
   339 	{
   340 		return SMCResult_Continue;
   341 	}
   342 
   343 	if (m_ParseState == PSTATE_GAMEDEFS_OFFSETS_OFFSET)
   344 	{
   345 		if (strcmp(key, "class") == 0)
   346 		{
   347 			strncopy(m_Class, value, sizeof(m_Class));
   348 		} else if (strcmp(key, "prop") == 0) {
   349 			strncopy(m_Prop, value, sizeof(m_Prop));
   350 		} else if (strcmp(key, PLATFORM_NAME) == 0) {
   351 			m_Offsets.replace(m_offset, atoi(value));
   352 		}
   353 	} else if (m_ParseState == PSTATE_GAMEDEFS_KEYS) {
   354 		ke::AString vstr(value);
   355 		m_Keys.replace(key, ke::Move(vstr));
   356 	} else if (m_ParseState == PSTATE_GAMEDEFS_SUPPORTED) {
   357 		if (strcmp(key, "game") == 0)
   358 		{
   359 			had_game = true;
   360 			if (DoesGameMatch(value))
   361 			{
   362 				matched_game = true;
   363 			}
   364 			if ((!had_engine && matched_game) || (matched_engine && matched_game))
   365 			{
   366 				bShouldBeReadingDefault = true;
   367 			}
   368 		}
   369 		else if (strcmp(key, "engine") == 0)
   370 		{
   371 			had_engine = true;
   372 			if (DoesEngineMatch(value))
   373 			{
   374 				matched_engine = true;
   375 			}
   376 			if ((!had_game && matched_engine) || (matched_game && matched_engine))
   377 			{
   378 				bShouldBeReadingDefault = true;
   379 			}
   380 		}
   381 	} else if (m_ParseState == PSTATE_GAMEDEFS_SIGNATURES_SIG) {
   382 		if (strcmp(key, PLATFORM_NAME) == 0)
   383 		{
   384 			strncopy(s_TempSig.sig, value, sizeof(s_TempSig.sig));
   385 		} else if (strcmp(key, "library") == 0) {
   386 			strncopy(s_TempSig.library, value, sizeof(s_TempSig.library));
   387 		}
   388 	} else if (m_ParseState == PSTATE_GAMEDEFS_CRC_BINARY) {
   389 		if (strcmp(key, PLATFORM_NAME) == 0 
   390 			&& s_ServerBinCRC_Ok
   391 			&& !bShouldBeReadingDefault)
   392 		{
   393 			unsigned int crc = 0;
   394 			sscanf(value, "%08X", &crc);
   395 			if (s_ServerBinCRC == crc)
   396 			{
   397 				bShouldBeReadingDefault = true;
   398 			}
   399 		}
   400 	} else if (m_ParseState == PSTATE_GAMEDEFS_ADDRESSES_ADDRESS || m_ParseState == PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ) {
   401 		if (strcmp(key, "read") == 0) {
   402 			int limit = sizeof(m_AddressRead)/sizeof(m_AddressRead[0]);
   403 			if (m_AddressReadCount < limit)
   404 			{
   405 				m_AddressRead[m_AddressReadCount] = atoi(value);
   406 				m_AddressReadCount++;
   407 			}
   408 			else
   409 			{
   410 				smcore.LogError("[SM] Error parsing Address \"%s\", does not support more than %d read offsets (gameconf \"%s\")", m_Address, limit, m_CurFile);
   411 			}
   412 		} else if (strcmp(key, "signature") == 0) {
   413 			strncopy(m_AddressSignature, value, sizeof(m_AddressSignature));
   414 		}
   415 	} else if (m_ParseState == PSTATE_GAMEDEFS_CUSTOM) {
   416 		return m_CustomHandler->ReadSMC_KeyValue(states, key, value);
   417 	}
   418 
   419 	return SMCResult_Continue;
   420 }
   421 
   422 SMCResult CGameConfig::ReadSMC_LeavingSection(const SMCStates *states)
   423 {
   424 	if (m_IgnoreLevel)
   425 	{
   426 		m_IgnoreLevel--;
   427 		return SMCResult_Continue;
   428 	}
   429 
   430 	if (m_CustomLevel)
   431 	{
   432 		m_CustomLevel--;
   433 		m_CustomHandler->ReadSMC_LeavingSection(states);
   434 		return SMCResult_Continue;
   435 	}
   436 
   437 	switch (m_ParseState)
   438 	{
   439 	case PSTATE_GAMES:
   440 		{
   441 			m_ParseState = PSTATE_NONE;
   442 			break;
   443 		}
   444 	case PSTATE_GAMEDEFS:
   445 		{
   446 			m_ParseState = PSTATE_GAMES;
   447 			break;
   448 		}
   449 	case PSTATE_GAMEDEFS_CUSTOM:
   450 		{
   451 			m_ParseState = PSTATE_GAMEDEFS;
   452 			m_CustomHandler->ReadSMC_ParseEnd(false, false);
   453 			break;
   454 		}
   455 	case PSTATE_GAMEDEFS_KEYS:
   456 	case PSTATE_GAMEDEFS_OFFSETS:
   457 		{
   458 			m_ParseState = PSTATE_GAMEDEFS;
   459 			break;
   460 		}
   461 	case PSTATE_GAMEDEFS_OFFSETS_OFFSET:
   462 		{
   463 			/* Parse the offset... */
   464 			if (m_Class[0] != '\0'
   465 				&& m_Prop[0] != '\0')
   466 			{
   467 				SendProp *pProp = gamehelpers->FindInSendTable(m_Class, m_Prop);
   468 				if (pProp)
   469 				{
   470 					int val = gamehelpers->GetSendPropOffset(pProp);
   471 					m_Offsets.replace(m_offset, val);
   472 					m_Props.replace(m_offset, pProp);
   473 				} else {
   474 					/* Check if it's a non-default game and no offsets exist */
   475 					if (((strcmp(m_Game, "*") != 0) && strcmp(m_Game, "#default") != 0)
   476 						&& (!m_Offsets.retrieve(m_offset)))
   477 					{
   478 						smcore.LogError("[SM] Unable to find property %s.%s (file \"%s\") (mod \"%s\")", 
   479 							m_Class,
   480 							m_Prop,
   481 							m_CurFile,
   482 							m_Game);
   483 					}
   484 				}
   485 			}
   486 			m_ParseState = PSTATE_GAMEDEFS_OFFSETS;
   487 			break;
   488 		}
   489 	case PSTATE_GAMEDEFS_CRC:
   490 	case PSTATE_GAMEDEFS_SUPPORTED:
   491 		{
   492 			if (!bShouldBeReadingDefault)
   493 			{
   494 				/* If we shouldn't read the rest of this section, set the ignore level. */
   495 				m_IgnoreLevel = 1;
   496 				m_ParseState = PSTATE_GAMES;
   497 			} else {
   498 				m_ParseState = PSTATE_GAMEDEFS;
   499 			}
   500 			break;
   501 		}
   502 	case PSTATE_GAMEDEFS_CRC_BINARY:
   503 		{
   504 			m_ParseState = PSTATE_GAMEDEFS_CRC;
   505 			break;
   506 		}
   507 	case PSTATE_GAMEDEFS_SIGNATURES:
   508 		{
   509 			m_ParseState = PSTATE_GAMEDEFS;
   510 			break;
   511 		}
   512 	case PSTATE_GAMEDEFS_SIGNATURES_SIG:
   513 		{
   514 			if (s_TempSig.library[0] == '\0')
   515 			{
   516 				/* assume server */
   517 				strncopy(s_TempSig.library, "server", sizeof(s_TempSig.library));
   518 			}
   519 			void *addrInBase = NULL;
   520 			if (strcmp(s_TempSig.library, "server") == 0)
   521 			{
   522 				addrInBase = smcore.serverFactory;
   523 			} else if (strcmp(s_TempSig.library, "engine") == 0) {
   524 				addrInBase = smcore.engineFactory;
   525 			} else if (strcmp(s_TempSig.library, "matchmaking_ds") == 0) {
   526 				addrInBase = smcore.matchmakingDSFactory;
   527 			}
   528 			void *final_addr = NULL;
   529 			if (addrInBase == NULL)
   530 			{
   531 				smcore.LogError("[SM] Unrecognized library \"%s\" (gameconf \"%s\")", 
   532 					s_TempSig.library, 
   533 					m_CurFile);
   534 			} else {
   535 				if (s_TempSig.sig[0] == '@')
   536 				{
   537 #if defined PLATFORM_WINDOWS
   538 					MEMORY_BASIC_INFORMATION mem;
   539 
   540 					if (VirtualQuery(addrInBase, &mem, sizeof(mem)))
   541 						final_addr = g_MemUtils.ResolveSymbol(mem.AllocationBase, &s_TempSig.sig[1]);
   542 					else
   543 						smcore.LogError("[SM] Unable to find library \"%s\" in memory (gameconf \"%s\")", s_TempSig.library, m_File);
   544 #elif defined PLATFORM_POSIX
   545 					Dl_info info;
   546 					/* GNU only: returns 0 on error, inconsistent! >:[ */
   547 					if (dladdr(addrInBase, &info) != 0)
   548 					{
   549 						void *handle = dlopen(info.dli_fname, RTLD_NOW);
   550 						if (handle)
   551 						{
   552 							if (smcore.SymbolsAreHidden())
   553 								final_addr = g_MemUtils.ResolveSymbol(handle, &s_TempSig.sig[1]);
   554 							else
   555 								final_addr = dlsym(handle, &s_TempSig.sig[1]);
   556 							dlclose(handle);
   557 						} else {
   558 							smcore.LogError("[SM] Unable to load library \"%s\" (gameconf \"%s\")",
   559 								s_TempSig.library,
   560 								m_File);
   561 						}
   562 					} else {
   563 						smcore.LogError("[SM] Unable to find library \"%s\" in memory (gameconf \"%s\")",
   564 							s_TempSig.library,
   565 							m_File);
   566 					}
   567 #endif
   568 				}
   569 				if (final_addr)
   570 				{
   571 					goto skip_find;
   572 				}
   573 				/* First, preprocess the signature */
   574 				unsigned char real_sig[511];
   575 				size_t real_bytes;
   576 				size_t length;
   577 
   578 				real_bytes = 0;
   579 				length = strlen(s_TempSig.sig);
   580 
   581 				real_bytes = UTIL_DecodeHexString(real_sig, sizeof(real_sig), s_TempSig.sig);
   582 
   583 				if (real_bytes >= 1)
   584 				{
   585 					final_addr = g_MemUtils.FindPattern(addrInBase, (char*)real_sig, real_bytes);
   586 				}
   587 			}
   588 
   589 skip_find:
   590 			m_Sigs.replace(m_offset, final_addr);
   591 			m_ParseState = PSTATE_GAMEDEFS_SIGNATURES;
   592 
   593 			break;
   594 		}
   595 	case PSTATE_GAMEDEFS_ADDRESSES:
   596 		{
   597 			m_ParseState = PSTATE_GAMEDEFS;
   598 			break;
   599 		}
   600 	case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS:
   601 		{
   602 			m_ParseState = PSTATE_GAMEDEFS_ADDRESSES;
   603 
   604 			if (m_Address[0] == '\0')
   605 			{
   606 				smcore.LogError("[SM] Address sections must have names (gameconf \"%s\")", m_CurFile);
   607 				break;
   608 			}
   609 			if (m_AddressSignature[0] == '\0')
   610 			{
   611 				smcore.LogError("[SM] Address section for \"%s\" did not specify a signature (gameconf \"%s\")", m_Address, m_CurFile);
   612 				break;
   613 			}
   614 
   615 			AddressConf addrConf(m_AddressSignature, sizeof(m_AddressSignature), m_AddressReadCount, m_AddressRead);
   616 			m_Addresses.replace(m_Address, addrConf);
   617 
   618 			break;
   619 		}
   620 	case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ:
   621 		{
   622 			m_ParseState = PSTATE_GAMEDEFS_ADDRESSES_ADDRESS;
   623 			break;
   624 		}
   625 	}
   626 
   627 	return SMCResult_Continue;
   628 }
   629 
   630 #define MSTATE_NONE		0
   631 #define MSTATE_MAIN		1
   632 #define MSTATE_FILE		2
   633 
   634 class MasterReader : public ITextListener_SMC
   635 {
   636 public:
   637 	virtual void ReadSMC_ParseStart()
   638 	{
   639 		state = MSTATE_NONE;
   640 		ignoreLevel = 0;
   641 	}
   642 
   643 	virtual SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name)
   644 	{
   645 		if (ignoreLevel)
   646 		{
   647 			return SMCResult_Continue;
   648 		}
   649 
   650 		if (state == MSTATE_NONE)
   651 		{
   652 			if (strcmp(name, "Game Master") == 0)
   653 			{
   654 				state = MSTATE_MAIN;
   655 			}
   656 			else
   657 			{
   658 				ignoreLevel++;
   659 			}
   660 		}
   661 		else if (state == MSTATE_MAIN)
   662 		{
   663 			strncopy(cur_file, name, sizeof(cur_file));
   664 			had_engine = false;
   665 			matched_engine = false;
   666 			had_game = false;
   667 			matched_game = false;
   668 			state = MSTATE_FILE;
   669 		}
   670 		else if (state == MSTATE_FILE)
   671 		{
   672 			ignoreLevel++;
   673 		}
   674 
   675 		return SMCResult_Continue;
   676 	}
   677 
   678 	virtual SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
   679 	{
   680 		if (ignoreLevel || state != MSTATE_FILE)
   681 		{
   682 			return SMCResult_Continue;
   683 		}
   684 
   685 		if (strcmp(key, "engine") == 0)
   686 		{
   687 			had_engine = true;
   688 			if (DoesEngineMatch(value))
   689 			{
   690 				matched_engine = true;
   691 			}
   692 		}
   693 		else if (strcmp(key, "game") == 0)
   694 		{
   695 			had_game = true;
   696 			if (DoesGameMatch(value))
   697 			{
   698 				matched_game = true;
   699 			}
   700 		}
   701 
   702 		return SMCResult_Continue;
   703 	}
   704 
   705 	virtual SMCResult ReadSMC_LeavingSection(const SMCStates *states)
   706 	{
   707 		if (ignoreLevel)
   708 		{
   709 			ignoreLevel--;
   710 			return SMCResult_Continue;
   711 		}
   712 
   713 		if (state == MSTATE_FILE)
   714 		{
   715 			/* The four success conditions:
   716 			 * 1. Needed nothing.
   717 			 * 2. Needed game only.
   718 			 * 3. Needed engine only.
   719 			 * 4. Needed both engine and game.
   720 			 * Final result is minimized via k-map.
   721 			 */
   722 #if 0
   723 			if ((!had_engine && !had_game) ||
   724 				(!had_engine && (had_game && matched_game)) ||
   725 				(!had_game && (had_engine && matched_engine)) ||
   726 				((had_game && had_engine) && (matched_game && matched_engine)))
   727 #endif
   728 			if ((!had_engine && !had_game) ||
   729 				(!had_engine && matched_game) ||
   730 				(!had_game && matched_engine) ||
   731 				(matched_engine && matched_game))
   732 			{
   733 				fileList->push_back(cur_file);
   734 			}
   735 			state = MSTATE_MAIN;
   736 		}
   737 		else if (state == MSTATE_MAIN)
   738 		{
   739 			state = MSTATE_NONE;
   740 		}
   741 
   742 		return SMCResult_Continue;
   743 	}
   744 public:
   745 	List<String> *fileList;
   746 	unsigned int state;
   747 	unsigned int ignoreLevel;
   748 	char cur_file[PLATFORM_MAX_PATH];
   749 	bool had_engine;
   750 	bool matched_engine;
   751 	bool had_game;
   752 	bool matched_game;
   753 };
   754 
   755 static MasterReader master_reader;
   756 
   757 bool CGameConfig::Reparse(char *error, size_t maxlength)
   758 {
   759 	/* Reset cached data */
   760 	m_Offsets.clear();
   761 	m_Props.clear();
   762 	m_Keys.clear();
   763 	m_Addresses.clear();
   764 
   765 	char path[PLATFORM_MAX_PATH];
   766 
   767 	/* See if we can use the extended gamedata format. */
   768 	g_pSM->BuildPath(Path_SM, path, sizeof(path), "gamedata/%s/master.games.txt", m_File);
   769 	if (!libsys->PathExists(path))
   770 	{
   771 		/* Nope, use the old mechanism. */
   772 		smcore.Format(path, sizeof(path), "%s.txt", m_File);
   773 		return EnterFile(path, error, maxlength);
   774 	}
   775 
   776 	/* Otherwise, it's time to parse the master. */
   777 	SMCError err;
   778 	SMCStates state = {0, 0};
   779 	List<String> fileList;
   780 	master_reader.fileList = &fileList;
   781 	const char *pEngine[2] = { m_pBaseEngine, m_pEngine  };
   782 
   783 	for (unsigned char iter = 0; iter < SM_ARRAYSIZE(pEngine); ++iter)
   784 	{
   785 		if (pEngine[iter] == NULL)
   786 		{
   787 			continue;
   788 		}
   789 
   790 		this->SetParseEngine(pEngine[iter]);
   791 		err = textparsers->ParseSMCFile(path, &master_reader, &state, error, maxlength);
   792 		if (err != SMCError_Okay)
   793 		{
   794 			const char *msg = textparsers->GetSMCErrorString(err);
   795 
   796 			smcore.LogError("[SM] Error parsing master gameconf file \"%s\":", path);
   797 			smcore.LogError("[SM] Error %d on line %d, col %d: %s", 
   798 				err,
   799 				state.line,
   800 				state.col,
   801 				msg ? msg : "Unknown error");
   802 			return false;
   803 		}
   804 	}
   805 
   806 	/* Go through each file we found and parse it. */
   807 	List<String>::iterator iter;
   808 	for (iter = fileList.begin(); iter != fileList.end(); iter++)
   809 	{
   810 		smcore.Format(path, sizeof(path), "%s/%s", m_File, (*iter).c_str());
   811 		if (!EnterFile(path, error, maxlength))
   812 		{
   813 			return false;
   814 		}
   815 	}
   816 
   817 	/* Parse the contents of the 'custom' directory */
   818 	g_pSM->BuildPath(Path_SM, path, sizeof(path), "gamedata/%s/custom", m_File);
   819 	IDirectory *customDir = libsys->OpenDirectory(path);
   820 
   821 	if (!customDir)
   822 	{
   823 		return true;
   824 	}
   825 
   826 	while (customDir->MoreFiles())
   827 	{
   828 		if (!customDir->IsEntryFile())
   829 		{
   830 			customDir->NextEntry();
   831 			continue;
   832 		}
   833 		
   834 		const char *curFile = customDir->GetEntryName();
   835 
   836 		/* Only allow .txt files */
   837 		int len = strlen(curFile);
   838 		if (len > 4 && strcmp(&curFile[len-4], ".txt") != 0)
   839 		{
   840 			customDir->NextEntry();
   841 			continue;	
   842 		}
   843 
   844 		smcore.Format(path, sizeof(path), "%s/custom/%s", m_File, curFile);
   845 		if (!EnterFile(path, error, maxlength))
   846 		{
   847 			libsys->CloseDirectory(customDir);
   848 			return false;
   849 		}
   850 
   851 		customDir->NextEntry();
   852 	}
   853 
   854 	libsys->CloseDirectory(customDir);
   855 	return true;
   856 }
   857 
   858 bool CGameConfig::EnterFile(const char *file, char *error, size_t maxlength)
   859 {
   860 	SMCError err;
   861 	SMCStates state = {0, 0};
   862 
   863 	g_pSM->BuildPath(Path_SM, m_CurFile, sizeof(m_CurFile), "gamedata/%s", file);
   864 
   865 	/* Initialize parse states */
   866 	m_IgnoreLevel = 0;
   867 	bShouldBeReadingDefault = true;
   868 	m_ParseState = PSTATE_NONE;
   869 	const char *pEngine[2] = { m_pBaseEngine, m_pEngine };
   870 
   871 	for (unsigned char iter = 0; iter < SM_ARRAYSIZE(pEngine); ++iter)
   872 	{
   873 		if (pEngine[iter] == NULL)
   874 		{
   875 			continue;
   876 		}
   877 
   878 		this->SetParseEngine(pEngine[iter]);
   879 		if ((err=textparsers->ParseSMCFile(m_CurFile, this, &state, error, maxlength))
   880 			!= SMCError_Okay)
   881 		{
   882 			const char *msg = textparsers->GetSMCErrorString(err);
   883 
   884 			smcore.LogError("[SM] Error parsing gameconfig file \"%s\":", m_CurFile);
   885 			smcore.LogError("[SM] Error %d on line %d, col %d: %s", 
   886 				err,
   887 				state.line,
   888 				state.col,
   889 				msg ? msg : "Unknown error");
   890 
   891 			if (m_ParseState == PSTATE_GAMEDEFS_CUSTOM)
   892 			{
   893 				//error occurred while parsing a custom section
   894 				m_CustomHandler->ReadSMC_ParseEnd(true, true);
   895 				m_CustomHandler = NULL;
   896 				m_CustomLevel = 0;
   897 			}
   898 
   899 			return false;
   900 		}
   901 	}
   902 
   903 	return true;
   904 }
   905 
   906 void CGameConfig::SetBaseEngine(const char *engine)
   907 {
   908 	m_pBaseEngine = engine;
   909 }
   910 
   911 void CGameConfig::SetParseEngine(const char *engine)
   912 {
   913 	g_pParseEngine = engine;
   914 }
   915 
   916 bool CGameConfig::GetOffset(const char *key, int *value)
   917 {
   918 	return m_Offsets.retrieve(key, value);
   919 }
   920 
   921 const char *CGameConfig::GetKeyValue(const char *key)
   922 {
   923 	StringHashMap<ke::AString>::Result r = m_Keys.find(key);
   924 	if (!r.found())
   925 		return NULL;
   926 	return r->value.chars();
   927 }
   928 
   929 //memory addresses below 0x10000 are automatically considered invalid for dereferencing
   930 #define VALID_MINIMUM_MEMORY_ADDRESS 0x10000
   931 
   932 bool CGameConfig::GetAddress(const char *key, void **retaddr)
   933 {
   934 	StringHashMap<AddressConf>::Result r = m_Addresses.find(key);
   935 	if (!r.found())
   936 	{
   937 		*retaddr = NULL;
   938 		return false;
   939 	}
   940 
   941 	AddressConf &addrConf = r->value;
   942 
   943 	void *addr;
   944 	if (!GetMemSig(addrConf.signatureName, &addr))
   945 	{
   946 		*retaddr = NULL;
   947 		return false;
   948 	}
   949 
   950 	for (int i = 0; i < addrConf.readCount; ++i)
   951 	{
   952 		int offset = addrConf.read[i];
   953 
   954 		//NULLs in the middle of an indirection chain are bad, end NULL is ok
   955 		if (addr ==  NULL || reinterpret_cast<uintptr_t>(addr) < VALID_MINIMUM_MEMORY_ADDRESS)
   956 		{
   957 			*retaddr = NULL;
   958 			return false;
   959 		}
   960 		addr = *(reinterpret_cast<void**>(reinterpret_cast<uint8_t*>(addr) + offset));
   961 	}
   962 
   963 	*retaddr = addr;
   964 	return true;
   965 }
   966 
   967 static inline unsigned minOf(unsigned a, unsigned b)
   968 {
   969 	return a <= b ? a : b;
   970 }
   971 
   972 CGameConfig::AddressConf::AddressConf(char *sigName, unsigned sigLength, unsigned readCount, int *read)
   973 {
   974 	unsigned readLimit = minOf(readCount, sizeof(this->read) / sizeof(this->read[0]));
   975 
   976 	strncopy(signatureName, sigName, sizeof(signatureName) / sizeof(signatureName[0]));
   977 	this->readCount = readLimit;
   978 	memcpy(&this->read[0], read, sizeof(this->read[0])*readLimit);
   979 }
   980 
   981 SendProp *CGameConfig::GetSendProp(const char *key)
   982 {
   983 	SendProp *prop;
   984 	if (!m_Props.retrieve(key, &prop))
   985 		return NULL;
   986 	return prop;
   987 }
   988 
   989 bool CGameConfig::GetMemSig(const char *key, void **addr)
   990 {
   991 	return m_Sigs.retrieve(key, addr);
   992 }
   993 
   994 GameConfigManager::GameConfigManager()
   995 {
   996 }
   997 
   998 GameConfigManager::~GameConfigManager()
   999 {
  1000 }
  1001 
  1002 void GameConfigManager::OnSourceModStartup(bool late)
  1003 {
  1004 	LoadGameConfigFile("core.games", &g_pGameConf, NULL, 0);
  1005 
  1006 	strncopy(g_Game, g_pSM->GetGameFolderName(), sizeof(g_Game));
  1007 	strncopy(g_GameDesc + 1, smcore.GetGameDescription(), sizeof(g_GameDesc) - 1);
  1008 	smcore.GetGameName(g_GameName + 1, sizeof(g_GameName) - 1);
  1009 }
  1010 
  1011 void GameConfigManager::OnSourceModAllInitialized()
  1012 {
  1013 	/* NOW initialize the game file */
  1014 	CGameConfig *pGameConf = (CGameConfig *)g_pGameConf;
  1015 	
  1016 	char error[255];
  1017 	if (!pGameConf->Reparse(error, sizeof(error)))
  1018 	{
  1019 		/* :TODO: log */
  1020 	}
  1021 
  1022 	sharesys->AddInterface(NULL, this);
  1023 }
  1024 
  1025 void GameConfigManager::OnSourceModAllShutdown()
  1026 {
  1027 	CloseGameConfigFile(g_pGameConf);
  1028 }
  1029 
  1030 bool GameConfigManager::LoadGameConfigFile(const char *file, IGameConfig **_pConfig, char *error, size_t maxlength)
  1031 {
  1032 #if 0
  1033 	/* A crash was detected during last load - We block the gamedata loading so it hopefully won't happen again */
  1034 	if (g_blockGameDataLoad)
  1035 	{
  1036 		UTIL_Format(error, maxlength, "GameData loaded blocked due to detected crash");
  1037 		return false;
  1038 	}
  1039 #endif
  1040 
  1041 	CGameConfig *pConfig;
  1042 
  1043 	if (m_Lookup.retrieve(file, &pConfig))
  1044 	{
  1045 		pConfig->AddRef();
  1046 		*_pConfig = pConfig;
  1047 		return true;
  1048 	}
  1049 
  1050 	pConfig = new CGameConfig(file);
  1051 
  1052 	/* :HACKHACK: Don't parse the main config file */
  1053 	bool retval = true;
  1054 	if (_pConfig != &g_pGameConf)
  1055 	{
  1056 		retval = pConfig->Reparse(error, maxlength);
  1057 	}
  1058 
  1059 	m_Lookup.insert(file, pConfig);
  1060 
  1061 	*_pConfig = pConfig;
  1062 	return retval;
  1063 }
  1064 
  1065 void GameConfigManager::CloseGameConfigFile(IGameConfig *cfg)
  1066 {
  1067 	CGameConfig *pConfig = (CGameConfig *)cfg;
  1068 	pConfig->Release();
  1069 }
  1070 
  1071 extern Handle_t g_GameConfigsType;
  1072 
  1073 IGameConfig *GameConfigManager::ReadHandle(Handle_t hndl, IdentityToken_t *ident, HandleError *err)
  1074 {
  1075 	HandleSecurity sec(ident, g_pCoreIdent);
  1076 	IGameConfig *conf = NULL;
  1077 	HandleError _err = handlesys->ReadHandle(hndl, g_GameConfigsType, &sec, (void **)&conf);
  1078 
  1079 	if (err)
  1080 	{
  1081 		*err = _err;
  1082 	}
  1083 
  1084 	return conf;
  1085 }
  1086 
  1087 void GameConfigManager::AddUserConfigHook(const char *sectionname, ITextListener_SMC *listener)
  1088 {
  1089 	m_customHandlers.insert(sectionname, listener);
  1090 }
  1091 
  1092 void GameConfigManager::RemoveUserConfigHook(const char *sectionname, ITextListener_SMC *aListener)
  1093 {
  1094 	ITextListener_SMC *listener;
  1095 	if (!m_customHandlers.retrieve(sectionname, &listener))
  1096 		return;
  1097 
  1098 	if (listener != aListener)
  1099 		return;
  1100 
  1101 	m_customHandlers.remove(sectionname);
  1102 	return;
  1103 }
  1104 
  1105 void GameConfigManager::AcquireLock()
  1106 {
  1107 }
  1108 
  1109 void GameConfigManager::ReleaseLock()
  1110 {
  1111 }
  1112 
  1113 void GameConfigManager::RemoveCachedConfig(CGameConfig *config)
  1114 {
  1115 	m_Lookup.remove(config->m_File);
  1116 }