From 50e69bde548c5a1af3385f0eff6aa14c088c21c5 Mon Sep 17 00:00:00 2001
From: BobTheBob <32057864+BobTheBob9@users.noreply.github.com>
Date: Tue, 15 Mar 2022 05:57:15 +0000
Subject: add mod binks, mod rpak loading, and support for non-preload
vpks/rpaks
---
.../NorthstarDedicatedTest.vcxproj | 2 +
.../NorthstarDedicatedTest.vcxproj.filters | 12 ++-
NorthstarDedicatedTest/clientvideooverrides.cpp | 42 ++++++++++
NorthstarDedicatedTest/clientvideooverrides.h | 2 +
NorthstarDedicatedTest/dllmain.cpp | 6 +-
NorthstarDedicatedTest/filesystem.cpp | 19 ++++-
NorthstarDedicatedTest/modmanager.cpp | 90 +++++++++++++++++++++-
NorthstarDedicatedTest/modmanager.h | 21 ++++-
NorthstarDedicatedTest/rpakfilesystem.cpp | 77 +++++++++++++++---
NorthstarDedicatedTest/rpakfilesystem.h | 10 ++-
10 files changed, 259 insertions(+), 22 deletions(-)
create mode 100644 NorthstarDedicatedTest/clientvideooverrides.cpp
create mode 100644 NorthstarDedicatedTest/clientvideooverrides.h
diff --git a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj
index c5a5ce60..e931fa51 100644
--- a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj
+++ b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj
@@ -114,6 +114,7 @@
+
@@ -568,6 +569,7 @@
+
diff --git a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters
index f384e81b..a5460132 100644
--- a/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters
+++ b/NorthstarDedicatedTest/NorthstarDedicatedTest.vcxproj.filters
@@ -1471,8 +1471,8 @@
Header Files\Client
- Header Files\Client
-
+ Header Files\Client
+
Header Files\Client
@@ -1485,6 +1485,9 @@
Header Files
+
+ Header Files\Client
+
@@ -1632,7 +1635,7 @@
Source Files\Client
- Source Files\Client
+ Source Files\Client
Source Files\Client
@@ -1640,6 +1643,9 @@
Source Files
+
+ Source Files\Client
+
diff --git a/NorthstarDedicatedTest/clientvideooverrides.cpp b/NorthstarDedicatedTest/clientvideooverrides.cpp
new file mode 100644
index 00000000..3a63c181
--- /dev/null
+++ b/NorthstarDedicatedTest/clientvideooverrides.cpp
@@ -0,0 +1,42 @@
+#include "pch.h"
+#include "clientvideooverrides.h"
+#include "modmanager.h"
+#include "dedicated.h"
+
+typedef void*(*BinkOpenType)(const char* path, uint32_t flags);
+BinkOpenType BinkOpen;
+
+void* BinkOpenHook(const char* path, uint32_t flags)
+{
+ std::string filename(fs::path(path).filename().string());
+ spdlog::info("BinkOpen {}", filename);
+
+ // figure out which mod is handling the bink
+ Mod* fileOwner = nullptr;
+ for (Mod& mod : g_ModManager->m_loadedMods)
+ {
+ if (!mod.Enabled)
+ continue;
+
+ if (std::find(mod.BinkVideos.begin(), mod.BinkVideos.end(), filename) != mod.BinkVideos.end())
+ fileOwner = &mod;
+ }
+
+ if (fileOwner)
+ {
+ // create new path
+ fs::path binkPath(fileOwner->ModDirectory / "media" / filename);
+ return BinkOpen(binkPath.string().c_str(), flags);
+ }
+ else
+ return BinkOpen(path, flags);
+}
+
+void InitialiseClientVideoOverrides(HMODULE baseAddress)
+{
+ if (IsDedicated())
+ return;
+
+ HookEnabler hook;
+ ENABLER_CREATEHOOK(hook, GetProcAddress(GetModuleHandleA("bink2w64.dll"), "BinkOpen"), &BinkOpenHook, reinterpret_cast(&BinkOpen));
+}
\ No newline at end of file
diff --git a/NorthstarDedicatedTest/clientvideooverrides.h b/NorthstarDedicatedTest/clientvideooverrides.h
new file mode 100644
index 00000000..8819e404
--- /dev/null
+++ b/NorthstarDedicatedTest/clientvideooverrides.h
@@ -0,0 +1,2 @@
+#pragma once
+void InitialiseClientVideoOverrides(HMODULE baseAddress);
\ No newline at end of file
diff --git a/NorthstarDedicatedTest/dllmain.cpp b/NorthstarDedicatedTest/dllmain.cpp
index 6a079da0..edf4161a 100644
--- a/NorthstarDedicatedTest/dllmain.cpp
+++ b/NorthstarDedicatedTest/dllmain.cpp
@@ -38,10 +38,11 @@
#include "clientchathooks.h"
#include "localchatwriter.h"
#include "scriptservertoclientstringcommand.h"
-#include
-#include "pch.h"
#include "plugin_abi.h"
#include "plugins.h"
+#include "clientvideooverrides.h"
+#include
+#include "pch.h"
#include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h"
@@ -241,6 +242,7 @@ bool InitialiseNorthstar()
AddDllLoadCallback("client.dll", InitialiseClientChatHooks);
AddDllLoadCallback("client.dll", InitialiseLocalChatWriter);
AddDllLoadCallback("client.dll", InitialiseScriptServerToClientStringCommands);
+ AddDllLoadCallback("client.dll", InitialiseClientVideoOverrides);
}
AddDllLoadCallback("engine.dll", InitialiseEngineSpewFuncHooks);
diff --git a/NorthstarDedicatedTest/filesystem.cpp b/NorthstarDedicatedTest/filesystem.cpp
index 0fa14132..702141bb 100644
--- a/NorthstarDedicatedTest/filesystem.cpp
+++ b/NorthstarDedicatedTest/filesystem.cpp
@@ -174,11 +174,22 @@ VPKData* MountVPKHook(IFileSystem* fileSystem, const char* vpkPath)
if (!mod.Enabled)
continue;
- for (std::string& vpkPath : mod.Vpks)
+ for (ModVPKEntry& vpkEntry : mod.Vpks)
{
- // note: could potentially not mount these if they're already mounted?
- spdlog::info("MountVPK {}", vpkPath);
- spdlog::info((void*)mountVPK(fileSystem, vpkPath.c_str()));
+ // if we're autoloading, just load no matter what
+ if (!vpkEntry.m_bAutoLoad)
+ {
+ // resolve vpk name and try to load one with the same name
+ // todo: we should be unloading these on map unload manually
+ std::string mapName(fs::path(vpkPath).filename().string());
+ std::string modMapName(fs::path(vpkEntry.m_sVpkPath.c_str()).filename().string());
+ if (mapName.compare(modMapName))
+ continue;
+ }
+
+ VPKData* loaded = mountVPK(fileSystem, vpkEntry.m_sVpkPath.c_str());
+ if (!ret) // this is primarily for map vpks and stuff, so the map's vpk is what gets returned from here
+ ret = loaded;
}
}
diff --git a/NorthstarDedicatedTest/modmanager.cpp b/NorthstarDedicatedTest/modmanager.cpp
index 4f2bb416..79529c99 100644
--- a/NorthstarDedicatedTest/modmanager.cpp
+++ b/NorthstarDedicatedTest/modmanager.cpp
@@ -14,6 +14,7 @@
#include
#include
#include "filesystem.h"
+#include "rpakfilesystem.h"
#include "configurables.h"
ModManager* g_ModManager;
@@ -289,6 +290,26 @@ void ModManager::LoadMods()
// read vpk paths
if (fs::exists(mod.ModDirectory / "vpk"))
{
+ // read vpk cfg
+ std::ifstream vpkJsonStream(mod.ModDirectory / "vpk/vpk.json");
+ std::stringstream vpkJsonStringStream;
+
+ bool bUseVPKJson = false;
+ rapidjson::Document dVpkJson;
+
+ if (!vpkJsonStream.fail())
+ {
+ while (vpkJsonStream.peek() != EOF)
+ vpkJsonStringStream << (char)vpkJsonStream.get();
+
+ vpkJsonStream.close();
+ dVpkJson.Parse(
+ vpkJsonStringStream.str().c_str());
+
+ bUseVPKJson =
+ !dVpkJson.HasParseError() && dVpkJson.IsObject();
+ }
+
for (fs::directory_entry file : fs::directory_iterator(mod.ModDirectory / "vpk"))
{
// a bunch of checks to make sure we're only adding dir vpks and their paths are good
@@ -302,14 +323,71 @@ void ModManager::LoadMods()
// this really fucking sucks but it'll work
std::string vpkName =
(file.path().parent_path() / formattedPath.substr(strlen("english"), formattedPath.find(".bsp") - 3)).string();
- mod.Vpks.push_back(vpkName);
- if (m_hasLoadedMods)
+ 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 = vpkName;
+
+ if (m_hasLoadedMods && modVpk.m_bAutoLoad)
(*g_Filesystem)->m_vtable->MountVPK(*g_Filesystem, vpkName.c_str());
}
}
}
+ // read rpak paths
+ if (fs::exists(mod.ModDirectory / "paks"))
+ {
+ // read rpak cfg
+ std::ifstream rpakJsonStream(mod.ModDirectory / "paks/rpak.json");
+ std::stringstream rpakJsonStringStream;
+
+ bool bUseRpakJson = false;
+ rapidjson::Document dRpakJson;
+
+ if (!rpakJsonStream.fail())
+ {
+ while (rpakJsonStream.peek() != EOF)
+ rpakJsonStringStream << (char)rpakJsonStream.get();
+
+ rpakJsonStream.close();
+ dRpakJson.Parse(
+ rpakJsonStringStream.str().c_str());
+
+ 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++)
+ {
+ if (!iterator->name.IsString() || !iterator->value.IsString())
+ continue;
+
+ mod.RpakAliases.insert(std::make_pair(iterator->name.GetString(), iterator->value.GetString()));
+ }
+ }
+
+ for (fs::directory_entry file : fs::directory_iterator(mod.ModDirectory / "paks"))
+ {
+ // ensure we're only loading rpaks
+ if (fs::is_regular_file(file) && file.path().extension() == ".rpak")
+ {
+ 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());
+ modPak.m_sPakPath = pakName;
+
+ // not using atm because we need to resolve path to rpak
+ //if (m_hasLoadedMods && modPak.m_bAutoLoad)
+ // g_PakLoadManager->LoadPakAsync(pakName.c_str());
+ }
+ }
+ }
+
// read keyvalues paths
if (fs::exists(mod.ModDirectory / "keyvalues"))
{
@@ -340,6 +418,14 @@ void ModManager::LoadMods()
}
}
+ // read bink video paths
+ if (fs::exists(mod.ModDirectory / "media"))
+ {
+ for (fs::directory_entry file : fs::recursive_directory_iterator(mod.ModDirectory / "media"))
+ if (fs::is_regular_file(file) && file.path().extension() == ".bik")
+ mod.BinkVideos.push_back(file.path().filename().string());
+ }
+
// try to load audio
if (fs::exists(mod.ModDirectory / "audio"))
{
diff --git a/NorthstarDedicatedTest/modmanager.h b/NorthstarDedicatedTest/modmanager.h
index 07e5bffb..c79c507d 100644
--- a/NorthstarDedicatedTest/modmanager.h
+++ b/NorthstarDedicatedTest/modmanager.h
@@ -44,6 +44,21 @@ struct ModScript
std::vector Callbacks;
};
+// 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_sPakPath;
+};
+
class Mod
{
public:
@@ -76,10 +91,14 @@ class Mod
// other files:
- std::vector Vpks;
+ std::vector Vpks;
std::unordered_map KeyValues;
+ std::vector BinkVideos;
std::string Pdiff; // only need one per mod
+ std::vector Rpaks;
+ std::unordered_map RpakAliases; // paks we alias to other rpaks, e.g. to load sp_crashsite paks on the map mp_crashsite
+
// other stuff
bool wasReadSuccessfully = false;
diff --git a/NorthstarDedicatedTest/rpakfilesystem.cpp b/NorthstarDedicatedTest/rpakfilesystem.cpp
index 4da22b56..a4c4fada 100644
--- a/NorthstarDedicatedTest/rpakfilesystem.cpp
+++ b/NorthstarDedicatedTest/rpakfilesystem.cpp
@@ -13,29 +13,88 @@ typedef void* (*LoadPakAsyncType)(const char* path, void* unknownSingleton, int
struct PakLoadFuncs
{
void* unknown[2];
- LoadPakSyncType func2;
+ LoadPakSyncType LoadPakSync;
LoadPakAsyncType LoadPakAsync;
};
PakLoadFuncs* g_pakLoadApi;
void** pUnknownPakLoadSingleton;
-void LoadPakAsync(const char* path) { g_pakLoadApi->LoadPakAsync(path, *pUnknownPakLoadSingleton, 2, nullptr, nullptr); }
+PakLoadManager* g_PakLoadManager;
+void PakLoadManager::LoadPakSync(const char* path) { g_pakLoadApi->LoadPakSync(path, *pUnknownPakLoadSingleton, 0); }
+void PakLoadManager::LoadPakAsync(const char* path) { g_pakLoadApi->LoadPakAsync(path, *pUnknownPakLoadSingleton, 2, nullptr, nullptr); }
-void LoadCommonPaksForMapHook(char* map)
+void HandlePakAliases(char** map)
{
- LoadCommonPaksForMap(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_ModManager->m_loadedMods.size() - 1; i > -1; i--)
+ {
+ Mod* mod = &g_ModManager->m_loadedMods[i];
+ if (!mod->Enabled)
+ continue;
- // for sp => mp conversions, load the sp rpaks
- if (!strcmp(map, "mp_skyway_v1") || !strcmp(map, "mp_crashsite") || !strcmp(map, "mp_hub_timeshift"))
- map[0] = 's';
+ if (mod->RpakAliases.find(*map) != mod->RpakAliases.end())
+ {
+ *map = &mod->RpakAliases[*map][0];
+ return;
+ }
+ }
+}
+
+bool bShouldPreload = true;
+void LoadPreloadPaks()
+{
+ // disable preloading while we're doing this
+ bShouldPreload = false;
+
+ // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks
+ for (Mod& mod : g_ModManager->m_loadedMods)
+ {
+ if (!mod.Enabled)
+ continue;
+
+ // need to get a relative path of mod to mod folder
+ fs::path modPakPath("./" / mod.ModDirectory / "paks");
+
+ for (ModRpakEntry& pak : mod.Rpaks)
+ if (pak.m_bAutoLoad)
+ g_PakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakPath).string().c_str());
+ }
+
+ bShouldPreload = true;
+}
+
+LoadPakSyncType LoadPakSyncOriginal;
+void* LoadPakSyncHook(char* path, void* unknownSingleton, int flags)
+{
+ HandlePakAliases(&path);
+ // note: we don't handle loading any preloaded custom paks synchronously since LoadPakSync is never actually called in retail, just load them async instead
+ LoadPreloadPaks();
+
+ spdlog::info("LoadPakSync {}", path);
+ return LoadPakSyncOriginal(path, unknownSingleton, flags);
+}
+
+LoadPakAsyncType LoadPakAsyncOriginal;
+void* LoadPakAsyncHook(char* path, void* unknownSingleton, int flags, void* callback0, void* callback1)
+{
+ HandlePakAliases(&path);
+
+ if (bShouldPreload)
+ LoadPreloadPaks();
+
+ spdlog::info("LoadPakAsync {}", path);
+ return LoadPakAsyncOriginal(path, unknownSingleton, flags, callback0, callback1);
}
void InitialiseEngineRpakFilesystem(HMODULE baseAddress)
{
- g_pakLoadApi = (PakLoadFuncs*)((char*)baseAddress + 0x5BED78);
+ g_PakLoadManager = new PakLoadManager;
+
+ g_pakLoadApi = *(PakLoadFuncs**)((char*)baseAddress + 0x5BED78);
pUnknownPakLoadSingleton = (void**)((char*)baseAddress + 0x7C5E20);
HookEnabler hook;
- ENABLER_CREATEHOOK(hook, (char*)baseAddress + 0x15AD20, &LoadCommonPaksForMapHook, reinterpret_cast(&LoadCommonPaksForMap));
+ ENABLER_CREATEHOOK(hook, g_pakLoadApi->LoadPakSync, &LoadPakSyncHook, reinterpret_cast(&LoadPakSyncOriginal));
+ ENABLER_CREATEHOOK(hook, g_pakLoadApi->LoadPakAsync, &LoadPakAsyncHook, reinterpret_cast(&LoadPakAsyncOriginal));
}
\ No newline at end of file
diff --git a/NorthstarDedicatedTest/rpakfilesystem.h b/NorthstarDedicatedTest/rpakfilesystem.h
index a9cc9a93..334104a9 100644
--- a/NorthstarDedicatedTest/rpakfilesystem.h
+++ b/NorthstarDedicatedTest/rpakfilesystem.h
@@ -1,4 +1,12 @@
#pragma once
void InitialiseEngineRpakFilesystem(HMODULE baseAddress);
-void LoadPakAsync(char* path);
\ No newline at end of file
+
+class PakLoadManager
+{
+ public:
+ void LoadPakSync(const char* path);
+ void LoadPakAsync(const char* path);
+};
+
+extern PakLoadManager* g_PakLoadManager;
\ No newline at end of file
--
cgit v1.2.3