aboutsummaryrefslogtreecommitdiff
path: root/primedev/core/filesystem
diff options
context:
space:
mode:
Diffstat (limited to 'primedev/core/filesystem')
-rw-r--r--primedev/core/filesystem/filesystem.cpp83
-rw-r--r--primedev/core/filesystem/filesystem.h3
-rw-r--r--primedev/core/filesystem/rpakfilesystem.cpp543
-rw-r--r--primedev/core/filesystem/rpakfilesystem.h86
4 files changed, 498 insertions, 217 deletions
diff --git a/primedev/core/filesystem/filesystem.cpp b/primedev/core/filesystem/filesystem.cpp
index b39939e4..3c711e8e 100644
--- a/primedev/core/filesystem/filesystem.cpp
+++ b/primedev/core/filesystem/filesystem.cpp
@@ -1,34 +1,32 @@
#include "filesystem.h"
-#include "core/sourceinterface.h"
+#include "core/tier1.h"
#include "mods/modmanager.h"
#include <iostream>
#include <sstream>
-AUTOHOOK_INIT()
-
bool bReadingOriginalFile = false;
std::string sCurrentModPath;
ConVar* Cvar_ns_fs_log_reads;
-SourceInterface<IFileSystem>* g_pFilesystem;
+IFileSystem* g_pFilesystem;
std::string ReadVPKFile(const char* path)
{
// read scripts.rson file, todo: check if this can be overwritten
- FileHandle_t fileHandle = (*g_pFilesystem)->m_vtable2->Open(&(*g_pFilesystem)->m_vtable2, path, "rb", "GAME", 0);
+ FileHandle_t fileHandle = g_pFilesystem->m_vtable2->Open(&g_pFilesystem->m_vtable2, path, "rb", "GAME", 0);
std::stringstream fileStream;
int bytesRead = 0;
char data[4096];
do
{
- bytesRead = (*g_pFilesystem)->m_vtable2->Read(&(*g_pFilesystem)->m_vtable2, data, (int)std::size(data), fileHandle);
+ bytesRead = g_pFilesystem->m_vtable2->Read(&g_pFilesystem->m_vtable2, data, (int)std::size(data), fileHandle);
fileStream.write(data, bytesRead);
} while (bytesRead == std::size(data));
- (*g_pFilesystem)->m_vtable2->Close(*g_pFilesystem, fileHandle);
+ g_pFilesystem->m_vtable2->Close(g_pFilesystem, fileHandle);
return fileStream.str();
}
@@ -44,18 +42,17 @@ std::string ReadVPKOriginalFile(const char* path)
return ret;
}
-// clang-format off
-HOOK(AddSearchPathHook, AddSearchPath,
-void, __fastcall, (IFileSystem * fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType))
-// clang-format on
+static void(__fastcall* o_pAddSearchPath)(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType) =
+ nullptr;
+static void __fastcall h_AddSearchPath(IFileSystem* fileSystem, const char* pPath, const char* pathID, SearchPathAdd_t addType)
{
- AddSearchPath(fileSystem, pPath, pathID, addType);
+ o_pAddSearchPath(fileSystem, pPath, pathID, addType);
// make sure current mod paths are at head
if (!strcmp(pathID, "GAME") && sCurrentModPath.compare(pPath) && addType == PATH_ADD_TO_HEAD)
{
- AddSearchPath(fileSystem, sCurrentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD);
- AddSearchPath(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD);
+ o_pAddSearchPath(fileSystem, sCurrentModPath.c_str(), "GAME", PATH_ADD_TO_HEAD);
+ o_pAddSearchPath(fileSystem, GetCompiledAssetsPath().string().c_str(), "GAME", PATH_ADD_TO_HEAD);
}
}
@@ -67,13 +64,13 @@ void SetNewModSearchPaths(Mod* mod)
{
if ((fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().compare(sCurrentModPath))
{
- AddSearchPath(
- &*(*g_pFilesystem), (fs::absolute(mod->m_ModDirectory) / MOD_OVERRIDE_DIR).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
+ o_pAddSearchPath(
+ 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();
}
}
else // push compiled to head
- AddSearchPath(&*(*g_pFilesystem), fs::absolute(GetCompiledAssetsPath()).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
+ o_pAddSearchPath(g_pFilesystem, fs::absolute(GetCompiledAssetsPath()).string().c_str(), "GAME", PATH_ADD_TO_HEAD);
}
bool TryReplaceFile(const char* pPath, bool shouldCompile)
@@ -97,22 +94,17 @@ bool TryReplaceFile(const char* pPath, bool shouldCompile)
}
// force modded files to be read from mods, not cache
-// clang-format off
-HOOK(ReadFromCacheHook, ReadFromCache,
-bool, __fastcall, (IFileSystem * filesystem, char* pPath, void* result))
-// clang-format off
+static bool(__fastcall* o_pReadFromCache)(IFileSystem* filesystem, char* pPath, void* result) = nullptr;
+static bool __fastcall h_ReadFromCache(IFileSystem* filesystem, char* pPath, void* result)
{
if (TryReplaceFile(pPath, true))
return false;
- return ReadFromCache(filesystem, pPath, result);
+ return o_pReadFromCache(filesystem, pPath, result);
}
-// force modded files to be read from mods, not vpk
-// clang-format off
-AUTOHOOK(ReadFileFromVPK, filesystem_stdio.dll + 0x5CBA0,
-FileHandle_t, __fastcall, (VPKData* vpkInfo, uint64_t* b, char* filename))
-// clang-format on
+static FileHandle_t(__fastcall* o_pReadFileFromVPK)(VPKData* vpkInfo, uint64_t* b, char* filename) = nullptr;
+static FileHandle_t __fastcall h_ReadFileFromVPK(VPKData* vpkInfo, uint64_t* b, char* filename)
{
// don't compile here because this is only ever called from OpenEx, which already compiles
if (TryReplaceFile(filename, false))
@@ -121,22 +113,24 @@ FileHandle_t, __fastcall, (VPKData* vpkInfo, uint64_t* b, char* filename))
return b;
}
- return ReadFileFromVPK(vpkInfo, b, filename);
+ return o_pReadFileFromVPK(vpkInfo, b, filename);
}
-// clang-format off
-AUTOHOOK(CBaseFileSystem__OpenEx, filesystem_stdio.dll + 0x15F50,
-FileHandle_t, __fastcall, (IFileSystem* filesystem, const char* pPath, const char* pOptions, uint32_t flags, const char* pPathID, char **ppszResolvedFilename))
-// clang-format on
+static FileHandle_t(__fastcall* o_pCBaseFileSystem__OpenEx)(
+ IFileSystem* filesystem, const char* pPath, const char* pOptions, uint32_t flags, const char* pPathID, char** ppszResolvedFilename) =
+ nullptr;
+static FileHandle_t __fastcall h_CBaseFileSystem__OpenEx(
+ IFileSystem* filesystem, const char* pPath, const char* pOptions, uint32_t flags, const char* pPathID, char** ppszResolvedFilename)
{
TryReplaceFile(pPath, true);
- return CBaseFileSystem__OpenEx(filesystem, pPath, pOptions, flags, pPathID, ppszResolvedFilename);
+ return o_pCBaseFileSystem__OpenEx(filesystem, pPath, pOptions, flags, pPathID, ppszResolvedFilename);
}
-HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char* pVpkPath))
+static VPKData* (*o_pMountVPK)(IFileSystem* fileSystem, const char* pVpkPath) = nullptr;
+static VPKData* h_MountVPK(IFileSystem* fileSystem, const char* pVpkPath)
{
NS::log::fs->info("MountVPK {}", pVpkPath);
- VPKData* ret = MountVPK(fileSystem, pVpkPath);
+ VPKData* ret = o_pMountVPK(fileSystem, pVpkPath);
for (Mod mod : g_pModManager->m_LoadedMods)
{
@@ -156,7 +150,7 @@ HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char*
continue;
}
- VPKData* loaded = MountVPK(fileSystem, vpkEntry.m_sVpkPath.c_str());
+ VPKData* loaded = o_pMountVPK(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;
}
@@ -167,11 +161,18 @@ HOOK(MountVPKHook, MountVPK, VPKData*, , (IFileSystem * fileSystem, const char*
ON_DLL_LOAD("filesystem_stdio.dll", Filesystem, (CModule module))
{
- AUTOHOOK_DISPATCH()
+ o_pReadFileFromVPK = module.Offset(0x5CBA0).RCast<decltype(o_pReadFileFromVPK)>();
+ HookAttach(&(PVOID&)o_pReadFileFromVPK, (PVOID)h_ReadFileFromVPK);
+
+ o_pCBaseFileSystem__OpenEx = module.Offset(0x15F50).RCast<decltype(o_pCBaseFileSystem__OpenEx)>();
+ HookAttach(&(PVOID&)o_pCBaseFileSystem__OpenEx, (PVOID)h_CBaseFileSystem__OpenEx);
- g_pFilesystem = new SourceInterface<IFileSystem>("filesystem_stdio.dll", "VFileSystem017");
+ g_pFilesystem = Sys_GetFactoryPtr("filesystem_stdio.dll", "VFileSystem017").RCast<IFileSystem*>();
- AddSearchPathHook.Dispatch((LPVOID)(*g_pFilesystem)->m_vtable->AddSearchPath);
- ReadFromCacheHook.Dispatch((LPVOID)(*g_pFilesystem)->m_vtable->ReadFromCache);
- MountVPKHook.Dispatch((LPVOID)(*g_pFilesystem)->m_vtable->MountVPK);
+ o_pAddSearchPath = reinterpret_cast<decltype(o_pAddSearchPath)>(g_pFilesystem->m_vtable->AddSearchPath);
+ HookAttach(&(PVOID&)o_pAddSearchPath, (PVOID)h_AddSearchPath);
+ o_pReadFromCache = reinterpret_cast<decltype(o_pReadFromCache)>(g_pFilesystem->m_vtable->ReadFromCache);
+ HookAttach(&(PVOID&)o_pReadFromCache, (PVOID)h_ReadFromCache);
+ o_pMountVPK = reinterpret_cast<decltype(o_pMountVPK)>(g_pFilesystem->m_vtable->MountVPK);
+ HookAttach(&(PVOID&)o_pMountVPK, (PVOID)h_MountVPK);
}
diff --git a/primedev/core/filesystem/filesystem.h b/primedev/core/filesystem/filesystem.h
index 4e2c18d9..2c592b3f 100644
--- a/primedev/core/filesystem/filesystem.h
+++ b/primedev/core/filesystem/filesystem.h
@@ -1,5 +1,4 @@
#pragma once
-#include "core/sourceinterface.h"
// taken from ttf2sdk
typedef void* FileHandle_t;
@@ -48,7 +47,7 @@ public:
VTable2* m_vtable2;
};
-extern SourceInterface<IFileSystem>* g_pFilesystem;
+extern IFileSystem* g_pFilesystem;
std::string ReadVPKFile(const char* path);
std::string ReadVPKOriginalFile(const char* path);
diff --git a/primedev/core/filesystem/rpakfilesystem.cpp b/primedev/core/filesystem/rpakfilesystem.cpp
index da72646b..c3e5e74e 100644
--- a/primedev/core/filesystem/rpakfilesystem.cpp
+++ b/primedev/core/filesystem/rpakfilesystem.cpp
@@ -2,206 +2,447 @@
#include "mods/modmanager.h"
#include "dedicated/dedicated.h"
#include "core/tier0.h"
+#include "util/utils.h"
-AUTOHOOK_INIT()
-
-// there are more i'm just too lazy to add
+#pragma pack(push, 1)
struct PakLoadFuncs
{
- void* unk0[3];
- int (*LoadPakAsync)(const char* pPath, void* unknownSingleton, int flags, void* callback0, void* callback1);
- void* unk1[2];
- void* (*UnloadPak)(int iPakHandle, void* callback);
- void* unk2[6];
- void* (*LoadFile)(const char* path); // unsure
- void* unk3[10];
- void* (*ReadFileAsync)(const char* pPath, void* a2);
+ void (*InitRpakSystem)();
+ void (*AddAssetLoaderWithJobDetails)(/*assetTypeHeader*/ void*, uint32_t, int);
+ void (*AddAssetLoader)(/*assetTypeHeader*/ void*);
+ PakHandle (*LoadRpakFileAsync)(const char* pPath, void* allocator, int flags);
+ void (*LoadRpakFile)(const char*, __int64(__fastcall*)(), __int64, void(__cdecl*)());
+ __int64 qword28;
+ void (*UnloadPak)(PakHandle iPakHandle, void* callback);
+ __int64 qword38;
+ __int64 qword40;
+ __int64 qword48;
+ __int64 qword50;
+ FARPROC (*GetDllCallback)(__int16 a1, const CHAR* a2);
+ __int64 (*GetAssetByHash)(__int64 hash);
+ __int64 (*GetAssetByName)(const char* a1);
+ __int64 qword70;
+ __int64 qword78;
+ __int64 qword80;
+ __int64 qword88;
+ __int64 qword90;
+ __int64 qword98;
+ __int64 qwordA0;
+ __int64 qwordA8;
+ __int64 qwordB0;
+ __int64 qwordBB;
+ void* (*OpenFile)(const char* pPath);
+ __int64 CloseFile;
+ __int64 qwordD0;
+ __int64 FileReadAsync;
+ __int64 ComplexFileReadAsync;
+ __int64 GetReadJobState;
+ __int64 WaitForFileReadJobComplete;
+ __int64 CancelFileReadJob;
+ __int64 CancelFileReadJobAsync;
+ __int64 qword108;
};
+static_assert(sizeof(PakLoadFuncs) == 0x110);
+#pragma pack(pop)
PakLoadFuncs* g_pakLoadApi;
-
PakLoadManager* g_pPakLoadManager;
-void** pUnknownPakLoadSingleton;
-int PakLoadManager::LoadPakAsync(const char* pPath, const ePakLoadSource nLoadSource)
+static char* pszCurrentMapRpakPath = nullptr;
+static PakHandle* piCurrentMapRpakHandle = nullptr;
+static PakHandle* piCurrentMapPatchRpakHandle = nullptr;
+static /*CModelLoader*/ void** ppModelLoader = nullptr;
+static void** rpakMemoryAllocator = nullptr;
+
+static __int64 (*o_pLoadGametypeSpecificRpaks)(const char* levelName) = nullptr;
+static __int64 (**o_pCleanMaterialSystemStuff)() = nullptr;
+static __int64 (**o_pCModelLoader_UnreferenceAllModels)(/*CModelLoader*/ void* a1) = nullptr;
+static char* (*o_pLoadlevelLoadscreen)(const char* levelName) = nullptr;
+static unsigned int (*o_pGetPakPatchNumber)(const char* pPakPath) = nullptr;
+
+// Marks all mod Paks to be unloaded on next map load.
+// Also cleans up any mod Paks that are already unloaded.
+void PakLoadManager::UnloadAllModPaks()
{
- int nHandle = g_pakLoadApi->LoadPakAsync(pPath, *pUnknownPakLoadSingleton, 2, nullptr, nullptr);
-
- // set the load source of the pak we just loaded
- if (nHandle != -1)
- GetPakInfo(nHandle)->m_nLoadSource = nLoadSource;
-
- return nHandle;
+ NS::log::rpak->info("Reloading RPaks on next map load...");
+ for (auto& modPak : m_modPaks)
+ {
+ modPak.m_markedForDelete = true;
+ }
+ // clean up any paks that are both marked for unload and already unloaded
+ CleanUpUnloadedPaks();
+ SetForceReloadOnMapLoad(true);
}
-void PakLoadManager::UnloadPak(const int nPakHandle)
+// Tracks all Paks related to a mod.
+void PakLoadManager::TrackModPaks(Mod& mod)
{
- g_pakLoadApi->UnloadPak(nPakHandle, nullptr);
+ const fs::path modPakPath("./" / mod.m_ModDirectory / "paks");
+
+ for (auto& modRpakEntry : mod.Rpaks)
+ {
+ ModPak_t pak;
+ pak.m_modName = mod.Name;
+ pak.m_path = (modPakPath / modRpakEntry.m_pakName).string();
+ pak.m_pathHash = STR_HASH(pak.m_path);
+
+ pak.m_preload = modRpakEntry.m_preload;
+ pak.m_dependentPakHash = modRpakEntry.m_dependentPakHash;
+ pak.m_mapRegex = modRpakEntry.m_loadRegex;
+
+ m_modPaks.push_back(pak);
+ }
}
-void PakLoadManager::UnloadMapPaks()
+// Untracks all paks that aren't currently loaded and are marked for unload.
+void PakLoadManager::CleanUpUnloadedPaks()
{
- for (auto& pair : m_vLoadedPaks)
- if (pair.second.m_nLoadSource == ePakLoadSource::MAP)
- UnloadPak(pair.first);
+ auto fnRemovePredicate = [](ModPak_t& pak) -> bool { return pak.m_markedForDelete && pak.m_handle == PakHandle::INVALID; };
+
+ m_modPaks.erase(std::remove_if(m_modPaks.begin(), m_modPaks.end(), fnRemovePredicate), m_modPaks.end());
}
-LoadedPak* PakLoadManager::TrackLoadedPak(ePakLoadSource nLoadSource, int nPakHandle, size_t nPakNameHash)
+// Unloads all paks that are marked for unload.
+void PakLoadManager::UnloadMarkedPaks()
{
- LoadedPak pak;
- pak.m_nLoadSource = nLoadSource;
- pak.m_nPakHandle = nPakHandle;
- pak.m_nPakNameHash = nPakNameHash;
+ ++m_reentranceCounter;
+ const ScopeGuard guard([&]() { --m_reentranceCounter; });
+
+ (*o_pCModelLoader_UnreferenceAllModels)(*ppModelLoader);
+ (*o_pCleanMaterialSystemStuff)();
+
+ for (auto& modPak : m_modPaks)
+ {
+ if (modPak.m_handle == PakHandle::INVALID || !modPak.m_markedForDelete)
+ continue;
- m_vLoadedPaks.insert(std::make_pair(nPakHandle, pak));
- return &m_vLoadedPaks.at(nPakHandle);
+ g_pakLoadApi->UnloadPak(modPak.m_handle, *o_pCleanMaterialSystemStuff);
+ modPak.m_handle = PakHandle::INVALID;
+ }
}
-void PakLoadManager::RemoveLoadedPak(int nPakHandle)
+// Loads all modded paks for the given map.
+void PakLoadManager::LoadModPaksForMap(const char* mapName)
{
- m_vLoadedPaks.erase(nPakHandle);
+ ++m_reentranceCounter;
+ const ScopeGuard guard([&]() { --m_reentranceCounter; });
+
+ for (auto& modPak : m_modPaks)
+ {
+ // don't load paks that are already loaded
+ if (modPak.m_handle != PakHandle::INVALID)
+ continue;
+ std::cmatch matches;
+ if (!std::regex_match(mapName, matches, modPak.m_mapRegex))
+ continue;
+
+ modPak.m_handle = g_pakLoadApi->LoadRpakFileAsync(modPak.m_path.c_str(), *rpakMemoryAllocator, 7);
+ m_mapPaks.push_back(modPak.m_pathHash);
+ }
}
-LoadedPak* PakLoadManager::GetPakInfo(const int nPakHandle)
+// Unloads all modded map paks.
+void PakLoadManager::UnloadModPaks()
{
- return &m_vLoadedPaks.at(nPakHandle);
+ ++m_reentranceCounter;
+ const ScopeGuard guard([&]() { --m_reentranceCounter; });
+
+ (*o_pCModelLoader_UnreferenceAllModels)(*ppModelLoader);
+ (*o_pCleanMaterialSystemStuff)();
+
+ for (auto& modPak : m_modPaks)
+ {
+ for (auto it = m_mapPaks.begin(); it != m_mapPaks.end(); ++it)
+ {
+ if (*it != modPak.m_pathHash)
+ continue;
+
+ m_mapPaks.erase(it, it + 1);
+ g_pakLoadApi->UnloadPak(modPak.m_handle, *o_pCleanMaterialSystemStuff);
+ modPak.m_handle = PakHandle::INVALID;
+ break;
+ }
+ }
+
+ // If this has happened, we may have leaked a pak?
+ // It basically means that none of the entries in m_modPaks matched the hash in m_mapPaks so we didn't end up unloading it
+ assert_msg(m_mapPaks.size() == 0, "Not all map paks were unloaded?");
}
-int PakLoadManager::GetPakHandle(const size_t nPakNameHash)
+// Called after a Pak was loaded.
+void PakLoadManager::OnPakLoaded(std::string& originalPath, std::string& resultingPath, PakHandle resultingHandle)
{
- for (auto& pair : m_vLoadedPaks)
- if (pair.second.m_nPakNameHash == nPakNameHash)
- return pair.first;
+ if (IsVanillaCall())
+ {
+ // add entry to loaded vanilla rpaks
+ m_vanillaPaks.emplace_back(originalPath, resultingHandle);
+ }
- return -1;
+ LoadDependentPaks(resultingPath, resultingHandle);
}
-int PakLoadManager::GetPakHandle(const char* pPath)
+// Called before a Pak was unloaded.
+void PakLoadManager::OnPakUnloading(PakHandle handle)
{
- return GetPakHandle(STR_HASH(pPath));
+ UnloadDependentPaks(handle);
+
+ if (IsVanillaCall())
+ {
+ // remove entry from loaded vanilla rpaks
+ auto fnRemovePredicate = [handle](std::pair<std::string, PakHandle>& pair) -> bool { return pair.second == handle; };
+
+ m_vanillaPaks.erase(std::remove_if(m_vanillaPaks.begin(), m_vanillaPaks.end(), fnRemovePredicate), m_vanillaPaks.end());
+
+ // no need to handle aliasing here, if vanilla wants it gone, it's gone
+ }
+ else
+ {
+ // note: aliasing is handled the old way, long term todo: move it over to the PakLoadManager
+ // handle the potential unloading of an aliased vanilla rpak (we aliased it, and we are now unloading the alias, so we should load
+ // the vanilla one again)
+ // for (auto& [path, resultingHandle] : m_vanillaPaks)
+ //{
+ // if (resultingHandle != handle)
+ // continue;
+
+ // // load vanilla rpak
+ //}
+ }
+
+ // set handle of the mod pak (if any) that has this handle for proper tracking
+ for (auto& modPak : m_modPaks)
+ {
+ if (modPak.m_handle == handle)
+ modPak.m_handle = PakHandle::INVALID;
+ }
}
-void* PakLoadManager::LoadFile(const char* path)
+// Whether the vanilla game has this rpak
+static bool VanillaHasPak(const char* pakName)
{
- return g_pakLoadApi->LoadFile(path);
+ fs::path originalPath = fs::path("./r2/paks/Win64") / pakName;
+ unsigned int highestPatch = o_pGetPakPatchNumber(pakName);
+ if (highestPatch)
+ {
+ // add the patch path to the extension
+ char buf[16];
+ snprintf(buf, sizeof(buf), "(%02u).rpak", highestPatch);
+ // remove the .rpak and add the new suffix
+ originalPath = originalPath.replace_extension().string() + buf;
+ }
+ else
+ {
+ originalPath /= pakName;
+ }
+
+ return fs::exists(originalPath);
}
-void HandlePakAliases(char** map)
+// If vanilla doesn't have an rpak for this path, tries to map it to a modded rpak of the same name.
+void PakLoadManager::FixupPakPath(std::string& pakPath)
{
- // 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--)
+ if (VanillaHasPak(pakPath.c_str()))
+ return;
+
+ for (ModPak_t& modPak : m_modPaks)
{
- Mod* mod = &g_pModManager->m_LoadedMods[i];
- if (!mod->m_bEnabled)
+ if (modPak.m_markedForDelete)
continue;
- if (mod->RpakAliases.find(*map) != mod->RpakAliases.end())
+ fs::path modPakFilename = fs::path(modPak.m_path).filename();
+ if (pakPath == modPakFilename.string())
{
- *map = &mod->RpakAliases[*map][0];
+ pakPath = modPak.m_path;
return;
}
}
}
-void LoadPreloadPaks()
+// Loads all "Preload" Paks. todo: deprecate Preload.
+void PakLoadManager::LoadPreloadPaks()
{
- // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks
- for (Mod& mod : g_pModManager->m_LoadedMods)
+ ++m_reentranceCounter;
+ const ScopeGuard guard([&]() { --m_reentranceCounter; });
+
+ for (auto& modPak : m_modPaks)
{
- if (!mod.m_bEnabled)
+ if (modPak.m_markedForDelete || modPak.m_handle != PakHandle::INVALID || !modPak.m_preload)
continue;
- // need to get a relative path of mod to mod folder
- fs::path modPakPath("./" / mod.m_ModDirectory / "paks");
+ modPak.m_handle = g_pakLoadApi->LoadRpakFileAsync(modPak.m_path.c_str(), *rpakMemoryAllocator, 7);
+ }
+}
- for (ModRpakEntry& pak : mod.Rpaks)
- if (pak.m_bAutoLoad)
- g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::CONSTANT);
+// Causes all "Postload" paks to be loaded again.
+void PakLoadManager::ReloadPostloadPaks()
+{
+ ++m_reentranceCounter;
+ const ScopeGuard guard([&]() { --m_reentranceCounter; });
+
+ // pretend that we just loaded all of these vanilla paks
+ for (auto& [path, handle] : m_vanillaPaks)
+ {
+ LoadDependentPaks(path, handle);
}
}
-void LoadPostloadPaks(const char* pPath)
+// Wrapper for Pak load API.
+void* PakLoadManager::OpenFile(const char* path)
{
- // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks
- for (Mod& mod : g_pModManager->m_LoadedMods)
+ return g_pakLoadApi->OpenFile(path);
+}
+
+// Loads Paks that depend on this Pak.
+void PakLoadManager::LoadDependentPaks(std::string& path, PakHandle handle)
+{
+ ++m_reentranceCounter;
+ const ScopeGuard guard([&]() { --m_reentranceCounter; });
+
+ const size_t hash = STR_HASH(path);
+ for (auto& modPak : m_modPaks)
{
- if (!mod.m_bEnabled)
+ if (modPak.m_handle != PakHandle::INVALID)
+ continue;
+ if (modPak.m_dependentPakHash != hash)
continue;
- // need to get a relative path of mod to mod folder
- fs::path modPakPath("./" / mod.m_ModDirectory / "paks");
-
- for (ModRpakEntry& pak : mod.Rpaks)
- if (pak.m_sLoadAfterPak == pPath)
- g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::CONSTANT);
+ // load pak
+ modPak.m_handle = g_pakLoadApi->LoadRpakFileAsync(modPak.m_path.c_str(), *rpakMemoryAllocator, 7);
+ m_dependentPaks.emplace_back(handle, hash);
}
}
-void LoadCustomMapPaks(char** pakName, bool* bNeedToFreePakName)
+// Unloads Paks that depend on this Pak.
+void PakLoadManager::UnloadDependentPaks(PakHandle handle)
{
- // whether the vanilla game has this rpak
- bool bHasOriginalPak = fs::exists(fs::path("r2/paks/Win64/") / *pakName);
+ ++m_reentranceCounter;
+ const ScopeGuard guard([&]() { --m_reentranceCounter; });
- // note, loading from ./ is necessary otherwise paks will load from gamedir/r2/paks
- for (Mod& mod : g_pModManager->m_LoadedMods)
+ auto fnRemovePredicate = [&](std::pair<PakHandle, size_t>& pair) -> bool
{
- if (!mod.m_bEnabled)
- continue;
+ if (pair.first != handle)
+ return false;
- // need to get a relative path of mod to mod folder
- fs::path modPakPath("./" / mod.m_ModDirectory / "paks");
+ for (auto& modPak : m_modPaks)
+ {
+ if (modPak.m_pathHash != pair.second || modPak.m_handle == PakHandle::INVALID)
+ continue;
- for (ModRpakEntry& pak : mod.Rpaks)
+ // unload pak
+ g_pakLoadApi->UnloadPak(modPak.m_handle, *o_pCleanMaterialSystemStuff);
+ modPak.m_handle = PakHandle::INVALID;
+ }
+
+ return true;
+ };
+ m_dependentPaks.erase(std::remove_if(m_dependentPaks.begin(), m_dependentPaks.end(), fnRemovePredicate), m_dependentPaks.end());
+}
+
+// Handles aliases for rpaks defined in rpak.json, effectively redirecting an rpak load to a different path.
+static void HandlePakAliases(std::string& originalPath)
+{
+ // convert the pak being loaded to its aliased one, e.g. aliasing mp_hub_timeshift => sp_hub_timeshift
+ for (int64_t i = g_pModManager->m_LoadedMods.size() - 1; i > -1; i--)
+ {
+ Mod* mod = &g_pModManager->m_LoadedMods[i];
+ if (!mod->m_bEnabled)
+ continue;
+
+ if (mod->RpakAliases.find(originalPath) != mod->RpakAliases.end())
{
- if (!pak.m_bAutoLoad && !pak.m_sPakName.compare(*pakName))
- {
- // if the game doesn't have the original pak, let it handle loading this one as if it was the one it was loading originally
- if (!bHasOriginalPak)
- {
- std::string path = (modPakPath / pak.m_sPakName).string();
- *pakName = new char[path.size() + 1];
- strcpy(*pakName, &path[0]);
- (*pakName)[path.size()] = '\0';
-
- bHasOriginalPak = true;
- *bNeedToFreePakName =
- true; // we can't free this memory until we're done with the pak, so let whatever's calling this deal with it
- }
- else
- g_pPakLoadManager->LoadPakAsync((modPakPath / pak.m_sPakName).string().c_str(), ePakLoadSource::MAP);
- }
+ originalPath = mod->RpakAliases[originalPath];
+ return;
}
}
}
-// clang-format off
-HOOK(LoadPakAsyncHook, LoadPakAsync,
-int, __fastcall, (char* pPath, void* unknownSingleton, int flags, void* pCallback0, void* pCallback1))
-// clang-format on
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool (*o_pLoadMapRpaks)(char* mapPath) = nullptr;
+static bool h_LoadMapRpaks(char* mapPath)
{
- HandlePakAliases(&pPath);
+ // unload all mod rpaks that are marked for unload
+ g_pPakLoadManager->UnloadMarkedPaks();
+ g_pPakLoadManager->CleanUpUnloadedPaks();
+
+ // strip file extension
+ const std::string mapName = fs::path(mapPath).replace_extension().string();
+
+ // load mp_common, sp_common etc.
+ o_pLoadGametypeSpecificRpaks(mapName.c_str());
- // dont load the pak if it's currently loaded already
- size_t nPathHash = STR_HASH(pPath);
- if (g_pPakLoadManager->GetPakHandle(nPathHash) != -1)
- return -1;
+ // unload old modded map paks
+ g_pPakLoadManager->UnloadModPaks();
+ // load modded map paks
+ g_pPakLoadManager->LoadModPaksForMap(mapName.c_str());
- bool bNeedToFreePakName = false;
+ // don't load/unload anything when going to the lobby, presumably to save load times when going back to the same map
+ if (!g_pPakLoadManager->GetForceReloadOnMapLoad() && !strcmp("mp_lobby", mapName.c_str()))
+ return false;
- static bool bShouldLoadPaks = true;
- if (bShouldLoadPaks)
+ if (g_pPakLoadManager->GetForceReloadOnMapLoad())
{
- // make a copy of the path for comparing to determine whether we should load this pak on dedi, before it could get overwritten by
- // LoadCustomMapPaks
- std::string originalPath(pPath);
+ g_pPakLoadManager->LoadPreloadPaks();
+ g_pPakLoadManager->ReloadPostloadPaks();
+ }
+
+ char mapRpakStr[272];
+ snprintf(mapRpakStr, 272, "%s.rpak", mapName.c_str());
+
+ // if level being loaded is the same as current level, do nothing
+ if (!g_pPakLoadManager->GetForceReloadOnMapLoad() && !strcmp(mapRpakStr, pszCurrentMapRpakPath))
+ return true;
+
+ strcpy(pszCurrentMapRpakPath, mapRpakStr);
- // disable preloading while we're doing this
- bShouldLoadPaks = false;
+ (*o_pCleanMaterialSystemStuff)();
+ o_pLoadlevelLoadscreen(mapName.c_str());
- LoadPreloadPaks();
- LoadCustomMapPaks(&pPath, &bNeedToFreePakName);
+ // unload old map rpaks
+ PakHandle curHandle = *piCurrentMapRpakHandle;
+ PakHandle curPatchHandle = *piCurrentMapPatchRpakHandle;
+ if (curHandle != PakHandle::INVALID)
+ {
+ (*o_pCModelLoader_UnreferenceAllModels)(*ppModelLoader);
+ (*o_pCleanMaterialSystemStuff)();
+ g_pakLoadApi->UnloadPak(curHandle, *o_pCleanMaterialSystemStuff);
+ *piCurrentMapRpakHandle = PakHandle::INVALID;
+ }
+ if (curPatchHandle != PakHandle::INVALID)
+ {
+ (*o_pCModelLoader_UnreferenceAllModels)(*ppModelLoader);
+ (*o_pCleanMaterialSystemStuff)();
+ g_pakLoadApi->UnloadPak(curPatchHandle, *o_pCleanMaterialSystemStuff);
+ *piCurrentMapPatchRpakHandle = PakHandle::INVALID;
+ }
- bShouldLoadPaks = true;
+ *piCurrentMapRpakHandle = g_pakLoadApi->LoadRpakFileAsync(mapRpakStr, *rpakMemoryAllocator, 7);
+
+ // load special _patch rpak (seemingly used for dev things?)
+ char levelPatchRpakStr[272];
+ snprintf(levelPatchRpakStr, 272, "%s_patch.rpak", mapName.c_str());
+ *piCurrentMapPatchRpakHandle = g_pakLoadApi->LoadRpakFileAsync(levelPatchRpakStr, *rpakMemoryAllocator, 7);
+
+ // we just reloaded the paks, so we don't need to force it again
+ g_pPakLoadManager->SetForceReloadOnMapLoad(false);
+ return true;
+}
+
+// clang-format off
+HOOK(LoadPakAsyncHook, LoadPakAsync,
+PakHandle, __fastcall, (const char* pPath, void* memoryAllocator, int flags))
+// clang-format on
+{
+ // make a copy of the path for comparing to determine whether we should load this pak on dedi, before it could get overwritten
+ std::string svOriginalPath(pPath);
+
+ std::string resultingPath(pPath);
+ HandlePakAliases(resultingPath);
+
+ if (g_pPakLoadManager->IsVanillaCall())
+ {
+ g_pPakLoadManager->LoadPreloadPaks();
+ g_pPakLoadManager->FixupPakPath(resultingPath);
// do this after custom paks load and in bShouldLoadPaks so we only ever call this on the root pakload call
// todo: could probably add some way to flag custom paks to not be loaded on dedicated servers in rpak.json
@@ -210,44 +451,27 @@ int, __fastcall, (char* pPath, void* unknownSingleton, int flags, void* pCallbac
// sp_<map> rpaks contain tutorial ghost data
// sucks to have to load the entire rpak for that but sp was never meant to be done on dedi
if (IsDedicatedServer() &&
- (CommandLine()->CheckParm("-nopakdedi") || strncmp(&originalPath[0], "common", 6) && strncmp(&originalPath[0], "sp_", 3)))
+ (CommandLine()->CheckParm("-nopakdedi") || strncmp(&svOriginalPath[0], "common", 6) && strncmp(&svOriginalPath[0], "sp_", 3)))
{
- if (bNeedToFreePakName)
- delete[] pPath;
-
- NS::log::rpak->info("Not loading pak {} for dedicated server", originalPath);
- return -1;
+ NS::log::rpak->info("Not loading pak {} for dedicated server", svOriginalPath);
+ return PakHandle::INVALID;
}
}
- int iPakHandle = LoadPakAsync(pPath, unknownSingleton, flags, pCallback0, pCallback1);
- NS::log::rpak->info("LoadPakAsync {} {}", pPath, iPakHandle);
-
- // trak the pak
- g_pPakLoadManager->TrackLoadedPak(ePakLoadSource::UNTRACKED, iPakHandle, nPathHash);
- LoadPostloadPaks(pPath);
+ PakHandle iPakHandle = LoadPakAsync(resultingPath.c_str(), memoryAllocator, flags);
+ NS::log::rpak->info("LoadPakAsync {} {}", resultingPath, iPakHandle);
- if (bNeedToFreePakName)
- delete[] pPath;
+ g_pPakLoadManager->OnPakLoaded(svOriginalPath, resultingPath, iPakHandle);
return iPakHandle;
}
// clang-format off
HOOK(UnloadPakHook, UnloadPak,
-void*, __fastcall, (int nPakHandle, void* pCallback))
+void*, __fastcall, (PakHandle nPakHandle, void* pCallback))
// clang-format on
{
- // stop tracking the pak
- g_pPakLoadManager->RemoveLoadedPak(nPakHandle);
-
- static bool bShouldUnloadPaks = true;
- if (bShouldUnloadPaks)
- {
- bShouldUnloadPaks = false;
- g_pPakLoadManager->UnloadMapPaks();
- bShouldUnloadPaks = true;
- }
+ g_pPakLoadManager->OnPakUnloading(nPakHandle);
NS::log::rpak->info("UnloadPak {}", nPakHandle);
return UnloadPak(nPakHandle, pCallback);
@@ -256,7 +480,7 @@ void*, __fastcall, (int nPakHandle, void* pCallback))
// we hook this exclusively for resolving stbsp paths, but seemingly it's also used for other stuff like vpk, rpak, mprj and starpak loads
// tbh this actually might be for memory mapped files or something, would make sense i think
// clang-format off
-HOOK(ReadFileAsyncHook, ReadFileAsync,
+HOOK(OpenFileHook, o_pOpenFile,
void*, __fastcall, (const char* pPath, void* pCallback))
// clang-format on
{
@@ -329,19 +553,34 @@ void*, __fastcall, (const char* pPath, void* pCallback))
NS::log::rpak->info("LoadStreamPak: {}", filename.string());
}
- return ReadFileAsync(pPath, pCallback);
+ return o_pOpenFile(pPath, pCallback);
}
ON_DLL_LOAD("engine.dll", RpakFilesystem, (CModule module))
{
- AUTOHOOK_DISPATCH();
-
g_pPakLoadManager = new PakLoadManager;
g_pakLoadApi = module.Offset(0x5BED78).Deref().RCast<PakLoadFuncs*>();
- pUnknownPakLoadSingleton = module.Offset(0x7C5E20).RCast<void**>();
- LoadPakAsyncHook.Dispatch((LPVOID*)g_pakLoadApi->LoadPakAsync);
+ LoadPakAsyncHook.Dispatch((LPVOID*)g_pakLoadApi->LoadRpakFileAsync);
UnloadPakHook.Dispatch((LPVOID*)g_pakLoadApi->UnloadPak);
- ReadFileAsyncHook.Dispatch((LPVOID*)g_pakLoadApi->ReadFileAsync);
+ OpenFileHook.Dispatch((LPVOID*)g_pakLoadApi->OpenFile);
+
+ pszCurrentMapRpakPath = module.Offset(0x1315C3E0).RCast<decltype(pszCurrentMapRpakPath)>();
+ piCurrentMapRpakHandle = module.Offset(0x7CB5A0).RCast<decltype(piCurrentMapRpakHandle)>();
+ piCurrentMapPatchRpakHandle = module.Offset(0x7CB5A4).RCast<decltype(piCurrentMapPatchRpakHandle)>();
+ ppModelLoader = module.Offset(0x7C4AC0).RCast<decltype(ppModelLoader)>();
+ rpakMemoryAllocator = module.Offset(0x7C5E20).RCast<decltype(rpakMemoryAllocator)>();
+
+ o_pLoadGametypeSpecificRpaks = module.Offset(0x15AD20).RCast<decltype(o_pLoadGametypeSpecificRpaks)>();
+ o_pCleanMaterialSystemStuff = module.Offset(0x12A11F00).RCast<decltype(o_pCleanMaterialSystemStuff)>();
+ o_pCModelLoader_UnreferenceAllModels = module.Offset(0x5ED580).RCast<decltype(o_pCModelLoader_UnreferenceAllModels)>();
+ o_pLoadlevelLoadscreen = module.Offset(0x15A810).RCast<decltype(o_pLoadlevelLoadscreen)>();
+
+ o_pLoadMapRpaks = module.Offset(0x15A8C0).RCast<decltype(o_pLoadMapRpaks)>();
+ HookAttach(&(PVOID&)o_pLoadMapRpaks, (PVOID)h_LoadMapRpaks);
+
+ // kinda bad, doing things in rtech in an engine callback but it seems fine for now
+ CModule rtechModule(GetModuleHandleA("rtech_game.dll"));
+ o_pGetPakPatchNumber = rtechModule.Offset(0x9A00).RCast<decltype(o_pGetPakPatchNumber)>();
}
diff --git a/primedev/core/filesystem/rpakfilesystem.h b/primedev/core/filesystem/rpakfilesystem.h
index bcd57a73..87a41e7b 100644
--- a/primedev/core/filesystem/rpakfilesystem.h
+++ b/primedev/core/filesystem/rpakfilesystem.h
@@ -1,39 +1,81 @@
#pragma once
-enum class ePakLoadSource
-{
- UNTRACKED = -1, // not a pak we loaded, we shouldn't touch this one
+#include <regex>
- CONSTANT, // should be loaded at all times
- MAP // loaded from a map, should be unloaded when the map is unloaded
+enum PakHandle : int
+{
+ INVALID = -1,
};
-struct LoadedPak
+struct ModPak_t
{
- ePakLoadSource m_nLoadSource;
- int m_nPakHandle;
- size_t m_nPakNameHash;
+ std::string m_modName;
+
+ std::string m_path;
+ size_t m_pathHash = 0;
+
+ // If the map being loaded matches this regex, this pak will be loaded.
+ std::regex m_mapRegex;
+ // If a pak with a hash matching this is loaded, this pak will be loaded.
+ size_t m_dependentPakHash = 0;
+ // If this is set, this pak will be loaded whenever any other pak is loaded.
+ bool m_preload = false;
+
+ // If this is set, the Pak will be unloaded on next map load
+ bool m_markedForDelete = false;
+ // The current rpak handle associated with this Pak
+ PakHandle m_handle = PakHandle::INVALID;
};
class PakLoadManager
{
-private:
- std::map<int, LoadedPak> m_vLoadedPaks {};
- std::unordered_map<size_t, int> m_HashToPakHandle {};
-
public:
- int LoadPakAsync(const char* pPath, const ePakLoadSource nLoadSource);
- void UnloadPak(const int nPakHandle);
- void UnloadMapPaks();
- void* LoadFile(const char* path); // this is a guess
+ void UnloadAllModPaks();
+ void TrackModPaks(Mod& mod);
+
+ void CleanUpUnloadedPaks();
+ void UnloadMarkedPaks();
+
+ void LoadModPaksForMap(const char* mapName);
+ void UnloadModPaks();
+
+ // Whether the current context is a vanilla call to a function, or a modded one
+ bool IsVanillaCall() const { return m_reentranceCounter == 0; }
+ // Whether paks will be forced to reload on the next map load
+ bool GetForceReloadOnMapLoad() const { return m_forceReloadOnMapLoad; }
+ void SetForceReloadOnMapLoad(bool value) { m_forceReloadOnMapLoad = value; }
+
+ void OnPakLoaded(std::string& originalPath, std::string& resultingPath, PakHandle resultingHandle);
+ void OnPakUnloading(PakHandle handle);
+
+ void FixupPakPath(std::string& path);
+
+ void LoadPreloadPaks();
+ void ReloadPostloadPaks();
+
+ void* OpenFile(const char* path);
+
+private:
+ void LoadDependentPaks(std::string& path, PakHandle handle);
+ void UnloadDependentPaks(PakHandle handle);
- LoadedPak* TrackLoadedPak(ePakLoadSource nLoadSource, int nPakHandle, size_t nPakNameHash);
- void RemoveLoadedPak(int nPakHandle);
+ // All paks that vanilla has attempted to load. (they may have been aliased away)
+ // Also known as a list of rpaks that the vanilla game would have loaded at this point in time.
+ std::vector<std::pair<std::string, PakHandle>> m_vanillaPaks;
- LoadedPak* GetPakInfo(const int nPakHandle);
+ // All mod Paks that are currently tracked
+ std::vector<ModPak_t> m_modPaks;
+ // Hashes of the currently loaded map mod paks
+ std::vector<size_t> m_mapPaks;
+ // Currently loaded Pak path hashes that depend on a handle to remain loaded (Postload)
+ std::vector<std::pair<PakHandle, size_t>> m_dependentPaks;
- int GetPakHandle(const size_t nPakNameHash);
- int GetPakHandle(const char* pPath);
+ // Used to force rpaks to be unloaded and reloaded on the next map load.
+ // Vanilla behaviour is to not do this when loading into mp_lobby, or loading into the same map you were last in.
+ bool m_forceReloadOnMapLoad = false;
+ // Used to track if the current hook call is a vanilla call or not.
+ // When loading/unloading a mod Pak, increment this before doing so, and decrement afterwards.
+ int m_reentranceCounter = 0;
};
extern PakLoadManager* g_pPakLoadManager;