diff options
Diffstat (limited to 'primedev/core')
-rw-r--r-- | primedev/core/convar/convar.cpp | 5 | ||||
-rw-r--r-- | primedev/core/convar/convar.h | 1 | ||||
-rw-r--r-- | primedev/core/convar/cvar.cpp | 1 | ||||
-rw-r--r-- | primedev/core/convar/cvar.h | 1 | ||||
-rw-r--r-- | primedev/core/filesystem/filesystem.cpp | 83 | ||||
-rw-r--r-- | primedev/core/filesystem/filesystem.h | 3 | ||||
-rw-r--r-- | primedev/core/filesystem/rpakfilesystem.cpp | 543 | ||||
-rw-r--r-- | primedev/core/filesystem/rpakfilesystem.h | 86 | ||||
-rw-r--r-- | primedev/core/hooks.cpp | 38 | ||||
-rw-r--r-- | primedev/core/hooks.h | 17 | ||||
-rw-r--r-- | primedev/core/math/bitbuf.h | 115 | ||||
-rw-r--r-- | primedev/core/math/color.h | 80 | ||||
-rw-r--r-- | primedev/core/math/vector.h | 60 | ||||
-rw-r--r-- | primedev/core/memalloc.h | 5 | ||||
-rw-r--r-- | primedev/core/sourceinterface.cpp | 49 | ||||
-rw-r--r-- | primedev/core/sourceinterface.h | 38 | ||||
-rw-r--r-- | primedev/core/tier0.cpp | 29 | ||||
-rw-r--r-- | primedev/core/tier1.h | 6 | ||||
-rw-r--r-- | primedev/core/vanilla.h | 5 |
19 files changed, 668 insertions, 497 deletions
diff --git a/primedev/core/convar/convar.cpp b/primedev/core/convar/convar.cpp index 87a1e159..802c088d 100644 --- a/primedev/core/convar/convar.cpp +++ b/primedev/core/convar/convar.cpp @@ -1,7 +1,7 @@ #include "bits.h" #include "cvar.h" #include "convar.h" -#include "core/sourceinterface.h" +#include "core/tier1.h" #include <float.h> @@ -35,8 +35,7 @@ ON_DLL_LOAD("engine.dll", ConVar, (CModule module)) g_pConVar_Vtable = module.Offset(0x67FD28); g_pIConVar_Vtable = module.Offset(0x67FDC8); - g_pCVarInterface = new SourceInterface<CCvar>("vstdlib.dll", "VEngineCvar007"); - g_pCVar = *g_pCVarInterface; + g_pCVar = Sys_GetFactoryPtr("vstdlib.dll", "VEngineCvar007").RCast<CCvar*>(); } //----------------------------------------------------------------------------- diff --git a/primedev/core/convar/convar.h b/primedev/core/convar/convar.h index f0366b46..33a50c1c 100644 --- a/primedev/core/convar/convar.h +++ b/primedev/core/convar/convar.h @@ -1,5 +1,4 @@ #pragma once -#include "core/sourceinterface.h" #include "core/math/color.h" #include "cvar.h" #include "concommand.h" diff --git a/primedev/core/convar/cvar.cpp b/primedev/core/convar/cvar.cpp index aa5f0365..78da1ad2 100644 --- a/primedev/core/convar/cvar.cpp +++ b/primedev/core/convar/cvar.cpp @@ -22,5 +22,4 @@ std::unordered_map<std::string, ConCommandBase*> CCvar::DumpToMap() return allConVars; } -SourceInterface<CCvar>* g_pCVarInterface; CCvar* g_pCVar; diff --git a/primedev/core/convar/cvar.h b/primedev/core/convar/cvar.h index beaa84f4..024f2b87 100644 --- a/primedev/core/convar/cvar.h +++ b/primedev/core/convar/cvar.h @@ -34,5 +34,4 @@ public: std::unordered_map<std::string, ConCommandBase*> DumpToMap(); }; -extern SourceInterface<CCvar>* g_pCVarInterface; extern CCvar* g_pCVar; 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; diff --git a/primedev/core/hooks.cpp b/primedev/core/hooks.cpp index 5026f837..6146c93c 100644 --- a/primedev/core/hooks.cpp +++ b/primedev/core/hooks.cpp @@ -12,8 +12,6 @@ namespace fs = std::filesystem; -AUTOHOOK_INIT() - // called from the ON_DLL_LOAD macros __dllLoadCallback::__dllLoadCallback( eDllLoadCallbackSide side, const std::string dllName, DllLoadCallbackFuncType callback, std::string uniqueStr, std::string reliesOn) @@ -85,18 +83,18 @@ void __fileAutohook::DispatchForModule(const char* pModuleName) hook->Dispatch(); } -ManualHook::ManualHook(const char* funcName, LPVOID func) : pHookFunc(func), ppOrigFunc(nullptr) +ManualHook::ManualHook(const char* funcName, LPVOID func) + : svFuncName(funcName) + , pHookFunc(func) + , ppOrigFunc(nullptr) { - const size_t iFuncNameStrlen = strlen(funcName); - pFuncName = new char[iFuncNameStrlen]; - memcpy(pFuncName, funcName, iFuncNameStrlen); } -ManualHook::ManualHook(const char* funcName, LPVOID* orig, LPVOID func) : pHookFunc(func), ppOrigFunc(orig) +ManualHook::ManualHook(const char* funcName, LPVOID* orig, LPVOID func) + : svFuncName(funcName) + , pHookFunc(func) + , ppOrigFunc(orig) { - const size_t iFuncNameStrlen = strlen(funcName); - pFuncName = new char[iFuncNameStrlen]; - memcpy(pFuncName, funcName, iFuncNameStrlen); } bool ManualHook::Dispatch(LPVOID addr, LPVOID* orig) @@ -105,19 +103,19 @@ bool ManualHook::Dispatch(LPVOID addr, LPVOID* orig) ppOrigFunc = orig; if (!addr) - spdlog::error("Address for hook {} is invalid", pFuncName); + spdlog::error("Address for hook {} is invalid", svFuncName); else if (MH_CreateHook(addr, pHookFunc, ppOrigFunc) == MH_OK) { if (MH_EnableHook(addr) == MH_OK) { - spdlog::info("Enabling hook {}", pFuncName); + spdlog::info("Enabling hook {}", svFuncName); return true; } else - spdlog::error("MH_EnableHook failed for function {}", pFuncName); + spdlog::error("MH_EnableHook failed for function {}", svFuncName); } else - spdlog::error("MH_CreateHook failed for function {}", pFuncName); + spdlog::error("MH_CreateHook failed for function {}", svFuncName); return false; } @@ -223,14 +221,15 @@ void MakeHook(LPVOID pTarget, LPVOID pDetour, void* ppOriginal, const char* pFun spdlog::error("MH_CreateHook failed for function {}", pStrippedFuncName); } -AUTOHOOK_ABSOLUTEADDR(_GetCommandLineA, (LPVOID)GetCommandLineA, LPSTR, WINAPI, ()) +static LPSTR(WINAPI* o_pGetCommandLineA)() = nullptr; +static LPSTR WINAPI h_GetCommandLineA() { static char* cmdlineModified; static char* cmdlineOrg; if (cmdlineOrg == nullptr || cmdlineModified == nullptr) { - cmdlineOrg = _GetCommandLineA(); + cmdlineOrg = o_pGetCommandLineA(); bool isDedi = strstr(cmdlineOrg, "-dedicated"); // well, this one has to be a real argument bool ignoreStartupArgs = strstr(cmdlineOrg, "-nostartupargs"); @@ -328,8 +327,6 @@ void CallLoadLibraryACallbacks(LPCSTR lpLibFileName, HMODULE moduleAddress) void CallLoadLibraryWCallbacks(LPCWSTR lpLibFileName, HMODULE moduleAddress) { - CModule cModule(moduleAddress); - while (true) { bool bDoneCalling = true; @@ -396,6 +393,7 @@ void HookSys_Init() { spdlog::error("MH_Initialize (minhook initialization) failed"); } - // todo: remove remaining instances of autohook in this file - AUTOHOOK_DISPATCH() + + o_pGetCommandLineA = GetCommandLineA; + HookAttach(&(PVOID&)o_pGetCommandLineA, (PVOID)h_GetCommandLineA); } diff --git a/primedev/core/hooks.h b/primedev/core/hooks.h index facf51bf..26d9483c 100644 --- a/primedev/core/hooks.h +++ b/primedev/core/hooks.h @@ -140,7 +140,9 @@ public: __autohook() = delete; __autohook(__fileAutohook* autohook, const char* funcName, LPVOID absoluteAddress, LPVOID* orig, LPVOID func) - : pHookFunc(func), ppOrigFunc(orig), iAbsoluteAddress(absoluteAddress) + : pHookFunc(func) + , ppOrigFunc(orig) + , iAbsoluteAddress(absoluteAddress) { iAddressResolutionMode = ABSOLUTE_ADDR; @@ -152,7 +154,8 @@ public: } __autohook(__fileAutohook* autohook, const char* funcName, const char* addrString, LPVOID* orig, LPVOID func) - : pHookFunc(func), ppOrigFunc(orig) + : pHookFunc(func) + , ppOrigFunc(orig) { iAddressResolutionMode = OFFSET_STRING; @@ -168,7 +171,8 @@ public: } __autohook(__fileAutohook* autohook, const char* funcName, const char* moduleName, const char* procName, LPVOID* orig, LPVOID func) - : pHookFunc(func), ppOrigFunc(orig) + : pHookFunc(func) + , ppOrigFunc(orig) { iAddressResolutionMode = PROCADDRESS; @@ -278,7 +282,7 @@ public: class ManualHook { public: - char* pFuncName; + std::string svFuncName; LPVOID pHookFunc; LPVOID* ppOrigFunc; @@ -327,10 +331,7 @@ public: pAutohook->vars.push_back(this); } - void Dispatch() - { - *m_pTarget = (void*)ParseDLLOffsetString(m_pAddrString); - } + void Dispatch() { *m_pTarget = (void*)ParseDLLOffsetString(m_pAddrString); } }; // VAR_AT(engine.dll+0x404, ConVar*, Cvar_host_timescale) diff --git a/primedev/core/math/bitbuf.h b/primedev/core/math/bitbuf.h index a06dab17..33056e63 100644 --- a/primedev/core/math/bitbuf.h +++ b/primedev/core/math/bitbuf.h @@ -90,25 +90,13 @@ enum EBitCoordType class BitBufferBase { protected: - INLINE void SetName(const char* name) - { - m_BufferName = name; - } + INLINE void SetName(const char* name) { m_BufferName = name; } public: - INLINE bool IsOverflowed() - { - return m_Overflow; - } - INLINE void SetOverflowed() - { - m_Overflow = true; - } + INLINE bool IsOverflowed() { return m_Overflow; } + INLINE void SetOverflowed() { m_Overflow = true; } - INLINE const char* GetName() - { - return m_BufferName; - } + INLINE const char* GetName() { return m_BufferName; } private: const char* m_BufferName = ""; @@ -437,28 +425,13 @@ public: return fReturn; } - INLINE i32 ReadChar() - { - return ReadSBitLong(sizeof(char) << 3); - } - INLINE u32 ReadByte() - { - return ReadUBitLong(sizeof(unsigned char) << 3); - } + INLINE i32 ReadChar() { return ReadSBitLong(sizeof(char) << 3); } + INLINE u32 ReadByte() { return ReadUBitLong(sizeof(unsigned char) << 3); } - INLINE i32 ReadShort() - { - return ReadSBitLong(sizeof(short) << 3); - } - INLINE u32 ReadWord() - { - return ReadUBitLong(sizeof(unsigned short) << 3); - } + INLINE i32 ReadShort() { return ReadSBitLong(sizeof(short) << 3); } + INLINE u32 ReadWord() { return ReadUBitLong(sizeof(unsigned short) << 3); } - INLINE i32 ReadLong() - { - return (i32)(ReadUBitLong(sizeof(i32) << 3)); - } + INLINE i32 ReadLong() { return (i32)(ReadUBitLong(sizeof(i32) << 3)); } INLINE float ReadFloat() { u32 temp = ReadUBitLong(sizeof(float) << 3); @@ -687,24 +660,12 @@ public: return std::min(nCurOfs + nAdjust, m_DataBits); } - INLINE bool SeekRelative(size_t offset) - { - return Seek(GetNumBitsRead() + offset); - } + INLINE bool SeekRelative(size_t offset) { return Seek(GetNumBitsRead() + offset); } - INLINE size_t TotalBytesAvailable() - { - return m_DataBytes; - } + INLINE size_t TotalBytesAvailable() { return m_DataBytes; } - INLINE size_t GetNumBitsLeft() - { - return m_DataBits - GetNumBitsRead(); - } - INLINE size_t GetNumBytesLeft() - { - return GetNumBitsLeft() >> 3; - } + INLINE size_t GetNumBitsLeft() { return m_DataBits - GetNumBitsRead(); } + INLINE size_t GetNumBytesLeft() { return GetNumBitsLeft() >> 3; } private: size_t m_DataBits; // 0x0010 @@ -743,10 +704,7 @@ public: m_DataEnd = reinterpret_cast<u32*>(reinterpret_cast<u8*>(m_Data) + m_DataBytes); } - INLINE int GetNumBitsLeft() - { - return m_OutBitsLeft + (32 * (m_DataEnd - m_DataOut - 1)); - } + INLINE int GetNumBitsLeft() { return m_OutBitsLeft + (32 * (m_DataEnd - m_DataOut - 1)); } INLINE void Reset() { @@ -775,10 +733,7 @@ public: return reinterpret_cast<u8*>(m_Data); } - INLINE u8* GetData() - { - return GetBasePointer(); - } + INLINE u8* GetData() { return GetBasePointer(); } INLINE void Finish() { @@ -851,10 +806,7 @@ public: } } - INLINE void WriteSBitLong(i32 data, i32 numBits) - { - WriteUBitLong((u32)data, numBits, false); - } + INLINE void WriteSBitLong(i32 data, i32 numBits) { WriteUBitLong((u32)data, numBits, false); } INLINE void WriteUBitVar(u32 n) { @@ -911,40 +863,19 @@ public: return !IsOverflowed(); } - INLINE bool WriteBytes(const uptr data, i32 numBytes) - { - return WriteBits(data, numBytes << 3); - } + INLINE bool WriteBytes(const uptr data, i32 numBytes) { return WriteBits(data, numBytes << 3); } - INLINE i32 GetNumBitsWritten() - { - return (32 - m_OutBitsLeft) + (32 * (m_DataOut - m_Data)); - } + INLINE i32 GetNumBitsWritten() { return (32 - m_OutBitsLeft) + (32 * (m_DataOut - m_Data)); } - INLINE i32 GetNumBytesWritten() - { - return (GetNumBitsWritten() + 7) >> 3; - } + INLINE i32 GetNumBytesWritten() { return (GetNumBitsWritten() + 7) >> 3; } - INLINE void WriteChar(i32 val) - { - WriteSBitLong(val, sizeof(char) << 3); - } + INLINE void WriteChar(i32 val) { WriteSBitLong(val, sizeof(char) << 3); } - INLINE void WriteByte(i32 val) - { - WriteUBitLong(val, sizeof(unsigned char) << 3, false); - } + INLINE void WriteByte(i32 val) { WriteUBitLong(val, sizeof(unsigned char) << 3, false); } - INLINE void WriteShort(i32 val) - { - WriteSBitLong(val, sizeof(short) << 3); - } + INLINE void WriteShort(i32 val) { WriteSBitLong(val, sizeof(short) << 3); } - INLINE void WriteWord(i32 val) - { - WriteUBitLong(val, sizeof(unsigned short) << 3); - } + INLINE void WriteWord(i32 val) { WriteUBitLong(val, sizeof(unsigned short) << 3); } INLINE bool WriteString(const char* str) { diff --git a/primedev/core/math/color.h b/primedev/core/math/color.h index 013c4e4c..a46bc089 100644 --- a/primedev/core/math/color.h +++ b/primedev/core/math/color.h @@ -7,22 +7,10 @@ struct color24 typedef struct color32_s { - bool operator!=(const struct color32_s& other) const - { - return r != other.r || g != other.g || b != other.b || a != other.a; - } - inline unsigned* asInt(void) - { - return reinterpret_cast<unsigned*>(this); - } - inline const unsigned* asInt(void) const - { - return reinterpret_cast<const unsigned*>(this); - } - inline void Copy(const color32_s& rhs) - { - *asInt() = *rhs.asInt(); - } + bool operator!=(const struct color32_s& other) const { return r != other.r || g != other.g || b != other.b || a != other.a; } + inline unsigned* asInt(void) { return reinterpret_cast<unsigned*>(this); } + inline const unsigned* asInt(void) const { return reinterpret_cast<const unsigned*>(this); } + inline void Copy(const color32_s& rhs) { *asInt() = *rhs.asInt(); } uint8_t r, g, b, a; } color32; @@ -79,55 +67,22 @@ public: _b = _color[2]; _a = _color[3]; } - int GetValue(int index) const - { - return _color[index]; - } - void SetRawColor(int color32) - { - *((int*)this) = color32; - } - int GetRawColor(void) const - { - return *((int*)this); - } + int GetValue(int index) const { return _color[index]; } + void SetRawColor(int color32) { *((int*)this) = color32; } + int GetRawColor(void) const { return *((int*)this); } - inline int r() const - { - return _color[0]; - } - inline int g() const - { - return _color[1]; - } - inline int b() const - { - return _color[2]; - } - inline int a() const - { - return _color[3]; - } + inline int r() const { return _color[0]; } + inline int g() const { return _color[1]; } + inline int b() const { return _color[2]; } + inline int a() const { return _color[3]; } - unsigned char& operator[](int index) - { - return _color[index]; - } + unsigned char& operator[](int index) { return _color[index]; } - const unsigned char& operator[](int index) const - { - return _color[index]; - } + const unsigned char& operator[](int index) const { return _color[index]; } - bool operator==(const Color& rhs) const - { - return (*((int*)this) == *((int*)&rhs)); - } + bool operator==(const Color& rhs) const { return (*((int*)this) == *((int*)&rhs)); } - bool operator!=(const Color& rhs) const - { - return !(operator==(rhs)); - } + bool operator!=(const Color& rhs) const { return !(operator==(rhs)); } Color& operator=(const Color& rhs) { @@ -164,10 +119,7 @@ public: return out; } - SourceColor ToSourceColor() - { - return SourceColor(_color[0], _color[1], _color[2], _color[3]); - } + SourceColor ToSourceColor() { return SourceColor(_color[0], _color[1], _color[2], _color[3]); } private: unsigned char _color[4]; diff --git a/primedev/core/math/vector.h b/primedev/core/math/vector.h index e62f2c93..ea1de65e 100644 --- a/primedev/core/math/vector.h +++ b/primedev/core/math/vector.h @@ -13,15 +13,27 @@ class Vector3 public: float x, y, z; - Vector3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {} - Vector3(float _f) : x(_f), y(_f), z(_f) {} - Vector3() : x(0.0f), y(0.0f), z(0.0f) {} - - inline bool IsValid() + Vector3(float _x, float _y, float _z) + : x(_x) + , y(_y) + , z(_z) + { + } + Vector3(float _f) + : x(_f) + , y(_f) + , z(_f) + { + } + Vector3() + : x(0.0f) + , y(0.0f) + , z(0.0f) { - return IsFinite(x) && IsFinite(y) && IsFinite(z); } + inline bool IsValid() { return IsFinite(x) && IsFinite(y) && IsFinite(z); } + inline void Init(float fX = 0.0f, float fY = 0.0f, float fZ = 0.0f) { x = fX; @@ -29,10 +41,7 @@ public: z = fZ; } - inline float Length() - { - return FastSqrt(x * x + y * y + z * z); - } + inline float Length() { return FastSqrt(x * x + y * y + z * z); } inline float DistTo(const Vector3& vOther) { @@ -44,10 +53,7 @@ public: return vDelta.Length(); } - float Dot(const Vector3& vOther) const - { - return x * vOther.x + y * vOther.y + z * vOther.z; - } + float Dot(const Vector3& vOther) const { return x * vOther.x + y * vOther.y + z * vOther.z; } // Cross product between two vectors. Vector3 Cross(const Vector3& vOther) const; @@ -311,17 +317,29 @@ public: float y; float z; - QAngle(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {} - QAngle(float _f) : x(_f), y(_f), z(_f) {} - QAngle() : x(0.0f), y(0.0f), z(0.0f) {} + QAngle(float _x, float _y, float _z) + : x(_x) + , y(_y) + , z(_z) + { + } + QAngle(float _f) + : x(_f) + , y(_f) + , z(_f) + { + } + QAngle() + : x(0.0f) + , y(0.0f) + , z(0.0f) + { + } Vector3 GetNormal() const; // todo: more operators maybe - bool operator==(const QAngle& other) - { - return x == other.x && y == other.y && z == other.z; - } + bool operator==(const QAngle& other) { return x == other.x && y == other.y && z == other.z; } }; inline Vector3 QAngle::GetNormal() const diff --git a/primedev/core/memalloc.h b/primedev/core/memalloc.h index 73e078f5..a55ce361 100644 --- a/primedev/core/memalloc.h +++ b/primedev/core/memalloc.h @@ -40,10 +40,7 @@ public: } return _realloc_base(originalPtr, newSize); } - static void Free(void* ptr) - { - _free_base(ptr); - } + static void Free(void* ptr) { _free_base(ptr); } }; typedef rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::MemoryPoolAllocator<SourceAllocator>, SourceAllocator> rapidjson_document; diff --git a/primedev/core/sourceinterface.cpp b/primedev/core/sourceinterface.cpp index 5a72beb0..7ce33925 100644 --- a/primedev/core/sourceinterface.cpp +++ b/primedev/core/sourceinterface.cpp @@ -1,16 +1,11 @@ -#include "sourceinterface.h" #include "logging/sourceconsole.h" -AUTOHOOK_INIT() - // really wanted to do a modular callback system here but honestly couldn't be bothered so hardcoding stuff for now: todo later -// clang-format off -AUTOHOOK_PROCADDRESS(ClientCreateInterface, client.dll, CreateInterface, -void*, __fastcall, (const char* pName, const int* pReturnCode)) -// clang-format on +static void*(__fastcall* o_pClientCreateInterface)(const char* pName, const int* pReturnCode) = nullptr; +static void* __fastcall h_ClientCreateInterface(const char* pName, const int* pReturnCode) { - void* ret = ClientCreateInterface(pName, pReturnCode); + void* ret = o_pClientCreateInterface(pName, pReturnCode); spdlog::info("CreateInterface CLIENT {}", pName); if (!strcmp(pName, "GameClientExports001")) @@ -19,30 +14,38 @@ void*, __fastcall, (const char* pName, const int* pReturnCode)) return ret; } -// clang-format off -AUTOHOOK_PROCADDRESS(ServerCreateInterface, server.dll, CreateInterface, -void*, __fastcall, (const char* pName, const int* pReturnCode)) -// clang-format on +static void*(__fastcall* o_pServerCreateInterface)(const char* pName, const int* pReturnCode) = nullptr; +static void* __fastcall h_ServerCreateInterface(const char* pName, const int* pReturnCode) { - void* ret = ServerCreateInterface(pName, pReturnCode); + void* ret = o_pServerCreateInterface(pName, pReturnCode); spdlog::info("CreateInterface SERVER {}", pName); return ret; } -// clang-format off -AUTOHOOK_PROCADDRESS(EngineCreateInterface, engine.dll, CreateInterface, -void*, __fastcall, (const char* pName, const int* pReturnCode)) -// clang-format on +static void*(__fastcall* o_pEngineCreateInterface)(const char* pName, const int* pReturnCode) = nullptr; +static void* __fastcall h_EngineCreateInterface(const char* pName, const int* pReturnCode) { - void* ret = EngineCreateInterface(pName, pReturnCode); + void* ret = o_pEngineCreateInterface(pName, pReturnCode); spdlog::info("CreateInterface ENGINE {}", pName); return ret; } -// clang-format off -ON_DLL_LOAD("client.dll", ClientInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(client.dll)} -ON_DLL_LOAD("server.dll", ServerInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(server.dll)} -ON_DLL_LOAD("engine.dll", EngineInterface, (CModule module)) {AUTOHOOK_DISPATCH_MODULE(engine.dll)} -// clang-format on +ON_DLL_LOAD("client.dll", ClientInterface, (CModule module)) +{ + o_pClientCreateInterface = module.GetExportedFunction("CreateInterface").RCast<decltype(o_pClientCreateInterface)>(); + HookAttach(&(PVOID&)o_pClientCreateInterface, (PVOID)h_ClientCreateInterface); +} + +ON_DLL_LOAD("server.dll", ServerInterface, (CModule module)) +{ + o_pServerCreateInterface = module.GetExportedFunction("CreateInterface").RCast<decltype(o_pServerCreateInterface)>(); + HookAttach(&(PVOID&)o_pServerCreateInterface, (PVOID)h_ServerCreateInterface); +} + +ON_DLL_LOAD("engine.dll", EngineInterface, (CModule module)) +{ + o_pEngineCreateInterface = module.GetExportedFunction("CreateInterface").RCast<decltype(o_pEngineCreateInterface)>(); + HookAttach(&(PVOID&)o_pEngineCreateInterface, (PVOID)h_EngineCreateInterface); +} diff --git a/primedev/core/sourceinterface.h b/primedev/core/sourceinterface.h deleted file mode 100644 index bbbbd3bc..00000000 --- a/primedev/core/sourceinterface.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once -#include <string> - -// interface return status -enum class InterfaceStatus : int -{ - IFACE_OK = 0, - IFACE_FAILED, -}; - -// literally just copied from ttf2sdk definition -typedef void* (*CreateInterfaceFn)(const char* pName, int* pReturnCode); - -template <typename T> class SourceInterface -{ -private: - T* m_interface; - -public: - SourceInterface(const std::string& moduleName, const std::string& interfaceName) - { - HMODULE handle = GetModuleHandleA(moduleName.c_str()); - CreateInterfaceFn createInterface = (CreateInterfaceFn)GetProcAddress(handle, "CreateInterface"); - m_interface = (T*)createInterface(interfaceName.c_str(), NULL); - if (m_interface == nullptr) - spdlog::error("Failed to call CreateInterface for %s in %s", interfaceName, moduleName); - } - - T* operator->() const - { - return m_interface; - } - - operator T*() const - { - return m_interface; - } -}; diff --git a/primedev/core/tier0.cpp b/primedev/core/tier0.cpp index dd5ac245..639b3bf8 100644 --- a/primedev/core/tier0.cpp +++ b/primedev/core/tier0.cpp @@ -18,11 +18,40 @@ void TryCreateGlobalMemAlloc() g_pMemAllocSingleton = CreateGlobalMemAlloc(); // if it already exists, this returns the preexisting IMemAlloc instance } +HRESULT WINAPI _SetThreadDescription(HANDLE hThread, PCWSTR lpThreadDescription) +{ + // need to grab it dynamically as this function was only introduced at some point in Windows 10 + static decltype(&SetThreadDescription) _SetThreadDescription = + CModule("KernelBase.dll").GetExportedFunction("SetThreadDescription").RCast<decltype(&SetThreadDescription)>(); + + if (_SetThreadDescription) + return _SetThreadDescription(hThread, lpThreadDescription); + + return ERROR_OLD_WIN_VERSION; +} + +static void(__fastcall* o_pThreadSetDebugName)(HANDLE threadHandle, const char* name) = nullptr; +static void __fastcall h_ThreadSetDebugName(HANDLE threadHandle, const char* name) +{ + if (threadHandle == 0) + threadHandle = GetCurrentThread(); + + // TODO: This "method" of "charset conversion" from string to wstring is abhorrent. Change it to a proper one + // as soon as Northstar has some helper function to do proper charset conversions. + auto tmp = std::string(name); + _SetThreadDescription(threadHandle, std::wstring(tmp.begin(), tmp.end()).c_str()); + + o_pThreadSetDebugName(threadHandle, name); +} + ON_DLL_LOAD("tier0.dll", Tier0GameFuncs, (CModule module)) { // shouldn't be necessary, but do this just in case TryCreateGlobalMemAlloc(); + o_pThreadSetDebugName = module.GetExportedFunction("ThreadSetDebugName").RCast<decltype(o_pThreadSetDebugName)>(); + HookAttach(&(PVOID&)o_pThreadSetDebugName, (PVOID)h_ThreadSetDebugName); + // setup tier0 funcs CommandLine = module.GetExportedFunction("CommandLine").RCast<CommandLineType>(); Plat_FloatTime = module.GetExportedFunction("Plat_FloatTime").RCast<Plat_FloatTimeType>(); diff --git a/primedev/core/tier1.h b/primedev/core/tier1.h index d162e7c8..36f577cc 100644 --- a/primedev/core/tier1.h +++ b/primedev/core/tier1.h @@ -7,6 +7,12 @@ #define CREATEINTERFACE_PROCNAME "CreateInterface" +enum class InterfaceStatus : int +{ + IFACE_OK = 0, + IFACE_FAILED, +}; + typedef void* (*CreateInterfaceFn)(const char* pName, int* pReturnCode); CMemory Sys_GetFactoryPtr(const std::string& svModuleName, const std::string& svFact); diff --git a/primedev/core/vanilla.h b/primedev/core/vanilla.h index b0797803..356495c8 100644 --- a/primedev/core/vanilla.h +++ b/primedev/core/vanilla.h @@ -17,10 +17,7 @@ public: m_bIsVanillaCompatible = isVanilla; } - bool GetVanillaCompatibility() - { - return m_bIsVanillaCompatible; - } + bool GetVanillaCompatibility() { return m_bIsVanillaCompatible; } private: bool m_bIsVanillaCompatible = false; |