From a4e798b2778a8606f56cc78ce11a5889cf5a8b5a Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 10 Feb 2023 21:55:36 +0000 Subject: temp commit for switch --- NorthstarDLL/core/convar/concommand.h | 1 - NorthstarDLL/core/convar/convar.cpp | 2 ++ NorthstarDLL/mods/modmanager.cpp | 36 +++++++++++++++++++++++++++-------- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/NorthstarDLL/core/convar/concommand.h b/NorthstarDLL/core/convar/concommand.h index 89363bc7..c170c6a5 100644 --- a/NorthstarDLL/core/convar/concommand.h +++ b/NorthstarDLL/core/convar/concommand.h @@ -97,7 +97,6 @@ class ConCommandBase bool IsCommand(void) const; bool IsRegistered(void) const; bool IsFlagSet(int nFlags) const; - static bool IsFlagSet(ConCommandBase* pCommandBase, int nFlags); // For hooking to engine's implementation. int GetFlags(void) const; ConCommandBase* GetNext(void) const; diff --git a/NorthstarDLL/core/convar/convar.cpp b/NorthstarDLL/core/convar/convar.cpp index 11411c0a..81648f53 100644 --- a/NorthstarDLL/core/convar/convar.cpp +++ b/NorthstarDLL/core/convar/convar.cpp @@ -527,4 +527,6 @@ int ParseConVarFlagsString(std::string modName, std::string sFlags) sCurrentFlag += sFlags[i]; } } + + return FCVAR_NONE; } diff --git a/NorthstarDLL/mods/modmanager.cpp b/NorthstarDLL/mods/modmanager.cpp index 6310b7ec..891ab978 100644 --- a/NorthstarDLL/mods/modmanager.cpp +++ b/NorthstarDLL/mods/modmanager.cpp @@ -438,16 +438,37 @@ void ModManager::LoadMods() continue; // register convars - // for reloads, this is sorta barebones, when we have a good findconvar method, we could probably reset flags and stuff on - // preexisting convars note: we don't delete convars if they already exist because they're used for script stuff, unfortunately this - // causes us to leak memory on reload, but not much, potentially find a way to not do this at some point for (ModConVar* convar : mod.ConVars) { - // make sure convar isn't registered yet, unsure if necessary but idk what - // behaviour is for defining same convar multiple times - if (!R2::g_pCVar->FindVar(convar->Name.c_str())) - { + ConVar* pVar = R2::g_pCVar->FindVar(convar->Name.c_str()); + + // make sure convar isn't registered yet, if it is then modify its flags, helpstring etc + if (!pVar) new ConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.c_str()); + else + { + // TODO: should probably make sure this is actually a mod convar we're messing with + + pVar->m_ConCommandBase.m_nFlags = convar->Flags; + + // unfortunately this leaks memory and we can't really not leak memory because we don't know who allocated this + // so we can't delete it without risking a crash + if (convar->HelpString.compare(pVar->GetHelpText())) + { + int nHelpSize = convar->HelpString.size(); + char* pNewHelpString = new char[nHelpSize + 1]; + strncpy_s(pNewHelpString, nHelpSize + 1, convar->HelpString.c_str(), convar->HelpString.size()); + pVar->m_ConCommandBase.m_pszHelpString = pNewHelpString; + } + + if (convar->DefaultValue.compare(pVar->m_pszDefaultValue)) + { + int nDefaultValueSize = convar->DefaultValue.size(); + char* pNewDefaultValueString = new char[nDefaultValueSize + 1]; + strncpy_s(pNewDefaultValueString, nDefaultValueSize + 1, convar->DefaultValue.c_str(), convar->DefaultValue.size()); + pVar->m_pszDefaultValue = pNewDefaultValueString; + pVar->SetValue(pNewDefaultValueString); + } } } @@ -456,7 +477,6 @@ void ModManager::LoadMods() // make sure command isnt't registered multiple times. if (!R2::g_pCVar->FindCommand(command->Name.c_str())) { - ConCommand* newCommand = new ConCommand(); std::string funcName = command->Function; RegisterConCommand(command->Name.c_str(), ModConCommandCallback, command->HelpString.c_str(), command->Flags); } -- cgit v1.2.3 From c333ba84bee44549bb15675f353e04d22fb9f53c Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 Feb 2023 03:08:43 +0000 Subject: commit for merge --- NorthstarDLL/NorthstarDLL.vcxproj | 7 +- NorthstarDLL/NorthstarDLL.vcxproj.filters | 9 +- NorthstarDLL/client/audio.h | 2 - NorthstarDLL/client/clientvideooverrides.cpp | 10 +- NorthstarDLL/client/modlocalisation.cpp | 4 +- NorthstarDLL/core/filesystem/filesystem.cpp | 6 +- NorthstarDLL/core/filesystem/rpakfilesystem.cpp | 21 ++-- NorthstarDLL/logging/crashhandler.cpp | 4 +- NorthstarDLL/mods/compiled/kb_act.cpp | 8 +- NorthstarDLL/mods/compiled/modkeyvalues.cpp | 16 +-- NorthstarDLL/mods/compiled/modpdef.cpp | 119 --------------------- NorthstarDLL/mods/compiled/modscriptsrson.cpp | 8 +- NorthstarDLL/mods/modmanager.cpp | 132 +++++++++++------------- NorthstarDLL/mods/modmanager.h | 35 +++++-- NorthstarDLL/pch.h | 2 + NorthstarDLL/scripts/client/scriptmodmenu.cpp | 18 ++-- NorthstarDLL/squirrel/squirrel.cpp | 8 +- NorthstarDLL/squirrel/squirrel.h | 8 +- NorthstarDLL/util/printmaps.cpp | 2 +- 19 files changed, 154 insertions(+), 265 deletions(-) delete mode 100644 NorthstarDLL/mods/compiled/modpdef.cpp diff --git a/NorthstarDLL/NorthstarDLL.vcxproj b/NorthstarDLL/NorthstarDLL.vcxproj index ebbeb375..68b172fa 100644 --- a/NorthstarDLL/NorthstarDLL.vcxproj +++ b/NorthstarDLL/NorthstarDLL.vcxproj @@ -447,7 +447,7 @@ - + @@ -496,7 +496,6 @@ - @@ -538,7 +537,7 @@ - + @@ -547,4 +546,4 @@ - + \ No newline at end of file diff --git a/NorthstarDLL/NorthstarDLL.vcxproj.filters b/NorthstarDLL/NorthstarDLL.vcxproj.filters index d8437ba5..e149396f 100644 --- a/NorthstarDLL/NorthstarDLL.vcxproj.filters +++ b/NorthstarDLL/NorthstarDLL.vcxproj.filters @@ -1173,8 +1173,8 @@ Header Files\core - - Header Files + + Header Files Header Files\util @@ -1240,9 +1240,6 @@ Source Files\mods\compiled - - Source Files\mods\compiled - Source Files\mods\compiled @@ -1435,4 +1432,4 @@ Source Files - + \ No newline at end of file diff --git a/NorthstarDLL/client/audio.h b/NorthstarDLL/client/audio.h index 26cda205..915d52ce 100644 --- a/NorthstarDLL/client/audio.h +++ b/NorthstarDLL/client/audio.h @@ -1,7 +1,5 @@ #pragma once -#include -#include #include #include diff --git a/NorthstarDLL/client/clientvideooverrides.cpp b/NorthstarDLL/client/clientvideooverrides.cpp index 1a5924c7..ab78b943 100644 --- a/NorthstarDLL/client/clientvideooverrides.cpp +++ b/NorthstarDLL/client/clientvideooverrides.cpp @@ -12,20 +12,20 @@ void*, __fastcall, (const char* path, uint32_t flags)) spdlog::info("BinkOpen {}", filename); // figure out which mod is handling the bink - Mod* fileOwner = nullptr; - for (Mod& mod : g_pModManager->m_LoadedMods) + Mod* pFileOwner = nullptr; + for (Mod& mod : g_pModManager->GetMods()) { if (!mod.m_bEnabled) continue; if (std::find(mod.BinkVideos.begin(), mod.BinkVideos.end(), filename) != mod.BinkVideos.end()) - fileOwner = &mod; + pFileOwner = &mod; } - if (fileOwner) + if (pFileOwner) { // create new path - fs::path binkPath(fileOwner->m_ModDirectory / "media" / filename); + fs::path binkPath(pFileOwner->m_ModDirectory / "media" / filename); return BinkOpen(binkPath.string().c_str(), flags); } else diff --git a/NorthstarDLL/client/modlocalisation.cpp b/NorthstarDLL/client/modlocalisation.cpp index e5afc793..db138315 100644 --- a/NorthstarDLL/client/modlocalisation.cpp +++ b/NorthstarDLL/client/modlocalisation.cpp @@ -26,7 +26,7 @@ void, __fastcall, (void* pVguiLocalize)) // clang-format on { // load all mod localization manually, so we keep track of all files, not just previously loaded ones - for (Mod mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) if (mod.m_bEnabled) for (std::string& localisationFile : mod.LocalisationFiles) CLocalize__AddFile(g_pVguiLocalize, localisationFile.c_str(), nullptr, false); @@ -44,7 +44,7 @@ void, __fastcall, (void* self)) // previously we did this in CLocalize::AddFile, but for some reason it won't properly overwrite localization from // files loaded previously if done there, very weird but this works so whatever - for (Mod mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) if (mod.m_bEnabled) for (std::string& localisationFile : mod.LocalisationFiles) CLocalize__AddFile(g_pVguiLocalize, localisationFile.c_str(), nullptr, false); diff --git a/NorthstarDLL/core/filesystem/filesystem.cpp b/NorthstarDLL/core/filesystem/filesystem.cpp index ac42d00f..bfdfdb19 100644 --- a/NorthstarDLL/core/filesystem/filesystem.cpp +++ b/NorthstarDLL/core/filesystem/filesystem.cpp @@ -95,8 +95,8 @@ bool TryReplaceFile(const char* pPath, bool shouldCompile) // idk how efficient the lexically normal check is // can't just set all /s in path to \, since some paths aren't in writeable memory - auto file = g_pModManager->m_ModFiles.find(g_pModManager->NormaliseModFilePath(fs::path(pPath))); - if (file != g_pModManager->m_ModFiles.end()) + auto file = g_pModManager->GetModFiles().find(g_pModManager->NormaliseModFilePath(fs::path(pPath))); + if (file != g_pModManager->GetModFiles().end()) { SetNewModSearchPaths(file->second.m_pOwningMod); return true; @@ -147,7 +147,7 @@ HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char* NS::log::fs->info("MountVPK {}", pVpkPath); VPKData* ret = MountVPK(fileSystem, pVpkPath); - for (Mod mod : g_pModManager->m_LoadedMods) + for (Mod mod : g_pModManager->GetMods()) { if (!mod.m_bEnabled) continue; diff --git a/NorthstarDLL/core/filesystem/rpakfilesystem.cpp b/NorthstarDLL/core/filesystem/rpakfilesystem.cpp index b46218e0..4284d5b7 100644 --- a/NorthstarDLL/core/filesystem/rpakfilesystem.cpp +++ b/NorthstarDLL/core/filesystem/rpakfilesystem.cpp @@ -90,15 +90,14 @@ void* PakLoadManager::LoadFile(const char* path) void HandlePakAliases(char** map) { // convert the pak being loaded to it's aliased one, e.g. aliasing mp_hub_timeshift => sp_hub_timeshift - for (int64_t i = g_pModManager->m_LoadedMods.size() - 1; i > -1; i--) + for (Mod& mod : g_pModManager->GetMods() | std::views::reverse) { - Mod* mod = &g_pModManager->m_LoadedMods[i]; - if (!mod->m_bEnabled) + if (!mod.m_bEnabled) continue; - if (mod->RpakAliases.find(*map) != mod->RpakAliases.end()) + if (mod.RpakAliases.find(*map) != mod.RpakAliases.end()) { - *map = &mod->RpakAliases[*map][0]; + *map = &mod.RpakAliases[*map][0]; return; } } @@ -107,7 +106,7 @@ void HandlePakAliases(char** map) void LoadPreloadPaks() { // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks - for (Mod& mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { if (!mod.m_bEnabled) continue; @@ -124,7 +123,7 @@ void LoadPreloadPaks() void LoadPostloadPaks(const char* pPath) { // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks - for (Mod& mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { if (!mod.m_bEnabled) continue; @@ -144,7 +143,7 @@ void LoadCustomMapPaks(char** pakName, bool* bNeedToFreePakName) bool bHasOriginalPak = fs::exists(fs::path("r2/paks/Win64/") / *pakName); // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks - for (Mod& mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { if (!mod.m_bEnabled) continue; @@ -270,8 +269,8 @@ void*, __fastcall, (const char* pPath, void* pCallback)) NS::log::rpak->info("LoadStreamBsp: {}", filename.string()); // resolve modded stbsp path so we can load mod stbsps - auto modFile = g_pModManager->m_ModFiles.find(g_pModManager->NormaliseModFilePath(fs::path("maps" / filename))); - if (modFile != g_pModManager->m_ModFiles.end()) + auto modFile = g_pModManager->GetModFiles().find(g_pModManager->NormaliseModFilePath(fs::path("maps" / filename))); + if (modFile != g_pModManager->GetModFiles().end()) { newPath = (modFile->second.m_pOwningMod->m_ModDirectory / "mod" / modFile->second.m_Path).string(); pPath = newPath.c_str(); @@ -299,7 +298,7 @@ void*, __fastcall, (const char* pPath, void* pCallback)) size_t hashed = STR_HASH(starpakPath); // loop through all loaded mods - for (Mod& mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { // ignore non-loaded mods if (!mod.m_bEnabled) diff --git a/NorthstarDLL/logging/crashhandler.cpp b/NorthstarDLL/logging/crashhandler.cpp index d4a54169..84f8ac99 100644 --- a/NorthstarDLL/logging/crashhandler.cpp +++ b/NorthstarDLL/logging/crashhandler.cpp @@ -34,12 +34,10 @@ void PrintExceptionLog(ExceptionLog& exc) if (g_pModManager) { spdlog::error("Loaded mods: "); - for (const auto& mod : g_pModManager->m_LoadedMods) + for (const Mod& mod : g_pModManager->GetMods()) { if (mod.m_bEnabled) - { spdlog::error("{} {}", mod.Name, mod.Version); - } } } spdlog::error(exc.cause); diff --git a/NorthstarDLL/mods/compiled/kb_act.cpp b/NorthstarDLL/mods/compiled/kb_act.cpp index 4a011dc7..f3ad460f 100644 --- a/NorthstarDLL/mods/compiled/kb_act.cpp +++ b/NorthstarDLL/mods/compiled/kb_act.cpp @@ -17,7 +17,7 @@ void ModManager::BuildKBActionsList() // write vanilla file's content to compiled file soCompiledKeys << R2::ReadVPKOriginalFile(KB_ACT_PATH); - for (Mod& mod : m_LoadedMods) + for (Mod& mod : GetMods()) { if (!mod.m_bEnabled) continue; @@ -38,8 +38,8 @@ void ModManager::BuildKBActionsList() overrideFile.m_pOwningMod = nullptr; overrideFile.m_Path = KB_ACT_PATH; - if (m_ModFiles.find(KB_ACT_PATH) == m_ModFiles.end()) - m_ModFiles.insert(std::make_pair(KB_ACT_PATH, overrideFile)); + if (GetModFiles().find(KB_ACT_PATH) == GetModFiles().end()) + GetModFiles().insert(std::make_pair(KB_ACT_PATH, overrideFile)); else - m_ModFiles[KB_ACT_PATH] = overrideFile; + GetModFiles()[KB_ACT_PATH] = overrideFile; } diff --git a/NorthstarDLL/mods/compiled/modkeyvalues.cpp b/NorthstarDLL/mods/compiled/modkeyvalues.cpp index 774be0eb..051b193e 100644 --- a/NorthstarDLL/mods/compiled/modkeyvalues.cpp +++ b/NorthstarDLL/mods/compiled/modkeyvalues.cpp @@ -25,14 +25,14 @@ void ModManager::TryBuildKeyValues(const char* filename) // copy over patch kv files, and add #bases to new file, last mods' patches should be applied first // note: #include should be identical but it's actually just broken, thanks respawn - for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--) + for (Mod& mod : GetMods() | std::views::reverse) { - if (!m_LoadedMods[i].m_bEnabled) + if (mod.m_bEnabled) continue; size_t fileHash = STR_HASH(normalisedPath); - auto modKv = m_LoadedMods[i].KeyValues.find(fileHash); - if (modKv != m_LoadedMods[i].KeyValues.end()) + auto modKv = mod.KeyValues.find(fileHash); + if (modKv != mod.KeyValues.end()) { // should result in smth along the lines of #include "mod_patch_5_mp_weapon_car.txt" @@ -47,7 +47,7 @@ void ModManager::TryBuildKeyValues(const char* filename) fs::remove(compiledDir / patchFilePath); - fs::copy_file(m_LoadedMods[i].m_ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath); + fs::copy_file(mod.m_ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath); } } @@ -100,8 +100,8 @@ void ModManager::TryBuildKeyValues(const char* filename) overrideFile.m_pOwningMod = nullptr; overrideFile.m_Path = normalisedPath; - if (m_ModFiles.find(normalisedPath) == m_ModFiles.end()) - m_ModFiles.insert(std::make_pair(normalisedPath, overrideFile)); + if (GetModFiles().find(normalisedPath) == GetModFiles().end()) + GetModFiles().insert(std::make_pair(normalisedPath, overrideFile)); else - m_ModFiles[normalisedPath] = overrideFile; + GetModFiles()[normalisedPath] = overrideFile; } diff --git a/NorthstarDLL/mods/compiled/modpdef.cpp b/NorthstarDLL/mods/compiled/modpdef.cpp deleted file mode 100644 index 219c744b..00000000 --- a/NorthstarDLL/mods/compiled/modpdef.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "pch.h" -#include "mods/modmanager.h" -#include "core/filesystem/filesystem.h" - -#include -#include -#include - -const fs::path MOD_PDEF_SUFFIX = "cfg/server/persistent_player_data_version_231.pdef"; -const char* VPK_PDEF_PATH = "cfg/server/persistent_player_data_version_231.pdef"; - -void ModManager::BuildPdef() -{ - spdlog::info("Building persistent_player_data_version_231.pdef..."); - - fs::path MOD_PDEF_PATH = fs::path(GetCompiledAssetsPath() / MOD_PDEF_SUFFIX); - - fs::remove(MOD_PDEF_PATH); - std::string pdef = R2::ReadVPKOriginalFile(VPK_PDEF_PATH); - - for (Mod& mod : m_LoadedMods) - { - if (!mod.m_bEnabled || !mod.Pdiff.size()) - continue; - - // this code probably isn't going to be pretty lol - // refer to shared/pjson.js for an actual okish parser of the pdiff format - // but pretty much, $ENUM_ADD blocks define members added to preexisting enums - // $PROP_START ends the custom stuff, and from there it's just normal props we append to the pdef - - std::map> enumAdds; - - // read pdiff - bool inEnum = false; - bool inProp = false; - std::string currentEnum; - std::string currentLine; - std::istringstream pdiffStream(mod.Pdiff); - - while (std::getline(pdiffStream, currentLine)) - { - if (inProp) - { - // just append to pdef here - pdef += currentLine; - pdef += '\n'; - continue; - } - - // trim leading whitespace - size_t start = currentLine.find_first_not_of(" \n\r\t\f\v"); - size_t end = currentLine.find("//"); - if (end == std::string::npos) - end = currentLine.size() - 1; // last char - - if (!currentLine.size() || !currentLine.compare(start, 2, "//")) - continue; - - if (inEnum) - { - if (!currentLine.compare(start, 9, "$ENUM_END")) - inEnum = false; - else - enumAdds[currentEnum].push_back(currentLine); // only need to push_back current line, if there's syntax errors then game - // pdef parser will handle them - } - else if (!currentLine.compare(start, 9, "$ENUM_ADD")) - { - inEnum = true; - currentEnum = currentLine.substr(start + 10 /*$ENUM_ADD + 1*/, currentLine.size() - end - (start + 10)); - enumAdds.insert(std::make_pair(currentEnum, std::vector())); - } - else if (!currentLine.compare(start, 11, "$PROP_START")) - { - inProp = true; - pdef += "\n// $PROP_START "; - pdef += mod.Name; - pdef += "\n"; - } - } - - // add new members to preexisting enums - // note: this code could 100% be messed up if people put //$ENUM_START comments and the like - // could make it protect against this, but honestly not worth atm - for (auto enumAdd : enumAdds) - { - std::string addStr; - for (std::string enumMember : enumAdd.second) - { - addStr += enumMember; - addStr += '\n'; - } - - // start of enum we're adding to - std::string startStr = "$ENUM_START "; - startStr += enumAdd.first; - - // insert enum values into enum - size_t insertIdx = pdef.find("$ENUM_END", pdef.find(startStr)); - pdef.reserve(addStr.size()); - pdef.insert(insertIdx, addStr); - } - } - - fs::create_directories(MOD_PDEF_PATH.parent_path()); - - std::ofstream writeStream(MOD_PDEF_PATH, std::ios::binary); - writeStream << pdef; - writeStream.close(); - - ModOverrideFile overrideFile; - overrideFile.m_pOwningMod = nullptr; - overrideFile.m_Path = VPK_PDEF_PATH; - - if (m_ModFiles.find(VPK_PDEF_PATH) == m_ModFiles.end()) - m_ModFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile)); - else - m_ModFiles[VPK_PDEF_PATH] = overrideFile; -} diff --git a/NorthstarDLL/mods/compiled/modscriptsrson.cpp b/NorthstarDLL/mods/compiled/modscriptsrson.cpp index 15fcdd13..b24aabe9 100644 --- a/NorthstarDLL/mods/compiled/modscriptsrson.cpp +++ b/NorthstarDLL/mods/compiled/modscriptsrson.cpp @@ -17,7 +17,7 @@ void ModManager::BuildScriptsRson() std::string scriptsRson = R2::ReadVPKOriginalFile(VPK_SCRIPTS_RSON_PATH); scriptsRson += "\n\n// START MODDED SCRIPT CONTENT\n\n"; // newline before we start custom stuff - for (Mod& mod : m_LoadedMods) + for (Mod& mod : GetMods()) { if (!mod.m_bEnabled) continue; @@ -56,10 +56,10 @@ void ModManager::BuildScriptsRson() overrideFile.m_pOwningMod = nullptr; overrideFile.m_Path = VPK_SCRIPTS_RSON_PATH; - if (m_ModFiles.find(VPK_SCRIPTS_RSON_PATH) == m_ModFiles.end()) - m_ModFiles.insert(std::make_pair(VPK_SCRIPTS_RSON_PATH, overrideFile)); + if (GetModFiles().find(VPK_SCRIPTS_RSON_PATH) == GetModFiles().end()) + GetModFiles().insert(std::make_pair(VPK_SCRIPTS_RSON_PATH, overrideFile)); else - m_ModFiles[VPK_SCRIPTS_RSON_PATH] = overrideFile; + GetModFiles()[VPK_SCRIPTS_RSON_PATH] = overrideFile; // todo: for preventing dupe scripts in scripts.rson, we could actually parse when conditions with the squirrel vm, just need a way to // get a result out of squirrelmanager.ExecuteCode this would probably be the best way to do this, imo diff --git a/NorthstarDLL/mods/modmanager.cpp b/NorthstarDLL/mods/modmanager.cpp index 891ab978..754f648b 100644 --- a/NorthstarDLL/mods/modmanager.cpp +++ b/NorthstarDLL/mods/modmanager.cpp @@ -98,27 +98,27 @@ Mod::Mod(fs::path modDir, char* jsonBuf) // have to allocate this manually, otherwise convar registration will break // unfortunately this causes us to leak memory on reload, unsure of a way around this rn - ModConVar* convar = new ModConVar; - convar->Name = convarObj["Name"].GetString(); - convar->DefaultValue = convarObj["DefaultValue"].GetString(); + ModConVar convar; + convar.Name = convarObj["Name"].GetString(); + convar.DefaultValue = convarObj["DefaultValue"].GetString(); if (convarObj.HasMember("HelpString")) - convar->HelpString = convarObj["HelpString"].GetString(); + convar.HelpString = convarObj["HelpString"].GetString(); else - convar->HelpString = ""; + convar.HelpString = ""; - convar->Flags = FCVAR_NONE; + convar.Flags = FCVAR_NONE; if (convarObj.HasMember("Flags")) { // read raw integer flags if (convarObj["Flags"].IsInt()) - convar->Flags = convarObj["Flags"].GetInt(); + convar.Flags = convarObj["Flags"].GetInt(); else if (convarObj["Flags"].IsString()) { // parse cvar flags from string // example string: ARCHIVE_PLAYERPROFILE | GAMEDLL - convar->Flags |= ParseConVarFlagsString(convar->Name, convarObj["Flags"].GetString()); + convar.Flags |= ParseConVarFlagsString(convar.Name, convarObj["Flags"].GetString()); } } @@ -126,6 +126,7 @@ Mod::Mod(fs::path modDir, char* jsonBuf) } } + // mod commands if (modJson.HasMember("ConCommands") && modJson["ConCommands"].IsArray()) { for (auto& concommandObj : modJson["ConCommands"].GetArray()) @@ -138,35 +139,35 @@ Mod::Mod(fs::path modDir, char* jsonBuf) // have to allocate this manually, otherwise concommand registration will break // unfortunately this causes us to leak memory on reload, unsure of a way around this rn - ModConCommand* concommand = new ModConCommand; - concommand->Name = concommandObj["Name"].GetString(); - concommand->Function = concommandObj["Function"].GetString(); - concommand->Context = ScriptContextFromString(concommandObj["Context"].GetString()); - if (concommand->Context == ScriptContext::INVALID) + ModConCommand concommand; + concommand.Name = concommandObj["Name"].GetString(); + concommand.Function = concommandObj["Function"].GetString(); + concommand.Context = ScriptContextFromString(concommandObj["Context"].GetString()); + if (concommand.Context == ScriptContext::INVALID) { - spdlog::warn("Mod ConCommand {} has invalid context {}", concommand->Name, concommandObj["Context"].GetString()); + spdlog::warn("Mod ConCommand {} has invalid context {}", concommand.Name, concommandObj["Context"].GetString()); continue; } if (concommandObj.HasMember("HelpString")) - concommand->HelpString = concommandObj["HelpString"].GetString(); + concommand.HelpString = concommandObj["HelpString"].GetString(); else - concommand->HelpString = ""; + concommand.HelpString = ""; - concommand->Flags = FCVAR_NONE; + concommand.Flags = FCVAR_NONE; if (concommandObj.HasMember("Flags")) { // read raw integer flags if (concommandObj["Flags"].IsInt()) { - concommand->Flags = concommandObj["Flags"].GetInt(); + concommand.Flags = concommandObj["Flags"].GetInt(); } else if (concommandObj["Flags"].IsString()) { // parse cvar flags from string // example string: ARCHIVE_PLAYERPROFILE | GAMEDLL - concommand->Flags |= ParseConVarFlagsString(concommand->Name, concommandObj["Flags"].GetString()); + concommand.Flags |= ParseConVarFlagsString(concommand.Name, concommandObj["Flags"].GetString()); } } @@ -280,66 +281,54 @@ ModManager::ModManager() LoadMods(); } -struct Test -{ - std::string funcName; - ScriptContext context; -}; - template auto ModConCommandCallback_Internal(std::string name, const CCommand& command) { - if (g_pSquirrel->m_pSQVM && g_pSquirrel->m_pSQVM) + if (g_pSquirrel->m_pSQVM && g_pSquirrel->m_pSQVM->sqvm) { - std::vector args; - args.reserve(command.ArgC()); + std::vector vArgs; + vArgs.reserve(command.ArgC()); for (int i = 1; i < command.ArgC(); i++) - args.push_back(command.Arg(i)); - g_pSquirrel->AsyncCall(name, args); + vArgs.push_back(command.Arg(i)); + + g_pSquirrel->AsyncCall(name, vArgs); } else - { - spdlog::warn("ConCommand `{}` was called while the associated Squirrel VM `{}` was unloaded", name, GetContextName(context)); - } + spdlog::warn("ConCommand \"{}\" was called while the associated Squirrel VM \"{}\" was unloaded", name, GetContextName(context)); } auto ModConCommandCallback(const CCommand& command) { - ModConCommand* found = nullptr; - auto commandString = std::string(command.GetCommandString()); - - // Finding the first space to remove the command's name - auto firstSpace = commandString.find(' '); - if (firstSpace) - { - commandString = commandString.substr(0, firstSpace); - } + ModConCommand* pFoundCommand = nullptr; + std::string sCommandName = command.Arg(0); // Find the mod this command belongs to - for (auto& mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { auto res = std::find_if( mod.ConCommands.begin(), mod.ConCommands.end(), - [&commandString](const ModConCommand* other) { return other->Name == commandString; }); + [&sCommandName](const ModConCommand* other) { return other->Name == sCommandName; }); + if (res != mod.ConCommands.end()) { - found = *res; + pFoundCommand = &*res; break; } } - if (!found) + + if (!pFoundCommand) return; - switch (found->Context) + switch (pFoundCommand->Context) { case ScriptContext::CLIENT: - ModConCommandCallback_Internal(found->Function, command); + ModConCommandCallback_Internal(pFoundCommand->Function, command); break; case ScriptContext::SERVER: - ModConCommandCallback_Internal(found->Function, command); + ModConCommandCallback_Internal(pFoundCommand->Function, command); break; case ScriptContext::UI: - ModConCommandCallback_Internal(found->Function, command); + ModConCommandCallback_Internal(pFoundCommand->Function, command); break; }; } @@ -430,7 +419,10 @@ void ModManager::LoadMods() } // sort by load prio, lowest-highest - std::sort(m_LoadedMods.begin(), m_LoadedMods.end(), [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; }); + std::sort( + m_ModLoadState.m_LoadedMods.begin(), + m_ModLoadState.m_LoadedMods.end(), + [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; }); for (Mod& mod : m_LoadedMods) { @@ -438,47 +430,47 @@ void ModManager::LoadMods() continue; // register convars - for (ModConVar* convar : mod.ConVars) + for (ModConVar convar : mod.ConVars) { - ConVar* pVar = R2::g_pCVar->FindVar(convar->Name.c_str()); + ConVar* pVar = R2::g_pCVar->FindVar(convar.Name.c_str()); // make sure convar isn't registered yet, if it is then modify its flags, helpstring etc if (!pVar) - new ConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.c_str()); + new ConVar(convar.Name.c_str(), convar.DefaultValue.c_str(), convar.Flags, convar.HelpString.c_str()); else { // TODO: should probably make sure this is actually a mod convar we're messing with - pVar->m_ConCommandBase.m_nFlags = convar->Flags; + pVar->m_ConCommandBase.m_nFlags = convar.Flags; // unfortunately this leaks memory and we can't really not leak memory because we don't know who allocated this // so we can't delete it without risking a crash - if (convar->HelpString.compare(pVar->GetHelpText())) + if (convar.HelpString.compare(pVar->GetHelpText())) { - int nHelpSize = convar->HelpString.size(); + int nHelpSize = convar.HelpString.size(); char* pNewHelpString = new char[nHelpSize + 1]; - strncpy_s(pNewHelpString, nHelpSize + 1, convar->HelpString.c_str(), convar->HelpString.size()); + strncpy_s(pNewHelpString, nHelpSize + 1, convar.HelpString.c_str(), convar.HelpString.size()); pVar->m_ConCommandBase.m_pszHelpString = pNewHelpString; } - if (convar->DefaultValue.compare(pVar->m_pszDefaultValue)) + if (convar.DefaultValue.compare(pVar->m_pszDefaultValue)) { - int nDefaultValueSize = convar->DefaultValue.size(); + int nDefaultValueSize = convar.DefaultValue.size(); char* pNewDefaultValueString = new char[nDefaultValueSize + 1]; - strncpy_s(pNewDefaultValueString, nDefaultValueSize + 1, convar->DefaultValue.c_str(), convar->DefaultValue.size()); + strncpy_s(pNewDefaultValueString, nDefaultValueSize + 1, convar.DefaultValue.c_str(), convar.DefaultValue.size()); pVar->m_pszDefaultValue = pNewDefaultValueString; pVar->SetValue(pNewDefaultValueString); } } } - for (ModConCommand* command : mod.ConCommands) + for (ModConCommand command : mod.ConCommands) { // make sure command isnt't registered multiple times. - if (!R2::g_pCVar->FindCommand(command->Name.c_str())) + if (!R2::g_pCVar->FindCommand(command.Name.c_str())) { - std::string funcName = command->Function; - RegisterConCommand(command->Name.c_str(), ModConCommandCallback, command->HelpString.c_str(), command->Flags); + std::string funcName = command.Function; + RegisterConCommand(command.Name.c_str(), ModConCommandCallback, command.HelpString.c_str(), command.Flags); } } @@ -741,6 +733,8 @@ void ModManager::LoadMods() void ModManager::UnloadMods() { + m_LastModLoadState = m_ModLoadState; + // clean up stuff from mods before we unload m_ModFiles.clear(); fs::remove_all(GetCompiledAssetsPath()); @@ -756,8 +750,6 @@ void ModManager::UnloadMods() for (std::pair kvPaths : mod.KeyValues) fs::remove(GetCompiledAssetsPath() / fs::path(kvPaths.second).lexically_relative(mod.m_ModDirectory)); - mod.KeyValues.clear(); - // write to m_enabledModsCfg // should we be doing this here or should scripts be doing this manually? // main issue with doing this here is when we reload mods for connecting to a server, we write enabled mods, which isn't necessarily @@ -796,13 +788,15 @@ void ModManager::CompileAssetsForFile(const char* filename) if (fileHash == m_hScriptsRsonHash) BuildScriptsRson(); else if (fileHash == m_hPdefHash) - BuildPdef(); + { + // BuildPdef(); todo + } else if (fileHash == m_hKBActHash) BuildKBActionsList(); else { // check if we should build keyvalues, depending on whether any of our mods have patch kvs for this file - for (Mod& mod : m_LoadedMods) + for (Mod& mod : GetMods()) { if (!mod.m_bEnabled) continue; diff --git a/NorthstarDLL/mods/modmanager.h b/NorthstarDLL/mods/modmanager.h index ded6ff06..57ae8581 100644 --- a/NorthstarDLL/mods/modmanager.h +++ b/NorthstarDLL/mods/modmanager.h @@ -96,9 +96,9 @@ class Mod // custom scripts used by the mod std::vector Scripts; // convars created by the mod - std::vector ConVars; + std::vector ConVars; // concommands created by the mod - std::vector ConCommands; + std::vector ConCommands; // custom localisation files created by the mod std::vector LocalisationFiles; @@ -125,10 +125,18 @@ class Mod struct ModOverrideFile { public: - Mod* m_pOwningMod; + Mod* m_pOwningMod; // don't need to explicitly clean this up fs::path m_Path; }; +class ModLoadState +{ + public: + std::vector m_LoadedMods; + std::unordered_map m_ModFiles; + std::unordered_map m_DependencyConstants; +}; + class ModManager { private: @@ -142,9 +150,8 @@ class ModManager size_t m_hKBActHash; public: - std::vector m_LoadedMods; - std::unordered_map m_ModFiles; - std::unordered_map m_DependencyConstants; + ModLoadState m_LastModLoadState; + ModLoadState m_ModLoadState; public: ModManager(); @@ -153,6 +160,22 @@ class ModManager std::string NormaliseModFilePath(const fs::path path); void CompileAssetsForFile(const char* filename); + // getters + inline std::vector& GetMods() + { + return m_ModLoadState.m_LoadedMods; + }; + + inline std::unordered_map& GetModFiles() + { + return m_ModLoadState.m_ModFiles; + }; + + inline std::unordered_map& GetDependencyConstants() + { + return m_ModLoadState.m_DependencyConstants; + }; + // compile asset type stuff, these are done in files under runtime/compiled/ void BuildScriptsRson(); void TryBuildKeyValues(const char* filename); diff --git a/NorthstarDLL/pch.h b/NorthstarDLL/pch.h index 55ebba8b..433030b6 100644 --- a/NorthstarDLL/pch.h +++ b/NorthstarDLL/pch.h @@ -17,6 +17,8 @@ #include #include #include +#include +#include namespace fs = std::filesystem; diff --git a/NorthstarDLL/scripts/client/scriptmodmenu.cpp b/NorthstarDLL/scripts/client/scriptmodmenu.cpp index 75d05acc..0beea4f2 100644 --- a/NorthstarDLL/scripts/client/scriptmodmenu.cpp +++ b/NorthstarDLL/scripts/client/scriptmodmenu.cpp @@ -6,7 +6,7 @@ ADD_SQFUNC("array", NSGetModNames, "", "", ScriptContext::SERVER | Scrip { g_pSquirrel->newarray(sqvm, 0); - for (Mod& mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { g_pSquirrel->pushstring(sqvm, mod.Name.c_str()); g_pSquirrel->arrayappend(sqvm, -2); @@ -20,7 +20,7 @@ ADD_SQFUNC("bool", NSIsModEnabled, "string modName", "", ScriptContext::SERVER | const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { if (!mod.Name.compare(modName)) { @@ -38,7 +38,7 @@ ADD_SQFUNC("void", NSSetModEnabled, "string modName, bool enabled", "", ScriptCo const SQBool enabled = g_pSquirrel->getbool(sqvm, 2); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { if (!mod.Name.compare(modName)) { @@ -55,7 +55,7 @@ ADD_SQFUNC("string", NSGetModDescriptionByModName, "string modName", "", ScriptC const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { if (!mod.Name.compare(modName)) { @@ -72,7 +72,7 @@ ADD_SQFUNC("string", NSGetModVersionByModName, "string modName", "", ScriptConte const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { if (!mod.Name.compare(modName)) { @@ -89,7 +89,7 @@ ADD_SQFUNC("string", NSGetModDownloadLinkByModName, "string modName", "", Script const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { if (!mod.Name.compare(modName)) { @@ -106,7 +106,7 @@ ADD_SQFUNC("int", NSGetModLoadPriority, "string modName", "", ScriptContext::SER const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { if (!mod.Name.compare(modName)) { @@ -123,7 +123,7 @@ ADD_SQFUNC("bool", NSIsModRequiredOnClient, "string modName", "", ScriptContext: const SQChar* modName = g_pSquirrel->getstring(sqvm, 1); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { if (!mod.Name.compare(modName)) { @@ -142,7 +142,7 @@ ADD_SQFUNC( g_pSquirrel->newarray(sqvm, 0); // manual lookup, not super performant but eh not a big deal - for (Mod& mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { if (!mod.Name.compare(modName)) { diff --git a/NorthstarDLL/squirrel/squirrel.cpp b/NorthstarDLL/squirrel/squirrel.cpp index 4771bf3f..60d0eab3 100644 --- a/NorthstarDLL/squirrel/squirrel.cpp +++ b/NorthstarDLL/squirrel/squirrel.cpp @@ -189,11 +189,11 @@ template void SquirrelManager::VMCreated(CSquir RegisterSquirrelFunc(m_pSQVM, funcReg, 1); } - for (auto& pair : g_pModManager->m_DependencyConstants) + for (auto& pair : g_pModManager->GetDependencyConstants()) { bool bWasFound = false; - for (Mod& dependency : g_pModManager->m_LoadedMods) + for (Mod& dependency : g_pModManager->GetMods()) { if (!dependency.m_bEnabled) continue; @@ -444,7 +444,7 @@ template bool __fastcall CallScriptInitCallbackHook(void if (bShouldCallCustomCallbacks) { - for (Mod mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { if (!mod.m_bEnabled) continue; @@ -471,7 +471,7 @@ template bool __fastcall CallScriptInitCallbackHook(void // run after callbacks if (bShouldCallCustomCallbacks) { - for (Mod mod : g_pModManager->m_LoadedMods) + for (Mod& mod : g_pModManager->GetMods()) { if (!mod.m_bEnabled) continue; diff --git a/NorthstarDLL/squirrel/squirrel.h b/NorthstarDLL/squirrel/squirrel.h index ce758d7c..65bda7e3 100644 --- a/NorthstarDLL/squirrel/squirrel.h +++ b/NorthstarDLL/squirrel/squirrel.h @@ -239,17 +239,15 @@ class SquirrelManagerBase { SQStackInfos stackInfo {}; if (1 + depth >= sqvm->_callstacksize) - { return nullptr; - } + sq_stackinfos(sqvm, 1 + depth, stackInfo); std::string sourceName = stackInfo._sourceName; std::replace(sourceName.begin(), sourceName.end(), '/', '\\'); std::string filename = "scripts\\vscripts\\" + sourceName; - if (auto res = g_pModManager->m_ModFiles.find(filename); res != g_pModManager->m_ModFiles.end()) - { + if (auto res = g_pModManager->GetModFiles().find(filename); res != g_pModManager->GetModFiles().end()) return res->second.m_pOwningMod; - } + return nullptr; } template inline SQRESULT getuserdata(HSquirrelVM* sqvm, const SQInteger stackpos, T* data, uint64_t* typeId) diff --git a/NorthstarDLL/util/printmaps.cpp b/NorthstarDLL/util/printmaps.cpp index e0192d69..0b4593de 100644 --- a/NorthstarDLL/util/printmaps.cpp +++ b/NorthstarDLL/util/printmaps.cpp @@ -37,7 +37,7 @@ void RefreshMapList() // get modded maps // TODO: could probably check mod vpks to get mapnames from there too? - for (auto& modFilePair : g_pModManager->m_ModFiles) + for (auto& modFilePair : g_pModManager->GetModFiles()) { ModOverrideFile file = modFilePair.second; if (file.m_Path.extension() == ".bsp" && file.m_Path.parent_path().string() == "maps") // only allow mod maps actually in /maps atm -- cgit v1.2.3 From 56372efa9706898c8646376182b544bc6350c5c7 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 13 Feb 2023 13:51:40 +0000 Subject: refactor modloading and implement initial automatic reload code --- NorthstarDLL/client/clientvideooverrides.cpp | 5 +- NorthstarDLL/client/modlocalisation.cpp | 14 +- NorthstarDLL/config/profile.cpp | 4 +- NorthstarDLL/config/profile.h | 4 +- NorthstarDLL/core/convar/concommand.cpp | 8 +- NorthstarDLL/core/convar/concommand.h | 4 +- NorthstarDLL/core/filesystem/filesystem.cpp | 5 +- NorthstarDLL/core/filesystem/rpakfilesystem.cpp | 15 +- NorthstarDLL/dllmain.cpp | 2 +- NorthstarDLL/logging/crashhandler.cpp | 11 +- NorthstarDLL/logging/logging.cpp | 2 +- NorthstarDLL/mods/compiled/kb_act.cpp | 5 +- NorthstarDLL/mods/compiled/modkeyvalues.cpp | 5 +- NorthstarDLL/mods/compiled/modscriptsrson.cpp | 5 +- NorthstarDLL/mods/modmanager.cpp | 894 ++++++++++++++++-------- NorthstarDLL/mods/modmanager.h | 106 ++- NorthstarDLL/scripts/client/scriptmodmenu.cpp | 4 +- NorthstarDLL/server/auth/bansystem.cpp | 16 +- NorthstarDLL/squirrel/squirrel.cpp | 18 +- 19 files changed, 715 insertions(+), 412 deletions(-) diff --git a/NorthstarDLL/client/clientvideooverrides.cpp b/NorthstarDLL/client/clientvideooverrides.cpp index ab78b943..d3362adc 100644 --- a/NorthstarDLL/client/clientvideooverrides.cpp +++ b/NorthstarDLL/client/clientvideooverrides.cpp @@ -13,11 +13,8 @@ void*, __fastcall, (const char* path, uint32_t flags)) // figure out which mod is handling the bink Mod* pFileOwner = nullptr; - for (Mod& mod : g_pModManager->GetMods()) + for (Mod& mod : g_pModManager->GetMods() | ModManager::FilterEnabled) { - if (!mod.m_bEnabled) - continue; - if (std::find(mod.BinkVideos.begin(), mod.BinkVideos.end(), filename) != mod.BinkVideos.end()) pFileOwner = &mod; } diff --git a/NorthstarDLL/client/modlocalisation.cpp b/NorthstarDLL/client/modlocalisation.cpp index db138315..04719934 100644 --- a/NorthstarDLL/client/modlocalisation.cpp +++ b/NorthstarDLL/client/modlocalisation.cpp @@ -26,10 +26,9 @@ void, __fastcall, (void* pVguiLocalize)) // clang-format on { // load all mod localization manually, so we keep track of all files, not just previously loaded ones - for (Mod& mod : g_pModManager->GetMods()) - if (mod.m_bEnabled) - for (std::string& localisationFile : mod.LocalisationFiles) - CLocalize__AddFile(g_pVguiLocalize, localisationFile.c_str(), nullptr, false); + for (Mod& mod : g_pModManager->GetMods() | ModManager::FilterEnabled) + for (std::string& localisationFile : mod.LocalisationFiles) + CLocalize__AddFile(g_pVguiLocalize, localisationFile.c_str(), nullptr, false); spdlog::info("reloading localization..."); CLocalize__ReloadLocalizationFiles(pVguiLocalize); @@ -44,10 +43,9 @@ void, __fastcall, (void* self)) // previously we did this in CLocalize::AddFile, but for some reason it won't properly overwrite localization from // files loaded previously if done there, very weird but this works so whatever - for (Mod& mod : g_pModManager->GetMods()) - if (mod.m_bEnabled) - for (std::string& localisationFile : mod.LocalisationFiles) - CLocalize__AddFile(g_pVguiLocalize, localisationFile.c_str(), nullptr, false); + for (Mod& mod : g_pModManager->GetMods() | ModManager::FilterEnabled) + for (std::string& localisationFile : mod.LocalisationFiles) + CLocalize__AddFile(g_pVguiLocalize, localisationFile.c_str(), nullptr, false); } ON_DLL_LOAD_CLIENT("localize.dll", Localize, (CModule module)) diff --git a/NorthstarDLL/config/profile.cpp b/NorthstarDLL/config/profile.cpp index 7518a2b3..270ec4d3 100644 --- a/NorthstarDLL/config/profile.cpp +++ b/NorthstarDLL/config/profile.cpp @@ -3,7 +3,7 @@ #include "dedicated/dedicated.h" #include -std::string GetNorthstarPrefix() +fs::path GetNorthstarPrefix() { return NORTHSTAR_FOLDER_PREFIX; } @@ -40,5 +40,5 @@ void InitialiseNorthstarPrefix() // set the console title to show the current profile // dont do this on dedi as title contains useful information on dedi and setting title breaks it as well if (!IsDedicatedServer()) - SetConsoleTitleA((std::string("NorthstarLauncher | ") + NORTHSTAR_FOLDER_PREFIX).c_str()); + SetConsoleTitleA((fs::path("NorthstarLauncher | ") / NORTHSTAR_FOLDER_PREFIX).string().c_str()); } diff --git a/NorthstarDLL/config/profile.h b/NorthstarDLL/config/profile.h index 9f3087fb..8e9d2ce9 100644 --- a/NorthstarDLL/config/profile.h +++ b/NorthstarDLL/config/profile.h @@ -1,7 +1,7 @@ #pragma once #include -static std::string NORTHSTAR_FOLDER_PREFIX; +static fs::path NORTHSTAR_FOLDER_PREFIX; void InitialiseNorthstarPrefix(); -std::string GetNorthstarPrefix(); +fs::path GetNorthstarPrefix(); diff --git a/NorthstarDLL/core/convar/concommand.cpp b/NorthstarDLL/core/convar/concommand.cpp index 094fa813..49c8c806 100644 --- a/NorthstarDLL/core/convar/concommand.cpp +++ b/NorthstarDLL/core/convar/concommand.cpp @@ -127,16 +127,18 @@ typedef void (*ConCommandConstructorType)( ConCommand* newCommand, const char* name, FnCommandCallback_t callback, const char* helpString, int flags, void* parent); ConCommandConstructorType ConCommandConstructor; -void RegisterConCommand(const char* name, FnCommandCallback_t callback, const char* helpString, int flags) +ConCommand* RegisterConCommand(const char* name, FnCommandCallback_t callback, const char* helpString, int flags) { spdlog::info("Registering ConCommand {}", name); // no need to free this ever really, it should exist as long as game does ConCommand* newCommand = new ConCommand; ConCommandConstructor(newCommand, name, callback, helpString, flags, nullptr); + + return newCommand; } -void RegisterConCommand( +ConCommand* RegisterConCommand( const char* name, FnCommandCallback_t callback, const char* helpString, int flags, FnCommandCompletionCallback completionCallback) { spdlog::info("Registering ConCommand {}", name); @@ -145,6 +147,8 @@ void RegisterConCommand( ConCommand* newCommand = new ConCommand; ConCommandConstructor(newCommand, name, callback, helpString, flags, nullptr); newCommand->m_pCompletionCallback = completionCallback; + + return newCommand; } ON_DLL_LOAD("engine.dll", ConCommand, (CModule module)) diff --git a/NorthstarDLL/core/convar/concommand.h b/NorthstarDLL/core/convar/concommand.h index c170c6a5..81c3cf6d 100644 --- a/NorthstarDLL/core/convar/concommand.h +++ b/NorthstarDLL/core/convar/concommand.h @@ -134,6 +134,6 @@ class ConCommand : public ConCommandBase int unk1; // 0x005C }; // Size: 0x0060 -void RegisterConCommand(const char* name, void (*callback)(const CCommand&), const char* helpString, int flags); -void RegisterConCommand( +ConCommand* RegisterConCommand(const char* name, void (*callback)(const CCommand&), const char* helpString, int flags); +ConCommand* RegisterConCommand( const char* name, void (*callback)(const CCommand&), const char* helpString, int flags, FnCommandCompletionCallback completionCallback); diff --git a/NorthstarDLL/core/filesystem/filesystem.cpp b/NorthstarDLL/core/filesystem/filesystem.cpp index bfdfdb19..8ca73130 100644 --- a/NorthstarDLL/core/filesystem/filesystem.cpp +++ b/NorthstarDLL/core/filesystem/filesystem.cpp @@ -147,11 +147,8 @@ HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char* NS::log::fs->info("MountVPK {}", pVpkPath); VPKData* ret = MountVPK(fileSystem, pVpkPath); - for (Mod mod : g_pModManager->GetMods()) + for (Mod mod : g_pModManager->GetMods() | ModManager::FilterEnabled) { - if (!mod.m_bEnabled) - continue; - for (ModVPKEntry& vpkEntry : mod.Vpks) { // if we're autoloading, just load no matter what diff --git a/NorthstarDLL/core/filesystem/rpakfilesystem.cpp b/NorthstarDLL/core/filesystem/rpakfilesystem.cpp index 2bddd8fe..82747bdb 100644 --- a/NorthstarDLL/core/filesystem/rpakfilesystem.cpp +++ b/NorthstarDLL/core/filesystem/rpakfilesystem.cpp @@ -90,11 +90,8 @@ void* PakLoadManager::LoadFile(const char* path) void HandlePakAliases(char** map) { // convert the pak being loaded to it's aliased one, e.g. aliasing mp_hub_timeshift => sp_hub_timeshift - for (Mod& mod : g_pModManager->GetMods() | std::views::reverse) + for (Mod& mod : g_pModManager->GetMods() | ModManager::FilterEnabled | std::views::reverse) { - if (!mod.m_bEnabled) - continue; - if (mod.RpakAliases.find(*map) != mod.RpakAliases.end()) { *map = &mod.RpakAliases[*map][0]; @@ -106,11 +103,8 @@ void HandlePakAliases(char** map) void LoadPreloadPaks() { // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks - for (Mod& mod : g_pModManager->GetMods()) + for (Mod& mod : g_pModManager->GetMods() | ModManager::FilterEnabled) { - if (!mod.m_bEnabled) - continue; - // need to get a relative path of mod to mod folder fs::path modPakPath("./" / mod.m_ModDirectory / "paks"); @@ -123,11 +117,8 @@ void LoadPreloadPaks() void LoadPostloadPaks(const char* pPath) { // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks - for (Mod& mod : g_pModManager->GetMods()) + for (Mod& mod : g_pModManager->GetMods() | ModManager::FilterEnabled) { - if (!mod.m_bEnabled) - continue; - // need to get a relative path of mod to mod folder fs::path modPakPath("./" / mod.m_ModDirectory / "paks"); diff --git a/NorthstarDLL/dllmain.cpp b/NorthstarDLL/dllmain.cpp index cd52940d..31243e6d 100644 --- a/NorthstarDLL/dllmain.cpp +++ b/NorthstarDLL/dllmain.cpp @@ -46,7 +46,7 @@ bool LoadPlugins() std::vector paths; - std::string pluginPath = GetNorthstarPrefix() + "/plugins"; + fs::path pluginPath = GetNorthstarPrefix() / "plugins"; if (!fs::exists(pluginPath)) { spdlog::warn("Could not find a plugins directory. Skipped loading plugins"); diff --git a/NorthstarDLL/logging/crashhandler.cpp b/NorthstarDLL/logging/crashhandler.cpp index 3427e10a..baa75923 100644 --- a/NorthstarDLL/logging/crashhandler.cpp +++ b/NorthstarDLL/logging/crashhandler.cpp @@ -34,16 +34,15 @@ void PrintExceptionLog(ExceptionLog& exc) if (g_pModManager) { spdlog::error("Loaded mods: "); - for (const Mod& mod : g_pModManager->GetMods()) - { - if (mod.m_bEnabled) - spdlog::error("{} {}", mod.Name, mod.Version); - } + for (const Mod& mod : g_pModManager->GetMods() | ModManager::FilterEnabled) + spdlog::error("{} {}", mod.Name, mod.Version); } + spdlog::error(exc.cause); // If this was a runtime error, print the message if (exc.runtimeInfo.length() != 0) spdlog::error("\"{}\"", exc.runtimeInfo); + spdlog::error("At: {} + {}", exc.trace[0].name, exc.trace[0].relativeAddress); spdlog::error(""); spdlog::error("Stack trace:"); @@ -236,7 +235,7 @@ void CreateMiniDump(EXCEPTION_POINTERS* exceptionInfo) time_t time = std::time(nullptr); tm currentTime = *std::localtime(&time); std::stringstream stream; - stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nsdump%Y-%m-%d %H-%M-%S.dmp").c_str()); + stream << std::put_time(¤tTime, (GetNorthstarPrefix() / "logs/nsdump%Y-%m-%d %H-%M-%S.dmp").string().c_str()); auto hMinidumpFile = CreateFileA(stream.str().c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (hMinidumpFile) diff --git a/NorthstarDLL/logging/logging.cpp b/NorthstarDLL/logging/logging.cpp index 8b906911..200d861c 100644 --- a/NorthstarDLL/logging/logging.cpp +++ b/NorthstarDLL/logging/logging.cpp @@ -47,7 +47,7 @@ void CreateLogFiles() tm currentTime = *std::localtime(&time); std::stringstream stream; - stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nslog%Y-%m-%d %H-%M-%S.txt").c_str()); + stream << std::put_time(¤tTime, (GetNorthstarPrefix() / "logs/nslog%Y-%m-%d %H-%M-%S.txt").string().c_str()); auto sink = std::make_shared(stream.str(), false); sink->set_pattern("[%H:%M:%S] [%n] [%l] %v"); for (auto& logger : loggers) diff --git a/NorthstarDLL/mods/compiled/kb_act.cpp b/NorthstarDLL/mods/compiled/kb_act.cpp index f3ad460f..83f469f4 100644 --- a/NorthstarDLL/mods/compiled/kb_act.cpp +++ b/NorthstarDLL/mods/compiled/kb_act.cpp @@ -17,11 +17,8 @@ void ModManager::BuildKBActionsList() // write vanilla file's content to compiled file soCompiledKeys << R2::ReadVPKOriginalFile(KB_ACT_PATH); - for (Mod& mod : GetMods()) + for (Mod& mod : GetMods() | ModManager::FilterEnabled) { - if (!mod.m_bEnabled) - continue; - // write content of each modded file to compiled file std::ifstream siModKeys(mod.m_ModDirectory / "kb_act.lst"); diff --git a/NorthstarDLL/mods/compiled/modkeyvalues.cpp b/NorthstarDLL/mods/compiled/modkeyvalues.cpp index 051b193e..df35de64 100644 --- a/NorthstarDLL/mods/compiled/modkeyvalues.cpp +++ b/NorthstarDLL/mods/compiled/modkeyvalues.cpp @@ -25,11 +25,8 @@ void ModManager::TryBuildKeyValues(const char* filename) // copy over patch kv files, and add #bases to new file, last mods' patches should be applied first // note: #include should be identical but it's actually just broken, thanks respawn - for (Mod& mod : GetMods() | std::views::reverse) + for (Mod& mod : GetMods() | ModManager::FilterEnabled | std::views::reverse) { - if (mod.m_bEnabled) - continue; - size_t fileHash = STR_HASH(normalisedPath); auto modKv = mod.KeyValues.find(fileHash); if (modKv != mod.KeyValues.end()) diff --git a/NorthstarDLL/mods/compiled/modscriptsrson.cpp b/NorthstarDLL/mods/compiled/modscriptsrson.cpp index b24aabe9..1a27aefa 100644 --- a/NorthstarDLL/mods/compiled/modscriptsrson.cpp +++ b/NorthstarDLL/mods/compiled/modscriptsrson.cpp @@ -17,11 +17,8 @@ void ModManager::BuildScriptsRson() std::string scriptsRson = R2::ReadVPKOriginalFile(VPK_SCRIPTS_RSON_PATH); scriptsRson += "\n\n// START MODDED SCRIPT CONTENT\n\n"; // newline before we start custom stuff - for (Mod& mod : GetMods()) + for (Mod& mod : GetMods() | ModManager::FilterEnabled) { - if (!mod.m_bEnabled) - continue; - // this isn't needed at all, just nice to have imo scriptsRson += "// MOD: "; scriptsRson += mod.Name; diff --git a/NorthstarDLL/mods/modmanager.cpp b/NorthstarDLL/mods/modmanager.cpp index 5011f0fb..1c3c416d 100644 --- a/NorthstarDLL/mods/modmanager.cpp +++ b/NorthstarDLL/mods/modmanager.cpp @@ -3,10 +3,12 @@ #include "core/convar/convar.h" #include "core/convar/concommand.h" #include "client/audio.h" +#include "engine/r2engine.h" #include "masterserver/masterserver.h" #include "core/filesystem/filesystem.h" #include "core/filesystem/rpakfilesystem.h" #include "config/profile.h" +#include "dedicated/dedicated.h" #include "rapidjson/error/en.h" #include "rapidjson/document.h" @@ -20,14 +22,15 @@ ModManager* g_pModManager; -Mod::Mod(fs::path modDir, char* jsonBuf) +Mod::Mod(fs::path modDir, std::string sJson, bool bRemote) { m_bWasReadSuccessfully = false; m_ModDirectory = modDir; + m_bRemote = bRemote; rapidjson_document modJson; - modJson.Parse(jsonBuf); + modJson.Parse(sJson); // fail if parse error if (modJson.HasParseError()) @@ -96,8 +99,6 @@ Mod::Mod(fs::path modDir, char* jsonBuf) if (!convarObj.IsObject() || !convarObj.HasMember("Name") || !convarObj.HasMember("DefaultValue")) continue; - // have to allocate this manually, otherwise convar registration will break - // unfortunately this causes us to leak memory on reload, unsure of a way around this rn ModConVar convar; convar.Name = convarObj["Name"].GetString(); convar.DefaultValue = convarObj["DefaultValue"].GetString(); @@ -137,12 +138,11 @@ Mod::Mod(fs::path modDir, char* jsonBuf) continue; } - // have to allocate this manually, otherwise concommand registration will break - // unfortunately this causes us to leak memory on reload, unsure of a way around this rn ModConCommand concommand; concommand.Name = concommandObj["Name"].GetString(); concommand.Function = concommandObj["Function"].GetString(); concommand.Context = ScriptContextFromString(concommandObj["Context"].GetString()); + if (concommand.Context == ScriptContext::INVALID) { spdlog::warn("Mod ConCommand {} has invalid context {}", concommand.Name, concommandObj["Context"].GetString()); @@ -160,9 +160,7 @@ Mod::Mod(fs::path modDir, char* jsonBuf) { // read raw integer flags if (concommandObj["Flags"].IsInt()) - { concommand.Flags = concommandObj["Flags"].GetInt(); - } else if (concommandObj["Flags"].IsString()) { // parse cvar flags from string @@ -171,6 +169,13 @@ Mod::Mod(fs::path modDir, char* jsonBuf) } } + // for commands, client should always be FCVAR_CLIENTDLL, and server should always be FCVAR_GAMEDLL + if (concommand.Context == ScriptContext::CLIENT) + concommand.Flags |= FCVAR_CLIENTDLL; + else if (concommand.Context == ScriptContext::SERVER) + concommand.Flags |= FCVAR_GAMEDLL; + + ConCommands.push_back(concommand); } } @@ -287,6 +292,9 @@ ModManager::ModManager() ); m_hKBActHash = STR_HASH("scripts\\kb_act.lst"); + m_LastModLoadState = nullptr; + m_ModLoadState = new ModLoadState; + LoadMods(); } @@ -311,12 +319,12 @@ auto ModConCommandCallback(const CCommand& command) std::string sCommandName = command.Arg(0); // Find the mod this command belongs to - for (Mod& mod : g_pModManager->GetMods()) + for (Mod& mod : g_pModManager->GetMods() | ModManager::FilterEnabled) { auto res = std::find_if( mod.ConCommands.begin(), mod.ConCommands.end(), - [&sCommandName](const ModConCommand* other) { return other->Name == sCommandName; }); + [&sCommandName](const ModConCommand& other) { return other.Name == sCommandName; }); if (res != mod.ConCommands.end()) { @@ -342,81 +350,151 @@ auto ModConCommandCallback(const CCommand& command) }; } + + + void ModManager::LoadMods() { + // reset state of all currently loaded mods, if we've loaded once already if (m_bHasLoadedMods) UnloadMods(); - std::vector modDirs; - // ensure dirs exist - fs::remove_all(GetCompiledAssetsPath()); fs::create_directories(GetModFolderPath()); fs::create_directories(GetRemoteModFolderPath()); - m_DependencyConstants.clear(); + // load definitions (mod.json files) + LoadModDefinitions(); - // read enabled mods cfg - std::ifstream enabledModsStream(GetNorthstarPrefix() + "/enabledmods.json"); - std::stringstream enabledModsStringStream; + // install mods (load all files) + InstallMods(); - if (!enabledModsStream.fail()) + // write json storing currently enabled mods + SaveEnabledMods(); + + // build modinfo obj for masterserver + rapidjson_document modinfoDoc; + auto& alloc = modinfoDoc.GetAllocator(); + modinfoDoc.SetObject(); + modinfoDoc.AddMember("Mods", rapidjson::kArrayType, alloc); + + int currentModIndex = 0; + for (Mod& mod : GetMods()) { - while (enabledModsStream.peek() != EOF) - enabledModsStringStream << (char)enabledModsStream.get(); + if (!mod.m_bEnabled || !mod.RequiredOnClient) // (!mod.RequiredOnClient && !mod.Pdiff.size()) + continue; - enabledModsStream.close(); - m_EnabledModsCfg.Parse( - enabledModsStringStream.str().c_str()); + modinfoDoc["Mods"].PushBack(rapidjson::kObjectType, modinfoDoc.GetAllocator()); + modinfoDoc["Mods"][currentModIndex].AddMember("Name", rapidjson::StringRef(&mod.Name[0]), modinfoDoc.GetAllocator()); + modinfoDoc["Mods"][currentModIndex].AddMember("Version", rapidjson::StringRef(&mod.Version[0]), modinfoDoc.GetAllocator()); + modinfoDoc["Mods"][currentModIndex].AddMember("RequiredOnClient", mod.RequiredOnClient, modinfoDoc.GetAllocator()); - m_bHasEnabledModsCfg = m_EnabledModsCfg.IsObject(); + currentModIndex++; } - // get mod directories - std::filesystem::directory_iterator classicModsDir = fs::directory_iterator(GetModFolderPath()); - std::filesystem::directory_iterator remoteModsDir = fs::directory_iterator(GetRemoteModFolderPath()); + rapidjson::StringBuffer buffer; + buffer.Clear(); + rapidjson::Writer writer(buffer); + modinfoDoc.Accept(writer); + g_pMasterServerManager->m_sOwnModInfoJson = std::string(buffer.GetString()); - for (std::filesystem::directory_iterator modIterator : {classicModsDir, remoteModsDir}) - for (fs::directory_entry dir : modIterator) - if (fs::exists(dir.path() / "mod.json")) - modDirs.push_back(dir.path()); + // don't need this anymore + delete m_LastModLoadState; + m_LastModLoadState = nullptr; - for (fs::path modDir : modDirs) + m_bHasLoadedMods = true; +} + +void ModManager::LoadModDefinitions() +{ + bool bHasEnabledModsCfg = false; + rapidjson_document enabledModsCfg; + + // read enabled mods cfg { - // read mod json file - std::ifstream jsonStream(modDir / "mod.json"); - std::stringstream jsonStringStream; + std::ifstream enabledModsStream(GetNorthstarPrefix() / "enabledmods.json"); + std::stringstream enabledModsStringStream; - // fail if no mod json - if (jsonStream.fail()) + if (!enabledModsStream.fail()) { - spdlog::warn("Mod {} has a directory but no mod.json", modDir.string()); - continue; + while (enabledModsStream.peek() != EOF) + enabledModsStringStream << (char)enabledModsStream.get(); + + enabledModsStream.close(); + enabledModsCfg.Parse( + enabledModsStringStream.str().c_str()); + + bHasEnabledModsCfg = enabledModsCfg.IsObject(); } + } + + // get mod directories for both local and remote mods + std::vector> vModDirs; + for (fs::directory_entry dir : fs::directory_iterator(GetModFolderPath())) + if (fs::exists(dir.path() / "mod.json")) + vModDirs.push_back({dir.path(), false}); + + for (fs::directory_entry dir : fs::directory_iterator(GetRemoteModFolderPath())) + if (fs::exists(dir.path() / "mod.json")) + vModDirs.push_back({dir.path(), true}); + + for (auto remoteOrLocalModDir : vModDirs) + { + fs::path modDir = std::get<0>(remoteOrLocalModDir); + bool bRemote = std::get<1>(remoteOrLocalModDir); - while (jsonStream.peek() != EOF) - jsonStringStream << (char)jsonStream.get(); + std::string sJsonString; - jsonStream.close(); + // read mod json file + { + std::stringstream jsonStringStream; + std::ifstream jsonStream(modDir / "mod.json"); + + // fail if no mod json + if (jsonStream.fail()) + { + spdlog::warn("Mod {} has a directory but no mod.json", modDir.string()); + continue; + } - Mod mod(modDir, (char*)jsonStringStream.str().c_str()); + while (jsonStream.peek() != EOF) + jsonStringStream << (char)jsonStream.get(); + + jsonStream.close(); + sJsonString = jsonStringStream.str(); + } + // read mod + Mod mod(modDir, sJsonString, bRemote); + + // maybe this should be in InstallMods()? unsure for (auto& pair : mod.DependencyConstants) { - if (m_DependencyConstants.find(pair.first) != m_DependencyConstants.end() && m_DependencyConstants[pair.first] != pair.second) + if (m_ModLoadState->m_DependencyConstants.find(pair.first) != m_ModLoadState->m_DependencyConstants.end() && + m_ModLoadState->m_DependencyConstants[pair.first] != pair.second) { spdlog::error("Constant {} in mod {} already exists in another mod.", pair.first, mod.Name); mod.m_bWasReadSuccessfully = false; break; } - if (m_DependencyConstants.find(pair.first) == m_DependencyConstants.end()) - m_DependencyConstants.emplace(pair); + + if (m_ModLoadState->m_DependencyConstants.find(pair.first) == m_ModLoadState->m_DependencyConstants.end()) + m_ModLoadState->m_DependencyConstants.emplace(pair); } - if (m_bHasEnabledModsCfg && m_EnabledModsCfg.HasMember(mod.Name.c_str())) - mod.m_bEnabled = m_EnabledModsCfg[mod.Name.c_str()].IsTrue(); + if (!bRemote) + { + if (bHasEnabledModsCfg && enabledModsCfg.HasMember(mod.Name.c_str())) + mod.m_bEnabled = enabledModsCfg[mod.Name.c_str()].IsTrue(); + else + mod.m_bEnabled = true; + } else - mod.m_bEnabled = true; + { + // todo: need custom logic for deciding whether to enable remote mods, but should be off by default + // in the future, remote mods should only be enabled explicitly at runtime, never based on any file or persistent state + mod.m_bEnabled = false; + } if (mod.m_bWasReadSuccessfully) { @@ -426,7 +504,7 @@ void ModManager::LoadMods() else spdlog::info("Mod {} is disabled", mod.Name); - m_LoadedMods.push_back(mod); + m_ModLoadState->m_LoadedMods.push_back(mod); } else spdlog::warn("Skipping loading mod file {}", (modDir / "mod.json").string()); @@ -434,353 +512,567 @@ void ModManager::LoadMods() // sort by load prio, lowest-highest std::sort( - m_ModLoadState.m_LoadedMods.begin(), - m_ModLoadState.m_LoadedMods.end(), + m_ModLoadState->m_LoadedMods.begin(), + m_ModLoadState->m_LoadedMods.end(), [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; }); +} - for (Mod& mod : m_LoadedMods) +#pragma region Mod asset installation funcs +void ModManager::InstallModCvars(Mod& mod) +{ + // register convars + for (ModConVar convar : mod.ConVars) { - if (!mod.m_bEnabled) - continue; + ConVar* pVar = R2::g_pCVar->FindVar(convar.Name.c_str()); - // register convars - for (ModConVar convar : mod.ConVars) + // make sure convar isn't registered yet, if it is then modify its flags, helpstring etc + if (!pVar) { - ConVar* pVar = R2::g_pCVar->FindVar(convar.Name.c_str()); + // allocate there here, we can delete later if needed + int nNameSize = convar.Name.size(); + char* pName = new char[nNameSize + 1]; + strncpy_s(pName, nNameSize + 1, convar.Name.c_str(), convar.Name.size()); - // make sure convar isn't registered yet, if it is then modify its flags, helpstring etc - if (!pVar) - new ConVar(convar.Name.c_str(), convar.DefaultValue.c_str(), convar.Flags, convar.HelpString.c_str()); - else - { - // TODO: should probably make sure this is actually a mod convar we're messing with + int nDefaultValueSize = convar.DefaultValue.size(); + char* pDefaultValue = new char[nDefaultValueSize + 1]; + strncpy_s(pDefaultValue, nDefaultValueSize + 1, convar.DefaultValue.c_str(), convar.DefaultValue.size()); - pVar->m_ConCommandBase.m_nFlags = convar.Flags; + int nHelpSize = convar.HelpString.size(); + char* pHelpString = new char[nHelpSize + 1]; + strncpy_s(pHelpString, nHelpSize + 1, convar.HelpString.c_str(), convar.HelpString.size()); - // unfortunately this leaks memory and we can't really not leak memory because we don't know who allocated this - // so we can't delete it without risking a crash - if (convar.HelpString.compare(pVar->GetHelpText())) - { - int nHelpSize = convar.HelpString.size(); - char* pNewHelpString = new char[nHelpSize + 1]; - strncpy_s(pNewHelpString, nHelpSize + 1, convar.HelpString.c_str(), convar.HelpString.size()); - pVar->m_ConCommandBase.m_pszHelpString = pNewHelpString; - } - - if (convar.DefaultValue.compare(pVar->m_pszDefaultValue)) - { - int nDefaultValueSize = convar.DefaultValue.size(); - char* pNewDefaultValueString = new char[nDefaultValueSize + 1]; - strncpy_s(pNewDefaultValueString, nDefaultValueSize + 1, convar.DefaultValue.c_str(), convar.DefaultValue.size()); - pVar->m_pszDefaultValue = pNewDefaultValueString; - pVar->SetValue(pNewDefaultValueString); - } - } + pVar = new ConVar(pName, pDefaultValue, convar.Flags, pHelpString); + m_RegisteredModConVars.insert(pVar); } - - for (ModConCommand command : mod.ConCommands) + else { - // make sure command isnt't registered multiple times. - if (!R2::g_pCVar->FindCommand(command.Name.c_str())) + // not a mod cvar, don't let us edit it! + if (!m_RegisteredModConVars.contains(pVar)) { - std::string funcName = command.Function; - RegisterConCommand(command.Name.c_str(), ModConCommandCallback, command.HelpString.c_str(), command.Flags); + spdlog::warn("Mod {} tried to create ConVar {} that was already defined in native code!", mod.Name, convar.Name); + continue; } - } - // read vpk paths - if (fs::exists(mod.m_ModDirectory / "vpk")) - { - // read vpk cfg - std::ifstream vpkJsonStream(mod.m_ModDirectory / "vpk/vpk.json"); - std::stringstream vpkJsonStringStream; + pVar->m_ConCommandBase.m_nFlags = convar.Flags; - bool bUseVPKJson = false; - rapidjson::Document dVpkJson; + if (convar.HelpString.compare(pVar->GetHelpText())) + { + int nHelpSize = convar.HelpString.size(); + char* pNewHelpString = new char[nHelpSize + 1]; + strncpy_s(pNewHelpString, nHelpSize + 1, convar.HelpString.c_str(), convar.HelpString.size()); - if (!vpkJsonStream.fail()) + // delete old, assign new + delete pVar->m_ConCommandBase.m_pszHelpString; + pVar->m_ConCommandBase.m_pszHelpString = pNewHelpString; + } + + if (convar.DefaultValue.compare(pVar->m_pszDefaultValue)) { - while (vpkJsonStream.peek() != EOF) - vpkJsonStringStream << (char)vpkJsonStream.get(); + bool bIsDefaultValue = !strcmp(pVar->GetString(), pVar->m_pszDefaultValue); - vpkJsonStream.close(); - dVpkJson.Parse( - vpkJsonStringStream.str().c_str()); + int nDefaultValueSize = convar.DefaultValue.size(); + char* pNewDefaultValue = new char[nDefaultValueSize + 1]; + strncpy_s(pNewDefaultValue, nDefaultValueSize + 1, convar.DefaultValue.c_str(), convar.DefaultValue.size()); - bUseVPKJson = !dVpkJson.HasParseError() && dVpkJson.IsObject(); + // delete old, assign new + delete pVar->m_pszDefaultValue; + pVar->m_pszDefaultValue = pNewDefaultValue; + + if (bIsDefaultValue) // only set value if it's currently default value, if changed then don't + pVar->SetValue(pNewDefaultValue); } + } + } + + // register command + for (ModConCommand command : mod.ConCommands) + { + // make sure command isnt't registered multiple times. + ConCommand* pCommand = R2::g_pCVar->FindCommand(command.Name.c_str()); - for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "vpk")) + if (!pCommand) + { + // allocate there here, we can delete later if needed + int nNameSize = command.Name.size(); + char* pName = new char[nNameSize + 1]; + strncpy_s(pName, nNameSize + 1, command.Name.c_str(), command.Name.size()); + + int nHelpSize = command.HelpString.size(); + char* pHelpString = new char[nHelpSize + 1]; + strncpy_s(pHelpString, nHelpSize + 1, command.HelpString.c_str(), command.HelpString.size()); + + pCommand = RegisterConCommand(pName, ModConCommandCallback, pHelpString, command.Flags); + m_RegisteredModConCommands.insert(pCommand); + } + else + { + if (!m_RegisteredModConCommands.contains(pCommand)) { - // a bunch of checks to make sure we're only adding dir vpks and their paths are good - // note: the game will literally only load vpks with the english prefix - if (fs::is_regular_file(file) && file.path().extension() == ".vpk" && - file.path().string().find("english") != std::string::npos && - file.path().string().find(".bsp.pak000_dir") != std::string::npos) - { - std::string formattedPath = file.path().filename().string(); + spdlog::warn("Mod {} tried to create ConCommand {} that was already defined in native code!", mod.Name, command.Name); + continue; + } - // this really fucking sucks but it'll work - std::string vpkName = formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3); + pCommand->m_nFlags = command.Flags; - ModVPKEntry& modVpk = mod.Vpks.emplace_back(); - modVpk.m_bAutoLoad = !bUseVPKJson || (dVpkJson.HasMember("Preload") && dVpkJson["Preload"].IsObject() && - dVpkJson["Preload"].HasMember(vpkName) && dVpkJson["Preload"][vpkName].IsTrue()); - modVpk.m_sVpkPath = (file.path().parent_path() / vpkName).string(); + if (command.HelpString.compare(pCommand->GetHelpText())) + { + int nHelpSize = command.HelpString.size(); + char* pNewHelpString = new char[nHelpSize + 1]; + strncpy_s(pNewHelpString, nHelpSize + 1, command.HelpString.c_str(), command.HelpString.size()); - if (m_bHasLoadedMods && modVpk.m_bAutoLoad) - (*R2::g_pFilesystem)->m_vtable->MountVPK(*R2::g_pFilesystem, vpkName.c_str()); - } + // delete old, assign new + delete pCommand->m_pszHelpString; + pCommand->m_pszHelpString = pNewHelpString; } } + } +} + +void ModManager::InstallModVpks(Mod& mod) +{ + // read vpk paths + if (fs::exists(mod.m_ModDirectory / "vpk")) + { + // read vpk cfg + std::ifstream vpkJsonStream(mod.m_ModDirectory / "vpk/vpk.json"); + std::stringstream vpkJsonStringStream; - // read rpak paths - if (fs::exists(mod.m_ModDirectory / "paks")) + bool bUseVPKJson = false; + rapidjson::Document dVpkJson; + + if (!vpkJsonStream.fail()) { - // read rpak cfg - std::ifstream rpakJsonStream(mod.m_ModDirectory / "paks/rpak.json"); - std::stringstream rpakJsonStringStream; + while (vpkJsonStream.peek() != EOF) + vpkJsonStringStream << (char)vpkJsonStream.get(); + + vpkJsonStream.close(); + dVpkJson.Parse( + vpkJsonStringStream.str().c_str()); - bool bUseRpakJson = false; - rapidjson::Document dRpakJson; + bUseVPKJson = !dVpkJson.HasParseError() && dVpkJson.IsObject(); + } - if (!rpakJsonStream.fail()) + for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "vpk")) + { + // a bunch of checks to make sure we're only adding dir vpks and their paths are good + // note: the game will literally only load vpks with the english prefix + if (fs::is_regular_file(file) && file.path().extension() == ".vpk" && + file.path().string().find("english") != std::string::npos && + file.path().string().find(".bsp.pak000_dir") != std::string::npos) { - while (rpakJsonStream.peek() != EOF) - rpakJsonStringStream << (char)rpakJsonStream.get(); + std::string formattedPath = file.path().filename().string(); - rpakJsonStream.close(); - dRpakJson.Parse( - rpakJsonStringStream.str().c_str()); + // this really fucking sucks but it'll work + std::string vpkName = formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3); - bUseRpakJson = !dRpakJson.HasParseError() && dRpakJson.IsObject(); + ModVPKEntry& modVpk = mod.Vpks.emplace_back(); + modVpk.m_bAutoLoad = !bUseVPKJson || (dVpkJson.HasMember("Preload") && dVpkJson["Preload"].IsObject() && + dVpkJson["Preload"].HasMember(vpkName) && dVpkJson["Preload"][vpkName].IsTrue()); + modVpk.m_sVpkPath = (file.path().parent_path() / vpkName).string(); + + if (m_bHasLoadedMods && modVpk.m_bAutoLoad) + (*R2::g_pFilesystem)->m_vtable->MountVPK(*R2::g_pFilesystem, vpkName.c_str()); } + } + } +} + +void ModManager::InstallModRpaks(Mod& mod) +{ + // read rpak paths + if (fs::exists(mod.m_ModDirectory / "paks")) + { + // read rpak cfg + std::ifstream rpakJsonStream(mod.m_ModDirectory / "paks/rpak.json"); + std::stringstream rpakJsonStringStream; + + bool bUseRpakJson = false; + rapidjson::Document dRpakJson; + + if (!rpakJsonStream.fail()) + { + while (rpakJsonStream.peek() != EOF) + rpakJsonStringStream << (char)rpakJsonStream.get(); + + rpakJsonStream.close(); + dRpakJson.Parse( + rpakJsonStringStream.str().c_str()); - // read pak aliases - if (bUseRpakJson && dRpakJson.HasMember("Aliases") && dRpakJson["Aliases"].IsObject()) + bUseRpakJson = !dRpakJson.HasParseError() && dRpakJson.IsObject(); + } + + // read pak aliases + if (bUseRpakJson && dRpakJson.HasMember("Aliases") && dRpakJson["Aliases"].IsObject()) + { + for (rapidjson::Value::ConstMemberIterator iterator = dRpakJson["Aliases"].MemberBegin(); + iterator != dRpakJson["Aliases"].MemberEnd(); + iterator++) { - for (rapidjson::Value::ConstMemberIterator iterator = dRpakJson["Aliases"].MemberBegin(); - iterator != dRpakJson["Aliases"].MemberEnd(); - iterator++) - { - if (!iterator->name.IsString() || !iterator->value.IsString()) - continue; + if (!iterator->name.IsString() || !iterator->value.IsString()) + continue; - mod.RpakAliases.insert(std::make_pair(iterator->name.GetString(), iterator->value.GetString())); - } + mod.RpakAliases.insert(std::make_pair(iterator->name.GetString(), iterator->value.GetString())); } + } - for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "paks")) + for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "paks")) + { + // ensure we're only loading rpaks + if (fs::is_regular_file(file) && file.path().extension() == ".rpak") { - // ensure we're only loading rpaks - if (fs::is_regular_file(file) && file.path().extension() == ".rpak") - { - std::string pakName(file.path().filename().string()); + std::string pakName(file.path().filename().string()); - ModRpakEntry& modPak = mod.Rpaks.emplace_back(); - modPak.m_bAutoLoad = - !bUseRpakJson || (dRpakJson.HasMember("Preload") && dRpakJson["Preload"].IsObject() && - dRpakJson["Preload"].HasMember(pakName) && dRpakJson["Preload"][pakName].IsTrue()); + ModRpakEntry& modPak = mod.Rpaks.emplace_back(); + modPak.m_bAutoLoad = !bUseRpakJson || (dRpakJson.HasMember("Preload") && dRpakJson["Preload"].IsObject() && + dRpakJson["Preload"].HasMember(pakName) && dRpakJson["Preload"][pakName].IsTrue()); - // postload things - if (!bUseRpakJson || - (dRpakJson.HasMember("Postload") && dRpakJson["Postload"].IsObject() && dRpakJson["Postload"].HasMember(pakName))) - modPak.m_sLoadAfterPak = dRpakJson["Postload"][pakName].GetString(); + // postload things + if (!bUseRpakJson || + (dRpakJson.HasMember("Postload") && dRpakJson["Postload"].IsObject() && dRpakJson["Postload"].HasMember(pakName))) + modPak.m_sLoadAfterPak = dRpakJson["Postload"][pakName].GetString(); - modPak.m_sPakName = pakName; + modPak.m_sPakName = pakName; - // read header of file and get the starpak paths - // this is done here as opposed to on starpak load because multiple rpaks can load a starpak - // and there is seemingly no good way to tell which rpak is causing the load of a starpak :/ + // read header of file and get the starpak paths + // this is done here as opposed to on starpak load because multiple rpaks can load a starpak + // and there is seemingly no good way to tell which rpak is causing the load of a starpak :/ - std::ifstream rpakStream(file.path(), std::ios::binary); + std::ifstream rpakStream(file.path(), std::ios::binary); - // seek to the point in the header where the starpak reference size is - rpakStream.seekg(0x38, std::ios::beg); - int starpaksSize = 0; - rpakStream.read((char*)&starpaksSize, 2); + // seek to the point in the header where the starpak reference size is + rpakStream.seekg(0x38, std::ios::beg); + int starpaksSize = 0; + rpakStream.read((char*)&starpaksSize, 2); - // seek to just after the header - rpakStream.seekg(0x58, std::ios::beg); - // read the starpak reference(s) - std::vector buf(starpaksSize); - rpakStream.read(buf.data(), starpaksSize); + // seek to just after the header + rpakStream.seekg(0x58, std::ios::beg); + // read the starpak reference(s) + std::vector buf(starpaksSize); + rpakStream.read(buf.data(), starpaksSize); - rpakStream.close(); + rpakStream.close(); - // split the starpak reference(s) into strings to hash - std::string str = ""; - for (int i = 0; i < starpaksSize; i++) + // split the starpak reference(s) into strings to hash + std::string str = ""; + for (int i = 0; i < starpaksSize; i++) + { + // if the current char is null, that signals the end of the current starpak path + if (buf[i] != 0x00) { - // if the current char is null, that signals the end of the current starpak path - if (buf[i] != 0x00) - { - str += buf[i]; - } - else + str += buf[i]; + } + else + { + // only add the string we are making if it isnt empty + if (!str.empty()) { - // only add the string we are making if it isnt empty - if (!str.empty()) - { - mod.StarpakPaths.push_back(STR_HASH(str)); - spdlog::info("Mod {} registered starpak '{}'", mod.Name, str); - str = ""; - } + mod.StarpakPaths.push_back(STR_HASH(str)); + spdlog::info("Mod {} registered starpak '{}'", mod.Name, str); + str = ""; } } - - // not using atm because we need to resolve path to rpak - // if (m_hasLoadedMods && modPak.m_bAutoLoad) - // g_pPakLoadManager->LoadPakAsync(pakName.c_str()); } + + // not using atm because we need to resolve path to rpak + // if (m_hasLoadedMods && modPak.m_bAutoLoad) + // g_pPakLoadManager->LoadPakAsync(pakName.c_str()); } } + } +} - // read keyvalues paths - if (fs::exists(mod.m_ModDirectory / "keyvalues")) +void ModManager::InstallModKeyValues(Mod& mod) +{ + // read keyvalues paths + if (fs::exists(mod.m_ModDirectory / "keyvalues")) + { + for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / "keyvalues")) { - for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / "keyvalues")) + if (fs::is_regular_file(file)) { - if (fs::is_regular_file(file)) + std::string kvStr = g_pModManager->NormaliseModFilePath(file.path().lexically_relative(mod.m_ModDirectory / "keyvalues")); + mod.KeyValues.emplace(STR_HASH(kvStr), kvStr); + } + } + } +} + +void ModManager::InstallModBinks(Mod& mod) +{ + // read bink video paths + if (fs::exists(mod.m_ModDirectory / "media")) + { + for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / "media")) + if (fs::is_regular_file(file) && file.path().extension() == ".bik") + mod.BinkVideos.push_back(file.path().filename().string()); + } +} + +void ModManager::InstallModAudioOverrides(Mod& mod) +{ + // try to load audio + if (fs::exists(mod.m_ModDirectory / "audio")) + { + for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "audio")) + { + if (fs::is_regular_file(file) && file.path().extension().string() == ".json") + { + if (!g_CustomAudioManager.TryLoadAudioOverride(file.path())) { - std::string kvStr = - g_pModManager->NormaliseModFilePath(file.path().lexically_relative(mod.m_ModDirectory / "keyvalues")); - mod.KeyValues.emplace(STR_HASH(kvStr), kvStr); + spdlog::warn("Mod {} has an invalid audio def {}", mod.Name, file.path().filename().string()); + continue; } } } + } +} - // read pdiff - if (fs::exists(mod.m_ModDirectory / "mod.pdiff")) +void ModManager::InstallModFileOverrides(Mod& mod) +{ + // install all "normal" file overrides (source/vpk filesystem) e.g. files in Northstar.CustomServers/mod + if (fs::exists(mod.m_ModDirectory / MOD_OVERRIDE_DIR)) + { + for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / MOD_OVERRIDE_DIR)) { - std::ifstream pdiffStream(mod.m_ModDirectory / "mod.pdiff"); - - if (!pdiffStream.fail()) + std::string path = g_pModManager->NormaliseModFilePath(file.path().lexically_relative(mod.m_ModDirectory / MOD_OVERRIDE_DIR)); + if (file.is_regular_file() && m_ModLoadState->m_ModFiles.find(path) == m_ModLoadState->m_ModFiles.end()) { - std::stringstream pdiffStringStream; - while (pdiffStream.peek() != EOF) - pdiffStringStream << (char)pdiffStream.get(); - - pdiffStream.close(); - - mod.Pdiff = pdiffStringStream.str(); + ModOverrideFile modFile; + modFile.m_pOwningMod = &mod; + modFile.m_Path = path; + modFile.m_tLastWriteTime = fs::last_write_time(file.path()); // need real path for this + m_ModLoadState->m_ModFiles.insert(std::make_pair(path, modFile)); } } + } +} +#pragma endregion + +void ModManager::CheckModFilesForChanges() +{ + // normal mod files + { + // check which file overrides have changed + // we need to trigger a reload of a given asset if + // a) the asset was overriden previously but has changed owner + // b) the asset no longer has any overrides (use vanilla file) + // c) the asset was using vanilla file but isn't anymore - // read bink video paths - if (fs::exists(mod.m_ModDirectory / "media")) + std::vector vpChangedFiles; + + // check currently loaded mods for any removed or updated files vs last load + for (auto& filePair : m_ModLoadState->m_ModFiles) { - for (fs::directory_entry file : fs::recursive_directory_iterator(mod.m_ModDirectory / "media")) - if (fs::is_regular_file(file) && file.path().extension() == ".bik") - mod.BinkVideos.push_back(file.path().filename().string()); + auto findFile = m_LastModLoadState->m_ModFiles.find(filePair.first); + if (findFile == m_LastModLoadState->m_ModFiles.end() || findFile->second.m_tLastWriteTime != filePair.second.m_tLastWriteTime) + vpChangedFiles.push_back(&filePair.second); } - // try to load audio - if (fs::exists(mod.m_ModDirectory / "audio")) + // check last load for any files removed + for (auto& filePair : m_LastModLoadState->m_ModFiles) + if (filePair.second.m_pOwningMod != nullptr && + m_ModLoadState->m_ModFiles.find(filePair.first) == m_ModLoadState->m_ModFiles.end()) + vpChangedFiles.push_back(&filePair.second); + + for (ModOverrideFile* pChangedFile : vpChangedFiles) { - for (fs::directory_entry file : fs::directory_iterator(mod.m_ModDirectory / "audio")) + if (IsDedicatedServer()) { - if (fs::is_regular_file(file) && file.path().extension().string() == ".json") + // could check localisation here? but what's the point, localisation shouldn't be in mod fs + // if (m_AssetTypesToReload.bLocalisation) + + if (!m_AssetTypesToReload.bAimAssistSettings && !pChangedFile->m_Path.parent_path().compare("cfg/aimassist/")) + { + m_AssetTypesToReload.bAimAssistSettings = true; + continue; + } + + if (!m_AssetTypesToReload.bMaterials && !pChangedFile->m_Path.parent_path().compare("materials/")) + { + m_AssetTypesToReload.bMaterials = true; + continue; + } + + if (!m_AssetTypesToReload.bUiScript) { - if (!g_CustomAudioManager.TryLoadAudioOverride(file.path())) + + // TODO: need to check whether any ui scripts have changed + + if (!pChangedFile->m_Path.parent_path().compare("resource/ui/")) { - spdlog::warn("Mod {} has an invalid audio def {}", mod.Name, file.path().filename().string()); + m_AssetTypesToReload.bUiScript = true; continue; } } } - } - } - // in a seperate loop because we register mod files in reverse order, since mods loaded later should have their files prioritised - for (int64_t i = m_LoadedMods.size() - 1; i > -1; i--) - { - if (!m_LoadedMods[i].m_bEnabled) - continue; + if (!m_AssetTypesToReload.bModels && !pChangedFile->m_Path.parent_path().compare("models/")) + { + m_AssetTypesToReload.bModels = true; + continue; + } - if (fs::exists(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR)) - { - for (fs::directory_entry file : fs::recursive_directory_iterator(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR)) + // could also check this but no point as it should only be changed from mod keyvalues + // if (!m_AssetTypesToReload.bPlaylists && !pChangedFile->m_Path.compare("playlists_v2.txt")) + + // we also check these on change of mod keyvalues + if (!m_AssetTypesToReload.bWeaponSettings && !pChangedFile->m_Path.parent_path().compare("scripts/weapons/")) { - std::string path = - g_pModManager->NormaliseModFilePath(file.path().lexically_relative(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR)); - if (file.is_regular_file() && m_ModFiles.find(path) == m_ModFiles.end()) - { - ModOverrideFile modFile; - modFile.m_pOwningMod = &m_LoadedMods[i]; - modFile.m_Path = path; - m_ModFiles.insert(std::make_pair(path, modFile)); - } + m_AssetTypesToReload.bWeaponSettings = true; + continue; + } + + if (!m_AssetTypesToReload.bPlayerSettings && !pChangedFile->m_Path.parent_path().compare("scripts/players/")) + { + m_AssetTypesToReload.bPlayerSettings = true; + continue; + } + + // maybe also aibehaviour? + if (!m_AssetTypesToReload.bAiSettings && !pChangedFile->m_Path.parent_path().compare("scripts/aisettings/")) + { + m_AssetTypesToReload.bAiSettings = true; + continue; + } + + if (!m_AssetTypesToReload.bDamageDefs && !pChangedFile->m_Path.parent_path().compare("scripts/damage/")) + { + m_AssetTypesToReload.bDamageDefs = true; + continue; } } } - // build modinfo obj for masterserver - rapidjson_document modinfoDoc; - auto& alloc = modinfoDoc.GetAllocator(); - modinfoDoc.SetObject(); - modinfoDoc.AddMember("Mods", rapidjson::kArrayType, alloc); + // keyvalues + + //if (!m_AssetTypesToReload.bWeaponSettings && kvStr.compare("scripts/weapons/")) + //{ + // m_AssetTypesToReload.bWeaponSettings = true; + // continue; + //} + // + //if (!m_AssetTypesToReload.bPlayerSettings && kvStr.compare("scripts/players/")) + //{ + // m_AssetTypesToReload.bPlayerSettings = true; + // continue; + //} + // + //// maybe also aibehaviour? + //if (!m_AssetTypesToReload.bAiSettings && kvStr.compare("scripts/aisettings/")) + //{ + // m_AssetTypesToReload.bAiSettings = true; + // continue; + //} + // + //if (!m_AssetTypesToReload.bDamageDefs && kvStr.compare("scripts/damage/")) + //{ + // m_AssetTypesToReload.bDamageDefs = true; + // continue; + //} +} - int currentModIndex = 0; - for (Mod& mod : m_LoadedMods) +void ModManager::ReloadNecessaryModAssets() +{ + std::vector vReloadCommands; + + if (m_AssetTypesToReload.bUiScript) + vReloadCommands.push_back("uiscript_reset"); + + if (m_AssetTypesToReload.bLocalisation) + vReloadCommands.push_back("reload_localization"); + + // after we reload_localization, we need to loadPlaylists, to keep playlist localisation + if (m_AssetTypesToReload.bPlaylists || m_AssetTypesToReload.bLocalisation) + vReloadCommands.push_back("loadPlaylists"); + + if (m_AssetTypesToReload.bAimAssistSettings) + vReloadCommands.push_back("ReloadAimAssistSettings"); + + // need to reimplement mat_reloadmaterials for this + //if (m_AssetTypesToReload.bMaterials) + // R2::Cbuf_AddText(R2::Cbuf_GetCurrentPlayer(), "mat_reloadmaterials", R2::cmd_source_t::kCommandSrcCode); + + //if (m_AssetTypesToReload.bWeaponSettings) + //if (m_AssetTypesToReload.bPlayerSettings) + //if (m_AssetTypesToReload.bAiSettings) + //if (m_AssetTypesToReload.bDamageDefs) + + if (m_AssetTypesToReload.bModels) + spdlog::warn("Need to reload models but can't without a restart!"); + + for (std::string& sReloadCommand : vReloadCommands) { - if (!mod.m_bEnabled || (!mod.RequiredOnClient && !mod.Pdiff.size())) - continue; + spdlog::info("Executing command {} for asset reload", sReloadCommand); + R2::Cbuf_AddText(R2::Cbuf_GetCurrentPlayer(), sReloadCommand.c_str(), R2::cmd_source_t::kCommandSrcCode); + } - modinfoDoc["Mods"].PushBack(rapidjson::kObjectType, modinfoDoc.GetAllocator()); - modinfoDoc["Mods"][currentModIndex].AddMember("Name", rapidjson::StringRef(&mod.Name[0]), modinfoDoc.GetAllocator()); - modinfoDoc["Mods"][currentModIndex].AddMember("Version", rapidjson::StringRef(&mod.Version[0]), modinfoDoc.GetAllocator()); - modinfoDoc["Mods"][currentModIndex].AddMember("RequiredOnClient", mod.RequiredOnClient, modinfoDoc.GetAllocator()); - modinfoDoc["Mods"][currentModIndex].AddMember("Pdiff", rapidjson::StringRef(&mod.Pdiff[0]), modinfoDoc.GetAllocator()); + R2::Cbuf_Execute(); +} - currentModIndex++; +void ModManager::InstallMods() +{ + for (Mod& mod : GetMods() | FilterEnabled) + { + InstallModCvars(mod); + InstallModVpks(mod); + InstallModRpaks(mod); + InstallModKeyValues(mod); + InstallModBinks(mod); + InstallModAudioOverrides(mod); } - rapidjson::StringBuffer buffer; - buffer.Clear(); - rapidjson::Writer writer(buffer); - modinfoDoc.Accept(writer); - g_pMasterServerManager->m_sOwnModInfoJson = std::string(buffer.GetString()); + // in a seperate loop because we register mod files in reverse order, since mods loaded later should have their files prioritised + for (Mod& mod : GetMods() | FilterEnabled | std::views::reverse) + InstallModFileOverrides(mod); - m_bHasLoadedMods = true; + if (m_bHasLoadedMods) // only reload assets after initial load + { + CheckModFilesForChanges(); + ReloadNecessaryModAssets(); + } +} + +void ModManager::SaveEnabledMods() +{ + // write from scratch every time, don't include unnecessary mods + rapidjson_document enabledModsCfg; + enabledModsCfg.SetObject(); + + // add values + for (Mod& mod : GetMods()) + enabledModsCfg.AddMember(rapidjson_document::StringRefType(mod.Name.c_str()), mod.m_bEnabled, enabledModsCfg.GetAllocator()); + + // write + std::ofstream sWriteStream(GetNorthstarPrefix() / "enabledmods.json"); + rapidjson::OStreamWrapper sWriteStreamWrapper(sWriteStream); + rapidjson::PrettyWriter writer(sWriteStreamWrapper); + enabledModsCfg.Accept(writer); } void ModManager::UnloadMods() { + // save last state so we know what we need to reload m_LastModLoadState = m_ModLoadState; + m_ModLoadState = new ModLoadState; + + // reset assets to reload + m_AssetTypesToReload.bUiScript = false; + m_AssetTypesToReload.bLocalisation = false; + m_AssetTypesToReload.bPlaylists = false; + m_AssetTypesToReload.bAimAssistSettings = false; + m_AssetTypesToReload.bMaterials = false; + m_AssetTypesToReload.bRPaks = false; + m_AssetTypesToReload.bWeaponSettings = false; + m_AssetTypesToReload.bPlayerSettings = false; + m_AssetTypesToReload.bAiSettings = false; + m_AssetTypesToReload.bDamageDefs = false; + m_AssetTypesToReload.bModels = false; // clean up stuff from mods before we unload - m_ModFiles.clear(); fs::remove_all(GetCompiledAssetsPath()); - g_CustomAudioManager.ClearAudioOverrides(); - - if (!m_bHasEnabledModsCfg) - m_EnabledModsCfg.SetObject(); - - for (Mod& mod : m_LoadedMods) - { - // remove all built kvs - for (std::pair kvPaths : mod.KeyValues) - fs::remove(GetCompiledAssetsPath() / fs::path(kvPaths.second).lexically_relative(mod.m_ModDirectory)); - - // write to m_enabledModsCfg - // should we be doing this here or should scripts be doing this manually? - // main issue with doing this here is when we reload mods for connecting to a server, we write enabled mods, which isn't necessarily - // what we wanna do - if (!m_EnabledModsCfg.HasMember(mod.Name.c_str())) - m_EnabledModsCfg.AddMember(rapidjson_document::StringRefType(mod.Name.c_str()), false, m_EnabledModsCfg.GetAllocator()); - - m_EnabledModsCfg[mod.Name.c_str()].SetBool(mod.m_bEnabled); - } - - std::ofstream writeStream(GetNorthstarPrefix() + "/enabledmods.json"); - rapidjson::OStreamWrapper writeStreamWrapper(writeStream); - rapidjson::PrettyWriter writer(writeStreamWrapper); - m_EnabledModsCfg.Accept(writer); - - // do we need to dealloc individual entries in m_loadedMods? idk, rework - m_LoadedMods.clear(); + // TODO: remove, should only reload required overrides, and don't do it here + g_CustomAudioManager.ClearAudioOverrides(); } std::string ModManager::NormaliseModFilePath(const fs::path path) @@ -801,10 +1093,10 @@ void ModManager::CompileAssetsForFile(const char* filename) if (fileHash == m_hScriptsRsonHash) BuildScriptsRson(); - else if (fileHash == m_hPdefHash) - { - // BuildPdef(); todo - } + //else if (fileHash == m_hPdefHash) + //{ + // // BuildPdef(); todo + //} else if (fileHash == m_hKBActHash) BuildKBActionsList(); else @@ -831,15 +1123,15 @@ void ConCommand_reload_mods(const CCommand& args) fs::path GetModFolderPath() { - return fs::path(GetNorthstarPrefix() + MOD_FOLDER_SUFFIX); + return GetNorthstarPrefix() / MOD_FOLDER_SUFFIX; } fs::path GetRemoteModFolderPath() { - return fs::path(GetNorthstarPrefix() + REMOTE_MOD_FOLDER_SUFFIX); + return GetNorthstarPrefix() / REMOTE_MOD_FOLDER_SUFFIX; } fs::path GetCompiledAssetsPath() { - return fs::path(GetNorthstarPrefix() + COMPILED_ASSETS_SUFFIX); + return GetNorthstarPrefix() / COMPILED_ASSETS_SUFFIX; } ON_DLL_LOAD_RELIESON("engine.dll", ModManager, (ConCommand, MasterServer), (CModule module)) diff --git a/NorthstarDLL/mods/modmanager.h b/NorthstarDLL/mods/modmanager.h index d44a202f..0bb696e8 100644 --- a/NorthstarDLL/mods/modmanager.h +++ b/NorthstarDLL/mods/modmanager.h @@ -7,15 +7,15 @@ #include #include #include +#include -const std::string MOD_FOLDER_SUFFIX = "/mods"; -const std::string REMOTE_MOD_FOLDER_SUFFIX = "/runtime/remote/mods"; +const std::string MOD_FOLDER_SUFFIX = "mods"; +const std::string REMOTE_MOD_FOLDER_SUFFIX = "runtime/remote/mods"; const fs::path MOD_OVERRIDE_DIR = "mod"; -const std::string COMPILED_ASSETS_SUFFIX = "/runtime/compiled"; +const std::string COMPILED_ASSETS_SUFFIX = "runtime/compiled"; struct ModConVar { - public: std::string Name; std::string DefaultValue; std::string HelpString; @@ -24,7 +24,6 @@ struct ModConVar struct ModConCommand { - public: std::string Name; std::string Function; std::string HelpString; @@ -34,7 +33,6 @@ struct ModConCommand struct ModScriptCallback { - public: ScriptContext Context; // called before the codecallback is executed @@ -47,7 +45,6 @@ struct ModScriptCallback struct ModScript { - public: std::string Path; std::string RunOn; @@ -57,27 +54,24 @@ struct ModScript // these are pretty much identical, could refactor to use the same stuff? struct ModVPKEntry { - public: bool m_bAutoLoad; std::string m_sVpkPath; }; struct ModRpakEntry { - public: bool m_bAutoLoad; std::string m_sPakName; std::string m_sLoadAfterPak; }; -class Mod +struct Mod { - public: // runtime stuff bool m_bEnabled = true; bool m_bWasReadSuccessfully = false; fs::path m_ModDirectory; - // bool m_bIsRemote; + bool m_bRemote; // whether the mod was automatically downloaded // mod.json stuff: @@ -109,7 +103,8 @@ class Mod std::vector Vpks; std::unordered_map KeyValues; std::vector BinkVideos; - std::string Pdiff; // only need one per mod + + // todo audio override struct std::vector Rpaks; std::unordered_map @@ -121,22 +116,14 @@ class Mod std::unordered_map DependencyConstants; public: - Mod(fs::path modPath, char* jsonBuf); + Mod(fs::path modPath, std::string sJson, bool bRemote); }; struct ModOverrideFile { - public: Mod* m_pOwningMod; // don't need to explicitly clean this up fs::path m_Path; -}; - -class ModLoadState -{ - public: - std::vector m_LoadedMods; - std::unordered_map m_ModFiles; - std::unordered_map m_DependencyConstants; + fs::file_time_type m_tLastWriteTime; }; class ModManager @@ -144,45 +131,100 @@ class ModManager private: bool m_bHasLoadedMods = false; bool m_bHasEnabledModsCfg; - rapidjson_document m_EnabledModsCfg; // precalculated hashes size_t m_hScriptsRsonHash; size_t m_hPdefHash; size_t m_hKBActHash; - public: - ModLoadState m_LastModLoadState; - ModLoadState m_ModLoadState; + void LoadModDefinitions(); + void SaveEnabledMods(); + void InstallMods(); + + // mod installation funcs + void InstallModCvars(Mod& mod); + void InstallModVpks(Mod& mod); + void InstallModRpaks(Mod& mod); + void InstallModKeyValues(Mod& mod); + void InstallModBinks(Mod& mod); + void InstallModAudioOverrides(Mod& mod); + + void InstallModFileOverrides(Mod& mod); + + void UnloadMods(); + + std::unordered_set m_RegisteredModConVars; + std::unordered_set m_RegisteredModConCommands; + + + struct + { + // assets types we need to reload completely after mods are reloaded (can't reload individually) + bool bUiScript = false; // also includes .menu files + bool bLocalisation = false; + bool bPlaylists = false; + bool bAimAssistSettings = false; + bool bMaterials = false; // vmts + bool bWeaponSettings = false; + bool bPlayerSettings = false; + bool bAiSettings = false; + bool bDamageDefs = false; // damagedefs + + // can't actually reload this atm, just print a warning (todo, could maybe restart client to ensure loaded?) + bool bModels = false; + + bool bRPaks = false; + + // assets that we can reload individually + //std::vector vAudioOverrides + } m_AssetTypesToReload; + + void CheckModFilesForChanges(); + void ReloadNecessaryModAssets(); + + + struct ModLoadState + { + std::vector m_LoadedMods; + std::unordered_map m_ModFiles; + std::unordered_map m_DependencyConstants; + }; + + // unfortunately need to be ptrs, so we can copy m_ModLoadState => m_LastModLoadState + // without ptrs to any elements (only ModOverrideFile::m_pOwningMod atm) decaying + // unfortunately means we need to manually manage memory of them, but oh well + ModLoadState* m_LastModLoadState; + ModLoadState* m_ModLoadState; public: ModManager(); void LoadMods(); - void UnloadMods(); std::string NormaliseModFilePath(const fs::path path); void CompileAssetsForFile(const char* filename); // getters inline std::vector& GetMods() { - return m_ModLoadState.m_LoadedMods; + return m_ModLoadState->m_LoadedMods; }; inline std::unordered_map& GetModFiles() { - return m_ModLoadState.m_ModFiles; + return m_ModLoadState->m_ModFiles; }; inline std::unordered_map& GetDependencyConstants() { - return m_ModLoadState.m_DependencyConstants; + return m_ModLoadState->m_DependencyConstants; }; // compile asset type stuff, these are done in files under runtime/compiled/ void BuildScriptsRson(); void TryBuildKeyValues(const char* filename); - void BuildPdef(); void BuildKBActionsList(); + + // for std::views::filter, e.g. for (Mod& mod : g_pModManager::GetMods() | ModManager::FilterEnabled) + static inline constexpr auto FilterEnabled = std::views::filter([](Mod& mod) { return mod.m_bEnabled; }); }; fs::path GetModFolderPath(); diff --git a/NorthstarDLL/scripts/client/scriptmodmenu.cpp b/NorthstarDLL/scripts/client/scriptmodmenu.cpp index 0beea4f2..6c0684e2 100644 --- a/NorthstarDLL/scripts/client/scriptmodmenu.cpp +++ b/NorthstarDLL/scripts/client/scriptmodmenu.cpp @@ -146,9 +146,9 @@ ADD_SQFUNC( { if (!mod.Name.compare(modName)) { - for (ModConVar* cvar : mod.ConVars) + for (ModConVar& cvar : mod.ConVars) { - g_pSquirrel->pushstring(sqvm, cvar->Name.c_str()); + g_pSquirrel->pushstring(sqvm, cvar.Name.c_str()); g_pSquirrel->arrayappend(sqvm, -2); } diff --git a/NorthstarDLL/server/auth/bansystem.cpp b/NorthstarDLL/server/auth/bansystem.cpp index 56719ed9..ab855622 100644 --- a/NorthstarDLL/server/auth/bansystem.cpp +++ b/NorthstarDLL/server/auth/bansystem.cpp @@ -17,7 +17,7 @@ ServerBanSystem* g_pBanSystem; void ServerBanSystem::OpenBanlist() { - std::ifstream banlistStream(GetNorthstarPrefix() + "/banlist.txt"); + std::ifstream banlistStream(GetNorthstarPrefix() / "banlist.txt"); if (!banlistStream.fail()) { @@ -47,12 +47,12 @@ void ServerBanSystem::OpenBanlist() } // open write stream for banlist // dont do this to allow for all time access - // m_sBanlistStream.open(GetNorthstarPrefix() + "/banlist.txt", std::ofstream::out | std::ofstream::binary | std::ofstream::app); + // m_sBanlistStream.open(GetNorthstarPrefix() / "banlist.txt", std::ofstream::out | std::ofstream::binary | std::ofstream::app); } void ServerBanSystem::ReloadBanlist() { - std::ifstream fsBanlist(GetNorthstarPrefix() + "/banlist.txt"); + std::ifstream fsBanlist(GetNorthstarPrefix() / "banlist.txt"); if (!fsBanlist.fail()) { @@ -72,18 +72,18 @@ void ServerBanSystem::ClearBanlist() // reopen the file, don't provide std::ofstream::app so it clears on open m_sBanlistStream.close(); - m_sBanlistStream.open(GetNorthstarPrefix() + "/banlist.txt", std::ofstream::out | std::ofstream::binary); + m_sBanlistStream.open(GetNorthstarPrefix() / "banlist.txt", std::ofstream::out | std::ofstream::binary); m_sBanlistStream.close(); } void ServerBanSystem::BanUID(uint64_t uid) { // checking if last char is \n to make sure uids arent getting fucked - std::ifstream fsBanlist(GetNorthstarPrefix() + "/banlist.txt"); + std::ifstream fsBanlist(GetNorthstarPrefix() / "banlist.txt"); std::string content((std::istreambuf_iterator(fsBanlist)), (std::istreambuf_iterator())); fsBanlist.close(); - m_sBanlistStream.open(GetNorthstarPrefix() + "/banlist.txt", std::ofstream::out | std::ofstream::binary | std::ofstream::app); + m_sBanlistStream.open(GetNorthstarPrefix() / "banlist.txt", std::ofstream::out | std::ofstream::binary | std::ofstream::app); if (content.back() != '\n') m_sBanlistStream << std::endl; @@ -102,7 +102,7 @@ void ServerBanSystem::UnbanUID(uint64_t uid) m_vBannedUids.erase(findResult); std::vector banlistText; - std::ifstream fs_readBanlist(GetNorthstarPrefix() + "/banlist.txt"); + std::ifstream fs_readBanlist(GetNorthstarPrefix() / "banlist.txt"); if (!fs_readBanlist.fail()) { @@ -164,7 +164,7 @@ void ServerBanSystem::UnbanUID(uint64_t uid) // open write stream for banlist // without append so we clear the file if (m_sBanlistStream.is_open()) m_sBanlistStream.close(); - m_sBanlistStream.open(GetNorthstarPrefix() + "/banlist.txt", std::ofstream::out | std::ofstream::binary); + m_sBanlistStream.open(GetNorthstarPrefix() / "banlist.txt", std::ofstream::out | std::ofstream::binary); for (std::string updatedLine : banlistText) m_sBanlistStream << updatedLine << std::endl; diff --git a/NorthstarDLL/squirrel/squirrel.cpp b/NorthstarDLL/squirrel/squirrel.cpp index 0bbc496f..0c538b21 100644 --- a/NorthstarDLL/squirrel/squirrel.cpp +++ b/NorthstarDLL/squirrel/squirrel.cpp @@ -194,11 +194,8 @@ template void SquirrelManager::VMCreated(CSquir { bool bWasFound = false; - for (Mod& dependency : g_pModManager->GetMods()) + for (Mod& dependency : g_pModManager->GetMods() | ModManager::FilterEnabled) { - if (!dependency.m_bEnabled) - continue; - if (dependency.Name == pair.second) { bWasFound = true; @@ -208,6 +205,7 @@ template void SquirrelManager::VMCreated(CSquir defconst(m_pSQVM, pair.first.c_str(), bWasFound); } + g_pSquirrel->messageBuffer = new SquirrelMessageBuffer(); } @@ -218,7 +216,7 @@ template void SquirrelManager::VMDestroyed() { NS::log::squirrel_logger()->info("Calling Destroy callbacks for all loaded mods."); - for (const Mod& loadedMod : g_pModManager->m_LoadedMods) + for (const Mod& loadedMod : g_pModManager->GetMods()) { for (const ModScript& script : loadedMod.Scripts) { @@ -472,11 +470,8 @@ template bool __fastcall CallScriptInitCallbackHook(void if (bShouldCallCustomCallbacks) { - for (Mod& mod : g_pModManager->GetMods()) + for (Mod& mod : g_pModManager->GetMods() | ModManager::FilterEnabled) { - if (!mod.m_bEnabled) - continue; - for (ModScript script : mod.Scripts) { for (ModScriptCallback modCallback : script.Callbacks) @@ -499,11 +494,8 @@ template bool __fastcall CallScriptInitCallbackHook(void // run after callbacks if (bShouldCallCustomCallbacks) { - for (Mod& mod : g_pModManager->GetMods()) + for (Mod& mod : g_pModManager->GetMods() | ModManager::FilterEnabled) { - if (!mod.m_bEnabled) - continue; - for (ModScript script : mod.Scripts) { for (ModScriptCallback modCallback : script.Callbacks) -- cgit v1.2.3 From 9dec8feed649024b04af82b4bf2875d1d4705664 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 13 Feb 2023 16:29:29 +0000 Subject: local commit for merging main --- NorthstarDLL/engine/hoststate.cpp | 4 ++++ NorthstarDLL/mods/modmanager.cpp | 2 +- NorthstarDLL/mods/modmanager.h | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/NorthstarDLL/engine/hoststate.cpp b/NorthstarDLL/engine/hoststate.cpp index 4900539b..d89e6c12 100644 --- a/NorthstarDLL/engine/hoststate.cpp +++ b/NorthstarDLL/engine/hoststate.cpp @@ -134,6 +134,10 @@ void, __fastcall, (CHostState* self)) g_pServerPresence->SetMap(g_pHostState->m_levelName); } +// TODO: due to exception in code for singleplayer (see connect command) this func will never run +// when connect is ran on the ip localhost, while connected to a local listen server +// i dont really care + // clang-format off AUTOHOOK(CHostState__State_GameShutdown, engine.dll + 0x16E640, void, __fastcall, (CHostState* self)) diff --git a/NorthstarDLL/mods/modmanager.cpp b/NorthstarDLL/mods/modmanager.cpp index 1c3c416d..d2321583 100644 --- a/NorthstarDLL/mods/modmanager.cpp +++ b/NorthstarDLL/mods/modmanager.cpp @@ -1039,7 +1039,7 @@ void ModManager::SaveEnabledMods() enabledModsCfg.SetObject(); // add values - for (Mod& mod : GetMods()) + for (Mod& mod : GetMods() | FilterLocal) enabledModsCfg.AddMember(rapidjson_document::StringRefType(mod.Name.c_str()), mod.m_bEnabled, enabledModsCfg.GetAllocator()); // write diff --git a/NorthstarDLL/mods/modmanager.h b/NorthstarDLL/mods/modmanager.h index 0bb696e8..ea5c4105 100644 --- a/NorthstarDLL/mods/modmanager.h +++ b/NorthstarDLL/mods/modmanager.h @@ -225,6 +225,8 @@ class ModManager // for std::views::filter, e.g. for (Mod& mod : g_pModManager::GetMods() | ModManager::FilterEnabled) static inline constexpr auto FilterEnabled = std::views::filter([](Mod& mod) { return mod.m_bEnabled; }); + static inline constexpr auto FilterRemote = std::views::filter([](Mod& mod) { return mod.m_bRemote; }); + static inline constexpr auto FilterLocal = std::views::filter([](Mod& mod) { return !mod.m_bRemote; }); }; fs::path GetModFolderPath(); -- cgit v1.2.3 From d2079959609f7d1ca444a5903bbdc9f87d7aaa6d Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 14 Feb 2023 13:34:48 +0000 Subject: temp commit for changing branch --- NorthstarDLL/core/filesystem/filesystem.cpp | 2 -- NorthstarDLL/engine/r2engine.h | 2 +- NorthstarDLL/mods/modmanager.cpp | 46 +++++++++++++++++++++++++++-- NorthstarDLL/mods/modmanager.h | 1 + NorthstarDLL/shared/misccommands.cpp | 18 +++++++++++ 5 files changed, 64 insertions(+), 5 deletions(-) diff --git a/NorthstarDLL/core/filesystem/filesystem.cpp b/NorthstarDLL/core/filesystem/filesystem.cpp index 8ca73130..6cff7927 100644 --- a/NorthstarDLL/core/filesystem/filesystem.cpp +++ b/NorthstarDLL/core/filesystem/filesystem.cpp @@ -74,8 +74,6 @@ void SetNewModSearchPaths(Mod* mod) { if ((fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().compare(sCurrentModPath)) { - NS::log::fs->info("Changing mod search path from {} to {}", sCurrentModPath, mod->m_ModDirectory.string()); - AddSearchPath( &*(*g_pFilesystem), (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD); sCurrentModPath = (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string(); diff --git a/NorthstarDLL/engine/r2engine.h b/NorthstarDLL/engine/r2engine.h index ff8876b8..edf49890 100644 --- a/NorthstarDLL/engine/r2engine.h +++ b/NorthstarDLL/engine/r2engine.h @@ -245,7 +245,7 @@ namespace R2 FIELDS(0x3C, // Simulation ticks - does not increase when game is paused - uint32_t m_nTickCount; // this is weird and doesn't seem to increase once per frame? + DWORD m_nTickCount; // this is weird and doesn't seem to increase once per frame? // Simulation tick interval float m_flTickInterval; diff --git a/NorthstarDLL/mods/modmanager.cpp b/NorthstarDLL/mods/modmanager.cpp index d2321583..40ed9f3e 100644 --- a/NorthstarDLL/mods/modmanager.cpp +++ b/NorthstarDLL/mods/modmanager.cpp @@ -940,6 +940,12 @@ void ModManager::CheckModFilesForChanges() m_AssetTypesToReload.bDamageDefs = true; continue; } + + if (m_AssetTypesToReload.bDatatables && !pChangedFile->m_Path.parent_path().compare("scripts/datatable/")) + { + m_AssetTypesToReload.bDatatables = true; + continue; + } } } @@ -988,6 +994,11 @@ void ModManager::ReloadNecessaryModAssets() if (m_AssetTypesToReload.bAimAssistSettings) vReloadCommands.push_back("ReloadAimAssistSettings"); + if (m_AssetTypesToReload.bDatatables) + { + // TODO: clear disk datatable cache in scriptdatatables.cpp + } + // need to reimplement mat_reloadmaterials for this //if (m_AssetTypesToReload.bMaterials) // R2::Cbuf_AddText(R2::Cbuf_GetCurrentPlayer(), "mat_reloadmaterials", R2::cmd_source_t::kCommandSrcCode); @@ -1007,6 +1018,14 @@ void ModManager::ReloadNecessaryModAssets() } R2::Cbuf_Execute(); + + // reset everything we've already reloaded at this point + m_AssetTypesToReload.bUiScript = false; + m_AssetTypesToReload.bLocalisation = false; + m_AssetTypesToReload.bPlaylists = false; + m_AssetTypesToReload.bAimAssistSettings = false; + m_AssetTypesToReload.bDatatables = false; + m_AssetTypesToReload.bModels = false; } void ModManager::InstallMods() @@ -1116,11 +1135,32 @@ void ModManager::CompileAssetsForFile(const char* filename) } } -void ConCommand_reload_mods(const CCommand& args) +void ConCommand_mods_reload(const CCommand& args) { g_pModManager->LoadMods(); } +void ConCommand_mods_getfileowner(const CCommand& args) +{ + if (args.ArgC() < 2) + { + spdlog::warn("usage: mods_getfileowner path/to/file.mdl"); + return; + } + + auto findFile = g_pModManager->GetModFiles().find(g_pModManager->NormaliseModFilePath(args.Arg(1))); + if (findFile != g_pModManager->GetModFiles().end()) + { + // this can be null if asset is compiled! + if (findFile->second.m_pOwningMod != nullptr) + spdlog::info("file \"{}\" is owned by mod {}", args.Arg(1), findFile->second.m_pOwningMod->Name); + else + spdlog::info("file \"{}\" is overriden by a runtime compiled asset", args.Arg(1)); + } + else + spdlog::warn("file not override not found"); +} + fs::path GetModFolderPath() { return GetNorthstarPrefix() / MOD_FOLDER_SUFFIX; @@ -1138,5 +1178,7 @@ ON_DLL_LOAD_RELIESON("engine.dll", ModManager, (ConCommand, MasterServer), (CMod { g_pModManager = new ModManager; - RegisterConCommand("reload_mods", ConCommand_reload_mods, "reloads mods", FCVAR_NONE); + RegisterConCommand("reload_mods", ConCommand_mods_reload, "reloads mods", FCVAR_NONE); + RegisterConCommand("mods_reload", ConCommand_mods_reload, "reloads mods", FCVAR_NONE); + RegisterConCommand("mods_getfileowner", ConCommand_mods_getfileowner, "find the mod that owns a given file", FCVAR_NONE); } diff --git a/NorthstarDLL/mods/modmanager.h b/NorthstarDLL/mods/modmanager.h index ea5c4105..253b7812 100644 --- a/NorthstarDLL/mods/modmanager.h +++ b/NorthstarDLL/mods/modmanager.h @@ -169,6 +169,7 @@ class ModManager bool bPlayerSettings = false; bool bAiSettings = false; bool bDamageDefs = false; // damagedefs + bool bDatatables = false; // can't actually reload this atm, just print a warning (todo, could maybe restart client to ensure loaded?) bool bModels = false; diff --git a/NorthstarDLL/shared/misccommands.cpp b/NorthstarDLL/shared/misccommands.cpp index f3817867..5894f5a2 100644 --- a/NorthstarDLL/shared/misccommands.cpp +++ b/NorthstarDLL/shared/misccommands.cpp @@ -11,6 +11,8 @@ #include "server/auth/serverauthentication.h" #include "squirrel/squirrel.h" +AUTOHOOK_INIT() + void ConCommand_force_newgame(const CCommand& arg) { if (arg.ArgC() < 2) @@ -141,6 +143,22 @@ void AddMiscConCommands() RegisterConCommand("cvar_reset", ConCommand_cvar_reset, "resets a cvar's value to its default value", FCVAR_NONE); } +AUTOHOOK(Connect_f, engine.dll + 0x76720, void, , (const CCommand& arg)) +{ + // tickcount number is weird, but consistent! + // todo this check sucks + + if (*R2::g_pServerState != R2::server_state_t::ss_dead && R2::g_pGlobals->m_nTickCount != 60 && !strncmp(arg.Arg(1), "localhost", 9)) + strncpy(const_cast(arg.GetCommandString() + 8), "127.0.0.1", 9); + + Connect_f(arg); +} + +ON_DLL_LOAD("engine.dll", connecttest, (CModule module)) +{ + AUTOHOOK_DISPATCH() +} + // fixes up various cvar flags to have more sane values void FixupCvarFlags() { -- cgit v1.2.3 From d811af85e0740d1ea9635ecc16d809050e79f2ce Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 14 Feb 2023 14:47:21 +0000 Subject: remove modpdef --- NorthstarDLL/mods/compiled/modpdef.cpp | 118 --------------------------------- 1 file changed, 118 deletions(-) delete mode 100644 NorthstarDLL/mods/compiled/modpdef.cpp diff --git a/NorthstarDLL/mods/compiled/modpdef.cpp b/NorthstarDLL/mods/compiled/modpdef.cpp deleted file mode 100644 index 4b1b12b7..00000000 --- a/NorthstarDLL/mods/compiled/modpdef.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "mods/modmanager.h" -#include "core/filesystem/filesystem.h" - -#include -#include -#include - -const fs::path MOD_PDEF_SUFFIX = "cfg/server/persistent_player_data_version_231.pdef"; -const char* VPK_PDEF_PATH = "cfg/server/persistent_player_data_version_231.pdef"; - -void ModManager::BuildPdef() -{ - spdlog::info("Building persistent_player_data_version_231.pdef..."); - - fs::path MOD_PDEF_PATH = fs::path(GetCompiledAssetsPath() / MOD_PDEF_SUFFIX); - - fs::remove(MOD_PDEF_PATH); - std::string pdef = R2::ReadVPKOriginalFile(VPK_PDEF_PATH); - - for (Mod& mod : m_LoadedMods) - { - if (!mod.m_bEnabled || !mod.Pdiff.size()) - continue; - - // this code probably isn't going to be pretty lol - // refer to shared/pjson.js for an actual okish parser of the pdiff format - // but pretty much, $ENUM_ADD blocks define members added to preexisting enums - // $PROP_START ends the custom stuff, and from there it's just normal props we append to the pdef - - std::map> enumAdds; - - // read pdiff - bool inEnum = false; - bool inProp = false; - std::string currentEnum; - std::string currentLine; - std::istringstream pdiffStream(mod.Pdiff); - - while (std::getline(pdiffStream, currentLine)) - { - if (inProp) - { - // just append to pdef here - pdef += currentLine; - pdef += '\n'; - continue; - } - - // trim leading whitespace - size_t start = currentLine.find_first_not_of(" \n\r\t\f\v"); - size_t end = currentLine.find("//"); - if (end == std::string::npos) - end = currentLine.size() - 1; // last char - - if (!currentLine.size() || !currentLine.compare(start, 2, "//")) - continue; - - if (inEnum) - { - if (!currentLine.compare(start, 9, "$ENUM_END")) - inEnum = false; - else - enumAdds[currentEnum].push_back(currentLine); // only need to push_back current line, if there's syntax errors then game - // pdef parser will handle them - } - else if (!currentLine.compare(start, 9, "$ENUM_ADD")) - { - inEnum = true; - currentEnum = currentLine.substr(start + 10 /*$ENUM_ADD + 1*/, currentLine.size() - end - (start + 10)); - enumAdds.insert(std::make_pair(currentEnum, std::vector())); - } - else if (!currentLine.compare(start, 11, "$PROP_START")) - { - inProp = true; - pdef += "\n// $PROP_START "; - pdef += mod.Name; - pdef += "\n"; - } - } - - // add new members to preexisting enums - // note: this code could 100% be messed up if people put //$ENUM_START comments and the like - // could make it protect against this, but honestly not worth atm - for (auto enumAdd : enumAdds) - { - std::string addStr; - for (std::string enumMember : enumAdd.second) - { - addStr += enumMember; - addStr += '\n'; - } - - // start of enum we're adding to - std::string startStr = "$ENUM_START "; - startStr += enumAdd.first; - - // insert enum values into enum - size_t insertIdx = pdef.find("$ENUM_END", pdef.find(startStr)); - pdef.reserve(addStr.size()); - pdef.insert(insertIdx, addStr); - } - } - - fs::create_directories(MOD_PDEF_PATH.parent_path()); - - std::ofstream writeStream(MOD_PDEF_PATH, std::ios::binary); - writeStream << pdef; - writeStream.close(); - - ModOverrideFile overrideFile; - overrideFile.m_pOwningMod = nullptr; - overrideFile.m_Path = VPK_PDEF_PATH; - - if (m_ModFiles.find(VPK_PDEF_PATH) == m_ModFiles.end()) - m_ModFiles.insert(std::make_pair(VPK_PDEF_PATH, overrideFile)); - else - m_ModFiles[VPK_PDEF_PATH] = overrideFile; -} -- cgit v1.2.3