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
68 #define PSTATE_GAMEDEFS_CUSTOM 11
70 #if defined PLATFORM_WINDOWS
71 #define PLATFORM_NAME "windows"
72 #define PLATFORM_SERVER_BINARY "server.dll"
73 #elif defined PLATFORM_LINUX
74 #define PLATFORM_NAME "linux"
75 #define PLATFORM_SERVER_BINARY "server_i486.so"
76 #elif defined PLATFORM_APPLE
77 #define PLATFORM_NAME "undef"
78 #define PLATFORM_SERVER_BINARY "undef.dylib"
91 unsigned int s_ServerBinCRC;
92 bool s_ServerBinCRC_Ok = false;
94 CGameConfig::CGameConfig(const char *file)
96 m_pFile = sm_strdup(file);
97 m_pOffsets = sm_trie_create();
98 m_pProps = sm_trie_create();
99 m_pKeys = sm_trie_create();
100 m_pSigs = sm_trie_create();
101 m_pStrings = new BaseStringTable(512);
105 m_CustomHandler = NULL;
108 CGameConfig::~CGameConfig()
110 sm_trie_destroy(m_pOffsets);
111 sm_trie_destroy(m_pProps);
112 sm_trie_destroy(m_pKeys);
113 sm_trie_destroy(m_pSigs);
118 SMCResult CGameConfig::ReadSMC_NewSection(const SMCStates *states, const char *name)
123 return SMCResult_Continue;
126 switch (m_ParseState)
130 if (strcmp(name, "Games") == 0)
132 m_ParseState = PSTATE_GAMES;
140 if ((strcmp(name, "*") == 0)
141 || (strcmp(name, "#default") == 0)
142 || (strcasecmp(name, g_Game) == 0)
143 || (strcasecmp(name, g_GameDesc) == 0)
144 || (strcasecmp(name, g_GameName) == 0))
146 bShouldBeReadingDefault = true;
147 m_ParseState = PSTATE_GAMEDEFS;
148 strncopy(m_Game, name, sizeof(m_Game));
154 case PSTATE_GAMEDEFS:
156 if (strcmp(name, "Offsets") == 0)
158 m_ParseState = PSTATE_GAMEDEFS_OFFSETS;
160 else if (strcmp(name, "Keys") == 0)
162 m_ParseState = PSTATE_GAMEDEFS_KEYS;
164 else if ((strcmp(name, "#supported") == 0)
165 && (strcmp(m_Game, "#default") == 0))
167 m_ParseState = PSTATE_GAMEDEFS_SUPPORTED;
168 /* Ignore this section unless we get a game. */
169 bShouldBeReadingDefault = false;
171 else if (strcmp(name, "Signatures") == 0)
173 m_ParseState = PSTATE_GAMEDEFS_SIGNATURES;
175 else if (strcmp(name, "CRC") == 0)
177 m_ParseState = PSTATE_GAMEDEFS_CRC;
178 bShouldBeReadingDefault = false;
182 ITextListener_SMC **pListen = g_GameConfigs.m_customHandlers.retrieve(name);
187 m_ParseState = PSTATE_GAMEDEFS_CUSTOM;
188 m_CustomHandler = *pListen;
189 m_CustomHandler->ReadSMC_ParseStart();
197 case PSTATE_GAMEDEFS_OFFSETS:
201 strncopy(m_offset, name, sizeof(m_offset));
202 m_ParseState = PSTATE_GAMEDEFS_OFFSETS_OFFSET;
205 case PSTATE_GAMEDEFS_SIGNATURES:
207 strncopy(m_offset, name, sizeof(m_offset));
209 m_ParseState = PSTATE_GAMEDEFS_SIGNATURES_SIG;
212 case PSTATE_GAMEDEFS_CRC:
216 if (strcmp(name, "server") != 0)
218 UTIL_Format(error, sizeof(error), "Unrecognized library \"%s\"", name);
220 else if (!s_ServerBinCRC_Ok)
223 char path[PLATFORM_MAX_PATH];
225 g_SourceMod.BuildPath(Path_Game, path, sizeof(path), "bin/" PLATFORM_SERVER_BINARY);
226 if ((fp = fopen(path, "rb")) == NULL)
228 UTIL_Format(error, sizeof(error), "Could not open binary: %s", path);
233 fseek(fp, 0, SEEK_END);
235 fseek(fp, 0, SEEK_SET);
237 buffer = malloc(size);
238 fread(buffer, size, 1, fp);
239 s_ServerBinCRC = UTIL_CRC32(buffer, size);
241 s_ServerBinCRC_Ok = true;
245 if (error[0] != '\0')
248 g_Logger.LogError("[SM] Error while parsing CRC section for \"%s\" (%s):", m_Game, m_pFile);
249 g_Logger.LogError("[SM] %s", error);
251 m_ParseState = PSTATE_GAMEDEFS_CRC_BINARY;
255 case PSTATE_GAMEDEFS_CUSTOM:
258 return m_CustomHandler->ReadSMC_NewSection(states, name);
261 /* No sub-sections allowed:
262 case PSTATE_GAMEDEFS_OFFSETS_OFFSET:
263 case PSTATE_GAMEDEFS_KEYS:
264 case PSTATE_GAMEDEFS_SUPPORTED:
265 case PSTATE_GAMEDEFS_SIGNATURES_SIG:
266 case PSTATE_GAMEDEFS_CRC_BINARY:
270 /* If we don't know what we got, start ignoring */
276 return SMCResult_Continue;
279 SMCResult CGameConfig::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
283 return SMCResult_Continue;
286 if (m_ParseState == PSTATE_GAMEDEFS_OFFSETS_OFFSET)
288 if (strcmp(key, "class") == 0)
290 strncopy(m_Class, value, sizeof(m_Class));
291 } else if (strcmp(key, "prop") == 0) {
292 strncopy(m_Prop, value, sizeof(m_Prop));
293 } else if (strcmp(key, PLATFORM_NAME) == 0) {
294 int val = atoi(value);
295 sm_trie_replace(m_pOffsets, m_offset, (void *)val);
297 } else if (m_ParseState == PSTATE_GAMEDEFS_KEYS) {
298 int id = m_pStrings->AddString(value);
299 sm_trie_replace(m_pKeys, key, (void *)id);
300 } else if (m_ParseState == PSTATE_GAMEDEFS_SUPPORTED) {
301 if (strcmp(key, "game") == 0
302 && (strcasecmp(value, g_Game) == 0
303 || strcasecmp(value, g_GameDesc) == 0
304 || strcasecmp(value, g_GameName) == 0)
307 bShouldBeReadingDefault = true;
309 } else if (m_ParseState == PSTATE_GAMEDEFS_SIGNATURES_SIG) {
310 if (strcmp(key, PLATFORM_NAME) == 0)
312 strncopy(s_TempSig.sig, value, sizeof(s_TempSig.sig));
313 } else if (strcmp(key, "library") == 0) {
314 strncopy(s_TempSig.library, value, sizeof(s_TempSig.library));
316 } else if (m_ParseState == PSTATE_GAMEDEFS_CRC_BINARY) {
317 if (strcmp(key, PLATFORM_NAME) == 0
319 && !bShouldBeReadingDefault)
321 unsigned int crc = 0;
322 sscanf(value, "%08X", &crc);
323 if (s_ServerBinCRC == crc)
325 bShouldBeReadingDefault = true;
328 } else if (m_ParseState == PSTATE_GAMEDEFS_CUSTOM) {
329 return m_CustomHandler->ReadSMC_KeyValue(states, key, value);
332 return SMCResult_Continue;
335 SMCResult CGameConfig::ReadSMC_LeavingSection(const SMCStates *states)
340 return SMCResult_Continue;
346 m_CustomHandler->ReadSMC_LeavingSection(states);
347 return SMCResult_Continue;
350 switch (m_ParseState)
354 m_ParseState = PSTATE_NONE;
357 case PSTATE_GAMEDEFS:
359 m_ParseState = PSTATE_GAMES;
362 case PSTATE_GAMEDEFS_CUSTOM:
364 m_ParseState = PSTATE_GAMEDEFS;
365 m_CustomHandler->ReadSMC_ParseEnd(false, false);
368 case PSTATE_GAMEDEFS_KEYS:
369 case PSTATE_GAMEDEFS_OFFSETS:
371 m_ParseState = PSTATE_GAMEDEFS;
374 case PSTATE_GAMEDEFS_OFFSETS_OFFSET:
376 /* Parse the offset... */
377 if (m_Class[0] != '\0'
378 && m_Prop[0] != '\0')
380 SendProp *pProp = g_HL2.FindInSendTable(m_Class, m_Prop);
383 int val = pProp->GetOffset();
384 sm_trie_replace(m_pOffsets, m_offset, (void *)val);
385 sm_trie_replace(m_pProps, m_offset, pProp);
387 /* Check if it's a non-default game and no offsets exist */
388 if (((strcmp(m_Game, "*") != 0) && strcmp(m_Game, "#default") != 0)
389 && (!sm_trie_retrieve(m_pOffsets, m_offset, NULL)))
391 g_Logger.LogError("[SM] Unable to find property %s.%s (file \"%s\") (mod \"%s\")",
399 m_ParseState = PSTATE_GAMEDEFS_OFFSETS;
402 case PSTATE_GAMEDEFS_CRC:
403 case PSTATE_GAMEDEFS_SUPPORTED:
405 if (!bShouldBeReadingDefault)
407 /* If we shouldn't read the rest of this section, set the ignore level. */
409 m_ParseState = PSTATE_GAMES;
411 m_ParseState = PSTATE_GAMEDEFS;
415 case PSTATE_GAMEDEFS_CRC_BINARY:
417 m_ParseState = PSTATE_GAMEDEFS_CRC;
420 case PSTATE_GAMEDEFS_SIGNATURES:
422 m_ParseState = PSTATE_GAMEDEFS;
425 case PSTATE_GAMEDEFS_SIGNATURES_SIG:
427 if (s_TempSig.library[0] == '\0')
430 strncopy(s_TempSig.library, "server", sizeof(s_TempSig.library));
432 void *addrInBase = NULL;
433 if (strcmp(s_TempSig.library, "server") == 0)
435 addrInBase = (void *)g_SMAPI->GetServerFactory(false);
436 } else if (strcmp(s_TempSig.library, "engine") == 0) {
437 addrInBase = (void *)g_SMAPI->GetEngineFactory(false);
439 void *final_addr = NULL;
440 if (addrInBase == NULL)
442 g_Logger.LogError("[SM] Unrecognized library \"%s\" (gameconf \"%s\")",
446 #if defined PLATFORM_LINUX
447 if (s_TempSig.sig[0] == '@')
450 /* GNU only: returns 0 on error, inconsistent! >:[ */
451 if (dladdr(addrInBase, &info) != 0)
453 void *handle = dlopen(info.dli_fname, RTLD_NOW);
456 final_addr = dlsym(handle, &s_TempSig.sig[1]);
459 g_Logger.LogError("[SM] Unable to load library \"%s\" (gameconf \"%s\")",
464 g_Logger.LogError("[SM] Unable to find library \"%s\" in memory (gameconf \"%s\")",
474 /* First, preprocess the signature */
480 length = strlen(s_TempSig.sig);
482 for (size_t i=0; i<length; i++)
484 if (real_bytes >= sizeof(real_sig))
488 real_sig[real_bytes++] = s_TempSig.sig[i];
489 if (s_TempSig.sig[i] == '\\'
490 && s_TempSig.sig[i+1] == 'x')
496 /* Get the hex part */
499 s_byte[0] = s_TempSig.sig[i+2];
500 s_byte[1] = s_TempSig.sig[i+3];
502 /* Read it as an integer */
503 sscanf(s_byte, "%x", &r_byte);
505 real_sig[real_bytes-1] = r_byte;
513 final_addr = g_MemUtils.FindPattern(addrInBase, real_sig, real_bytes);
517 #if defined PLATFORM_LINUX
520 sm_trie_replace(m_pSigs, m_offset, final_addr);
521 m_ParseState = PSTATE_GAMEDEFS_SIGNATURES;
527 return SMCResult_Continue;
530 bool CGameConfig::Reparse(char *error, size_t maxlength)
533 SMCStates state = {0, 0};
535 char path[PLATFORM_MAX_PATH];
536 g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "gamedata/%s.txt", m_pFile);
537 /* Backwards compatibility */
538 if (!g_LibSys.PathExists(path))
540 g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "configs/gamedata/%s.txt", m_pFile);
543 /* Initialize parse states */
545 bShouldBeReadingDefault = true;
546 m_ParseState = PSTATE_NONE;
547 /* Reset cached data */
549 sm_trie_clear(m_pOffsets);
550 sm_trie_clear(m_pProps);
551 sm_trie_clear(m_pKeys);
553 if ((err=textparsers->ParseSMCFile(path, this, &state, error, maxlength))
558 msg = textparsers->GetSMCErrorString(err);
560 g_Logger.LogError("[SM] Error parsing gameconfig file \"%s\":", path);
561 g_Logger.LogError("[SM] Error %d on line %d, col %d: %s",
565 msg ? msg : "Unknown error");
567 if (m_ParseState == PSTATE_GAMEDEFS_CUSTOM)
569 //error occurred while parsing a custom section
570 m_CustomHandler->ReadSMC_ParseEnd(true, true);
571 m_CustomHandler = NULL;
581 bool CGameConfig::GetOffset(const char *key, int *value)
585 if (!sm_trie_retrieve(m_pOffsets, key, &obj))
595 const char *CGameConfig::GetKeyValue(const char *key)
598 if (!sm_trie_retrieve(m_pKeys, key, &obj))
602 return m_pStrings->GetString((int)obj);
605 SendProp *CGameConfig::GetSendProp(const char *key)
609 if (!sm_trie_retrieve(m_pProps, key, (void **)&pProp))
617 bool CGameConfig::GetMemSig(const char *key, void **addr)
619 return sm_trie_retrieve(m_pSigs, key, addr);
622 void CGameConfig::IncRefCount()
627 unsigned int CGameConfig::DecRefCount()
633 GameConfigManager::GameConfigManager()
635 m_pLookup = sm_trie_create();
638 GameConfigManager::~GameConfigManager()
640 sm_trie_destroy(m_pLookup);
643 void GameConfigManager::OnSourceModStartup(bool late)
645 LoadGameConfigFile("core.games", &g_pGameConf, NULL, 0);
647 strncopy(g_Game, g_SourceMod.GetGameFolderName(), sizeof(g_Game));
648 strncopy(g_GameDesc + 1, SERVER_CALL(GetGameDescription)(), sizeof(g_GameDesc) - 1);
650 KeyValues *pGameInfo = new KeyValues("GameInfo");
651 if (g_HL2.KVLoadFromFile(pGameInfo, basefilesystem, "gameinfo.txt"))
654 if ((str = pGameInfo->GetString("game", NULL)) != NULL)
656 strncopy(g_GameName + 1, str, sizeof(g_GameName) - 1);
659 pGameInfo->deleteThis();
662 void GameConfigManager::OnSourceModAllInitialized()
664 /* NOW initialize the game file */
665 CGameConfig *pGameConf = (CGameConfig *)g_pGameConf;
668 if (!pGameConf->Reparse(error, sizeof(error)))
673 g_ShareSys.AddInterface(NULL, this);
676 void GameConfigManager::OnSourceModAllShutdown()
678 CloseGameConfigFile(g_pGameConf);
681 bool GameConfigManager::LoadGameConfigFile(const char *file, IGameConfig **_pConfig, char *error, size_t maxlength)
683 /* A crash was detected during last load - We block the gamedata loading so it hopefully won't happen again */
684 if (g_blockGameDataLoad)
686 UTIL_Format(error, maxlength, "GameData loaded blocked due to detected crash");
690 CGameConfig *pConfig;
692 if (sm_trie_retrieve(m_pLookup, file, (void **)&pConfig))
694 pConfig->IncRefCount();
699 pConfig = new CGameConfig(file);
701 /* :HACKHACK: Don't parse the main config file */
703 if (_pConfig != &g_pGameConf)
705 retval = pConfig->Reparse(error, maxlength);
708 m_cfgs.push_back(pConfig);
709 sm_trie_insert(m_pLookup, file, pConfig);
711 pConfig->IncRefCount();
718 void GameConfigManager::CloseGameConfigFile(IGameConfig *cfg)
720 CGameConfig *pConfig = (CGameConfig *)cfg;
722 if (pConfig->DecRefCount() == 0)
724 sm_trie_delete(m_pLookup, pConfig->m_pFile);
729 extern HandleType_t g_GameConfigsType;
731 IGameConfig *GameConfigManager::ReadHandle(Handle_t hndl, IdentityToken_t *ident, HandleError *err)
733 HandleSecurity sec(ident, g_pCoreIdent);
734 IGameConfig *conf = NULL;
735 HandleError _err = g_HandleSys.ReadHandle(hndl, g_GameConfigsType, &sec, (void **)&conf);
745 void GameConfigManager::AddUserConfigHook( const char *sectionname, ITextListener_SMC *listener )
747 m_customHandlers.insert(sectionname, listener);
750 void GameConfigManager::RemoveUserConfigHook( const char *sectionname, ITextListener_SMC *listener )
752 ITextListener_SMC **pListener = m_customHandlers.retrieve(sectionname);
754 if (pListener == NULL)
759 if (*pListener != listener)
764 m_customHandlers.remove(sectionname);