3 * =============================================================================
5 * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
6 * =============================================================================
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.
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
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/>.
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>.
34 #include "GameConfigs.h"
35 #include "sm_stringutil.h"
36 #include "sourcemod.h"
37 #include "sourcemm_api.h"
38 #include "HalfLife2.h"
41 #include "MemoryUtils.h"
42 #include "LibrarySys.h"
43 #include "HandleSys.h"
45 #include "GameDataFetcher.h"
47 #if defined PLATFORM_LINUX
51 GameConfigManager g_GameConfigs;
52 IGameConfig *g_pGameConf = NULL;
54 char g_GameDesc[256] = {'!', '\0'};
55 char g_GameName[256] = {'$', '\0'};
58 #define PSTATE_GAMES 1
59 #define PSTATE_GAMEDEFS 2
60 #define PSTATE_GAMEDEFS_OFFSETS 3
61 #define PSTATE_GAMEDEFS_OFFSETS_OFFSET 4
62 #define PSTATE_GAMEDEFS_KEYS 5
63 #define PSTATE_GAMEDEFS_SUPPORTED 6
64 #define PSTATE_GAMEDEFS_SIGNATURES 7
65 #define PSTATE_GAMEDEFS_SIGNATURES_SIG 8
66 #define PSTATE_GAMEDEFS_CRC 9
67 #define PSTATE_GAMEDEFS_CRC_BINARY 10
69 #if defined PLATFORM_WINDOWS
70 #define PLATFORM_NAME "windows"
71 #define PLATFORM_SERVER_BINARY "server.dll"
72 #elif defined PLATFORM_LINUX
73 #define PLATFORM_NAME "linux"
74 #define PLATFORM_SERVER_BINARY "server_i486.so"
87 unsigned int s_ServerBinCRC;
88 bool s_ServerBinCRC_Ok = false;
90 CGameConfig::CGameConfig(const char *file)
92 m_pFile = sm_strdup(file);
93 m_pOffsets = sm_trie_create();
94 m_pProps = sm_trie_create();
95 m_pKeys = sm_trie_create();
96 m_pSigs = sm_trie_create();
97 m_pStrings = new BaseStringTable(512);
101 CGameConfig::~CGameConfig()
103 sm_trie_destroy(m_pOffsets);
104 sm_trie_destroy(m_pProps);
105 sm_trie_destroy(m_pKeys);
106 sm_trie_destroy(m_pSigs);
111 SMCResult CGameConfig::ReadSMC_NewSection(const SMCStates *states, const char *name)
116 return SMCResult_Continue;
119 switch (m_ParseState)
123 if (strcmp(name, "Games") == 0)
125 m_ParseState = PSTATE_GAMES;
133 if ((strcmp(name, "*") == 0)
134 || (strcmp(name, "#default") == 0)
135 || (strcasecmp(name, g_Game) == 0)
136 || (strcasecmp(name, g_GameDesc) == 0)
137 || (strcasecmp(name, g_GameName) == 0))
139 bShouldBeReadingDefault = true;
140 m_ParseState = PSTATE_GAMEDEFS;
141 strncopy(m_Game, name, sizeof(m_Game));
147 case PSTATE_GAMEDEFS:
149 if (strcmp(name, "Offsets") == 0)
151 m_ParseState = PSTATE_GAMEDEFS_OFFSETS;
153 else if (strcmp(name, "Keys") == 0)
155 m_ParseState = PSTATE_GAMEDEFS_KEYS;
157 else if ((strcmp(name, "#supported") == 0)
158 && (strcmp(m_Game, "#default") == 0))
160 m_ParseState = PSTATE_GAMEDEFS_SUPPORTED;
161 /* Ignore this section unless we get a game. */
162 bShouldBeReadingDefault = false;
164 else if (strcmp(name, "Signatures") == 0)
166 m_ParseState = PSTATE_GAMEDEFS_SIGNATURES;
168 else if (strcmp(name, "CRC") == 0)
170 m_ParseState = PSTATE_GAMEDEFS_CRC;
171 bShouldBeReadingDefault = false;
179 case PSTATE_GAMEDEFS_OFFSETS:
183 strncopy(m_offset, name, sizeof(m_offset));
184 m_ParseState = PSTATE_GAMEDEFS_OFFSETS_OFFSET;
187 case PSTATE_GAMEDEFS_SIGNATURES:
189 strncopy(m_offset, name, sizeof(m_offset));
191 m_ParseState = PSTATE_GAMEDEFS_SIGNATURES_SIG;
194 case PSTATE_GAMEDEFS_CRC:
198 if (strcmp(name, "server") != 0)
200 UTIL_Format(error, sizeof(error), "Unrecognized library \"%s\"", name);
202 else if (!s_ServerBinCRC_Ok)
205 char path[PLATFORM_MAX_PATH];
207 g_SourceMod.BuildPath(Path_Game, path, sizeof(path), "bin/" PLATFORM_SERVER_BINARY);
208 if ((fp = fopen(path, "rb")) == NULL)
210 UTIL_Format(error, sizeof(error), "Could not open binary: %s", path);
215 fseek(fp, 0, SEEK_END);
217 fseek(fp, 0, SEEK_SET);
219 buffer = malloc(size);
220 fread(buffer, size, 1, fp);
221 s_ServerBinCRC = UTIL_CRC32(buffer, size);
223 s_ServerBinCRC_Ok = true;
226 if (error[0] != '\0')
229 g_Logger.LogError("[SM] Error while parsing CRC section for \"%s\" (%s):", m_Game, m_pFile);
230 g_Logger.LogError("[SM] %s", error);
232 m_ParseState = PSTATE_GAMEDEFS_CRC_BINARY;
236 /* No sub-sections allowed:
237 case PSTATE_GAMEDEFS_OFFSETS_OFFSET:
238 case PSTATE_GAMEDEFS_KEYS:
239 case PSTATE_GAMEDEFS_SUPPORTED:
240 case PSTATE_GAMEDEFS_SIGNATURES_SIG:
241 case PSTATE_GAMEDEFS_CRC_BINARY:
245 /* If we don't know what we got, start ignoring */
251 return SMCResult_Continue;
254 SMCResult CGameConfig::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
258 return SMCResult_Continue;
261 if (m_ParseState == PSTATE_GAMEDEFS_OFFSETS_OFFSET)
263 if (strcmp(key, "class") == 0)
265 strncopy(m_Class, value, sizeof(m_Class));
266 } else if (strcmp(key, "prop") == 0) {
267 strncopy(m_Prop, value, sizeof(m_Prop));
268 } else if (strcmp(key, PLATFORM_NAME) == 0) {
269 int val = atoi(value);
270 sm_trie_replace(m_pOffsets, m_offset, (void *)val);
272 } else if (m_ParseState == PSTATE_GAMEDEFS_KEYS) {
273 int id = m_pStrings->AddString(value);
274 sm_trie_replace(m_pKeys, key, (void *)id);
275 } else if (m_ParseState == PSTATE_GAMEDEFS_SUPPORTED) {
276 if (strcmp(key, "game") == 0
277 && (strcasecmp(value, g_Game) == 0
278 || strcasecmp(value, g_GameDesc) == 0
279 || strcasecmp(value, g_GameName) == 0)
282 bShouldBeReadingDefault = true;
284 } else if (m_ParseState == PSTATE_GAMEDEFS_SIGNATURES_SIG) {
285 if (strcmp(key, PLATFORM_NAME) == 0)
287 strncopy(s_TempSig.sig, value, sizeof(s_TempSig.sig));
288 } else if (strcmp(key, "library") == 0) {
289 strncopy(s_TempSig.library, value, sizeof(s_TempSig.library));
291 } else if (m_ParseState == PSTATE_GAMEDEFS_CRC_BINARY) {
292 if (strcmp(key, PLATFORM_NAME) == 0
294 && !bShouldBeReadingDefault)
296 unsigned int crc = 0;
297 sscanf(value, "%08X", &crc);
298 if (s_ServerBinCRC == crc)
300 bShouldBeReadingDefault = true;
305 return SMCResult_Continue;
308 SMCResult CGameConfig::ReadSMC_LeavingSection(const SMCStates *states)
313 return SMCResult_Continue;
316 switch (m_ParseState)
320 m_ParseState = PSTATE_NONE;
323 case PSTATE_GAMEDEFS:
325 m_ParseState = PSTATE_GAMES;
328 case PSTATE_GAMEDEFS_KEYS:
329 case PSTATE_GAMEDEFS_OFFSETS:
331 m_ParseState = PSTATE_GAMEDEFS;
334 case PSTATE_GAMEDEFS_OFFSETS_OFFSET:
336 /* Parse the offset... */
337 if (m_Class[0] != '\0'
338 && m_Prop[0] != '\0')
340 SendProp *pProp = g_HL2.FindInSendTable(m_Class, m_Prop);
343 int val = pProp->GetOffset();
344 sm_trie_replace(m_pOffsets, m_offset, (void *)val);
345 sm_trie_replace(m_pProps, m_offset, pProp);
347 /* Check if it's a non-default game and no offsets exist */
348 if (((strcmp(m_Game, "*") != 0) && strcmp(m_Game, "#default") != 0)
349 && (!sm_trie_retrieve(m_pOffsets, m_offset, NULL)))
351 g_Logger.LogError("[SM] Unable to find property %s.%s (file \"%s\") (mod \"%s\")",
359 m_ParseState = PSTATE_GAMEDEFS_OFFSETS;
362 case PSTATE_GAMEDEFS_CRC:
363 case PSTATE_GAMEDEFS_SUPPORTED:
365 if (!bShouldBeReadingDefault)
367 /* If we shouldn't read the rest of this section, set the ignore level. */
369 m_ParseState = PSTATE_GAMES;
371 m_ParseState = PSTATE_GAMEDEFS;
375 case PSTATE_GAMEDEFS_CRC_BINARY:
377 m_ParseState = PSTATE_GAMEDEFS_CRC;
380 case PSTATE_GAMEDEFS_SIGNATURES:
382 m_ParseState = PSTATE_GAMEDEFS;
385 case PSTATE_GAMEDEFS_SIGNATURES_SIG:
387 if (s_TempSig.library[0] == '\0')
390 strncopy(s_TempSig.library, "server", sizeof(s_TempSig.library));
392 void *addrInBase = NULL;
393 if (strcmp(s_TempSig.library, "server") == 0)
395 addrInBase = (void *)g_SMAPI->GetServerFactory(false);
396 } else if (strcmp(s_TempSig.library, "engine") == 0) {
397 addrInBase = (void *)g_SMAPI->GetEngineFactory(false);
399 void *final_addr = NULL;
400 if (addrInBase == NULL)
402 g_Logger.LogError("[SM] Unrecognized library \"%s\" (gameconf \"%s\")",
406 #if defined PLATFORM_LINUX
407 if (s_TempSig.sig[0] == '@')
410 /* GNU only: returns 0 on error, inconsistent! >:[ */
411 if (dladdr(addrInBase, &info) != 0)
413 void *handle = dlopen(info.dli_fname, RTLD_NOW);
416 final_addr = dlsym(handle, &s_TempSig.sig[1]);
419 g_Logger.LogError("[SM] Unable to load library \"%s\" (gameconf \"%s\")",
424 g_Logger.LogError("[SM] Unable to find library \"%s\" in memory (gameconf \"%s\")",
434 /* First, preprocess the signature */
440 length = strlen(s_TempSig.sig);
442 for (size_t i=0; i<length; i++)
444 if (real_bytes >= sizeof(real_sig))
448 real_sig[real_bytes++] = s_TempSig.sig[i];
449 if (s_TempSig.sig[i] == '\\'
450 && s_TempSig.sig[i+1] == 'x')
456 /* Get the hex part */
459 s_byte[0] = s_TempSig.sig[i+2];
460 s_byte[1] = s_TempSig.sig[i+3];
462 /* Read it as an integer */
463 sscanf(s_byte, "%x", &r_byte);
465 real_sig[real_bytes-1] = r_byte;
473 final_addr = g_MemUtils.FindPattern(addrInBase, real_sig, real_bytes);
477 #if defined PLATFORM_LINUX
480 sm_trie_replace(m_pSigs, m_offset, final_addr);
481 m_ParseState = PSTATE_GAMEDEFS_SIGNATURES;
487 return SMCResult_Continue;
490 bool CGameConfig::Reparse(char *error, size_t maxlength)
493 SMCStates state = {0, 0};
495 char path[PLATFORM_MAX_PATH];
496 g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "gamedata/%s.txt", m_pFile);
497 /* Backwards compatibility */
498 if (!g_LibSys.PathExists(path))
500 g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "configs/gamedata/%s.txt", m_pFile);
503 /* Initialize parse states */
505 bShouldBeReadingDefault = true;
506 m_ParseState = PSTATE_NONE;
507 /* Reset cached data */
509 sm_trie_clear(m_pOffsets);
510 sm_trie_clear(m_pProps);
511 sm_trie_clear(m_pKeys);
513 if ((err=textparsers->ParseSMCFile(path, this, &state, error, maxlength))
518 msg = textparsers->GetSMCErrorString(err);
520 g_Logger.LogError("[SM] Error parsing gameconfig file \"%s\":", path);
521 g_Logger.LogError("[SM] Error %d on line %d, col %d: %s",
525 msg ? msg : "Unknown error");
532 bool CGameConfig::GetOffset(const char *key, int *value)
536 if (!sm_trie_retrieve(m_pOffsets, key, &obj))
546 const char *CGameConfig::GetKeyValue(const char *key)
549 if (!sm_trie_retrieve(m_pKeys, key, &obj))
553 return m_pStrings->GetString((int)obj);
556 SendProp *CGameConfig::GetSendProp(const char *key)
560 if (!sm_trie_retrieve(m_pProps, key, (void **)&pProp))
568 bool CGameConfig::GetMemSig(const char *key, void **addr)
570 return sm_trie_retrieve(m_pSigs, key, addr);
573 void CGameConfig::IncRefCount()
578 unsigned int CGameConfig::DecRefCount()
584 GameConfigManager::GameConfigManager()
586 m_pLookup = sm_trie_create();
589 GameConfigManager::~GameConfigManager()
591 sm_trie_destroy(m_pLookup);
594 void GameConfigManager::OnSourceModStartup(bool late)
596 LoadGameConfigFile("core.games", &g_pGameConf, NULL, 0);
598 strncopy(g_Game, g_SourceMod.GetGameFolderName(), sizeof(g_Game));
599 strncopy(g_GameDesc + 1, SERVER_CALL(GetGameDescription)(), sizeof(g_GameDesc) - 1);
601 KeyValues *pGameInfo = new KeyValues("GameInfo");
602 if (g_HL2.KVLoadFromFile(pGameInfo, basefilesystem, "gameinfo.txt"))
605 if ((str = pGameInfo->GetString("game", NULL)) != NULL)
607 strncopy(g_GameName + 1, str, sizeof(g_GameName) - 1);
610 pGameInfo->deleteThis();
613 void GameConfigManager::OnSourceModAllInitialized()
615 /* NOW initialize the game file */
616 CGameConfig *pGameConf = (CGameConfig *)g_pGameConf;
619 if (!pGameConf->Reparse(error, sizeof(error)))
624 g_ShareSys.AddInterface(NULL, this);
627 void GameConfigManager::OnSourceModAllShutdown()
629 CloseGameConfigFile(g_pGameConf);
632 bool GameConfigManager::LoadGameConfigFile(const char *file, IGameConfig **_pConfig, char *error, size_t maxlength)
634 /* A crash was detected during last load - We block the gamedata loading so it hopefully won't happen again */
635 if (g_blockGameDataLoad)
637 UTIL_Format(error, maxlength, "GameData loaded blocked due to detected crash");
641 CGameConfig *pConfig;
643 if (sm_trie_retrieve(m_pLookup, file, (void **)&pConfig))
645 pConfig->IncRefCount();
650 pConfig = new CGameConfig(file);
652 /* :HACKHACK: Don't parse the main config file */
654 if (_pConfig != &g_pGameConf)
656 retval = pConfig->Reparse(error, maxlength);
659 m_cfgs.push_back(pConfig);
660 sm_trie_insert(m_pLookup, file, pConfig);
662 pConfig->IncRefCount();
669 void GameConfigManager::CloseGameConfigFile(IGameConfig *cfg)
671 CGameConfig *pConfig = (CGameConfig *)cfg;
673 if (pConfig->DecRefCount() == 0)
675 sm_trie_delete(m_pLookup, pConfig->m_pFile);
680 extern HandleType_t g_GameConfigsType;
682 IGameConfig *GameConfigManager::ReadHandle(Handle_t hndl, IdentityToken_t *ident, HandleError *err)
684 HandleSecurity sec(ident, g_pCoreIdent);
685 IGameConfig *conf = NULL;
686 HandleError _err = g_HandleSys.ReadHandle(hndl, g_GameConfigsType, &sec, (void **)&conf);