core/logic/GameConfigs.cpp
author David Anderson <dvander@alliedmods.net>
Sun Aug 25 11:59:45 2013 -0700 (2013-08-25)
changeset 4064 d7ca1e28949d
parent 4063 7b7372572e85
child 4114 58146f5766f0
permissions -rw-r--r--
Switch HandleSys off KTrie (bug 5884 part 2, r=ds).
     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_pStrings = new BaseStringTable(512);
   123 
   124 	m_CustomLevel = 0;
   125 	m_CustomHandler = NULL;
   126 
   127 	if (!engine)
   128 		m_pEngine = smcore.GetSourceEngineName();
   129 	else
   130 		m_pEngine = engine;
   131 
   132 	if (strcmp(m_pEngine, "css") == 0 || strcmp(m_pEngine, "dods") == 0 || strcmp(m_pEngine, "hl2dm") == 0 || strcmp(m_pEngine, "tf2") == 0)
   133 		this->SetBaseEngine("orangebox_valve");
   134 	else if (strcmp(m_pEngine, "nucleardawn") == 0)
   135 		this->SetBaseEngine("left4dead2");
   136 	else
   137 		this->SetBaseEngine(NULL);
   138 }
   139 
   140 CGameConfig::~CGameConfig()
   141 {
   142 	g_GameConfigs.RemoveCachedConfig(this);
   143 }
   144 
   145 SMCResult CGameConfig::ReadSMC_NewSection(const SMCStates *states, const char *name)
   146 {
   147 	if (m_IgnoreLevel)
   148 	{
   149 		m_IgnoreLevel++;
   150 		return SMCResult_Continue;
   151 	}
   152 
   153 	switch (m_ParseState)
   154 	{
   155 	case PSTATE_NONE:
   156 		{
   157 			if (strcmp(name, "Games") == 0)
   158 			{
   159 				m_ParseState = PSTATE_GAMES;
   160 			} else {
   161 				m_IgnoreLevel++;
   162 			}
   163 			break;
   164 		}
   165 	case PSTATE_GAMES:
   166 		{
   167 			if (strcmp(name, "*") == 0 ||
   168 				strcmp(name, "#default") == 0 ||
   169 				DoesGameMatch(name))
   170 			{
   171 				bShouldBeReadingDefault = true;
   172 				m_ParseState = PSTATE_GAMEDEFS;
   173 				strncopy(m_Game, name, sizeof(m_Game));
   174 			} else {
   175 				m_IgnoreLevel++;
   176 			}
   177 			break;
   178 		}
   179 	case PSTATE_GAMEDEFS:
   180 		{
   181 			if (strcmp(name, "Offsets") == 0)
   182 			{
   183 				m_ParseState = PSTATE_GAMEDEFS_OFFSETS;
   184 			}
   185 			else if (strcmp(name, "Keys") == 0)
   186 			{
   187 				m_ParseState = PSTATE_GAMEDEFS_KEYS;
   188 			}
   189 			else if ((strcmp(name, "#supported") == 0) && (strcmp(m_Game, "#default") == 0))
   190 			{
   191 				m_ParseState = PSTATE_GAMEDEFS_SUPPORTED;
   192 				/* Ignore this section unless we get a game. */
   193 				bShouldBeReadingDefault = false;
   194 				had_game = false;
   195 				matched_game = false;
   196 				had_engine = false;
   197 				matched_engine = false;
   198 			}
   199 			else if (strcmp(name, "Signatures") == 0)
   200 			{
   201 				m_ParseState = PSTATE_GAMEDEFS_SIGNATURES;
   202 			}
   203 			else if (strcmp(name, "CRC") == 0)
   204 			{
   205 				m_ParseState = PSTATE_GAMEDEFS_CRC;
   206 				bShouldBeReadingDefault = false;
   207 			}
   208 			else if (strcmp(name, "Addresses") == 0)
   209 			{
   210 				m_ParseState = PSTATE_GAMEDEFS_ADDRESSES;
   211 			}
   212 			else
   213 			{
   214 				if (g_GameConfigs.m_customHandlers.retrieve(name, &m_CustomHandler))
   215 				{
   216 					m_CustomLevel = 0;
   217 					m_ParseState = PSTATE_GAMEDEFS_CUSTOM;
   218 					m_CustomHandler->ReadSMC_ParseStart();
   219 					break;
   220 				}
   221 
   222 				m_IgnoreLevel++;
   223 			}
   224 			break;
   225 		}
   226 	case PSTATE_GAMEDEFS_OFFSETS:
   227 		{
   228 			m_Prop[0] = '\0';
   229 			m_Class[0] = '\0';
   230 			strncopy(m_offset, name, sizeof(m_offset));
   231 			m_ParseState = PSTATE_GAMEDEFS_OFFSETS_OFFSET;
   232 			break;
   233 		}
   234 	case PSTATE_GAMEDEFS_SIGNATURES:
   235 		{
   236 			strncopy(m_offset, name, sizeof(m_offset));
   237 			s_TempSig.Reset();
   238 			m_ParseState = PSTATE_GAMEDEFS_SIGNATURES_SIG;
   239 			break;
   240 		}
   241 	case PSTATE_GAMEDEFS_CRC:
   242 		{
   243 			char error[255];
   244 			error[0] = '\0';
   245 			if (strcmp(name, "server") != 0)
   246 			{
   247 				smcore.Format(error, sizeof(error), "Unrecognized library \"%s\"", name);
   248 			} 
   249 			else if (!s_ServerBinCRC_Ok)
   250 			{
   251 				FILE *fp;
   252 				char path[PLATFORM_MAX_PATH];
   253 
   254 				g_pSM->BuildPath(Path_Game, path, sizeof(path), "bin/" PLATFORM_SERVER_BINARY);
   255 				if ((fp = fopen(path, "rb")) == NULL)
   256 				{
   257 					smcore.Format(error, sizeof(error), "Could not open binary: %s", path);
   258 				} else {
   259 					size_t size;
   260 					void *buffer;
   261 
   262 					fseek(fp, 0, SEEK_END);
   263 					size = ftell(fp);
   264 					fseek(fp, 0, SEEK_SET);
   265 
   266 					buffer = malloc(size);
   267 					fread(buffer, size, 1, fp);
   268 					s_ServerBinCRC = UTIL_CRC32(buffer, size);
   269 					free(buffer);
   270 					s_ServerBinCRC_Ok = true;
   271 					fclose(fp);
   272 				}
   273 			}
   274 			if (error[0] != '\0')
   275 			{
   276 				m_IgnoreLevel = 1;
   277 				smcore.LogError("[SM] Error while parsing CRC section for \"%s\" (%s):", m_Game, m_CurFile);
   278 				smcore.LogError("[SM] %s", error);
   279 			} else {
   280 				m_ParseState = PSTATE_GAMEDEFS_CRC_BINARY;
   281 			}
   282 			break;
   283 		}
   284 	case PSTATE_GAMEDEFS_CUSTOM:
   285 		{
   286 			m_CustomLevel++;
   287 			return m_CustomHandler->ReadSMC_NewSection(states, name);
   288 			break;
   289 		}
   290 	case PSTATE_GAMEDEFS_ADDRESSES:
   291 		{
   292 			m_Address[0] = '\0';
   293 			m_AddressSignature[0] = '\0';
   294 			m_AddressReadCount = 0;
   295 
   296 			strncopy(m_Address, name, sizeof(m_Address));
   297 			m_ParseState = PSTATE_GAMEDEFS_ADDRESSES_ADDRESS;
   298 
   299 			break;
   300 		}
   301 	case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS:
   302 		{
   303 			if (strcmp(name, PLATFORM_NAME) == 0)
   304 			{
   305 				m_ParseState = PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ;
   306 			}
   307 			else
   308 			{
   309 				if (strcmp(name, "linux") != 0 && strcmp(name, "windows") != 0 && strcmp(name, "mac") != 0)
   310 				{
   311 					smcore.LogError("[SM] Error while parsing Address section for \"%s\" (%s):", m_Address, m_CurFile);
   312 					smcore.LogError("[SM] Unrecognized platform \"%s\"", name);
   313 				}
   314 				m_IgnoreLevel = 1;
   315 			}
   316 			break;
   317 		}
   318 	/* No sub-sections allowed:
   319 	 case PSTATE_GAMEDEFS_OFFSETS_OFFSET:
   320 	 case PSTATE_GAMEDEFS_KEYS:
   321 	 case PSTATE_GAMEDEFS_SUPPORTED:
   322 	 case PSTATE_GAMEDEFS_SIGNATURES_SIG:
   323 	 case PSTATE_GAMEDEFS_CRC_BINARY:
   324 	 case PSTATE_GAMEDEFS_ADDRESSES_ADDRESS_READ:
   325 	 */
   326 	default:
   327 		{
   328 			/* If we don't know what we got, start ignoring */
   329 			m_IgnoreLevel++;
   330 			break;
   331 		}
   332 	}
   333 
   334 	return SMCResult_Continue;
   335 }
   336 
   337 SMCResult CGameConfig::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
   338 {
   339 	if (m_IgnoreLevel)
   340 	{
   341 		return SMCResult_Continue;
   342 	}
   343 
   344 	if (m_ParseState == PSTATE_GAMEDEFS_OFFSETS_OFFSET)
   345 	{
   346 		if (strcmp(key, "class") == 0)
   347 		{
   348 			strncopy(m_Class, value, sizeof(m_Class));
   349 		} else if (strcmp(key, "prop") == 0) {
   350 			strncopy(m_Prop, value, sizeof(m_Prop));
   351 		} else if (strcmp(key, PLATFORM_NAME) == 0) {
   352 			m_Offsets.replace(m_offset, atoi(value));
   353 		}
   354 	} else if (m_ParseState == PSTATE_GAMEDEFS_KEYS) {
   355 		m_Keys.replace(key, m_pStrings->AddString(value));
   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_pStrings->Reset();
   761 	m_Offsets.clear();
   762 	m_Props.clear();
   763 	m_Keys.clear();
   764 	m_Addresses.clear();
   765 
   766 	char path[PLATFORM_MAX_PATH];
   767 
   768 	/* See if we can use the extended gamedata format. */
   769 	g_pSM->BuildPath(Path_SM, path, sizeof(path), "gamedata/%s/master.games.txt", m_File);
   770 	if (!libsys->PathExists(path))
   771 	{
   772 		/* Nope, use the old mechanism. */
   773 		smcore.Format(path, sizeof(path), "%s.txt", m_File);
   774 		return EnterFile(path, error, maxlength);
   775 	}
   776 
   777 	/* Otherwise, it's time to parse the master. */
   778 	SMCError err;
   779 	SMCStates state = {0, 0};
   780 	List<String> fileList;
   781 	master_reader.fileList = &fileList;
   782 	const char *pEngine[2] = { m_pBaseEngine, m_pEngine  };
   783 
   784 	for (unsigned char iter = 0; iter < SM_ARRAYSIZE(pEngine); ++iter)
   785 	{
   786 		if (pEngine[iter] == NULL)
   787 		{
   788 			continue;
   789 		}
   790 
   791 		this->SetParseEngine(pEngine[iter]);
   792 		err = textparsers->ParseSMCFile(path, &master_reader, &state, error, maxlength);
   793 		if (err != SMCError_Okay)
   794 		{
   795 			const char *msg = textparsers->GetSMCErrorString(err);
   796 
   797 			smcore.LogError("[SM] Error parsing master gameconf file \"%s\":", path);
   798 			smcore.LogError("[SM] Error %d on line %d, col %d: %s", 
   799 				err,
   800 				state.line,
   801 				state.col,
   802 				msg ? msg : "Unknown error");
   803 			return false;
   804 		}
   805 	}
   806 
   807 	/* Go through each file we found and parse it. */
   808 	List<String>::iterator iter;
   809 	for (iter = fileList.begin(); iter != fileList.end(); iter++)
   810 	{
   811 		smcore.Format(path, sizeof(path), "%s/%s", m_File, (*iter).c_str());
   812 		if (!EnterFile(path, error, maxlength))
   813 		{
   814 			return false;
   815 		}
   816 	}
   817 
   818 	/* Parse the contents of the 'custom' directory */
   819 	g_pSM->BuildPath(Path_SM, path, sizeof(path), "gamedata/%s/custom", m_File);
   820 	IDirectory *customDir = libsys->OpenDirectory(path);
   821 
   822 	if (!customDir)
   823 	{
   824 		return true;
   825 	}
   826 
   827 	while (customDir->MoreFiles())
   828 	{
   829 		if (!customDir->IsEntryFile())
   830 		{
   831 			customDir->NextEntry();
   832 			continue;
   833 		}
   834 		
   835 		const char *curFile = customDir->GetEntryName();
   836 
   837 		/* Only allow .txt files */
   838 		int len = strlen(curFile);
   839 		if (len > 4 && strcmp(&curFile[len-4], ".txt") != 0)
   840 		{
   841 			customDir->NextEntry();
   842 			continue;	
   843 		}
   844 
   845 		smcore.Format(path, sizeof(path), "%s/custom/%s", m_File, curFile);
   846 		if (!EnterFile(path, error, maxlength))
   847 		{
   848 			libsys->CloseDirectory(customDir);
   849 			return false;
   850 		}
   851 
   852 		customDir->NextEntry();
   853 	}
   854 
   855 	libsys->CloseDirectory(customDir);
   856 	return true;
   857 }
   858 
   859 bool CGameConfig::EnterFile(const char *file, char *error, size_t maxlength)
   860 {
   861 	SMCError err;
   862 	SMCStates state = {0, 0};
   863 
   864 	g_pSM->BuildPath(Path_SM, m_CurFile, sizeof(m_CurFile), "gamedata/%s", file);
   865 
   866 	/* Initialize parse states */
   867 	m_IgnoreLevel = 0;
   868 	bShouldBeReadingDefault = true;
   869 	m_ParseState = PSTATE_NONE;
   870 	const char *pEngine[2] = { m_pBaseEngine, m_pEngine };
   871 
   872 	for (unsigned char iter = 0; iter < SM_ARRAYSIZE(pEngine); ++iter)
   873 	{
   874 		if (pEngine[iter] == NULL)
   875 		{
   876 			continue;
   877 		}
   878 
   879 		this->SetParseEngine(pEngine[iter]);
   880 		if ((err=textparsers->ParseSMCFile(m_CurFile, this, &state, error, maxlength))
   881 			!= SMCError_Okay)
   882 		{
   883 			const char *msg = textparsers->GetSMCErrorString(err);
   884 
   885 			smcore.LogError("[SM] Error parsing gameconfig file \"%s\":", m_CurFile);
   886 			smcore.LogError("[SM] Error %d on line %d, col %d: %s", 
   887 				err,
   888 				state.line,
   889 				state.col,
   890 				msg ? msg : "Unknown error");
   891 
   892 			if (m_ParseState == PSTATE_GAMEDEFS_CUSTOM)
   893 			{
   894 				//error occurred while parsing a custom section
   895 				m_CustomHandler->ReadSMC_ParseEnd(true, true);
   896 				m_CustomHandler = NULL;
   897 				m_CustomLevel = 0;
   898 			}
   899 
   900 			return false;
   901 		}
   902 	}
   903 
   904 	return true;
   905 }
   906 
   907 void CGameConfig::SetBaseEngine(const char *engine)
   908 {
   909 	m_pBaseEngine = engine;
   910 }
   911 
   912 void CGameConfig::SetParseEngine(const char *engine)
   913 {
   914 	g_pParseEngine = engine;
   915 }
   916 
   917 bool CGameConfig::GetOffset(const char *key, int *value)
   918 {
   919 	return m_Offsets.retrieve(key, value);
   920 }
   921 
   922 const char *CGameConfig::GetKeyValue(const char *key)
   923 {
   924 	int address;
   925 	if (!m_Keys.retrieve(key, &address))
   926 		return NULL;
   927 	return m_pStrings->GetString(address);
   928 }
   929 
   930 //memory addresses below 0x10000 are automatically considered invalid for dereferencing
   931 #define VALID_MINIMUM_MEMORY_ADDRESS 0x10000
   932 
   933 bool CGameConfig::GetAddress(const char *key, void **retaddr)
   934 {
   935 	StringHashMap<AddressConf>::Result r = m_Addresses.find(key);
   936 	if (!r.found())
   937 	{
   938 		*retaddr = NULL;
   939 		return false;
   940 	}
   941 
   942 	AddressConf &addrConf = r->value;
   943 
   944 	void *addr;
   945 	if (!GetMemSig(addrConf.signatureName, &addr))
   946 	{
   947 		*retaddr = NULL;
   948 		return false;
   949 	}
   950 
   951 	for (int i = 0; i < addrConf.readCount; ++i)
   952 	{
   953 		int offset = addrConf.read[i];
   954 
   955 		//NULLs in the middle of an indirection chain are bad, end NULL is ok
   956 		if (addr ==  NULL || reinterpret_cast<uintptr_t>(addr) < VALID_MINIMUM_MEMORY_ADDRESS)
   957 		{
   958 			*retaddr = NULL;
   959 			return false;
   960 		}
   961 		addr = *(reinterpret_cast<void**>(reinterpret_cast<uint8_t*>(addr) + offset));
   962 	}
   963 
   964 	*retaddr = addr;
   965 	return true;
   966 }
   967 
   968 static inline unsigned minOf(unsigned a, unsigned b)
   969 {
   970 	return a <= b ? a : b;
   971 }
   972 
   973 CGameConfig::AddressConf::AddressConf(char *sigName, unsigned sigLength, unsigned readCount, int *read)
   974 {
   975 	unsigned readLimit = minOf(readCount, sizeof(this->read) / sizeof(this->read[0]));
   976 
   977 	strncopy(signatureName, sigName, sizeof(signatureName) / sizeof(signatureName[0]));
   978 	this->readCount = readLimit;
   979 	memcpy(&this->read[0], read, sizeof(this->read[0])*readLimit);
   980 }
   981 
   982 SendProp *CGameConfig::GetSendProp(const char *key)
   983 {
   984 	SendProp *prop;
   985 	if (!m_Props.retrieve(key, &prop))
   986 		return NULL;
   987 	return prop;
   988 }
   989 
   990 bool CGameConfig::GetMemSig(const char *key, void **addr)
   991 {
   992 	return m_Sigs.retrieve(key, addr);
   993 }
   994 
   995 GameConfigManager::GameConfigManager()
   996 {
   997 }
   998 
   999 GameConfigManager::~GameConfigManager()
  1000 {
  1001 }
  1002 
  1003 void GameConfigManager::OnSourceModStartup(bool late)
  1004 {
  1005 	LoadGameConfigFile("core.games", &g_pGameConf, NULL, 0);
  1006 
  1007 	strncopy(g_Game, g_pSM->GetGameFolderName(), sizeof(g_Game));
  1008 	strncopy(g_GameDesc + 1, smcore.GetGameDescription(), sizeof(g_GameDesc) - 1);
  1009 	smcore.GetGameName(g_GameName + 1, sizeof(g_GameName) - 1);
  1010 }
  1011 
  1012 void GameConfigManager::OnSourceModAllInitialized()
  1013 {
  1014 	/* NOW initialize the game file */
  1015 	CGameConfig *pGameConf = (CGameConfig *)g_pGameConf;
  1016 	
  1017 	char error[255];
  1018 	if (!pGameConf->Reparse(error, sizeof(error)))
  1019 	{
  1020 		/* :TODO: log */
  1021 	}
  1022 
  1023 	sharesys->AddInterface(NULL, this);
  1024 }
  1025 
  1026 void GameConfigManager::OnSourceModAllShutdown()
  1027 {
  1028 	CloseGameConfigFile(g_pGameConf);
  1029 }
  1030 
  1031 bool GameConfigManager::LoadGameConfigFile(const char *file, IGameConfig **_pConfig, char *error, size_t maxlength)
  1032 {
  1033 #if 0
  1034 	/* A crash was detected during last load - We block the gamedata loading so it hopefully won't happen again */
  1035 	if (g_blockGameDataLoad)
  1036 	{
  1037 		UTIL_Format(error, maxlength, "GameData loaded blocked due to detected crash");
  1038 		return false;
  1039 	}
  1040 #endif
  1041 
  1042 	CGameConfig *pConfig;
  1043 
  1044 	if (m_Lookup.retrieve(file, &pConfig))
  1045 	{
  1046 		pConfig->AddRef();
  1047 		*_pConfig = pConfig;
  1048 		return true;
  1049 	}
  1050 
  1051 	pConfig = new CGameConfig(file);
  1052 
  1053 	/* :HACKHACK: Don't parse the main config file */
  1054 	bool retval = true;
  1055 	if (_pConfig != &g_pGameConf)
  1056 	{
  1057 		retval = pConfig->Reparse(error, maxlength);
  1058 	}
  1059 
  1060 	m_Lookup.insert(file, pConfig);
  1061 
  1062 	*_pConfig = pConfig;
  1063 	return retval;
  1064 }
  1065 
  1066 void GameConfigManager::CloseGameConfigFile(IGameConfig *cfg)
  1067 {
  1068 	CGameConfig *pConfig = (CGameConfig *)cfg;
  1069 	pConfig->Release();
  1070 }
  1071 
  1072 extern Handle_t g_GameConfigsType;
  1073 
  1074 IGameConfig *GameConfigManager::ReadHandle(Handle_t hndl, IdentityToken_t *ident, HandleError *err)
  1075 {
  1076 	HandleSecurity sec(ident, g_pCoreIdent);
  1077 	IGameConfig *conf = NULL;
  1078 	HandleError _err = handlesys->ReadHandle(hndl, g_GameConfigsType, &sec, (void **)&conf);
  1079 
  1080 	if (err)
  1081 	{
  1082 		*err = _err;
  1083 	}
  1084 
  1085 	return conf;
  1086 }
  1087 
  1088 void GameConfigManager::AddUserConfigHook(const char *sectionname, ITextListener_SMC *listener)
  1089 {
  1090 	m_customHandlers.insert(sectionname, listener);
  1091 }
  1092 
  1093 void GameConfigManager::RemoveUserConfigHook(const char *sectionname, ITextListener_SMC *aListener)
  1094 {
  1095 	ITextListener_SMC *listener;
  1096 	if (!m_customHandlers.retrieve(sectionname, &listener))
  1097 		return;
  1098 
  1099 	if (listener != aListener)
  1100 		return;
  1101 
  1102 	m_customHandlers.remove(sectionname);
  1103 	return;
  1104 }
  1105 
  1106 void GameConfigManager::AcquireLock()
  1107 {
  1108 }
  1109 
  1110 void GameConfigManager::ReleaseLock()
  1111 {
  1112 }
  1113 
  1114 void GameConfigManager::RemoveCachedConfig(CGameConfig *config)
  1115 {
  1116 	m_Lookup.remove(config->m_File);
  1117 }