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