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 (limited to 'NorthstarDedicatedTest') 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