mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
- 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)).
347 lines
11 KiB
C++
347 lines
11 KiB
C++
//=============================================================================//
|
|
//
|
|
// Purpose: Manage loading mods
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "core/stdafx.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
|
|
// Input :
|
|
//-----------------------------------------------------------------------------
|
|
void CModSystem::Init()
|
|
{
|
|
if (!modsystem_enable->GetBool())
|
|
return;
|
|
|
|
// no mods installed, no point in initializing.
|
|
if (!FileSystem()->IsDirectory(MOD_BASE_DIRECTORY, "PLATFORM"))
|
|
return;
|
|
|
|
// 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);
|
|
|
|
CUtlVector<CUtlString> modFileList;
|
|
RecursiveFindFilesMatchingName(&modFileList,
|
|
MOD_BASE_DIRECTORY, MOD_SETTINGS_FILE, "PLATFORM", '/');
|
|
|
|
FOR_EACH_VEC(modFileList, i)
|
|
{
|
|
// 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());
|
|
|
|
if (!mod->IsLoaded())
|
|
{
|
|
delete mod;
|
|
continue;
|
|
}
|
|
|
|
m_ModList.AddToTail(mod);
|
|
}
|
|
|
|
UpdateModStatusList();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: loads, updates, enforces and writes the mod status list
|
|
//-----------------------------------------------------------------------------
|
|
void CModSystem::UpdateModStatusList()
|
|
{
|
|
CUtlMap<CUtlString, bool> 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();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: loads the mod status file from the disk
|
|
// Input : &enabledList -
|
|
//-----------------------------------------------------------------------------
|
|
void CModSystem::LoadModStatusList(CUtlMap<CUtlString, bool>& enabledList)
|
|
{
|
|
if (!FileSystem()->FileExists(MOD_STATUS_LIST_FILE, "PLATFORM"))
|
|
return;
|
|
|
|
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_EACH_VEC(m_ModList, i)
|
|
{
|
|
ModInstance_t* mod = m_ModList[i];
|
|
bool enabled = false;
|
|
|
|
if (mod->m_iState == eModState::ENABLED)
|
|
enabled = true;
|
|
|
|
pModListKV->SetBool(mod->m_ModID.Get(), enabled);
|
|
}
|
|
|
|
CUtlBuffer buf = CUtlBuffer(int64_t(0), 0, CUtlBuffer::TEXT_BUFFER);
|
|
kv.RecursiveSaveToFile(buf, 0);
|
|
|
|
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);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &basePath -
|
|
//-----------------------------------------------------------------------------
|
|
CModSystem::ModInstance_t::ModInstance_t(const CUtlString& basePath)
|
|
{
|
|
m_SettingsKV = nullptr;
|
|
m_bHasScriptCompileList = false;
|
|
|
|
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)
|
|
{
|
|
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 = GetRequiredSettingsKey(pSettingsPath, "name");
|
|
if (!pName)
|
|
return false;
|
|
|
|
m_Name = pName->GetString();
|
|
|
|
// "version" "1.0.0"
|
|
KeyValues* pVersion = GetRequiredSettingsKey(pSettingsPath, "version");
|
|
if (!pVersion)
|
|
return false;
|
|
|
|
m_Version = pVersion->GetString();
|
|
|
|
// "id" "r5reloaded.TestMod"
|
|
KeyValues* pId = GetRequiredSettingsKey(pSettingsPath, "id");
|
|
if (!pId)
|
|
return false;
|
|
|
|
m_ModID = pId->GetString();
|
|
|
|
// optional mod description field
|
|
m_Description = m_SettingsKV->GetString(pSettingsPath, "description");
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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())
|
|
{
|
|
const char* pszName = pSubKey->GetName();
|
|
const char* pszFlagsString = pSubKey->GetString("flags", "NONE");
|
|
const char* pszHelpString = pSubKey->GetString("helpText");
|
|
const char* pszUsageString = pSubKey->GetString("usageText");
|
|
|
|
KeyValues* pValues = pSubKey->FindKey("Values");
|
|
|
|
const char* pszDefaultValue = "0";
|
|
bool bMin = false;
|
|
bool bMax = false;
|
|
float fMin = 0.f;
|
|
float fMax = 0.f;
|
|
|
|
if (pValues)
|
|
{
|
|
pszDefaultValue = pValues->GetString("default", "0");
|
|
|
|
// minimum cvar value
|
|
if (pValues->FindKey("min"))
|
|
{
|
|
bMin = true; // has min value
|
|
fMin = pValues->GetFloat("min", 0.f);
|
|
}
|
|
|
|
// maximum cvar value
|
|
if (pValues->FindKey("max"))
|
|
{
|
|
bMax = true; // has max value
|
|
fMax = pValues->GetFloat("max", 1.f);
|
|
}
|
|
}
|
|
|
|
int flags = FCVAR_NONE;
|
|
|
|
if (ConVar_ParseFlagString(pszFlagsString, flags, pszName))
|
|
ConVar::StaticCreate(pszName, pszDefaultValue, flags,
|
|
pszHelpString, bMin, fMin, bMax, fMax, nullptr, pszUsageString);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: parses and stores localization file paths in a vector
|
|
//-----------------------------------------------------------------------------
|
|
void CModSystem::ModInstance_t::ParseLocalizationFiles()
|
|
{
|
|
Assert(m_SettingsKV);
|
|
|
|
KeyValues* pLocalizationFiles = m_SettingsKV->FindKey("LocalizationFiles");
|
|
|
|
if (pLocalizationFiles)
|
|
{
|
|
for (KeyValues* pSubKey = pLocalizationFiles->GetFirstSubKey();
|
|
pSubKey != nullptr; pSubKey = pSubKey->GetNextKey())
|
|
{
|
|
m_LocalizationFiles.AddToTail(m_BasePath + pSubKey->GetName());
|
|
}
|
|
}
|
|
}
|
|
|
|
CModSystem* g_pModSystem = new CModSystem();
|