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