aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NorthstarDLL/NorthstarDLL.vcxproj3
-rw-r--r--NorthstarDLL/NorthstarDLL.vcxproj.filters12
-rw-r--r--NorthstarDLL/client/audio.h2
-rw-r--r--NorthstarDLL/client/clientvideooverrides.cpp13
-rw-r--r--NorthstarDLL/client/modlocalisation.cpp14
-rw-r--r--NorthstarDLL/config/profile.cpp4
-rw-r--r--NorthstarDLL/config/profile.h4
-rw-r--r--NorthstarDLL/core/convar/concommand.cpp8
-rw-r--r--NorthstarDLL/core/convar/concommand.h5
-rw-r--r--NorthstarDLL/core/convar/convar.cpp2
-rw-r--r--NorthstarDLL/core/filesystem/filesystem.cpp11
-rw-r--r--NorthstarDLL/core/filesystem/rpakfilesystem.cpp35
-rw-r--r--NorthstarDLL/dllmain.cpp2
-rw-r--r--NorthstarDLL/engine/hoststate.cpp4
-rw-r--r--NorthstarDLL/engine/r2engine.h2
-rw-r--r--NorthstarDLL/logging/crashhandler.cpp13
-rw-r--r--NorthstarDLL/logging/logging.cpp2
-rw-r--r--NorthstarDLL/mods/compiled/kb_act.cpp11
-rw-r--r--NorthstarDLL/mods/compiled/modkeyvalues.cpp31
-rw-r--r--NorthstarDLL/mods/compiled/modpdef.cpp118
-rw-r--r--NorthstarDLL/mods/compiled/modscriptsrson.cpp11
-rw-r--r--NorthstarDLL/mods/modmanager.cpp1041
-rw-r--r--NorthstarDLL/mods/modmanager.h138
-rw-r--r--NorthstarDLL/mods/reload/reloadmodweapons.cpp124
-rw-r--r--NorthstarDLL/mods/reload/reloadmodweapons_misc.cpp45
-rw-r--r--NorthstarDLL/pch.h2
-rw-r--r--NorthstarDLL/scripts/client/scriptmodmenu.cpp28
-rw-r--r--NorthstarDLL/server/auth/bansystem.cpp16
-rw-r--r--NorthstarDLL/shared/misccommands.cpp18
-rw-r--r--NorthstarDLL/squirrel/squirrel.cpp20
-rw-r--r--NorthstarDLL/squirrel/squirrel.h8
-rw-r--r--NorthstarDLL/util/printmaps.cpp2
32 files changed, 1131 insertions, 618 deletions
diff --git a/NorthstarDLL/NorthstarDLL.vcxproj b/NorthstarDLL/NorthstarDLL.vcxproj
index 7aa68841..3ed99d25 100644
--- a/NorthstarDLL/NorthstarDLL.vcxproj
+++ b/NorthstarDLL/NorthstarDLL.vcxproj
@@ -500,9 +500,10 @@
<ClCompile Include="masterserver\masterserver.cpp" />
<ClCompile Include="mods\compiled\kb_act.cpp" />
<ClCompile Include="mods\compiled\modkeyvalues.cpp" />
- <ClCompile Include="mods\compiled\modpdef.cpp" />
<ClCompile Include="mods\compiled\modscriptsrson.cpp" />
<ClCompile Include="mods\modmanager.cpp" />
+ <ClCompile Include="mods\reload\reloadmodweapons.cpp" />
+ <ClCompile Include="mods\reload\reloadmodweapons_misc.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
diff --git a/NorthstarDLL/NorthstarDLL.vcxproj.filters b/NorthstarDLL/NorthstarDLL.vcxproj.filters
index bb880580..10f6d796 100644
--- a/NorthstarDLL/NorthstarDLL.vcxproj.filters
+++ b/NorthstarDLL/NorthstarDLL.vcxproj.filters
@@ -181,6 +181,9 @@
<Filter Include="Header Files\shared\exploit_fixes">
<UniqueIdentifier>{1a377c09-bd3d-4757-b3bc-9cd0a1e6ac0d}</UniqueIdentifier>
</Filter>
+ <Filter Include="Source Files\mods\reload">
+ <UniqueIdentifier>{f7aff0ef-a51c-4053-866a-4e5e3db71fce}</UniqueIdentifier>
+ </Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\httplib.h">
@@ -1243,9 +1246,6 @@
<ClCompile Include="mods\compiled\modkeyvalues.cpp">
<Filter>Source Files\mods\compiled</Filter>
</ClCompile>
- <ClCompile Include="mods\compiled\modpdef.cpp">
- <Filter>Source Files\mods\compiled</Filter>
- </ClCompile>
<ClCompile Include="mods\compiled\modscriptsrson.cpp">
<Filter>Source Files\mods\compiled</Filter>
</ClCompile>
@@ -1435,6 +1435,12 @@
<ClCompile Include="dedicated\dedicatedlogtoclient.cpp">
<Filter>Source Files\dedicated</Filter>
</ClCompile>
+ <ClCompile Include="mods\reload\reloadmodweapons.cpp">
+ <Filter>Source Files\mods\reload</Filter>
+ </ClCompile>
+ <ClCompile Include="mods\reload\reloadmodweapons_misc.cpp">
+ <Filter>Source Files\mods\reload</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<MASM Include="audio_asm.asm">
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 <vector>
-#include <filesystem>
#include <regex>
#include <shared_mutex>
diff --git a/NorthstarDLL/client/clientvideooverrides.cpp b/NorthstarDLL/client/clientvideooverrides.cpp
index d8aa2754..a6178cfc 100644
--- a/NorthstarDLL/client/clientvideooverrides.cpp
+++ b/NorthstarDLL/client/clientvideooverrides.cpp
@@ -11,20 +11,17 @@ 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() | ModManager::FilterEnabled)
{
- 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 2b73876b..16fff5f7 100644
--- a/NorthstarDLL/client/modlocalisation.cpp
+++ b/NorthstarDLL/client/modlocalisation.cpp
@@ -25,10 +25,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->m_LoadedMods)
- 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);
@@ -43,10 +42,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->m_LoadedMods)
- 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 08c91208..80ddb59f 100644
--- a/NorthstarDLL/config/profile.cpp
+++ b/NorthstarDLL/config/profile.cpp
@@ -2,7 +2,7 @@
#include "dedicated/dedicated.h"
#include <string>
-std::string GetNorthstarPrefix()
+fs::path GetNorthstarPrefix()
{
return NORTHSTAR_FOLDER_PREFIX;
}
@@ -39,5 +39,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 <string>
-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 67c867f8..fb5e137b 100644
--- a/NorthstarDLL/core/convar/concommand.cpp
+++ b/NorthstarDLL/core/convar/concommand.cpp
@@ -124,16 +124,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);
@@ -142,6 +144,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 89363bc7..81c3cf6d 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;
@@ -135,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/convar/convar.cpp b/NorthstarDLL/core/convar/convar.cpp
index 21fca8c0..b9c4173e 100644
--- a/NorthstarDLL/core/convar/convar.cpp
+++ b/NorthstarDLL/core/convar/convar.cpp
@@ -526,4 +526,6 @@ int ParseConVarFlagsString(std::string modName, std::string sFlags)
sCurrentFlag += sFlags[i];
}
}
+
+ return FCVAR_NONE;
}
diff --git a/NorthstarDLL/core/filesystem/filesystem.cpp b/NorthstarDLL/core/filesystem/filesystem.cpp
index 88622e5d..b06bd140 100644
--- a/NorthstarDLL/core/filesystem/filesystem.cpp
+++ b/NorthstarDLL/core/filesystem/filesystem.cpp
@@ -73,8 +73,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();
@@ -94,8 +92,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;
@@ -146,11 +144,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->m_LoadedMods)
+ 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 c863463c..26afda06 100644
--- a/NorthstarDLL/core/filesystem/rpakfilesystem.cpp
+++ b/NorthstarDLL/core/filesystem/rpakfilesystem.cpp
@@ -89,15 +89,11 @@ 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() | ModManager::FilterEnabled | std::views::reverse)
{
- Mod* mod = &g_pModManager->m_LoadedMods[i];
- 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;
}
}
@@ -106,11 +102,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->m_LoadedMods)
+ 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 +116,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->m_LoadedMods)
+ 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");
@@ -143,11 +133,8 @@ 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() | 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");
@@ -272,8 +259,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();
@@ -304,12 +291,8 @@ 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() | ModManager::FilterEnabled)
{
- // ignore non-loaded mods
- if (!mod.m_bEnabled)
- continue;
-
// loop through the stored starpak paths
for (size_t hash : mod.StarpakPaths)
{
diff --git a/NorthstarDLL/dllmain.cpp b/NorthstarDLL/dllmain.cpp
index a697da5b..91fb94a1 100644
--- a/NorthstarDLL/dllmain.cpp
+++ b/NorthstarDLL/dllmain.cpp
@@ -45,7 +45,7 @@ bool LoadPlugins()
std::vector<fs::path> 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/engine/hoststate.cpp b/NorthstarDLL/engine/hoststate.cpp
index 44a66113..ec4733d6 100644
--- a/NorthstarDLL/engine/hoststate.cpp
+++ b/NorthstarDLL/engine/hoststate.cpp
@@ -133,6 +133,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/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/logging/crashhandler.cpp b/NorthstarDLL/logging/crashhandler.cpp
index 5d120d1f..36f8b714 100644
--- a/NorthstarDLL/logging/crashhandler.cpp
+++ b/NorthstarDLL/logging/crashhandler.cpp
@@ -33,18 +33,15 @@ void PrintExceptionLog(ExceptionLog& exc)
if (g_pModManager)
{
spdlog::error("Loaded mods: ");
- for (const auto& mod : g_pModManager->m_LoadedMods)
- {
- 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:");
@@ -237,7 +234,7 @@ void CreateMiniDump(EXCEPTION_POINTERS* exceptionInfo)
time_t time = std::time(nullptr);
tm currentTime = *std::localtime(&time);
std::stringstream stream;
- stream << std::put_time(&currentTime, (GetNorthstarPrefix() + "/logs/nsdump%Y-%m-%d %H-%M-%S.dmp").c_str());
+ stream << std::put_time(&currentTime, (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 f88928e1..edf122be 100644
--- a/NorthstarDLL/logging/logging.cpp
+++ b/NorthstarDLL/logging/logging.cpp
@@ -46,7 +46,7 @@ void CreateLogFiles()
tm currentTime = *std::localtime(&time);
std::stringstream stream;
- stream << std::put_time(&currentTime, (GetNorthstarPrefix() + "/logs/nslog%Y-%m-%d %H-%M-%S.txt").c_str());
+ stream << std::put_time(&currentTime, (GetNorthstarPrefix() / "logs/nslog%Y-%m-%d %H-%M-%S.txt").string().c_str());
auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(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 3fc7ee30..ecfb8388 100644
--- a/NorthstarDLL/mods/compiled/kb_act.cpp
+++ b/NorthstarDLL/mods/compiled/kb_act.cpp
@@ -16,11 +16,8 @@ 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() | 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");
@@ -37,8 +34,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 fe262a60..37ee0ab2 100644
--- a/NorthstarDLL/mods/compiled/modkeyvalues.cpp
+++ b/NorthstarDLL/mods/compiled/modkeyvalues.cpp
@@ -24,29 +24,18 @@ 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--)
+ auto findKv = m_ModLoadState->m_KeyValues.find(filename);
+ if (findKv != m_ModLoadState->m_KeyValues.end())
{
- if (!m_LoadedMods[i].m_bEnabled)
- continue;
-
- size_t fileHash = STR_HASH(normalisedPath);
- auto modKv = m_LoadedMods[i].KeyValues.find(fileHash);
- if (modKv != m_LoadedMods[i].KeyValues.end())
+ for (ModOverrideFile& modKv : findKv->second)
{
- // should result in smth along the lines of #include "mod_patch_5_mp_weapon_car.txt"
-
- std::string patchFilePath = "mod_patch_";
- patchFilePath += std::to_string(patchNum++);
- patchFilePath += "_";
- patchFilePath += kvPath.filename().string();
-
- newKvs += "#base \"";
- newKvs += patchFilePath;
- newKvs += "\"\n";
+ // should result in smth along the lines of #include "_mod_patch_5_mp_weapon_car.txt"
+ std::string patchFilePath = fmt::format("_mod_patch_{}_{}", patchNum++, kvPath.filename().string());
fs::remove(compiledDir / patchFilePath);
+ fs::copy_file(modKv.m_pOwningMod->m_ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath);
- fs::copy_file(m_LoadedMods[i].m_ModDirectory / "keyvalues" / filename, compiledDir / patchFilePath);
+ newKvs += fmt::format("#base \"{}\"\n", patchFilePath);
}
}
@@ -99,8 +88,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 4b1b12b7..00000000
--- a/NorthstarDLL/mods/compiled/modpdef.cpp
+++ /dev/null
@@ -1,118 +0,0 @@
-#include "mods/modmanager.h"
-#include "core/filesystem/filesystem.h"
-
-#include <map>
-#include <sstream>
-#include <fstream>
-
-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<std::string, std::vector<std::string>> 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<std::string>()));
- }
- 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 cbe26651..5e837a60 100644
--- a/NorthstarDLL/mods/compiled/modscriptsrson.cpp
+++ b/NorthstarDLL/mods/compiled/modscriptsrson.cpp
@@ -16,11 +16,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 : m_LoadedMods)
+ 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;
@@ -55,10 +52,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 bc0cb2ed..ab1ef791 100644
--- a/NorthstarDLL/mods/modmanager.cpp
+++ b/NorthstarDLL/mods/modmanager.cpp
@@ -2,10 +2,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"
@@ -19,14 +21,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<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(jsonBuf);
+ modJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(sJson);
// fail if parse error
if (modJson.HasParseError())
@@ -95,29 +98,27 @@ 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 = 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());
}
}
@@ -125,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())
@@ -135,40 +137,44 @@ 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 = 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());
}
}
+ // 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);
}
}
@@ -285,148 +291,186 @@ ModManager::ModManager()
);
m_hKBActHash = STR_HASH("scripts\\kb_act.lst");
- LoadMods();
-}
+ m_LastModLoadState = nullptr;
+ m_ModLoadState = new ModLoadState;
-struct Test
-{
- std::string funcName;
- ScriptContext context;
-};
+ LoadMods(false);
+}
template <ScriptContext context> auto ModConCommandCallback_Internal(std::string name, const CCommand& command)
{
- if (g_pSquirrel<context>->m_pSQVM && g_pSquirrel<context>->m_pSQVM)
+ if (g_pSquirrel<context>->m_pSQVM && g_pSquirrel<context>->m_pSQVM->sqvm)
{
- std::vector<std::string> args;
- args.reserve(command.ArgC());
+ std::vector<std::string> vArgs;
+ vArgs.reserve(command.ArgC());
for (int i = 1; i < command.ArgC(); i++)
- args.push_back(command.Arg(i));
- g_pSquirrel<context>->AsyncCall(name, args);
+ vArgs.push_back(command.Arg(i));
+
+ g_pSquirrel<context>->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() | ModManager::FilterEnabled)
{
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<ScriptContext::CLIENT>(found->Function, command);
+ ModConCommandCallback_Internal<ScriptContext::CLIENT>(pFoundCommand->Function, command);
break;
case ScriptContext::SERVER:
- ModConCommandCallback_Internal<ScriptContext::SERVER>(found->Function, command);
+ ModConCommandCallback_Internal<ScriptContext::SERVER>(pFoundCommand->Function, command);
break;
case ScriptContext::UI:
- ModConCommandCallback_Internal<ScriptContext::UI>(found->Function, command);
+ ModConCommandCallback_Internal<ScriptContext::UI>(pFoundCommand->Function, command);
break;
};
}
-void ModManager::LoadMods()
+
+
+
+void ModManager::LoadMods(bool bDeferredAssetReload)
{
+ // reset state of all currently loaded mods, if we've loaded once already
if (m_bHasLoadedMods)
UnloadMods();
- std::vector<fs::path> 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(bDeferredAssetReload);
+
+ // write json storing currently enabled mods
+ SaveEnabledMods();
+
+ // build public-facing mod list for masterserver
+ BuildPublicModList();
+
+ // don't need this anymore
+ delete m_LastModLoadState;
+ m_LastModLoadState = nullptr;
+
+ m_bHasLoadedMods = true;
+}
+
+void ModManager::LoadModDefinitions()
+{
+ bool bHasEnabledModsCfg = false;
+ rapidjson_document enabledModsCfg;
- if (!enabledModsStream.fail())
+ // read enabled mods cfg
{
- while (enabledModsStream.peek() != EOF)
- enabledModsStringStream << (char)enabledModsStream.get();
+ std::ifstream enabledModsStream(GetNorthstarPrefix() / "enabledmods.json");
+ std::stringstream enabledModsStringStream;
- enabledModsStream.close();
- m_EnabledModsCfg.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
- enabledModsStringStream.str().c_str());
+ if (!enabledModsStream.fail())
+ {
+ while (enabledModsStream.peek() != EOF)
+ enabledModsStringStream << (char)enabledModsStream.get();
- m_bHasEnabledModsCfg = m_EnabledModsCfg.IsObject();
+ enabledModsStream.close();
+ enabledModsCfg.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
+ enabledModsStringStream.str().c_str());
+
+ bHasEnabledModsCfg = enabledModsCfg.IsObject();
+ }
}
- // get mod directories
- std::filesystem::directory_iterator classicModsDir = fs::directory_iterator(GetModFolderPath());
- std::filesystem::directory_iterator remoteModsDir = fs::directory_iterator(GetRemoteModFolderPath());
+ // get mod directories for both local and remote mods
+ std::vector<std::tuple<fs::path, bool>> vModDirs;
+ for (fs::directory_entry dir : fs::directory_iterator(GetModFolderPath()))
+ if (fs::exists(dir.path() / "mod.json"))
+ vModDirs.push_back({dir.path(), false});
- 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());
+ for (fs::directory_entry dir : fs::directory_iterator(GetRemoteModFolderPath()))
+ if (fs::exists(dir.path() / "mod.json"))
+ vModDirs.push_back({dir.path(), true});
- for (fs::path modDir : modDirs)
+ for (auto remoteOrLocalModDir : vModDirs)
{
- // read mod json file
- std::ifstream jsonStream(modDir / "mod.json");
- std::stringstream jsonStringStream;
+ fs::path modDir = std::get<0>(remoteOrLocalModDir);
+ bool bRemote = std::get<1>(remoteOrLocalModDir);
+
+ std::string sJsonString;
- // fail if no mod json
- if (jsonStream.fail())
+ // read mod json file
{
- spdlog::warn("Mod {} has a directory but no mod.json", modDir.string());
- continue;
- }
+ std::stringstream jsonStringStream;
+ std::ifstream jsonStream(modDir / "mod.json");
- while (jsonStream.peek() != EOF)
- jsonStringStream << (char)jsonStream.get();
+ // fail if no mod json
+ if (jsonStream.fail())
+ {
+ spdlog::warn("Mod {} has a directory but no mod.json", modDir.string());
+ continue;
+ }
+
+ while (jsonStream.peek() != EOF)
+ jsonStringStream << (char)jsonStream.get();
- jsonStream.close();
+ jsonStream.close();
+ sJsonString = jsonStringStream.str();
+ }
- Mod mod(modDir, (char*)jsonStringStream.str().c_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)
{
@@ -436,272 +480,629 @@ 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());
}
// 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)
+#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 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, if it is then modify its flags, helpstring etc
+ if (!pVar)
{
- // 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()))
- {
- new ConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.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());
+
+ int nDefaultValueSize = convar.DefaultValue.size();
+ char* pDefaultValue = new char[nDefaultValueSize + 1];
+ strncpy_s(pDefaultValue, nDefaultValueSize + 1, convar.DefaultValue.c_str(), convar.DefaultValue.size());
- for (ModConCommand* command : mod.ConCommands)
+ int nHelpSize = convar.HelpString.size();
+ char* pHelpString = new char[nHelpSize + 1];
+ strncpy_s(pHelpString, nHelpSize + 1, convar.HelpString.c_str(), convar.HelpString.size());
+
+ pVar = new ConVar(pName, pDefaultValue, convar.Flags, pHelpString);
+ m_RegisteredModConVars.insert(pVar);
+ }
+ 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))
{
- ConCommand* newCommand = new ConCommand();
- 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());
+
+ // delete old, assign new
+ delete pVar->m_ConCommandBase.m_pszHelpString;
+ pVar->m_ConCommandBase.m_pszHelpString = pNewHelpString;
+ }
- if (!vpkJsonStream.fail())
+ if (convar.DefaultValue.compare(pVar->m_pszDefaultValue))
{
- while (vpkJsonStream.peek() != EOF)
- vpkJsonStringStream << (char)vpkJsonStream.get();
+ bool bIsDefaultValue = !strcmp(pVar->GetString(), pVar->m_pszDefaultValue);
+
+ int nDefaultValueSize = convar.DefaultValue.size();
+ char* pNewDefaultValue = new char[nDefaultValueSize + 1];
+ strncpy_s(pNewDefaultValue, nDefaultValueSize + 1, convar.DefaultValue.c_str(), convar.DefaultValue.size());
- vpkJsonStream.close();
- dVpkJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
- vpkJsonStringStream.str().c_str());
+ // delete old, assign new
+ delete pVar->m_pszDefaultValue;
+ pVar->m_pszDefaultValue = pNewDefaultValue;
- bUseVPKJson = !dVpkJson.HasParseError() && dVpkJson.IsObject();
+ 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;
}
}
+ }
+}
- // read rpak paths
- if (fs::exists(mod.m_ModDirectory / "paks"))
+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;
+
+ 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();
- bool bUseRpakJson = false;
- rapidjson::Document dRpakJson;
+ vpkJsonStream.close();
+ dVpkJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
+ vpkJsonStringStream.str().c_str());
- if (!rpakJsonStream.fail())
+ bUseVPKJson = !dVpkJson.HasParseError() && dVpkJson.IsObject();
+ }
+
+ 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();
+
+ // this really fucking sucks but it'll work
+ std::string vpkName = formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3);
- rpakJsonStream.close();
- dRpakJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
- rpakJsonStringStream.str().c_str());
+ 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();
- bUseRpakJson = !dRpakJson.HasParseError() && dRpakJson.IsObject();
+ 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;
- // read pak aliases
- if (bUseRpakJson && dRpakJson.HasMember("Aliases") && dRpakJson["Aliases"].IsObject())
+ bool bUseRpakJson = false;
+ rapidjson::Document dRpakJson;
+
+ if (!rpakJsonStream.fail())
+ {
+ while (rpakJsonStream.peek() != EOF)
+ rpakJsonStringStream << (char)rpakJsonStream.get();
+
+ rpakJsonStream.close();
+ dRpakJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(
+ rpakJsonStringStream.str().c_str());
+
+ 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<char> 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<char> 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))
+ ModOverrideFile modKv;
+ modKv.m_pOwningMod = &mod;
+ modKv.m_Path = g_pModManager->NormaliseModFilePath(file.path().lexically_relative(mod.m_ModDirectory / "keyvalues"));
+ modKv.m_tLastWriteTime = fs::last_write_time(file);
+
+ m_ModLoadState->m_KeyValues[modKv.m_Path.string()].push_back(modKv);
+ }
+ }
+ }
+}
+
+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); // need real path for this
+ m_ModLoadState->m_ModFiles.insert(std::make_pair(path, modFile));
}
}
+ }
+}
+#pragma endregion
+
+void ModManager::CheckModFilesForChanges(ModAssetsToReload* pAssetsToReload)
+{
+ // 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
+ // d) the asset has been edited
+
+ std::vector<ModOverrideFile*> vpChangedFiles;
- // read bink video paths
- if (fs::exists(mod.m_ModDirectory / "media"))
+ // 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 (pAssetsToReload->bLocalisation)
+
+ if (!pAssetsToReload->bAimAssistSettings && pChangedFile->m_Path.parent_path().string().starts_with("cfg/aimassist/"))
+ {
+ pAssetsToReload->bAimAssistSettings = true;
+ continue;
+ }
+
+ if (!pAssetsToReload->bMaterials && pChangedFile->m_Path.parent_path().string().starts_with("materials/"))
{
- if (!g_CustomAudioManager.TryLoadAudioOverride(file.path()))
+ pAssetsToReload->bMaterials = true;
+ continue;
+ }
+
+ if (!pAssetsToReload->bUiScript)
+ {
+
+ // TODO: need to check whether any ui scripts have changed
+ // need to do this by calling LoadScriptsRson (client.dll+3177D0) and getting the list of scripts loaded from that maybe
+
+ if (pChangedFile->m_Path.parent_path().string().starts_with("resource/ui/"))
{
- spdlog::warn("Mod {} has an invalid audio def {}", mod.Name, file.path().filename().string());
+ pAssetsToReload->bUiScript = true;
continue;
}
}
}
+
+ if (!pAssetsToReload->bModels && pChangedFile->m_Path.parent_path().string().starts_with("models/"))
+ {
+ pAssetsToReload->bModels = true;
+ continue;
+ }
+
+ if (!pAssetsToReload->bPlayerSettings && pChangedFile->m_Path.parent_path().string().starts_with("scripts/players/"))
+ {
+ pAssetsToReload->bPlayerSettings = true;
+ continue;
+ }
+
+ // maybe also aibehaviour?
+ if (!pAssetsToReload->bAiSettings && pChangedFile->m_Path.parent_path().string().starts_with("scripts/aisettings/"))
+ {
+ pAssetsToReload->bAiSettings = true;
+ continue;
+ }
+
+ if (!pAssetsToReload->bDamageDefs && pChangedFile->m_Path.parent_path().string().starts_with("scripts/damage/"))
+ {
+ pAssetsToReload->bDamageDefs = true;
+ continue;
+ }
+
+ if (pAssetsToReload->bDatatables && pChangedFile->m_Path.parent_path().string().starts_with("scripts/datatable/"))
+ {
+ pAssetsToReload->bDatatables = 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--)
+ // keyvalues
{
- if (!m_LoadedMods[i].m_bEnabled)
- continue;
+ // check which file overrides have changed
+ // we need to trigger a reload of a given asset if
+ // a) the asset is being overriden by different mods than previously
+ // b) the asset has been edited
- if (fs::exists(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR))
+ std::vector<std::string> vsChangedFiles;
+
+ // check currently loaded mods for any removed or updated files vs last load
+ for (auto& filePair : m_ModLoadState->m_KeyValues)
{
- for (fs::directory_entry file : fs::recursive_directory_iterator(m_LoadedMods[i].m_ModDirectory / MOD_OVERRIDE_DIR))
+ auto findFile = m_LastModLoadState->m_KeyValues.find(filePair.first);
+ if (findFile == m_LastModLoadState->m_KeyValues.end() || findFile->second.size() != filePair.second.size())
+ vsChangedFiles.push_back(filePair.first);
+ else
{
- 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())
+ // check the actual override list to ensure it's the same files
+ // even if just file order has changed, we should still reload
+ for (int i = 0; i < filePair.second.size(); i++)
{
- ModOverrideFile modFile;
- modFile.m_pOwningMod = &m_LoadedMods[i];
- modFile.m_Path = path;
- m_ModFiles.insert(std::make_pair(path, modFile));
+ if (filePair.second[i].m_pOwningMod->m_ModDirectory != findFile->second[i].m_pOwningMod->m_ModDirectory)
+ {
+ vsChangedFiles.push_back(filePair.first);
+ break;
+ }
}
}
}
+
+ // check last load for any files removed
+ for (auto& filePair : m_LastModLoadState->m_KeyValues)
+ if (m_ModLoadState->m_KeyValues.find(filePair.first) == m_ModLoadState->m_KeyValues.end())
+ vsChangedFiles.push_back(filePair.first);
+
+ for (std::string& sChangedPath : vsChangedFiles)
+ {
+ fs::path fChangedPath(sChangedPath);
+
+ if (!pAssetsToReload->bPlaylists && fChangedPath == "playlists_v2.txt")
+ {
+ pAssetsToReload->bPlaylists = true;
+ continue;
+ }
+
+ if (!pAssetsToReload->bPlayerSettings && fChangedPath.parent_path().string().starts_with("scripts/players/"))
+ {
+ pAssetsToReload->bPlayerSettings = true;
+ continue;
+ }
+
+ if (!pAssetsToReload->bAiSettings && fChangedPath.parent_path().string().starts_with("scripts/aisettings/"))
+ {
+ pAssetsToReload->bAiSettings = true;
+ continue;
+ }
+
+ if (!pAssetsToReload->bDamageDefs && fChangedPath.parent_path().string().starts_with("scripts/damage/"))
+ {
+ pAssetsToReload->bDamageDefs = true;
+ continue;
+ }
+
+ if (!fChangedPath.parent_path().string().starts_with("scripts/weapons/"))
+ {
+ if (fChangedPath.filename() == "ammo_suck_behaviours.txt")
+ pAssetsToReload->bAmmoSuckBehaviours = true;
+ else if (fChangedPath.filename() == "springs.txt")
+ pAssetsToReload->bWeaponSprings = true;
+ else
+ pAssetsToReload->setsWeaponSettings.insert(fChangedPath.replace_extension().string());
+
+ continue;
+ }
+ }
}
+}
+void ModManager::ReloadNecessaryModAssets(bool bDeferred, const ModAssetsToReload* pAssetsToReload)
+{
+ std::vector<std::string> vReloadCommands;
+
+ if (pAssetsToReload->bLocalisation)
+ vReloadCommands.push_back("reload_localization");
+
+ // after we reload_localization, we need to loadPlaylists, to keep playlist localisation
+ if (pAssetsToReload->bPlaylists || pAssetsToReload->bLocalisation)
+ vReloadCommands.push_back("loadPlaylists");
+
+ if (pAssetsToReload->bUiScript)
+ vReloadCommands.push_back("uiscript_reset");
+
+ if (pAssetsToReload->bAimAssistSettings)
+ vReloadCommands.push_back("ReloadAimAssistSettings");
+
+ if (pAssetsToReload->bModels)
+ spdlog::warn("Need to reload models but can't without a restart!");
+
+ if (pAssetsToReload->bDatatables)
+ {
+ // TODO: clear disk datatable cache in scriptdatatables.cpp
+ }
+
+ // deferred - load files using engine functions where possible, on level load
+ if (bDeferred)
+ {
+ if (pAssetsToReload->bAimAssistSettings)
+ DeferredReloadADSPulls();
+
+ if (pAssetsToReload->bAmmoSuckBehaviours)
+ DeferredReloadAmmoSuckBehaviours();
+
+ if (pAssetsToReload->bDamageDefs)
+ DeferredReloadDamageFlags();
+
+ if (pAssetsToReload->bWeaponSprings)
+ DeferredReloadWeaponSprings();
+
+ DeferredReloadWeapons(pAssetsToReload->setsWeaponSettings);
+ }
+ else
+ {
+ // need to reimplement mat_reloadmaterials for this
+ // if (m_AssetTypesToReload.bMaterials)
+ // R2::Cbuf_AddText(R2::Cbuf_GetCurrentPlayer(), "mat_reloadmaterials", R2::cmd_source_t::kCommandSrcCode);
+ }
+
+ for (std::string& sReloadCommand : vReloadCommands)
+ {
+ spdlog::info("Executing command {} for asset reload", sReloadCommand);
+ R2::Cbuf_AddText(R2::Cbuf_GetCurrentPlayer(), sReloadCommand.c_str(), R2::cmd_source_t::kCommandSrcCode);
+ }
+
+ R2::Cbuf_Execute();
+}
+
+void ModManager::InstallMods(bool bDeferredAssetReload)
+{
+ for (Mod& mod : GetMods() | FilterEnabled)
+ {
+ InstallModCvars(mod);
+ InstallModVpks(mod);
+ InstallModRpaks(mod);
+ InstallModBinks(mod);
+ InstallModAudioOverrides(mod);
+ }
+
+ // 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)
+ {
+ InstallModKeyValues(mod);
+ InstallModFileOverrides(mod);
+ }
+
+ if (m_bHasLoadedMods) // only reload assets after initial load
+ {
+ ModAssetsToReload assetsToReload;
+
+ CheckModFilesForChanges(&assetsToReload);
+ ReloadNecessaryModAssets(bDeferredAssetReload, &assetsToReload);
+ }
+}
+
+void ModManager::SaveEnabledMods()
+{
+ // write from scratch every time, don't include unnecessary mods
+ rapidjson_document enabledModsCfg;
+ enabledModsCfg.SetObject();
+
+ // add values
+ for (Mod& mod : GetMods() | FilterLocal)
+ 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<rapidjson::OStreamWrapper> writer(sWriteStreamWrapper);
+ enabledModsCfg.Accept(writer);
+}
+
+void ModManager::BuildPublicModList()
+{
// build modinfo obj for masterserver
rapidjson_document modinfoDoc;
auto& alloc = modinfoDoc.GetAllocator();
@@ -709,16 +1110,15 @@ void ModManager::LoadMods()
modinfoDoc.AddMember("Mods", rapidjson::kArrayType, alloc);
int currentModIndex = 0;
- for (Mod& mod : m_LoadedMods)
+ for (Mod& mod : GetMods() | FilterEnabled)
{
- if (!mod.m_bEnabled || (!mod.RequiredOnClient && !mod.Pdiff.size()))
+ if (!mod.RequiredOnClient) // (!mod.RequiredOnClient && !mod.Pdiff.size())
continue;
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());
currentModIndex++;
}
@@ -728,46 +1128,19 @@ void ModManager::LoadMods()
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
modinfoDoc.Accept(writer);
g_pMasterServerManager->m_sOwnModInfoJson = std::string(buffer.GetString());
-
- m_bHasLoadedMods = true;
}
void ModManager::UnloadMods()
{
+ // save last state so we know what we need to reload
+ m_LastModLoadState = m_ModLoadState;
+ m_ModLoadState = new ModLoadState;
+
// 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<size_t, std::string> 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
- // 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<rapidjson::OStreamWrapper> 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)
@@ -788,19 +1161,18 @@ void ModManager::CompileAssetsForFile(const char* filename)
if (fileHash == m_hScriptsRsonHash)
BuildScriptsRson();
- else if (fileHash == m_hPdefHash)
- BuildPdef();
+ //else if (fileHash == m_hPdefHash)
+ //{
+ // // 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() | FilterEnabled)
{
- if (!mod.m_bEnabled)
- continue;
-
- if (mod.KeyValues.find(fileHash) != mod.KeyValues.end())
+ if (m_ModLoadState->m_KeyValues.find(filename) != m_ModLoadState->m_KeyValues.end())
{
TryBuildKeyValues(filename);
return;
@@ -809,27 +1181,56 @@ void ModManager::CompileAssetsForFile(const char* filename)
}
}
-void ConCommand_reload_mods(const CCommand& args)
+void ConCommand_mods_reload(const CCommand& args)
+{
+ g_pModManager->LoadMods(false);
+}
+
+void ConCommand_mods_reload_deferred(const CCommand& args)
+{
+ g_pModManager->LoadMods(true);
+}
+
+void ConCommand_mods_getfileowner(const CCommand& args)
{
- g_pModManager->LoadMods();
+ 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 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))
{
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_reload_deferred", ConCommand_mods_reload_deferred, "reloads mods, prefers reloading assets on level load rather than now", 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 369eb07b..9b194ea9 100644
--- a/NorthstarDLL/mods/modmanager.h
+++ b/NorthstarDLL/mods/modmanager.h
@@ -7,15 +7,15 @@
#include <string>
#include <vector>
#include <filesystem>
+#include <unordered_set>
-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:
@@ -98,18 +92,18 @@ class Mod
// custom scripts used by the mod
std::vector<ModScript> Scripts;
// convars created by the mod
- std::vector<ModConVar*> ConVars;
+ std::vector<ModConVar> ConVars;
// concommands created by the mod
- std::vector<ModConCommand*> ConCommands;
+ std::vector<ModConCommand> ConCommands;
// custom localisation files created by the mod
std::vector<std::string> LocalisationFiles;
// other files:
std::vector<ModVPKEntry> Vpks;
- std::unordered_map<size_t, std::string> KeyValues;
std::vector<std::string> BinkVideos;
- std::string Pdiff; // only need one per mod
+
+ // todo audio override struct
std::vector<ModRpakEntry> Rpaks;
std::unordered_map<std::string, std::string>
@@ -121,45 +115,133 @@ class Mod
std::unordered_map<std::string, std::string> DependencyConstants;
public:
- Mod(fs::path modPath, char* jsonBuf);
+ Mod(fs::path modPath, std::string sJson, bool bRemote);
};
struct ModOverrideFile
{
- public:
- Mod* m_pOwningMod;
+ Mod* m_pOwningMod; // don't need to explicitly clean this up
fs::path m_Path;
+ fs::file_time_type m_tLastWriteTime;
};
+// defined in reloadmodweapons.cpp
+extern struct SidedWeaponReloadPointers;
+
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:
- std::vector<Mod> m_LoadedMods;
- std::unordered_map<std::string, ModOverrideFile> m_ModFiles;
- std::unordered_map<std::string, std::string> m_DependencyConstants;
+ void LoadModDefinitions();
+ void SaveEnabledMods();
+ void BuildPublicModList();
+ void InstallMods(bool bDeferredAssetReload);
+
+ struct ModAssetsToReload
+ {
+ // 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 bPlayerSettings = false;
+ bool bAiSettings = false;
+ bool bDamageDefs = false; // damagedefs
+ bool bWeaponSprings = false;
+ bool bAmmoSuckBehaviours = false;
+ bool bDatatables = false;
+
+ // 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::unordered_set<std::string> setsWeaponSettings = {};
+ // std::vector<ModAudioOverride> vAudioOverrides
+ };
+
+ // 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<ConVar*> m_RegisteredModConVars;
+ std::unordered_set<ConCommand*> m_RegisteredModConCommands;
+
+
+ void CheckModFilesForChanges(ModAssetsToReload* pAssetsToReload);
+ void ReloadNecessaryModAssets(bool bDeferred, const ModAssetsToReload* pAssetsToReload);
+
+
+ struct ModLoadState
+ {
+ std::vector<Mod> m_LoadedMods;
+ std::unordered_map<std::string, ModOverrideFile> m_ModFiles;
+ std::unordered_map<std::string, std::string> m_DependencyConstants;
+ std::unordered_map<std::string, std::vector<ModOverrideFile>> m_KeyValues;
+ };
+
+ // 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();
+ void LoadMods(bool bDeferredAssetReload);
std::string NormaliseModFilePath(const fs::path path);
void CompileAssetsForFile(const char* filename);
+ // getters
+ inline std::vector<Mod>& GetMods()
+ {
+ return m_ModLoadState->m_LoadedMods;
+ };
+
+ inline std::unordered_map<std::string, ModOverrideFile>& GetModFiles()
+ {
+ return m_ModLoadState->m_ModFiles;
+ };
+
+ inline std::unordered_map<std::string, std::string>& 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);
- void BuildPdef();
void BuildKBActionsList();
+
+ // deferred asset reloading funcs (i.e. set engine flags to reload later)
+ void DeferredReloadDamageFlags();
+ void DeferredReloadWeaponSprings();
+ void DeferredReloadAmmoSuckBehaviours();
+ void DeferredReloadADSPulls();
+ void DeferredReloadWeapons(const std::unordered_set<std::string> setsWeapons);
+
+ // asset reloading funcs
+ bool TryReloadWeapon(const char* pWeaponName, const SidedWeaponReloadPointers* pReloadPointers);
+
+ // 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();
diff --git a/NorthstarDLL/mods/reload/reloadmodweapons.cpp b/NorthstarDLL/mods/reload/reloadmodweapons.cpp
new file mode 100644
index 00000000..4619fb31
--- /dev/null
+++ b/NorthstarDLL/mods/reload/reloadmodweapons.cpp
@@ -0,0 +1,124 @@
+#include "mods/modmanager.h"
+
+AUTOHOOK_INIT()
+
+OFFSET_STRUCT(WeaponDefinition)
+{
+ FIELD(5, bool bReloadScriptFuncs);
+ FIELD(6, char pWeaponName[]); // this probably has a max length but i do not know what it is
+};
+
+OFFSET_STRUCT(GlobalWeaponDefs)
+{
+ // each entry is 24 bytes, but no clue what the bytes after the def are, so just ignore atm
+ // need the full struct so we iterate properly
+ OFFSET_STRUCT(WeaponDefContainer)
+ {
+ STRUCT_SIZE(24);
+ WeaponDefinition* pWeaponDef;
+ };
+
+ FIELD(16, WeaponDefContainer m_Weapons[]);
+};
+
+VAR_AT(client.dll + 0xB33A02, uint16_t*, g_pnClientWeaponsLoaded);
+VAR_AT(client.dll + 0xB339E8, GlobalWeaponDefs**, g_ppClientWeaponDefs);
+
+FUNCTION_AT(client.dll + 0x3D2FB0, void,, ClientReparseWeapon, (WeaponDefinition* pWeapon));
+FUNCTION_AT(client.dll + 0x3CE270, void,, ClientReloadWeaponCallbacks, (int nWeaponIndex));
+
+std::unordered_set<std::string> setsClientWeaponsToReload;
+std::unordered_set<std::string> setsServerWeaponsToReload;
+
+// used for passing client/server funcs/data/pointers to TryReloadWeapon
+struct SidedWeaponReloadPointers
+{
+ // data pointers
+ uint16_t* m_pnWeaponsLoaded;
+ GlobalWeaponDefs** m_ppWeaponDefs;
+
+ std::unordered_set<std::string>* m_psetsWeaponsToReload;
+
+ // funcs
+ void (*m_fnReparseWeapon)(WeaponDefinition* pWeapon);
+ void (*m_fnReloadWeaponCallbacks)(int nWeaponIndex);
+
+ SidedWeaponReloadPointers(
+ uint16_t* pnWeaponsLoaded,
+ GlobalWeaponDefs** ppWeaponDefs,
+ std::unordered_set<std::string>* psetsWeaponsToReload,
+ void (*fnReparseWeapon)(WeaponDefinition*),
+ void (*fnReloadWeaponCallbacks)(int))
+ {
+ m_pnWeaponsLoaded = pnWeaponsLoaded;
+ m_ppWeaponDefs = ppWeaponDefs;
+ m_psetsWeaponsToReload = psetsWeaponsToReload;
+ m_fnReparseWeapon = fnReparseWeapon;
+ m_fnReloadWeaponCallbacks = fnReloadWeaponCallbacks;
+ }
+};
+
+
+int WeaponIndexByName(const char* pWeaponName, const SidedWeaponReloadPointers* pReloadPointers)
+{
+ for (int i = 0; i < *pReloadPointers->m_pnWeaponsLoaded; i++)
+ {
+ const WeaponDefinition* pWeapon = (*pReloadPointers->m_ppWeaponDefs)->m_Weapons[i].pWeaponDef;
+ if (!strcmp(pWeapon->pWeaponName, pWeaponName))
+ return i;
+ }
+
+ return -1;
+}
+
+void ModManager::DeferredReloadWeapons(const std::unordered_set<std::string> setsWeapons)
+{
+ // if there's still weapons that need reloading, then keep them, just reload the new stuff
+ for (const std::string& sWeapon : setsWeapons)
+ {
+ setsClientWeaponsToReload.insert(sWeapon);
+ setsServerWeaponsToReload.insert(sWeapon);
+ }
+}
+
+bool ModManager::TryReloadWeapon(const char* pWeaponName, const SidedWeaponReloadPointers* pReloadPointers)
+{
+ if (!pReloadPointers->m_psetsWeaponsToReload->contains(pWeaponName))
+ return false; // don't reload
+
+ int nWeaponIndex = WeaponIndexByName(pWeaponName, pReloadPointers);
+ if (nWeaponIndex == -1) // weapon isn't loaded at all, no need to reload!
+ return false;
+
+ spdlog::info("ModManager::TryReloadWeapon reloading weapon {}", pWeaponName);
+
+ WeaponDefinition* pWeapon = (*pReloadPointers->m_ppWeaponDefs)->m_Weapons[nWeaponIndex].pWeaponDef;
+ bool bReloadScriptFuncs = pWeapon->bReloadScriptFuncs; // this is reset after reparse
+ pReloadPointers->m_fnReparseWeapon(pWeapon);
+ if (bReloadScriptFuncs)
+ pReloadPointers->m_fnReloadWeaponCallbacks(nWeaponIndex);
+
+ pReloadPointers->m_psetsWeaponsToReload->erase(pWeaponName);
+ return true;
+}
+
+// TODO: server implementation for this?
+// clang-format off
+AUTOHOOK(ClientPrecacheWeaponFromStringtable, client.dll + 0x195A60,
+bool, __fastcall, (void* a1, void* a2, void* a3, const char* pWeaponName))
+// clang-format on
+{
+ static SidedWeaponReloadPointers clientReloadPointers(
+ g_pnClientWeaponsLoaded, g_ppClientWeaponDefs, &setsClientWeaponsToReload, ClientReparseWeapon, ClientReloadWeaponCallbacks);
+
+ if (g_pModManager->TryReloadWeapon(pWeaponName, &clientReloadPointers))
+ return true;
+
+ spdlog::info("PrecacheWeaponFromStringtable: {}", pWeaponName);
+ return ClientPrecacheWeaponFromStringtable(a1, a2, a3, pWeaponName);
+}
+
+ON_DLL_LOAD_CLIENT("client.dll", ModReloadWeaponsClient, (CModule module))
+{
+ AUTOHOOK_DISPATCH_MODULE(server.dll)
+}
diff --git a/NorthstarDLL/mods/reload/reloadmodweapons_misc.cpp b/NorthstarDLL/mods/reload/reloadmodweapons_misc.cpp
new file mode 100644
index 00000000..5f43b11f
--- /dev/null
+++ b/NorthstarDLL/mods/reload/reloadmodweapons_misc.cpp
@@ -0,0 +1,45 @@
+#include "mods/modmanager.h"
+
+AUTOHOOK_INIT()
+
+// these are all used in their respective precacheweapon functions to determine whether to load these files
+// just set these to make game reload assets during load!
+VAR_AT(client.dll + 0x23EF0C5, bool*, g_pbClientHasLoadedDamageFlags);
+VAR_AT(client.dll + 0x23EF0C6, bool*, g_pbClientHasLoadedWeaponSprings);
+VAR_AT(client.dll + 0x23EF0C7, bool*, g_pbClientHasLoadedWeaponAmmoSuckBehaviours);
+VAR_AT(client.dll + 0x23EF0C4, bool*, g_pbClientHasLoadedWeaponADSPulls);
+
+VAR_AT(server.dll + 0x160B474, bool*, g_pbServerHasLoadedDamageFlags);
+VAR_AT(server.dll + 0x160B475, bool*, g_pbServerHasLoadedWeaponSprings);
+VAR_AT(server.dll + 0x160B476, bool*, g_pbServerHasLoadedWeaponAmmoSuckBehaviours);
+VAR_AT(server.dll + 0x160B477, bool*, g_pbServerHasLoadedWeaponADSPulls);
+
+void ModManager::DeferredReloadDamageFlags()
+{
+ *g_pbClientHasLoadedDamageFlags = false;
+ *g_pbServerHasLoadedDamageFlags = false;
+}
+
+void ModManager::DeferredReloadWeaponSprings()
+{
+ *g_pbClientHasLoadedWeaponSprings = false;
+ *g_pbServerHasLoadedWeaponSprings = false;
+}
+
+void ModManager::DeferredReloadAmmoSuckBehaviours()
+{
+ *g_pbClientHasLoadedWeaponAmmoSuckBehaviours = false;
+ *g_pbServerHasLoadedWeaponAmmoSuckBehaviours = false;
+}
+
+void ModManager::DeferredReloadADSPulls()
+{
+ *g_pbClientHasLoadedWeaponADSPulls = false;
+ *g_pbServerHasLoadedWeaponADSPulls = false;
+}
+
+
+ON_DLL_LOAD_CLIENT("client.dll", ClientModReloadWeaponsMisc, (CModule module))
+{
+ AUTOHOOK_DISPATCH_MODULE(client.dll)
+}
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 <map>
#include <filesystem>
#include <sstream>
+#include <ranges>
+#include <vector>
namespace fs = std::filesystem;
diff --git a/NorthstarDLL/scripts/client/scriptmodmenu.cpp b/NorthstarDLL/scripts/client/scriptmodmenu.cpp
index a88478fb..e16216fd 100644
--- a/NorthstarDLL/scripts/client/scriptmodmenu.cpp
+++ b/NorthstarDLL/scripts/client/scriptmodmenu.cpp
@@ -5,7 +5,7 @@ ADD_SQFUNC("array<string>", NSGetModNames, "", "", ScriptContext::SERVER | Scrip
{
g_pSquirrel<context>->newarray(sqvm, 0);
- for (Mod& mod : g_pModManager->m_LoadedMods)
+ for (Mod& mod : g_pModManager->GetMods())
{
g_pSquirrel<context>->pushstring(sqvm, mod.Name.c_str());
g_pSquirrel<context>->arrayappend(sqvm, -2);
@@ -19,7 +19,7 @@ ADD_SQFUNC("bool", NSIsModEnabled, "string modName", "", ScriptContext::SERVER |
const SQChar* modName = g_pSquirrel<context>->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))
{
@@ -37,7 +37,7 @@ ADD_SQFUNC("void", NSSetModEnabled, "string modName, bool enabled", "", ScriptCo
const SQBool enabled = g_pSquirrel<context>->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))
{
@@ -54,7 +54,7 @@ ADD_SQFUNC("string", NSGetModDescriptionByModName, "string modName", "", ScriptC
const SQChar* modName = g_pSquirrel<context>->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))
{
@@ -71,7 +71,7 @@ ADD_SQFUNC("string", NSGetModVersionByModName, "string modName", "", ScriptConte
const SQChar* modName = g_pSquirrel<context>->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))
{
@@ -88,7 +88,7 @@ ADD_SQFUNC("string", NSGetModDownloadLinkByModName, "string modName", "", Script
const SQChar* modName = g_pSquirrel<context>->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))
{
@@ -105,7 +105,7 @@ ADD_SQFUNC("int", NSGetModLoadPriority, "string modName", "", ScriptContext::SER
const SQChar* modName = g_pSquirrel<context>->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))
{
@@ -122,7 +122,7 @@ ADD_SQFUNC("bool", NSIsModRequiredOnClient, "string modName", "", ScriptContext:
const SQChar* modName = g_pSquirrel<context>->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))
{
@@ -141,13 +141,13 @@ ADD_SQFUNC(
g_pSquirrel<context>->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))
{
- for (ModConVar* cvar : mod.ConVars)
+ for (ModConVar& cvar : mod.ConVars)
{
- g_pSquirrel<context>->pushstring(sqvm, cvar->Name.c_str());
+ g_pSquirrel<context>->pushstring(sqvm, cvar.Name.c_str());
g_pSquirrel<context>->arrayappend(sqvm, -2);
}
@@ -158,8 +158,10 @@ ADD_SQFUNC(
return SQRESULT_NOTNULL; // return empty array
}
-ADD_SQFUNC("void", NSReloadMods, "", "", ScriptContext::UI)
+ADD_SQFUNC("void", NSReloadMods, "bool deferredReload = false", "", ScriptContext::UI)
{
- g_pModManager->LoadMods();
+ const SQBool bDeferredReload = g_pSquirrel<context>->getbool(sqvm, 1);
+ g_pModManager->LoadMods(bDeferredReload);
+
return SQRESULT_NULL;
}
diff --git a/NorthstarDLL/server/auth/bansystem.cpp b/NorthstarDLL/server/auth/bansystem.cpp
index a438450f..c7c94c1c 100644
--- a/NorthstarDLL/server/auth/bansystem.cpp
+++ b/NorthstarDLL/server/auth/bansystem.cpp
@@ -15,7 +15,7 @@ ServerBanSystem* g_pBanSystem;
void ServerBanSystem::OpenBanlist()
{
- std::ifstream banlistStream(GetNorthstarPrefix() + "/banlist.txt");
+ std::ifstream banlistStream(GetNorthstarPrefix() / "banlist.txt");
if (!banlistStream.fail())
{
@@ -45,12 +45,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())
{
@@ -70,18 +70,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<char>(fsBanlist)), (std::istreambuf_iterator<char>()));
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;
@@ -100,7 +100,7 @@ void ServerBanSystem::UnbanUID(uint64_t uid)
m_vBannedUids.erase(findResult);
std::vector<std::string> banlistText;
- std::ifstream fs_readBanlist(GetNorthstarPrefix() + "/banlist.txt");
+ std::ifstream fs_readBanlist(GetNorthstarPrefix() / "banlist.txt");
if (!fs_readBanlist.fail())
{
@@ -162,7 +162,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/shared/misccommands.cpp b/NorthstarDLL/shared/misccommands.cpp
index 15e12720..ba00de29 100644
--- a/NorthstarDLL/shared/misccommands.cpp
+++ b/NorthstarDLL/shared/misccommands.cpp
@@ -10,6 +10,8 @@
#include "server/auth/serverauthentication.h"
#include "squirrel/squirrel.h"
+AUTOHOOK_INIT()
+
void ConCommand_force_newgame(const CCommand& arg)
{
if (arg.ArgC() < 2)
@@ -140,6 +142,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<char*>(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()
{
diff --git a/NorthstarDLL/squirrel/squirrel.cpp b/NorthstarDLL/squirrel/squirrel.cpp
index 4cf342db..06ba445c 100644
--- a/NorthstarDLL/squirrel/squirrel.cpp
+++ b/NorthstarDLL/squirrel/squirrel.cpp
@@ -189,15 +189,12 @@ template <ScriptContext context> void SquirrelManager<context>::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() | ModManager::FilterEnabled)
{
- if (!dependency.m_bEnabled)
- continue;
-
if (dependency.Name == pair.second)
{
bWasFound = true;
@@ -207,6 +204,7 @@ template <ScriptContext context> void SquirrelManager<context>::VMCreated(CSquir
defconst(m_pSQVM, pair.first.c_str(), bWasFound);
}
+
g_pSquirrel<context>->messageBuffer = new SquirrelMessageBuffer();
}
@@ -217,7 +215,7 @@ template <ScriptContext context> void SquirrelManager<context>::VMDestroyed()
{
NS::log::squirrel_logger<context>()->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)
{
@@ -471,11 +469,8 @@ template <ScriptContext context> bool __fastcall CallScriptInitCallbackHook(void
if (bShouldCallCustomCallbacks)
{
- for (Mod mod : g_pModManager->m_LoadedMods)
+ for (Mod& mod : g_pModManager->GetMods() | ModManager::FilterEnabled)
{
- if (!mod.m_bEnabled)
- continue;
-
for (ModScript script : mod.Scripts)
{
for (ModScriptCallback modCallback : script.Callbacks)
@@ -498,11 +493,8 @@ template <ScriptContext context> bool __fastcall CallScriptInitCallbackHook(void
// run after callbacks
if (bShouldCallCustomCallbacks)
{
- for (Mod mod : g_pModManager->m_LoadedMods)
+ for (Mod& mod : g_pModManager->GetMods() | ModManager::FilterEnabled)
{
- if (!mod.m_bEnabled)
- continue;
-
for (ModScript script : mod.Scripts)
{
for (ModScriptCallback modCallback : script.Callbacks)
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 <typename T> 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 99bda23c..449aa351 100644
--- a/NorthstarDLL/util/printmaps.cpp
+++ b/NorthstarDLL/util/printmaps.cpp
@@ -36,7 +36,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