diff --git a/configs/core.cfg b/configs/core.cfg index 8ee957e8..21b99c3e 100644 --- a/configs/core.cfg +++ b/configs/core.cfg @@ -4,14 +4,6 @@ */ "Core" { - /** - * Relative path to SourceMod's base directory. This is relative to the game/mod directory. - * Only change this if you have installed SourceMod in a non-default location. - * - * The default value is "addons/sourcemod" - */ - "BasePath" "addons/sourcemod" - /** * This option determines if SourceMod logging is enabled. * @@ -104,6 +96,12 @@ */ "ForceRestartAfterUpdate" "no" + /** + * URL to use for retrieving update information. + * SSL is not yet supported. + */ + "AutoUpdateURL" "http://www.sourcemod.net/update/" + /** * Whether to show debug spew. * Currently this will log details about the gamedata updating process. diff --git a/core/CoreConfig.cpp b/core/CoreConfig.cpp index db8235cf..6e71ee57 100644 --- a/core/CoreConfig.cpp +++ b/core/CoreConfig.cpp @@ -79,9 +79,7 @@ void Hook_ExecDispatchPre() const char *arg = cmd.Arg(1); - if (!g_bServerExecd - && arg != NULL - && strcmp(arg, g_ServerCfgFile->GetString()) == 0) + if (!g_bServerExecd && arg != NULL && strcmp(arg, g_ServerCfgFile->GetString()) == 0) { g_bGotTrigger = true; } @@ -103,8 +101,7 @@ void Hook_ExecDispatchPost() void CheckAndFinalizeConfigs() { - if ((g_bServerExecd || g_ServerCfgFile == NULL) - && g_bGotServerStart) + if ((g_bServerExecd || g_ServerCfgFile == NULL) && g_bGotServerStart) { #if SOURCE_ENGINE >= SE_ORANGEBOX g_PendingInternalPush = true; @@ -122,6 +119,14 @@ void CoreConfig::OnSourceModAllInitialized() g_pOnAutoConfigsBuffered = g_Forwards.CreateForward("OnAutoConfigsBuffered", ET_Ignore, 0, NULL); } +CoreConfig::CoreConfig() : m_Strings(512) +{ +} + +CoreConfig::~CoreConfig() +{ +} + void CoreConfig::OnSourceModShutdown() { g_RootMenu.RemoveRootConsoleCommand("config", this); @@ -218,6 +223,10 @@ void CoreConfig::Initialize() /* Format path to config file */ g_LibSys.PathFormat(filePath, sizeof(filePath), "%s/%s", g_SourceMod.GetGamePath(), corecfg); + /* Reset cached key values */ + m_KeyValues.clear(); + m_Strings.Reset(); + /* Parse config file */ if ((err=textparsers->ParseFile_SMC(filePath, this, NULL)) != SMCError_Okay) { @@ -256,9 +265,21 @@ ConfigResult CoreConfig::SetConfigOption(const char *option, const char *value, pBase = pBase->m_pGlobalClassNext; } + m_KeyValues.replace(option, m_Strings.AddString(value)); + return ConfigResult_Ignore; } +const char *CoreConfig::GetCoreConfigValue(const char *key) +{ + int *pKey = m_KeyValues.retrieve(key); + if (pKey == NULL) + { + return NULL; + } + return m_Strings.GetString(*pKey); +} + bool SM_AreConfigsExecuted() { return g_bConfigsExecd; diff --git a/core/CoreConfig.h b/core/CoreConfig.h index 5b1c605d..6bf28591 100644 --- a/core/CoreConfig.h +++ b/core/CoreConfig.h @@ -35,6 +35,8 @@ #include "sm_globals.h" #include #include +#include +#include "sm_memtable.h" using namespace SourceMod; @@ -43,6 +45,9 @@ class CoreConfig : public ITextListener_SMC, public IRootConsoleCommand { +public: + CoreConfig(); + ~CoreConfig(); public: // SMGlobalClass void OnSourceModAllInitialized(); void OnSourceModShutdown(); @@ -56,11 +61,15 @@ public: * Initializes CoreConfig by reading from core.cfg file */ void Initialize(); + const char *GetCoreConfigValue(const char *key); private: /** * Sets configuration option by notifying SourceMod components that rely on core.cfg */ ConfigResult SetConfigOption(const char *option, const char *value, ConfigSource, char *Error, size_t maxlength); +private: + BaseStringTable m_Strings; + KTrie m_KeyValues; }; extern bool SM_AreConfigsExecuted(); diff --git a/core/GameConfigs.cpp b/core/GameConfigs.cpp index caec131e..10d02907 100644 --- a/core/GameConfigs.cpp +++ b/core/GameConfigs.cpp @@ -749,6 +749,44 @@ bool CGameConfig::Reparse(char *error, size_t maxlength) } } + /* Parse the contents of the 'custom' directory */ + g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "gamedata/%s/custom", m_File); + IDirectory *customDir = g_LibSys.OpenDirectory(path); + + if (!customDir) + { + return true; + } + + while (customDir->MoreFiles()) + { + if (!customDir->IsEntryFile()) + { + customDir->NextEntry(); + continue; + } + + const char *curFile = customDir->GetEntryName(); + + /* Only allow .txt files */ + int len = strlen(curFile); + if (len > 4 && strcmp(&curFile[len-4], ".txt") != 0) + { + customDir->NextEntry(); + continue; + } + + UTIL_Format(path, sizeof(path), "%s/custom/%s", m_File, curFile); + if (!EnterFile(path, error, maxlength)) + { + g_LibSys.CloseDirectory(customDir); + return false; + } + + customDir->NextEntry(); + } + + g_LibSys.CloseDirectory(customDir); return true; } @@ -764,15 +802,11 @@ bool CGameConfig::EnterFile(const char *file, char *error, size_t maxlength) bShouldBeReadingDefault = true; m_ParseState = PSTATE_NONE; - g_GameConfigs.AcquireLock(); - if ((err=textparsers->ParseSMCFile(m_CurFile, this, &state, error, maxlength)) != SMCError_Okay) { const char *msg; - g_GameConfigs.ReleaseLock(); - msg = textparsers->GetSMCErrorString(err); g_Logger.LogError("[SM] Error parsing gameconfig file \"%s\":", m_CurFile); @@ -793,8 +827,6 @@ bool CGameConfig::EnterFile(const char *file, char *error, size_t maxlength) return false; } - g_GameConfigs.ReleaseLock(); - return true; } @@ -862,8 +894,6 @@ GameConfigManager::~GameConfigManager() void GameConfigManager::OnSourceModStartup(bool late) { - m_FileLock = g_pThreader->MakeMutex(); - LoadGameConfigFile("core.games", &g_pGameConf, NULL, 0); strncopy(g_Game, g_SourceMod.GetGameFolderName(), sizeof(g_Game)); @@ -898,7 +928,6 @@ void GameConfigManager::OnSourceModAllInitialized() void GameConfigManager::OnSourceModAllShutdown() { CloseGameConfigFile(g_pGameConf); - m_FileLock->DestroyThis(); } bool GameConfigManager::LoadGameConfigFile(const char *file, IGameConfig **_pConfig, char *error, size_t maxlength) @@ -993,10 +1022,8 @@ void GameConfigManager::RemoveUserConfigHook(const char *sectionname, ITextListe void GameConfigManager::AcquireLock() { - m_FileLock->Lock(); } void GameConfigManager::ReleaseLock() { - m_FileLock->Unlock(); } diff --git a/core/GameConfigs.h b/core/GameConfigs.h index 9fc512f5..162a85f4 100644 --- a/core/GameConfigs.h +++ b/core/GameConfigs.h @@ -120,7 +120,6 @@ public: //SMGlobalClass private: List m_cfgs; Trie *m_pLookup; - IMutex *m_FileLock; public: KTrie m_customHandlers; }; diff --git a/core/UserMessages.cpp b/core/UserMessages.cpp index 2618e80c..8b37dd33 100644 --- a/core/UserMessages.cpp +++ b/core/UserMessages.cpp @@ -348,6 +348,8 @@ void UserMessages::OnMessageEnd_Post() MsgIter iter; ListenerInfo *pInfo; + m_InHook = false; + pList = &m_msgIntercepts[m_CurId]; for (iter=pList->begin(); iter!=pList->end(); ) { @@ -367,8 +369,6 @@ void UserMessages::OnMessageEnd_Post() iter++; } - m_InHook = false; - pList = &m_msgHooks[m_CurId]; for (iter=pList->begin(); iter!=pList->end(); ) { diff --git a/core/sourcemod.cpp b/core/sourcemod.cpp index 633b3ade..0b3d7360 100644 --- a/core/sourcemod.cpp +++ b/core/sourcemod.cpp @@ -70,6 +70,12 @@ typedef ISourcePawnEngine *(*GET_SP_V1)(); typedef ISourcePawnEngine2 *(*GET_SP_V2)(); typedef void (*NOTIFYSHUTDOWN)(); +#ifdef PLATFORM_WINDOWS +ConVar sm_basepath("sm_basepath", "addons\\sourcemod", 0, "SourceMod base path (set via command line)"); +#elif defined PLATFORM_LINUX || defined PLATFORM_APPLE +ConVar sm_basepath("sm_basepath", "addons/sourcemod", 0, "SourceMod base path (set via command line)"); +#endif + void ShutdownJIT() { NOTIFYSHUTDOWN notify = (NOTIFYSHUTDOWN)g_pJIT->GetSymbolAddress("NotifyShutdown"); @@ -145,16 +151,26 @@ bool SourceModBase::InitializeSourceMod(char *error, size_t maxlength, bool late } } - /* Initialize CoreConfig so we can get SourceMod base path properly - this basically parses core.cfg */ - g_CoreConfig.Initialize(); - - /* This shouldn't happen, but can't hurt to be safe */ - if (!g_LibSys.PathExists(m_SMBaseDir) || !m_GotBasePath) + const char *basepath = icvar->GetCommandLineValue("sm_basepath"); + /* Set a custom base path if there is one. */ + if (basepath != NULL && basepath[0] != '\0') { - g_LibSys.PathFormat(m_SMBaseDir, sizeof(m_SMBaseDir), "%s/addons/sourcemod", g_BaseDir.c_str()); - g_LibSys.PathFormat(m_SMRelDir, sizeof(m_SMRelDir), "addons/sourcemod"); m_GotBasePath = true; } + /* Otherwise, use a default and keep the m_GotBasePath unlocked. */ + else + { + basepath = sm_basepath.GetDefault(); + } + + g_LibSys.PathFormat(m_SMBaseDir, sizeof(m_SMBaseDir), "%s/%s", g_BaseDir.c_str(), basepath); + g_LibSys.PathFormat(m_SMRelDir, sizeof(m_SMRelDir), "%s", basepath); + + /* Initialize CoreConfig to get the SourceMod base path properly - this parses core.cfg */ + g_CoreConfig.Initialize(); + + /* There will always be a path by this point, since it was force-set above. */ + m_GotBasePath = true; /* Attempt to load the JIT! */ char file[PLATFORM_MAX_PATH]; @@ -696,6 +712,11 @@ void SourceModBase::AddFrameAction(FRAMEACTION fn, void *data) ::AddFrameAction(FrameAction(fn, data)); } +const char *SourceModBase::GetCoreConfigValue(const char *key) +{ + return g_CoreConfig.GetCoreConfigValue(key); +} + SMGlobalClass *SMGlobalClass::head = NULL; SMGlobalClass::SMGlobalClass() diff --git a/core/sourcemod.h b/core/sourcemod.h index a436b42e..ecf23921 100644 --- a/core/sourcemod.h +++ b/core/sourcemod.h @@ -132,6 +132,7 @@ public: // ISourceMod size_t Format(char *buffer, size_t maxlength, const char *fmt, ...); size_t FormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list ap); void AddFrameAction(FRAMEACTION fn, void *data); + const char *GetCoreConfigValue(const char *key); private: CStack m_freepacks; char m_SMBaseDir[PLATFORM_MAX_PATH]; diff --git a/extensions/updater/Updater.cpp b/extensions/updater/Updater.cpp index 5df16414..c7d708e0 100644 --- a/extensions/updater/Updater.cpp +++ b/extensions/updater/Updater.cpp @@ -34,8 +34,6 @@ #include "Updater.h" #include "md5.h" -#define UPDATE_URL "http://www.sourcemod.net/update/" - #define USTATE_NONE 0 #define USTATE_FOLDERS 1 #define USTATE_CHANGED 2 @@ -43,7 +41,7 @@ using namespace SourceMod; -UpdateReader::UpdateReader() +UpdateReader::UpdateReader() : partFirst(NULL), partLast(NULL) { } @@ -125,7 +123,7 @@ SMCResult UpdateReader::ReadSMC_KeyValue(const SMCStates *states, } else if (strcmp(key, "location") == 0) { - url.assign(UPDATE_URL); + url.assign(update_url); url.append(value); } break; @@ -174,6 +172,21 @@ SMCResult UpdateReader::ReadSMC_LeavingSection(const SMCStates *states) return SMCResult_Continue; } +void UpdateReader::LinkPart(UpdatePart *part) +{ + part->next = NULL; + if (partFirst == NULL) + { + partFirst = part; + partLast = part; + } + else + { + partLast->next = part; + partLast = part; + } +} + void UpdateReader::HandleFile() { MD5 md5; @@ -192,6 +205,12 @@ void UpdateReader::HandleFile() md5.finalize(); md5.hex_digest(real_checksum); + if (mdl.GetSize() == 0) + { + AddUpdateError("Zero-length file returned for \"%s\"", curfile.c_str()); + return; + } + if (strcasecmp(checksum, real_checksum) != 0) { AddUpdateError("Checksums for file \"%s\" do not match:", curfile.c_str()); @@ -199,45 +218,20 @@ void UpdateReader::HandleFile() return; } - char path[PLATFORM_MAX_PATH]; - smutils->BuildPath(Path_SM, path, sizeof(path), "gamedata/%s", curfile.c_str()); - - gameconfs->AcquireLock(); - - FILE *fp = fopen(path, "wt"); - if (fp == NULL) - { - gameconfs->ReleaseLock(); - AddUpdateError("Could not open %s for writing", path); - return; - } - - fwrite(mdl.GetBuffer(), 1, mdl.GetSize(), fp); - fclose(fp); - - gameconfs->ReleaseLock(); - - AddUpdateMessage("Successfully updated gamedata file \"%s\"", curfile.c_str()); + UpdatePart *part = new UpdatePart; + part->data = (char*)malloc(mdl.GetSize()); + part->file = strdup(curfile.c_str()); + part->length = mdl.GetSize(); + LinkPart(part); } void UpdateReader::HandleFolder(const char *folder) { - char path[PLATFORM_MAX_PATH]; - - smutils->BuildPath(Path_SM, path, sizeof(path), "gamedata/%s", folder); - if (libsys->IsPathDirectory(path)) - { - return; - } - - if (!libsys->CreateFolder(path)) - { - AddUpdateError("Could not create folder: %s", path); - } - else - { - AddUpdateMessage("Created folder \"%s\" from updater", folder); - } + UpdatePart *part = new UpdatePart; + part->data = NULL; + part->length = 0; + part->file = strdup(folder); + LinkPart(part); } static bool md5_file(const char *file, char checksum[33]) @@ -334,18 +328,18 @@ static void add_folders(IWebForm *form, const char *root, unsigned int &num_file libsys->CloseDirectory(dir); } -void UpdateReader::PerformUpdate() +void UpdateReader::PerformUpdate(const char *url) { IWebForm *form; MemoryDownloader master; SMCStates states = {0, 0}; + update_url = url; + form = webternet->CreateForm(); xfer = webternet->CreateSession(); xfer->SetFailOnHTTPError(true); - const char *root_url = UPDATE_URL "gamedata.php"; - form->AddString("version", SVN_FULL_VERSION); form->AddString("build", SM_BUILD_UNIQUEID); @@ -356,9 +350,9 @@ void UpdateReader::PerformUpdate() smutils->Format(temp, sizeof(temp), "%d", num_files); form->AddString("files", temp); - if (!xfer->PostAndDownload(root_url, form, &master, NULL)) + if (!xfer->PostAndDownload(url, form, &master, NULL)) { - AddUpdateError("Could not download \"%s\"", root_url); + AddUpdateError("Could not download \"%s\"", url); AddUpdateError("Error: %s", xfer->LastErrorMessage()); goto cleanup; } @@ -381,3 +375,14 @@ cleanup: delete xfer; delete form; } + +UpdatePart *UpdateReader::DetachParts() +{ + UpdatePart *first; + + first = partFirst; + partFirst = NULL; + partLast = NULL; + + return first; +} diff --git a/extensions/updater/Updater.h b/extensions/updater/Updater.h index 0412f6cd..a8c3c6c7 100644 --- a/extensions/updater/Updater.h +++ b/extensions/updater/Updater.h @@ -37,6 +37,14 @@ #include #include "MemoryDownloader.h" +struct UpdatePart +{ + UpdatePart* next; + char *file; + char *data; + size_t length; +}; + namespace SourceMod { class UpdateReader : @@ -51,10 +59,12 @@ namespace SourceMod SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value); SMCResult ReadSMC_LeavingSection(const SMCStates *states); public: - void PerformUpdate(); + void PerformUpdate(const char *url); + UpdatePart *DetachParts(); private: void HandleFile(); void HandleFolder(const char *folder); + void LinkPart(UpdatePart *part); private: IWebTransfer *xfer; MemoryDownloader mdl; @@ -63,6 +73,9 @@ namespace SourceMod SourceHook::String curfile; SourceHook::String url; char checksum[33]; + UpdatePart *partFirst; + UpdatePart *partLast; + const char *update_url; }; } diff --git a/extensions/updater/extension.cpp b/extensions/updater/extension.cpp index 7c2da506..1dd0d8f3 100644 --- a/extensions/updater/extension.cpp +++ b/extensions/updater/extension.cpp @@ -38,6 +38,8 @@ #include #include +#define DEFAULT_UPDATE_URL "http://www.sourcemod.net/update/" + using namespace SourceHook; SmUpdater g_Updater; /**< Global singleton for extension's main interface */ @@ -45,8 +47,8 @@ SMEXT_LINK(&g_Updater); IWebternet *webternet; static List update_errors; -static List update_messages; static IThreadHandle *update_thread; +static String update_url; bool SmUpdater::SDK_OnLoad(char *error, size_t maxlength, bool late) { @@ -65,6 +67,13 @@ bool SmUpdater::SDK_OnLoad(char *error, size_t maxlength, bool late) return false; } + const char *url = smutils->GetCoreConfigValue("AutoUpdateURL"); + if (url == NULL) + { + url = DEFAULT_UPDATE_URL; + } + update_url.assign(url); + return true; } @@ -85,10 +94,6 @@ void SmUpdater::SDK_OnUnload() { iter = update_errors.erase(iter); } - while (iter != update_messages.end()) - { - iter = update_messages.erase(iter); - } } bool SmUpdater::QueryInterfaceDrop(SourceMod::SMInterface *pInterface) @@ -112,11 +117,61 @@ void SmUpdater::NotifyInterfaceDrop(SMInterface *pInterface) } } -static void LogAllMessages(void *data) +static void PumpUpdate(void *data) { String *str; List::iterator iter; + char path[PLATFORM_MAX_PATH]; + UpdatePart *temp; + UpdatePart *part = (UpdatePart*)data; + while (part != NULL) + { + if (strstr(part->file, "..") != NULL) + { + /* Naughty naughty */ + AddUpdateError("Detected invalid path escape (..): %s", part->file); + goto skip_create; + } + if (part->data == NULL) + { + smutils->BuildPath(Path_SM, path, sizeof(path), "gamedata/%s", part->file); + if (libsys->IsPathDirectory(path)) + { + continue; + } + if (!libsys->CreateFolder(path)) + { + AddUpdateError("Could not create folder: %s", path); + } + else + { + smutils->LogMessage(myself, "Created folder \"%s\" from updater", path); + } + } + else + { + smutils->BuildPath(Path_SM, path, sizeof(path), "gamedata/%s", part->file); + FILE *fp = fopen(path, "wt"); + if (fp == NULL) + { + AddUpdateError("Could not open %s for writing", path); + return; + } + fwrite(part->data, 1, part->length, fp); + fclose(fp); + smutils->LogMessage(myself, + "Successfully updated gamedata file \"%s\"", + part->file); + } +skip_create: + temp = part->next; + free(part->data); + free(part->file); + delete part; + part = temp; + } + if (update_errors.size()) { smutils->LogError(myself, "--- BEGIN ERRORS FROM AUTOMATIC UPDATER ---"); @@ -131,44 +186,21 @@ static void LogAllMessages(void *data) smutils->LogError(myself, "--- END ERRORS FROM AUTOMATIC UPDATER ---"); } - - for (iter = update_messages.begin(); - iter != update_messages.end(); - iter++) - { - str = (*iter); - smutils->LogMessage(myself, "%s", str->c_str()); - } } void SmUpdater::RunThread(IThreadHandle *pHandle) { UpdateReader ur; - ur.PerformUpdate(); + ur.PerformUpdate(update_url.c_str()); - if (update_errors.size() || update_messages.size()) - { - smutils->AddFrameAction(LogAllMessages, NULL); - } + smutils->AddFrameAction(PumpUpdate, ur.DetachParts()); } void SmUpdater::OnTerminate(IThreadHandle *pHandle, bool cancel) { } -void AddUpdateMessage(const char *fmt, ...) -{ - va_list ap; - char buffer[2048]; - - va_start(ap, fmt); - smutils->FormatArgs(buffer, sizeof(buffer), fmt, ap); - va_end(ap); - - update_messages.push_back(new String(buffer)); -} - void AddUpdateError(const char *fmt, ...) { va_list ap; diff --git a/gamedata/sm-tf2.games.txt b/gamedata/sm-tf2.games.txt index f809e1b2..32c559fa 100644 --- a/gamedata/sm-tf2.games.txt +++ b/gamedata/sm-tf2.games.txt @@ -38,7 +38,7 @@ { "library" "server" "linux" "@_ZN8CTFKnife26CalcIsAttackCriticalHelperEv" - "windows" "\x33\xC0\x83\xB9\x08\x13\x00\x00\x01\x0F\x94\xC0\xC3" + "windows" "\x33\xC0\x83\xB9\x30\x13\x00\x00\x01\x0F\x94\xC0\xC3" } } diff --git a/public/IGameConfigs.h b/public/IGameConfigs.h index e9c63e88..be00a1e5 100644 --- a/public/IGameConfigs.h +++ b/public/IGameConfigs.h @@ -163,12 +163,12 @@ namespace SourceMod virtual void RemoveUserConfigHook(const char *sectionname, ITextListener_SMC *listener) =0; /** - * @brief Acquires a file reading lock on the gamedata system. + * @brief Does nothing. */ virtual void AcquireLock() = 0; /** - * @brief Releases the file reading lock. + * @brief Does nothing. */ virtual void ReleaseLock() = 0; }; diff --git a/public/ISourceMod.h b/public/ISourceMod.h index 09d4bedf..69fe1f0c 100644 --- a/public/ISourceMod.h +++ b/public/ISourceMod.h @@ -43,7 +43,7 @@ #include #define SMINTERFACE_SOURCEMOD_NAME "ISourceMod" -#define SMINTERFACE_SOURCEMOD_VERSION 9 +#define SMINTERFACE_SOURCEMOD_VERSION 10 /** * @brief Forward declaration of the KeyValues class. @@ -287,6 +287,15 @@ namespace SourceMod * @param data Data to pass to function. */ virtual void AddFrameAction(FRAMEACTION fn, void *data) = 0; + + /** + * @brief Retrieves a core.cfg configuration value. + * + * @param key Core.cfg key phrase. + * @return Value string, or NULL on failure. + * The string will be destroyed on core.cfg reparses. + */ + virtual const char *GetCoreConfigValue(const char *key) = 0; }; }