From 160f503bc81bffdef6dbaa16eec7c73fccef0eee Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Sat, 7 Sep 2024 21:10:28 +0100 Subject: Big rpak loading refactor (#766) This reworks how rpaks are loaded, unloaded and tracked. It allows for rpak reloading between map loads, meaning that skins and map overhauls could be enabled and disabled on the fly. Previous methods of loading rpaks still work. --- primedev/core/filesystem/rpakfilesystem.cpp | 543 ++++++++++++++++++++-------- 1 file changed, 391 insertions(+), 152 deletions(-) (limited to 'primedev/core/filesystem/rpakfilesystem.cpp') diff --git a/primedev/core/filesystem/rpakfilesystem.cpp b/primedev/core/filesystem/rpakfilesystem.cpp index da72646b..ebb9085a 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; }); - m_vLoadedPaks.insert(std::make_pair(nPakHandle, pak)); - return &m_vLoadedPaks.at(nPakHandle); + (*o_pCModelLoader_UnreferenceAllModels)(*ppModelLoader); + (*o_pCleanMaterialSystemStuff)(); + + for (auto& modPak : m_modPaks) + { + if (modPak.m_handle == PakHandle::INVALID || !modPak.m_markedForDelete) + continue; + + 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& 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& 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 > PakHandle::INVALID; 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->m_ModDirectory / "paks" / mod->RpakAliases[originalPath]).string(); + 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(); - // dont load the pak if it's currently loaded already - size_t nPathHash = STR_HASH(pPath); - if (g_pPakLoadManager->GetPakHandle(nPathHash) != -1) - return -1; + // strip file extension + const std::string mapName = fs::path(mapPath).replace_extension().string(); - bool bNeedToFreePakName = false; + // load mp_common, sp_common etc. + o_pLoadGametypeSpecificRpaks(mapName.c_str()); - static bool bShouldLoadPaks = true; - if (bShouldLoadPaks) + // unload old modded map paks + g_pPakLoadManager->UnloadModPaks(); + // load modded map paks + g_pPakLoadManager->LoadModPaksForMap(mapName.c_str()); + + // 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; + + 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(); + } - // disable preloading while we're doing this - bShouldLoadPaks = false; + char mapRpakStr[272]; + snprintf(mapRpakStr, 272, "%s.rpak", mapName.c_str()); - LoadPreloadPaks(); - LoadCustomMapPaks(&pPath, &bNeedToFreePakName); + // if level being loaded is the same as current level, do nothing + if (!g_pPakLoadManager->GetForceReloadOnMapLoad() && !strcmp(mapRpakStr, pszCurrentMapRpakPath)) + return true; - bShouldLoadPaks = true; + strcpy(pszCurrentMapRpakPath, mapRpakStr); + + (*o_pCleanMaterialSystemStuff)(); + o_pLoadlevelLoadscreen(mapName.c_str()); + + // 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; + } + + *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_ 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(); - pUnknownPakLoadSingleton = module.Offset(0x7C5E20).RCast(); - 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(); + piCurrentMapRpakHandle = module.Offset(0x7CB5A0).RCast(); + piCurrentMapPatchRpakHandle = module.Offset(0x7CB5A4).RCast(); + ppModelLoader = module.Offset(0x7C4AC0).RCast(); + rpakMemoryAllocator = module.Offset(0x7C5E20).RCast(); + + o_pLoadGametypeSpecificRpaks = module.Offset(0x15AD20).RCast(); + o_pCleanMaterialSystemStuff = module.Offset(0x12A11F00).RCast(); + o_pCModelLoader_UnreferenceAllModels = module.Offset(0x5ED580).RCast(); + o_pLoadlevelLoadscreen = module.Offset(0x15A810).RCast(); + + o_pLoadMapRpaks = module.Offset(0x15A8C0).RCast(); + 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(); } -- cgit v1.2.3