mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
Fully implemented ConVar class so we could statically construct all SDK convars, this avoids a level of indirection, and allows for creating ConVar's everywhere in the project. This patch also removed the settings tab of the ImGui server browser, as it has threading issues, while it technically never caused a crash yet, it has been removed as there was no point keeping it vs the work required to make it thread save (it only managed 2 convars which are perfectly manageable through cfg's or the in-game console). Also temporarily disabled the creation of ConVar's in the mod system due to a memory leak, we would allocate and register a convar based on details parsed out of a mod file definition, but never unregister and free it.
353 lines
11 KiB
C++
353 lines
11 KiB
C++
//=============================================================================//
|
|
//
|
|
// Purpose: Manage loading mods
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "core/stdafx.h"
|
|
#include "tier0/commandline.h"
|
|
#include "tier1/cvar.h"
|
|
#include "tier2/fileutils.h"
|
|
#include "rtech/rson.h"
|
|
#include "localize/localize.h"
|
|
#include "modsystem.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Console variables
|
|
//-----------------------------------------------------------------------------
|
|
static ConVar modsystem_enable("modsystem_enable", "1", FCVAR_RELEASE, "Enable the modsystem");
|
|
static ConVar modsystem_debug("modsystem_debug", "0", FCVAR_RELEASE, "Debug the modsystem");
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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())
|
|
Msg(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())
|
|
Msg(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(ssize_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_ModSystem;
|