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"
46 #if defined PLATFORM_LINUX
50 GameConfigManager g_GameConfigs;
51 IGameConfig *g_pGameConf = NULL;
52 static char g_Game[256];
53 static char g_GameDesc[256] = {'!', '\0'};
54 static char g_GameName[256] = {'$', '\0'};
57 #define PSTATE_GAMES 1
58 #define PSTATE_GAMEDEFS 2
59 #define PSTATE_GAMEDEFS_OFFSETS 3
60 #define PSTATE_GAMEDEFS_OFFSETS_OFFSET 4
61 #define PSTATE_GAMEDEFS_KEYS 5
62 #define PSTATE_GAMEDEFS_SUPPORTED 6
63 #define PSTATE_GAMEDEFS_SIGNATURES 7
64 #define PSTATE_GAMEDEFS_SIGNATURES_SIG 8
65 #define PSTATE_GAMEDEFS_CRC 9
66 #define PSTATE_GAMEDEFS_CRC_BINARY 10
67 #define PSTATE_GAMEDEFS_CUSTOM 11
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"
75 #elif defined PLATFORM_APPLE
76 #define PLATFORM_NAME "undef"
77 #define PLATFORM_SERVER_BINARY "undef.dylib"
90 unsigned int s_ServerBinCRC;
91 bool s_ServerBinCRC_Ok = false;
93 static bool DoesGameMatch(const char *value)
95 if (strcmp(value, g_Game) == 0 ||
96 strcmp(value, g_GameDesc) == 0 ||
97 strcmp(value, g_GameName) == 0)
104 static bool DoesEngineMatch(const char *value)
106 #if SOURCE_ENGINE == SE_EPISODEONE
107 if (strcmp(value, "original") == 0)
108 #elif SOURCE_ENGINE == SE_DARKMESSIAH
109 if (strcmp(value, "darkmessiah") == 0)
110 #elif SOURCE_ENGINE == SE_ORANGEBOX
111 if (strcmp(value, "orangebox") == 0)
112 #elif SOURCE_ENGINE == SE_ORANGEBOXVALVE
113 if (strcmp(value, "orangebox_valve") == 0)
114 #elif SOURCE_ENGINE == SE_LEFT4DEAD
115 if (strcmp(value, "left4dead") == 0)
117 #error "Unknown engine type"
125 CGameConfig::CGameConfig(const char *file)
127 strncopy(m_File, file, sizeof(m_File));
128 m_pOffsets = sm_trie_create();
129 m_pProps = sm_trie_create();
130 m_pKeys = sm_trie_create();
131 m_pSigs = sm_trie_create();
132 m_pStrings = new BaseStringTable(512);
136 m_CustomHandler = NULL;
139 CGameConfig::~CGameConfig()
141 sm_trie_destroy(m_pOffsets);
142 sm_trie_destroy(m_pProps);
143 sm_trie_destroy(m_pKeys);
144 sm_trie_destroy(m_pSigs);
148 SMCResult CGameConfig::ReadSMC_NewSection(const SMCStates *states, const char *name)
153 return SMCResult_Continue;
156 switch (m_ParseState)
160 if (strcmp(name, "Games") == 0)
162 m_ParseState = PSTATE_GAMES;
170 if (strcmp(name, "*") == 0 ||
171 strcmp(name, "#default") == 0 ||
174 bShouldBeReadingDefault = true;
175 m_ParseState = PSTATE_GAMEDEFS;
176 strncopy(m_Game, name, sizeof(m_Game));
182 case PSTATE_GAMEDEFS:
184 if (strcmp(name, "Offsets") == 0)
186 m_ParseState = PSTATE_GAMEDEFS_OFFSETS;
188 else if (strcmp(name, "Keys") == 0)
190 m_ParseState = PSTATE_GAMEDEFS_KEYS;
192 else if ((strcmp(name, "#supported") == 0) && (strcmp(m_Game, "#default") == 0))
194 m_ParseState = PSTATE_GAMEDEFS_SUPPORTED;
195 /* Ignore this section unless we get a game. */
196 bShouldBeReadingDefault = false;
198 matched_game = false;
200 matched_engine = false;
202 else if (strcmp(name, "Signatures") == 0)
204 m_ParseState = PSTATE_GAMEDEFS_SIGNATURES;
206 else if (strcmp(name, "CRC") == 0)
208 m_ParseState = PSTATE_GAMEDEFS_CRC;
209 bShouldBeReadingDefault = false;
213 ITextListener_SMC **pListen = g_GameConfigs.m_customHandlers.retrieve(name);
218 m_ParseState = PSTATE_GAMEDEFS_CUSTOM;
219 m_CustomHandler = *pListen;
220 m_CustomHandler->ReadSMC_ParseStart();
228 case PSTATE_GAMEDEFS_OFFSETS:
232 strncopy(m_offset, name, sizeof(m_offset));
233 m_ParseState = PSTATE_GAMEDEFS_OFFSETS_OFFSET;
236 case PSTATE_GAMEDEFS_SIGNATURES:
238 strncopy(m_offset, name, sizeof(m_offset));
240 m_ParseState = PSTATE_GAMEDEFS_SIGNATURES_SIG;
243 case PSTATE_GAMEDEFS_CRC:
247 if (strcmp(name, "server") != 0)
249 UTIL_Format(error, sizeof(error), "Unrecognized library \"%s\"", name);
251 else if (!s_ServerBinCRC_Ok)
254 char path[PLATFORM_MAX_PATH];
256 g_SourceMod.BuildPath(Path_Game, path, sizeof(path), "bin/" PLATFORM_SERVER_BINARY);
257 if ((fp = fopen(path, "rb")) == NULL)
259 UTIL_Format(error, sizeof(error), "Could not open binary: %s", path);
264 fseek(fp, 0, SEEK_END);
266 fseek(fp, 0, SEEK_SET);
268 buffer = malloc(size);
269 fread(buffer, size, 1, fp);
270 s_ServerBinCRC = UTIL_CRC32(buffer, size);
272 s_ServerBinCRC_Ok = true;
276 if (error[0] != '\0')
279 g_Logger.LogError("[SM] Error while parsing CRC section for \"%s\" (%s):", m_Game, m_CurFile);
280 g_Logger.LogError("[SM] %s", error);
282 m_ParseState = PSTATE_GAMEDEFS_CRC_BINARY;
286 case PSTATE_GAMEDEFS_CUSTOM:
289 return m_CustomHandler->ReadSMC_NewSection(states, name);
292 /* No sub-sections allowed:
293 case PSTATE_GAMEDEFS_OFFSETS_OFFSET:
294 case PSTATE_GAMEDEFS_KEYS:
295 case PSTATE_GAMEDEFS_SUPPORTED:
296 case PSTATE_GAMEDEFS_SIGNATURES_SIG:
297 case PSTATE_GAMEDEFS_CRC_BINARY:
301 /* If we don't know what we got, start ignoring */
307 return SMCResult_Continue;
310 SMCResult CGameConfig::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
314 return SMCResult_Continue;
317 if (m_ParseState == PSTATE_GAMEDEFS_OFFSETS_OFFSET)
319 if (strcmp(key, "class") == 0)
321 strncopy(m_Class, value, sizeof(m_Class));
322 } else if (strcmp(key, "prop") == 0) {
323 strncopy(m_Prop, value, sizeof(m_Prop));
324 } else if (strcmp(key, PLATFORM_NAME) == 0) {
325 int val = atoi(value);
326 sm_trie_replace(m_pOffsets, m_offset, (void *)val);
328 } else if (m_ParseState == PSTATE_GAMEDEFS_KEYS) {
329 int id = m_pStrings->AddString(value);
330 sm_trie_replace(m_pKeys, key, (void *)id);
331 } else if (m_ParseState == PSTATE_GAMEDEFS_SUPPORTED) {
332 if (strcmp(key, "game") == 0)
335 if (DoesGameMatch(value))
339 if ((!had_engine && matched_game) || (matched_engine && matched_game))
341 bShouldBeReadingDefault = true;
344 else if (strcmp(key, "engine") == 0)
347 if (DoesEngineMatch(value))
349 matched_engine = true;
351 if ((!had_game && matched_engine) || (matched_game && matched_engine))
353 bShouldBeReadingDefault = true;
356 } else if (m_ParseState == PSTATE_GAMEDEFS_SIGNATURES_SIG) {
357 if (strcmp(key, PLATFORM_NAME) == 0)
359 strncopy(s_TempSig.sig, value, sizeof(s_TempSig.sig));
360 } else if (strcmp(key, "library") == 0) {
361 strncopy(s_TempSig.library, value, sizeof(s_TempSig.library));
363 } else if (m_ParseState == PSTATE_GAMEDEFS_CRC_BINARY) {
364 if (strcmp(key, PLATFORM_NAME) == 0
366 && !bShouldBeReadingDefault)
368 unsigned int crc = 0;
369 sscanf(value, "%08X", &crc);
370 if (s_ServerBinCRC == crc)
372 bShouldBeReadingDefault = true;
375 } else if (m_ParseState == PSTATE_GAMEDEFS_CUSTOM) {
376 return m_CustomHandler->ReadSMC_KeyValue(states, key, value);
379 return SMCResult_Continue;
382 SMCResult CGameConfig::ReadSMC_LeavingSection(const SMCStates *states)
387 return SMCResult_Continue;
393 m_CustomHandler->ReadSMC_LeavingSection(states);
394 return SMCResult_Continue;
397 switch (m_ParseState)
401 m_ParseState = PSTATE_NONE;
404 case PSTATE_GAMEDEFS:
406 m_ParseState = PSTATE_GAMES;
409 case PSTATE_GAMEDEFS_CUSTOM:
411 m_ParseState = PSTATE_GAMEDEFS;
412 m_CustomHandler->ReadSMC_ParseEnd(false, false);
415 case PSTATE_GAMEDEFS_KEYS:
416 case PSTATE_GAMEDEFS_OFFSETS:
418 m_ParseState = PSTATE_GAMEDEFS;
421 case PSTATE_GAMEDEFS_OFFSETS_OFFSET:
423 /* Parse the offset... */
424 if (m_Class[0] != '\0'
425 && m_Prop[0] != '\0')
427 SendProp *pProp = g_HL2.FindInSendTable(m_Class, m_Prop);
430 int val = pProp->GetOffset();
431 sm_trie_replace(m_pOffsets, m_offset, (void *)val);
432 sm_trie_replace(m_pProps, m_offset, pProp);
434 /* Check if it's a non-default game and no offsets exist */
435 if (((strcmp(m_Game, "*") != 0) && strcmp(m_Game, "#default") != 0)
436 && (!sm_trie_retrieve(m_pOffsets, m_offset, NULL)))
438 g_Logger.LogError("[SM] Unable to find property %s.%s (file \"%s\") (mod \"%s\")",
446 m_ParseState = PSTATE_GAMEDEFS_OFFSETS;
449 case PSTATE_GAMEDEFS_CRC:
450 case PSTATE_GAMEDEFS_SUPPORTED:
452 if (!bShouldBeReadingDefault)
454 /* If we shouldn't read the rest of this section, set the ignore level. */
456 m_ParseState = PSTATE_GAMES;
458 m_ParseState = PSTATE_GAMEDEFS;
462 case PSTATE_GAMEDEFS_CRC_BINARY:
464 m_ParseState = PSTATE_GAMEDEFS_CRC;
467 case PSTATE_GAMEDEFS_SIGNATURES:
469 m_ParseState = PSTATE_GAMEDEFS;
472 case PSTATE_GAMEDEFS_SIGNATURES_SIG:
474 if (s_TempSig.library[0] == '\0')
477 strncopy(s_TempSig.library, "server", sizeof(s_TempSig.library));
479 void *addrInBase = NULL;
480 if (strcmp(s_TempSig.library, "server") == 0)
482 addrInBase = (void *)g_SMAPI->GetServerFactory(false);
483 } else if (strcmp(s_TempSig.library, "engine") == 0) {
484 addrInBase = (void *)g_SMAPI->GetEngineFactory(false);
486 void *final_addr = NULL;
487 if (addrInBase == NULL)
489 g_Logger.LogError("[SM] Unrecognized library \"%s\" (gameconf \"%s\")",
493 #if defined PLATFORM_LINUX
494 if (s_TempSig.sig[0] == '@')
497 /* GNU only: returns 0 on error, inconsistent! >:[ */
498 if (dladdr(addrInBase, &info) != 0)
500 void *handle = dlopen(info.dli_fname, RTLD_NOW);
503 final_addr = dlsym(handle, &s_TempSig.sig[1]);
506 g_Logger.LogError("[SM] Unable to load library \"%s\" (gameconf \"%s\")",
511 g_Logger.LogError("[SM] Unable to find library \"%s\" in memory (gameconf \"%s\")",
521 /* First, preprocess the signature */
522 unsigned char real_sig[511];
527 length = strlen(s_TempSig.sig);
529 real_bytes = UTIL_DecodeHexString(real_sig, sizeof(real_sig), s_TempSig.sig);
533 final_addr = g_MemUtils.FindPattern(addrInBase, (char*)real_sig, real_bytes);
537 #if defined PLATFORM_LINUX
540 sm_trie_replace(m_pSigs, m_offset, final_addr);
541 m_ParseState = PSTATE_GAMEDEFS_SIGNATURES;
547 return SMCResult_Continue;
550 #define MSTATE_NONE 0
551 #define MSTATE_MAIN 1
552 #define MSTATE_FILE 2
554 class MasterReader : public ITextListener_SMC
557 virtual void ReadSMC_ParseStart()
563 virtual SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name)
567 return SMCResult_Continue;
570 if (state == MSTATE_NONE)
572 if (strcmp(name, "Game Master") == 0)
581 else if (state == MSTATE_MAIN)
583 strncopy(cur_file, name, sizeof(cur_file));
585 matched_engine = false;
587 matched_game = false;
590 else if (state == MSTATE_FILE)
595 return SMCResult_Continue;
598 virtual SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
600 if (ignoreLevel || state != MSTATE_FILE)
602 return SMCResult_Continue;
605 if (strcmp(key, "engine") == 0)
608 if (DoesEngineMatch(value))
610 matched_engine = true;
613 else if (strcmp(key, "game") == 0)
616 if (DoesGameMatch(value))
622 return SMCResult_Continue;
625 virtual SMCResult ReadSMC_LeavingSection(const SMCStates *states)
630 return SMCResult_Continue;
633 if (state == MSTATE_FILE)
635 /* The four success conditions:
637 * 2. Needed game only.
638 * 3. Needed engine only.
639 * 4. Needed both engine and game.
640 * Final result is minimized via k-map.
643 if ((!had_engine && !had_game) ||
644 (!had_engine && (had_game && matched_game)) ||
645 (!had_game && (had_engine && matched_engine)) ||
646 ((had_game && had_engine) && (matched_game && matched_engine)))
648 if ((!had_engine && !had_game) ||
649 (!had_engine && matched_game) ||
650 (!had_game && matched_engine) ||
651 (matched_engine && matched_game))
653 fileList->push_back(cur_file);
657 else if (state == MSTATE_MAIN)
662 return SMCResult_Continue;
665 List<String> *fileList;
667 unsigned int ignoreLevel;
668 char cur_file[PLATFORM_MAX_PATH];
675 static MasterReader master_reader;
677 bool CGameConfig::Reparse(char *error, size_t maxlength)
679 /* Reset cached data */
681 sm_trie_clear(m_pOffsets);
682 sm_trie_clear(m_pProps);
683 sm_trie_clear(m_pKeys);
685 char path[PLATFORM_MAX_PATH];
687 /* See if we can use the extended gamedata format. */
688 g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "gamedata/%s/master.games.txt", m_File);
689 if (!g_LibSys.PathExists(path))
691 /* Nope, use the old mechanism. */
692 UTIL_Format(path, sizeof(path), "%s.txt", m_File);
693 return EnterFile(path, error, maxlength);
696 /* Otherwise, it's time to parse the master. */
698 SMCStates state = {0, 0};
699 List<String> fileList;
700 master_reader.fileList = &fileList;
702 err = textparsers->ParseSMCFile(path, &master_reader, &state, error, maxlength);
703 if (err != SMCError_Okay)
705 const char *msg = textparsers->GetSMCErrorString(err);
707 g_Logger.LogError("[SM] Error parsing master gameconf file \"%s\":", path);
708 g_Logger.LogError("[SM] Error %d on line %d, col %d: %s",
712 msg ? msg : "Unknown error");
716 /* Go through each file we found and parse it. */
717 List<String>::iterator iter;
718 for (iter = fileList.begin(); iter != fileList.end(); iter++)
720 UTIL_Format(path, sizeof(path), "%s/%s", m_File, (*iter).c_str());
721 if (!EnterFile(path, error, maxlength))
727 /* Parse the contents of the 'custom' directory */
728 g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "gamedata/%s/custom", m_File);
729 IDirectory *customDir = g_LibSys.OpenDirectory(path);
736 while (customDir->MoreFiles())
738 if (!customDir->IsEntryFile())
740 customDir->NextEntry();
744 const char *curFile = customDir->GetEntryName();
746 /* Only allow .txt files */
747 int len = strlen(curFile);
748 if (len > 4 && strcmp(&curFile[len-4], ".txt") != 0)
750 customDir->NextEntry();
754 UTIL_Format(path, sizeof(path), "%s/custom/%s", m_File, curFile);
755 if (!EnterFile(path, error, maxlength))
757 g_LibSys.CloseDirectory(customDir);
761 customDir->NextEntry();
764 g_LibSys.CloseDirectory(customDir);
768 bool CGameConfig::EnterFile(const char *file, char *error, size_t maxlength)
771 SMCStates state = {0, 0};
773 g_SourceMod.BuildPath(Path_SM, m_CurFile, sizeof(m_CurFile), "gamedata/%s", file);
775 /* Initialize parse states */
777 bShouldBeReadingDefault = true;
778 m_ParseState = PSTATE_NONE;
780 if ((err=textparsers->ParseSMCFile(m_CurFile, this, &state, error, maxlength))
785 msg = textparsers->GetSMCErrorString(err);
787 g_Logger.LogError("[SM] Error parsing gameconfig file \"%s\":", m_CurFile);
788 g_Logger.LogError("[SM] Error %d on line %d, col %d: %s",
792 msg ? msg : "Unknown error");
794 if (m_ParseState == PSTATE_GAMEDEFS_CUSTOM)
796 //error occurred while parsing a custom section
797 m_CustomHandler->ReadSMC_ParseEnd(true, true);
798 m_CustomHandler = NULL;
808 bool CGameConfig::GetOffset(const char *key, int *value)
812 if (!sm_trie_retrieve(m_pOffsets, key, &obj))
822 const char *CGameConfig::GetKeyValue(const char *key)
825 if (!sm_trie_retrieve(m_pKeys, key, &obj))
829 return m_pStrings->GetString((int)obj);
832 SendProp *CGameConfig::GetSendProp(const char *key)
836 if (!sm_trie_retrieve(m_pProps, key, (void **)&pProp))
844 bool CGameConfig::GetMemSig(const char *key, void **addr)
846 return sm_trie_retrieve(m_pSigs, key, addr);
849 void CGameConfig::IncRefCount()
854 unsigned int CGameConfig::DecRefCount()
860 GameConfigManager::GameConfigManager()
862 m_pLookup = sm_trie_create();
865 GameConfigManager::~GameConfigManager()
867 sm_trie_destroy(m_pLookup);
870 void GameConfigManager::OnSourceModStartup(bool late)
872 LoadGameConfigFile("core.games", &g_pGameConf, NULL, 0);
874 strncopy(g_Game, g_SourceMod.GetGameFolderName(), sizeof(g_Game));
875 strncopy(g_GameDesc + 1, SERVER_CALL(GetGameDescription)(), sizeof(g_GameDesc) - 1);
877 KeyValues *pGameInfo = new KeyValues("GameInfo");
878 if (g_HL2.KVLoadFromFile(pGameInfo, basefilesystem, "gameinfo.txt"))
881 if ((str = pGameInfo->GetString("game", NULL)) != NULL)
883 strncopy(g_GameName + 1, str, sizeof(g_GameName) - 1);
886 pGameInfo->deleteThis();
889 void GameConfigManager::OnSourceModAllInitialized()
891 /* NOW initialize the game file */
892 CGameConfig *pGameConf = (CGameConfig *)g_pGameConf;
895 if (!pGameConf->Reparse(error, sizeof(error)))
900 g_ShareSys.AddInterface(NULL, this);
903 void GameConfigManager::OnSourceModAllShutdown()
905 CloseGameConfigFile(g_pGameConf);
908 bool GameConfigManager::LoadGameConfigFile(const char *file, IGameConfig **_pConfig, char *error, size_t maxlength)
911 /* A crash was detected during last load - We block the gamedata loading so it hopefully won't happen again */
912 if (g_blockGameDataLoad)
914 UTIL_Format(error, maxlength, "GameData loaded blocked due to detected crash");
919 CGameConfig *pConfig;
921 if (sm_trie_retrieve(m_pLookup, file, (void **)&pConfig))
923 pConfig->IncRefCount();
928 pConfig = new CGameConfig(file);
930 /* :HACKHACK: Don't parse the main config file */
932 if (_pConfig != &g_pGameConf)
934 retval = pConfig->Reparse(error, maxlength);
937 m_cfgs.push_back(pConfig);
938 sm_trie_insert(m_pLookup, file, pConfig);
940 pConfig->IncRefCount();
947 void GameConfigManager::CloseGameConfigFile(IGameConfig *cfg)
949 CGameConfig *pConfig = (CGameConfig *)cfg;
951 if (pConfig->DecRefCount() == 0)
953 sm_trie_delete(m_pLookup, pConfig->m_File);
958 extern HandleType_t g_GameConfigsType;
960 IGameConfig *GameConfigManager::ReadHandle(Handle_t hndl, IdentityToken_t *ident, HandleError *err)
962 HandleSecurity sec(ident, g_pCoreIdent);
963 IGameConfig *conf = NULL;
964 HandleError _err = g_HandleSys.ReadHandle(hndl, g_GameConfigsType, &sec, (void **)&conf);
974 void GameConfigManager::AddUserConfigHook(const char *sectionname, ITextListener_SMC *listener)
976 m_customHandlers.insert(sectionname, listener);
979 void GameConfigManager::RemoveUserConfigHook(const char *sectionname, ITextListener_SMC *listener)
981 ITextListener_SMC **pListener = m_customHandlers.retrieve(sectionname);
983 if (pListener == NULL)
988 if (*pListener != listener)
993 m_customHandlers.remove(sectionname);
998 void GameConfigManager::AcquireLock()
1002 void GameConfigManager::ReleaseLock()