From 15244bf27a24a158b8eb41c261052fc362b14361 Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Sun, 9 Jul 2023 22:49:46 +0200 Subject: [PATCH] Modsystem improvements - Added command line parameter '-modsystem_debug' to allow debugging during init, as we initialize before the first Cbuf_Execute() call (responsible for executing command line post engine init). - Added warning to 'CLocalize::LoadLocalizationFileLists', when a mod localization file fails to load. - All mod instances are now added to a single vector, but their 'state' determine whether or not they are enabled. This allows for toggling them on while in-game in the future, without having to rebuild the engine mod list. - Changed all filesystem calls to use that of the engine instead. - Changed all container types to valve ones, to maintain compatibility with the filesystem of the engine, and perhaps other things in the future. - Forced all loads/writes to "PLATFORM" path (this is where the 'mods' folder resides). - Forced localization files to be only read from the mod instance's directory. - Allocated each mod instance dynamically, and stored their pointers in the modlist vector to reduce memory overhead during the move operation to modlist, and potential growth of modlist vector (which if required, will reallocate everything, and thus move it all. This especially is expensive with nested vectors (CUtlVector anly supports being nested as a pointer)). --- r5dev/localize/localize.cpp | 24 +- r5dev/pluginsystem/modsystem.cpp | 349 ++++++++++++------ r5dev/pluginsystem/modsystem.h | 63 ++-- .../languages/squirrel_re/include/sqvm.h | 2 +- .../languages/squirrel_re/vsquirrel.cpp | 39 +- 5 files changed, 310 insertions(+), 167 deletions(-) diff --git a/r5dev/localize/localize.cpp b/r5dev/localize/localize.cpp index 3b97fa64..f1823b12 100644 --- a/r5dev/localize/localize.cpp +++ b/r5dev/localize/localize.cpp @@ -1,4 +1,5 @@ #include "core/stdafx.h" +#include "tier1/utlvector.h" #include "localize/localize.h" #include "pluginsystem/modsystem.h" @@ -6,19 +7,26 @@ bool Localize_LoadLocalizationFileLists(CLocalize* thisptr) { v_CLocalize__LoadLocalizationFileLists(thisptr); - for (auto& mod : g_pModSystem->GetModList()) + const CUtlVector& + modList = g_pModSystem->GetModList(); + + FOR_EACH_VEC(modList, i) { - if (mod.m_iState == CModSystem::eModState::ENABLED) + const CModSystem::ModInstance_t* mod = + modList.Element(i); + + if (!mod->IsEnabled()) + continue; + + FOR_EACH_VEC(mod->m_LocalizationFiles, j) { - for (auto& it : mod.m_vszLocalizationFiles) - { - v_CLocalize__AddFile(thisptr, it.c_str(), NULL); - } + const char* localizationFile = mod->m_LocalizationFiles.Element(j).Get(); + + if (!v_CLocalize__AddFile(thisptr, localizationFile, "PLATFORM")) + Warning(eDLL_T::ENGINE, "Failed to add localization file '%s'\n", localizationFile); } } - DevMsg(eDLL_T::ENGINE, "Loaded localization files.\n"); - return true; } diff --git a/r5dev/pluginsystem/modsystem.cpp b/r5dev/pluginsystem/modsystem.cpp index 024db33d..e7ce4f2c 100644 --- a/r5dev/pluginsystem/modsystem.cpp +++ b/r5dev/pluginsystem/modsystem.cpp @@ -1,16 +1,30 @@ //=============================================================================// // // Purpose: Manage loading mods -// +// //----------------------------------------------------------------------------- // //=============================================================================// #include "core/stdafx.h" -#include "modsystem.h" -#include "localize/localize.h" -#include "tier1/cvar.h" #include "vpc/rson.h" +#include "tier0/commandline.h" +#include "tier1/cvar.h" +#include "tier2/fileutils.h" +#include "localize/localize.h" +#include "modsystem.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CModSystem::~CModSystem() +{ + // clear all allocated mod instances. + FOR_EACH_VEC(m_ModList, i) + { + delete m_ModList.Element(i); + } +} //----------------------------------------------------------------------------- // Purpose: initialize the mod system @@ -21,171 +35,253 @@ void CModSystem::Init() if (!modsystem_enable->GetBool()) return; - LoadModStatusList(); + // no mods installed, no point in initializing. + if (!FileSystem()->IsDirectory(MOD_BASE_DIRECTORY, "PLATFORM")) + return; - CreateDirectories("platform\\mods"); + // mod system initializes before the first Cbuf_Execute call, which + // executes commands/convars over the command line. we check for an + // explicit modsystem debug flag, and set the convar from here. + if (CommandLine()->CheckParm("-modsystem_debug")) + modsystem_debug->SetValue(true); - for (auto& it : fs::directory_iterator("platform\\mods")) + CUtlVector modFileList; + RecursiveFindFilesMatchingName(&modFileList, + MOD_BASE_DIRECTORY, MOD_SETTINGS_FILE, "PLATFORM", '/'); + + FOR_EACH_VEC(modFileList, i) { - if (!fs::is_directory(it)) - continue; + // allocate dynamically, so less memory/resources are required when + // the vector has to grow and reallocate everything. we also got + // a vector member in the modinstance struct, which would ultimately + // lead into each item getting copy constructed into the mod list + // by the 'move' constructor (CUtlVector also doesn't support being + // nested unless its a pointer). + CModSystem::ModInstance_t* mod = + new CModSystem::ModInstance_t(modFileList.Element(i).DirName()); - fs::path basePath = it.path(); - - if (modsystem_debug->GetBool()) - DevMsg(eDLL_T::ENGINE, "Found mod at '%s'.\n", basePath.string().c_str()); - - fs::path settingsPath = basePath / "mod.vdf"; - - if (fs::exists(settingsPath)) + if (!mod->IsLoaded()) { - CModSystem::ModInstance_t modInst = CModSystem::ModInstance_t(basePath); - if (modInst.m_iState != eModState::UNLOADED) - m_vModList.push_back(modInst); + delete mod; + continue; + } + + m_ModList.AddToTail(mod); + } + + UpdateModStatusList(); +} + +//----------------------------------------------------------------------------- +// Purpose: loads, updates, enforces and writes the mod status list +//----------------------------------------------------------------------------- +void CModSystem::UpdateModStatusList() +{ + CUtlMap enabledList(UtlStringLessFunc); + LoadModStatusList(enabledList); + + // from here, we determine whether or not to enable the loaded mod. + FOR_EACH_VEC(m_ModList, i) + { + ModInstance_t* mod = m_ModList[i]; + Assert(mod->IsLoaded()); + + if (!enabledList.HasElement(mod->m_ModID)) + { + if (modsystem_debug->GetBool()) + DevMsg(eDLL_T::ENGINE, "Mod '%s' does not exist in '%s'; enabling...\n", + mod->m_ModID.Get(), MOD_STATUS_LIST_FILE); + + mod->SetState(eModState::ENABLED); + } + else + { + const bool bEnable = enabledList.FindElement(mod->m_ModID, false); + mod->SetState(bEnable ? eModState::ENABLED : eModState::DISABLED); + + if (modsystem_debug->GetBool()) + DevMsg(eDLL_T::ENGINE, "Mod '%s' exists in '%s' and is %s.\n", + mod->m_ModID.Get(), MOD_STATUS_LIST_FILE, bEnable ? "enabled" : "disabled"); } } WriteModStatusList(); } -void CModSystem::LoadModStatusList() +//----------------------------------------------------------------------------- +// Purpose: loads the mod status file from the disk +// Input : &enabledList - +//----------------------------------------------------------------------------- +void CModSystem::LoadModStatusList(CUtlMap& enabledList) { - if (FileSystem()->FileExists("platform/mods.vdf")) - { - KeyValues* pModList = FileSystem()->LoadKeyValues(IFileSystem::TYPE_COMMON, "platform/mods.vdf", "GAME"); + if (!FileSystem()->FileExists(MOD_STATUS_LIST_FILE, "PLATFORM")) + return; - for (KeyValues* pSubKey = pModList->GetFirstSubKey(); pSubKey != nullptr; pSubKey = pSubKey->GetNextKey()) - { - size_t idHash = std::hash{}(std::string(pSubKey->GetName())); - m_vEnabledList.emplace(idHash, pSubKey->GetBool()); - } + KeyValues* pModList = FileSystem()->LoadKeyValues( + IFileSystem::TYPE_COMMON, MOD_STATUS_LIST_FILE, "PLATFORM"); + + for (KeyValues* pSubKey = pModList->GetFirstSubKey(); + pSubKey != nullptr; pSubKey = pSubKey->GetNextKey()) + { + enabledList.Insert(pSubKey->GetName(), pSubKey->GetBool()); } } +//----------------------------------------------------------------------------- +// Purpose: writes the mod status file to the disk +//----------------------------------------------------------------------------- void CModSystem::WriteModStatusList() { KeyValues kv = KeyValues("ModList"); KeyValues* pModListKV = kv.FindKey("ModList", true); - for (auto& it : m_vModList) + FOR_EACH_VEC(m_ModList, i) { + ModInstance_t* mod = m_ModList[i]; bool enabled = false; - if (it.m_iState == eModState::ENABLED) + + if (mod->m_iState == eModState::ENABLED) enabled = true; - pModListKV->SetBool(it.m_szModID.c_str(), enabled); + pModListKV->SetBool(mod->m_ModID.Get(), enabled); } - CUtlBuffer uBuf = CUtlBuffer(int64_t(0), 0, CUtlBuffer::TEXT_BUFFER); + CUtlBuffer buf = CUtlBuffer(int64_t(0), 0, CUtlBuffer::TEXT_BUFFER); + kv.RecursiveSaveToFile(buf, 0); - kv.RecursiveSaveToFile(uBuf, 0); - - FileSystem()->WriteFile("platform/mods.vdf", NULL, uBuf); // NULL instead of "GAME" because otherwise for some reason the file clears itself when the process exits + if (!FileSystem()->WriteFile(MOD_STATUS_LIST_FILE, "PLATFORM", buf)) + Error(eDLL_T::ENGINE, NO_ERROR, "Failed to write mod status list '%s'\n", MOD_STATUS_LIST_FILE); } - -CModSystem::ModInstance_t::ModInstance_t(const fs::path& basePath) : m_szName(std::string()), m_szModID(std::string()), m_BasePath(basePath), m_szDescription(std::string()), m_szVersion(std::string()), m_SettingsKV(nullptr), m_iState(eModState::LOADING) +//----------------------------------------------------------------------------- +// Purpose: +// Input : &basePath - +//----------------------------------------------------------------------------- +CModSystem::ModInstance_t::ModInstance_t(const CUtlString& basePath) { - std::string settingsPath = (m_BasePath / "mod.vdf").string(); + m_SettingsKV = nullptr; + m_bHasScriptCompileList = false; - KeyValues* pSettingsKV = FileSystem()->LoadKeyValues(IFileSystem::TYPE_COMMON, settingsPath.c_str(), "GAME"); - m_SettingsKV = pSettingsKV; + m_BasePath = basePath; + m_BasePath.AppendSlash('/'); + + SetState(eModState::LOADING); + + if (!ParseSettings()) + { + SetState(eModState::UNLOADED); + return; + } + + // parse any additional info from mod.vdf + ParseConVars(); + ParseLocalizationFiles(); + + // add mod folder to search paths so files can be easily loaded from here + // [rexx]: maybe this isn't ideal as the only way of finding the mod's files, + // as there may be name clashes in files where the engine + // won't really care about the input file name. it may be better to, + // where possible, request files by file path relative to root + // (i.e. including platform/mods/{mod}/) + // [amos]: it might be better to pack core files into the VPK, and disable + // the filesystem cache to disk reroute to avoid the file name + // clashing problems, research required. + FileSystem()->AddSearchPath(m_BasePath.Get(), "PLATFORM", SearchPathAdd_t::PATH_ADD_TO_TAIL); + + CUtlString scriptsRsonPath = m_BasePath + GAME_SCRIPT_COMPILELIST; + + if (FileSystem()->FileExists(scriptsRsonPath.Get(), "PLATFORM")) + m_bHasScriptCompileList = true; + + SetState(eModState::LOADED); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CModSystem::ModInstance_t::~ModInstance_t() +{ + if (m_SettingsKV) + delete m_SettingsKV; +} + +//----------------------------------------------------------------------------- +// Purpose: gets a keyvalue from settings KV, and logs an error on failure +// Input : *settingsPath - +// *key - +// Output : pointer to KeyValues object +//----------------------------------------------------------------------------- +KeyValues* CModSystem::ModInstance_t::GetRequiredSettingsKey( + const char* settingsPath, const char* key) const +{ + KeyValues* pKeyValue = m_SettingsKV->FindKey(key); + if (!pKeyValue) + Error(eDLL_T::ENGINE, NO_ERROR, + "Mod settings '%s' has missing or invalid '%s' field; skipping...\n", + settingsPath, key); + + return pKeyValue; +} + +//----------------------------------------------------------------------------- +// Purpose: loads the settings KV and parses the main values +// Output : true on success, false otherwise +//----------------------------------------------------------------------------- +bool CModSystem::ModInstance_t::ParseSettings() +{ + CUtlString settingsPath = m_BasePath + MOD_SETTINGS_FILE; + const char* pSettingsPath = settingsPath.Get(); + + m_SettingsKV = FileSystem()->LoadKeyValues( + IFileSystem::TYPE_COMMON, pSettingsPath, "PLATFORM"); if (!m_SettingsKV) { - SetState(eModState::UNLOADED); - Error(eDLL_T::ENGINE, NO_ERROR, "Failed to parse mod.vdf for mod at path '%s'\n", m_BasePath.string().c_str()); - return; + Error(eDLL_T::ENGINE, NO_ERROR, + "Failed to parse mod settings '%s'; skipping...\n", m_BasePath.Get()); + return false; } - ///////////////////////////// // "name" "An R5Reloaded Mod" // [rexx]: could be optional and have id as fallback - KeyValues* pName = pSettingsKV->FindKey("name"); - + KeyValues* pName = GetRequiredSettingsKey(pSettingsPath, "name"); if (!pName) - { - SetState(eModState::UNLOADED); - Error(eDLL_T::ENGINE, NO_ERROR, "Mod settings file '%s' was missing required 'name' field. Skipping mod...\n", settingsPath.c_str()); - return; - } + return false; - m_szName = pName->GetString(); + m_Name = pName->GetString(); - ///////////////////////////// // "version" "1.0.0" - KeyValues* pVersion = pSettingsKV->FindKey("version"); - + KeyValues* pVersion = GetRequiredSettingsKey(pSettingsPath, "version"); if (!pVersion) - { - SetState(eModState::UNLOADED); - Error(eDLL_T::ENGINE, NO_ERROR, "Mod settings file '%s' was missing required 'version' field. Skipping mod...\n", settingsPath.c_str()); - return; - } + return false; - m_szVersion = pVersion->GetString(); + m_Version = pVersion->GetString(); - ///////////////////////////// // "id" "r5reloaded.TestMod" - KeyValues* pId = pSettingsKV->FindKey("id"); - + KeyValues* pId = GetRequiredSettingsKey(pSettingsPath, "id"); if (!pId) - { - SetState(eModState::UNLOADED); - Error(eDLL_T::ENGINE, NO_ERROR, "Mod settings file '%s' was missing required 'id' field. Skipping mod...\n", settingsPath.c_str()); - return; - } + return false; - m_szModID = pId->GetString(); + m_ModID = pId->GetString(); - ///////////////////////////// // optional mod description field - m_szDescription = pSettingsKV->GetString("description"); + m_Description = m_SettingsKV->GetString(pSettingsPath, "description"); - size_t idHash = std::hash{}(m_szModID); + return true; +} - auto& enabledList = g_pModSystem->GetEnabledList(); - if (enabledList.count(idHash) == 0) - { - if (modsystem_debug->GetBool()) - DevMsg(eDLL_T::ENGINE, "Mod does not exist in 'mods.vdf'. Enabling...\n"); - - SetState(eModState::ENABLED); - } - else - { - bool bEnable = enabledList[idHash]; - SetState(bEnable ? eModState::ENABLED : eModState::DISABLED); - - if (modsystem_debug->GetBool()) - DevMsg(eDLL_T::ENGINE, "Mod exists in 'mods.vdf' and is %s.\n", bEnable ? "enabled" : "disabled"); - } - - if (m_iState != eModState::ENABLED) - return; - - // parse any additional info from mod.vdf - - // add mod folder to search paths so files can be easily loaded from here - // [rexx]: maybe this isn't ideal as the only way of finding the mod's files, as there may be name clashes in files where the engine - // won't really care about the input file name. it may be better to, where possible, request files by file path relative to root (i.e. including platform/mods/{mod}/) - FileSystem()->AddSearchPath(m_BasePath.string().c_str(), "GAME", SearchPathAdd_t::PATH_ADD_TO_TAIL); - - KeyValues* pLocalizationFiles = pSettingsKV->FindKey("LocalizationFiles"); - - if (pLocalizationFiles) - { - for (KeyValues* pSubKey = pLocalizationFiles->GetFirstSubKey(); pSubKey != nullptr; pSubKey = pSubKey->GetNextKey()) - { - this->m_vszLocalizationFiles.push_back(pSubKey->GetName()); - } - } - - KeyValues* pConVars = pSettingsKV->FindKey("ConVars"); +//----------------------------------------------------------------------------- +// Purpose: parses and registers convars listed in settings KV +//----------------------------------------------------------------------------- +void CModSystem::ModInstance_t::ParseConVars() +{ + Assert(m_SettingsKV); + KeyValues* pConVars = m_SettingsKV->FindKey("ConVars"); if (pConVars) { - for (KeyValues* pSubKey = pConVars->GetFirstSubKey(); pSubKey != nullptr; pSubKey = pSubKey->GetNextKey()) + for (KeyValues* pSubKey = pConVars->GetFirstSubKey(); + pSubKey != nullptr; pSubKey = pSubKey->GetNextKey()) { const char* pszName = pSubKey->GetName(); const char* pszFlagsString = pSubKey->GetString("flags", "NONE"); @@ -222,14 +318,29 @@ CModSystem::ModInstance_t::ModInstance_t(const fs::path& basePath) : m_szName(st int flags = FCVAR_NONE; if (ConVar_ParseFlagString(pszFlagsString, flags, pszName)) - ConVar::StaticCreate(pszName, pszDefaultValue, flags, pszHelpString, bMin, fMin, bMax, fMax, nullptr, pszUsageString); + ConVar::StaticCreate(pszName, pszDefaultValue, flags, + pszHelpString, bMin, fMin, bMax, fMax, nullptr, pszUsageString); } } +} - std::string scriptsRsonPath = (m_BasePath / "scripts/vscripts/scripts.rson").string(); +//----------------------------------------------------------------------------- +// Purpose: parses and stores localization file paths in a vector +//----------------------------------------------------------------------------- +void CModSystem::ModInstance_t::ParseLocalizationFiles() +{ + Assert(m_SettingsKV); - if (FileSystem()->FileExists(scriptsRsonPath.c_str(), "GAME")) - m_bHasScriptCompileList = true; -}; + KeyValues* pLocalizationFiles = m_SettingsKV->FindKey("LocalizationFiles"); -CModSystem* g_pModSystem = new CModSystem(); \ No newline at end of file + if (pLocalizationFiles) + { + for (KeyValues* pSubKey = pLocalizationFiles->GetFirstSubKey(); + pSubKey != nullptr; pSubKey = pSubKey->GetNextKey()) + { + m_LocalizationFiles.AddToTail(m_BasePath + pSubKey->GetName()); + } + } +} + +CModSystem* g_pModSystem = new CModSystem(); diff --git a/r5dev/pluginsystem/modsystem.h b/r5dev/pluginsystem/modsystem.h index dfb2128b..a81c4cff 100644 --- a/r5dev/pluginsystem/modsystem.h +++ b/r5dev/pluginsystem/modsystem.h @@ -2,65 +2,78 @@ #include "vpc/keyvalues.h" #include "vpc/rson.h" -#include +#include "filesystem/filesystem.h" +#include "public/vscript/ivscript.h" + +#define MOD_STATUS_LIST_FILE "mods.vdf" +#define MOD_SETTINGS_FILE "mod.vdf" +#define MOD_BASE_DIRECTORY "mods" class CModAppSystemGroup; - class CModSystem { public: enum eModState : int8_t { UNLOADED = -1, // loading was unsuccessful (error occurred) - LOADING = 0, // if mod is being loaded - DISABLED = 1, // if disabled by user - ENABLED = 2, // if enabled by user and loaded properly + LOADING, // if mod is being loaded + LOADED, // if a mod has been loaded + DISABLED, // if disabled by user + ENABLED, // if enabled by user and loaded properly }; struct ModInstance_t { - ModInstance_t(const fs::path& basePath); + ModInstance_t(const CUtlString& basePath); + ~ModInstance_t(); + + bool ParseSettings(); + void ParseConVars(); + void ParseLocalizationFiles(); inline void SetState(eModState state) { m_iState = state; }; - inline bool IsEnabled() { return m_iState == eModState::ENABLED; }; + inline bool IsLoaded() const { return m_iState == eModState::LOADED; }; + inline bool IsEnabled() const { return m_iState == eModState::ENABLED; }; - inline const fs::path& GetBasePath() { return m_BasePath; }; + inline const CUtlString& GetBasePath() const { return m_BasePath; }; + inline CUtlString GetScriptCompileListPath() const { return m_BasePath + GAME_SCRIPT_COMPILELIST; }; - inline fs::path GetScriptCompileListPath() { return (m_BasePath / "scripts/vscripts/scripts.rson"); }; + KeyValues* GetRequiredSettingsKey(const char* settingsPath, const char* key) const; - RSON::Node_t* LoadScriptCompileList() + inline RSON::Node_t* LoadScriptCompileList() const { - return RSON::LoadFromFile(GetScriptCompileListPath().string().c_str()); + return RSON::LoadFromFile(GetScriptCompileListPath().Get(), "PLATFORM"); }; + KeyValues* m_SettingsKV; + eModState m_iState = eModState::UNLOADED; bool m_bHasScriptCompileList; // if this mod has a scripts.rson file that exists - string m_szName; // mod display name - string m_szModID; // internal mod identifier - fs::path m_BasePath; // path to folder containg all mod files - string m_szDescription; // mod description - string m_szVersion; // version string + CUtlVector m_LocalizationFiles; - KeyValues* m_SettingsKV; + CUtlString m_Name; + CUtlString m_ModID; + CUtlString m_Description; + CUtlString m_Version; - eModState m_iState = eModState::UNLOADED; - - std::vector m_vszLocalizationFiles; + CUtlString m_BasePath; }; + ~CModSystem(); + void Init(); // load mod enabled/disabled status from file on disk - void LoadModStatusList(); + void UpdateModStatusList(); + void LoadModStatusList(CUtlMap& enabledList); void WriteModStatusList(); - inline vector& GetModList() { return m_vModList; }; - inline std::map& GetEnabledList() { return m_vEnabledList; }; + const inline CUtlVector& GetModList() { return m_ModList; }; private: - vector m_vModList; - std::map m_vEnabledList; + CUtlVector m_ModList; }; + extern CModSystem* g_pModSystem; diff --git a/r5dev/vscript/languages/squirrel_re/include/sqvm.h b/r5dev/vscript/languages/squirrel_re/include/sqvm.h index dc835a12..9f35aa6d 100644 --- a/r5dev/vscript/languages/squirrel_re/include/sqvm.h +++ b/r5dev/vscript/languages/squirrel_re/include/sqvm.h @@ -35,7 +35,7 @@ struct SQVM #endif // !GAMEDLL_S0 && !GAMEDLL_S1 && !GAMEDLL_S2 } - eDLL_T GetNativePrintContext() const + eDLL_T GetNativeContext() const { return (eDLL_T)GetContext(); } diff --git a/r5dev/vscript/languages/squirrel_re/vsquirrel.cpp b/r5dev/vscript/languages/squirrel_re/vsquirrel.cpp index b754c852..ce7eebb0 100644 --- a/r5dev/vscript/languages/squirrel_re/vsquirrel.cpp +++ b/r5dev/vscript/languages/squirrel_re/vsquirrel.cpp @@ -122,18 +122,23 @@ void CSquirrelVM::SetAsCompiler(RSON::Node_t* rson) //--------------------------------------------------------------------------------- void CSquirrelVM::CompileModScripts() { - for (auto& mod : g_pModSystem->GetModList()) + FOR_EACH_VEC(g_pModSystem->GetModList(), i) { - if (!mod.IsEnabled()) + const CModSystem::ModInstance_t* mod = g_pModSystem->GetModList()[i]; + + if (!mod->IsEnabled()) continue; - if (!mod.m_bHasScriptCompileList) + if (!mod->m_bHasScriptCompileList) continue; - RSON::Node_t* rson = mod.LoadScriptCompileList(); // allocs parsed rson buffer + // allocs parsed rson buffer + RSON::Node_t* rson = mod->LoadScriptCompileList(); if (!rson) - Error(GetVM()->GetNativePrintContext(), EXIT_FAILURE, "%s: Failed to load RSON file %s\n", __FUNCTION__, mod.GetScriptCompileListPath().string().c_str()); + Error(GetVM()->GetNativeContext(), NO_ERROR, + "%s: Failed to load RSON file '%s'\n", + __FUNCTION__, mod->GetScriptCompileListPath().Get()); const char* scriptPathArray[MAX_PRECOMPILED_SCRIPTS]; int scriptCount = 0; @@ -142,25 +147,31 @@ void CSquirrelVM::CompileModScripts() if (Script_ParseScriptList( GetContext(), - mod.GetScriptCompileListPath().string().c_str(), + mod->GetScriptCompileListPath().Get(), rson, (char**)scriptPathArray, &scriptCount, nullptr, 0)) { std::vector newScriptPaths; - for (int i = 0; i < scriptCount; ++i) + for (int j = 0; j < scriptCount; ++j) { - // add "::MOD::" to the start of the script path so it can be identified from Script_LoadScript later - // this is so we can avoid script naming conflicts by removing the engine's forced directory of "scripts/vscripts/" - // and adding the mod path to the start - std::string scriptPath = MOD_SCRIPT_PATH_IDENTIFIER + (mod.GetBasePath() / "scripts/vscripts/" / scriptPathArray[i]).string(); - char* pszScriptPath = _strdup(scriptPath.c_str()); + // add "::MOD::" to the start of the script path so it can be + // identified from Script_LoadScript later, this is so we can + // avoid script naming conflicts by removing the engine's + // forced directory of "scripts/vscripts/" and adding the mod + // path to the start + CUtlString scriptPath; + scriptPath.Format("%s%s%s%s", + MOD_SCRIPT_PATH_IDENTIFIER, mod->GetBasePath().Get(), + GAME_SCRIPT_PATH, scriptPathArray[j]); + + char* pszScriptPath = _strdup(scriptPath.Get()); // normalise slash direction V_FixSlashes(pszScriptPath); newScriptPaths.emplace_back(pszScriptPath); - scriptPathArray[i] = pszScriptPath; + scriptPathArray[j] = pszScriptPath; } switch (GetVM()->GetContext()) @@ -185,7 +196,7 @@ void CSquirrelVM::CompileModScripts() // clean up our allocated script paths for (char* path : newScriptPaths) { - delete path; + free(path); } }