From 8bb3c1decaa08436649369c12d5fe88746496abf Mon Sep 17 00:00:00 2001 From: momo5502 Date: Thu, 6 Apr 2017 22:22:30 +0200 Subject: [PATCH] [Maps] Implement support for usermaps --- src/Components/Modules/FileSystem.cpp | 12 +- src/Components/Modules/FileSystem.hpp | 2 + src/Components/Modules/Maps.cpp | 180 +++++++++++++++++++++++++- src/Components/Modules/Maps.hpp | 48 ++++++- src/Components/Modules/QuickPatch.cpp | 7 +- src/Game/Functions.cpp | 5 +- src/Game/Functions.hpp | 8 +- 7 files changed, 247 insertions(+), 15 deletions(-) diff --git a/src/Components/Modules/FileSystem.cpp b/src/Components/Modules/FileSystem.cpp index f31715be..f4ad61b9 100644 --- a/src/Components/Modules/FileSystem.cpp +++ b/src/Components/Modules/FileSystem.cpp @@ -270,7 +270,8 @@ namespace Components void FileSystem::FsRestartSync(int a1, int a2) { std::lock_guard _(FileSystem::FSMutex); - return Utils::Hook::Call(0x461A50)(a1, a2); // FS_Restart + Utils::Hook::Call(0x461A50)(a1, a2); // FS_Restart + Maps::GetUserMap()->reloadIwd(); } void FileSystem::DelayLoadImagesSync() @@ -285,6 +286,12 @@ namespace Components return Game::Load_Texture(loadDef, image); } + void FileSystem::IwdFreeStub(Game::iwd_t* iwd) + { + Maps::GetUserMap()->handlePackfile(iwd); + Utils::Hook::Call(0x4291A0)(iwd); + } + FileSystem::FileSystem() { FileSystem::MemAllocator.clear(); @@ -325,6 +332,9 @@ namespace Components // Synchronize db image loading Utils::Hook(0x415AB8, FileSystem::DelayLoadImagesSync, HOOK_CALL).install()->quick(); Utils::Hook(0x4D32BC, FileSystem::LoadTextureSync, HOOK_CALL).install()->quick(); + + // Handle IWD freeing + Utils::Hook(0x642F60, FileSystem::IwdFreeStub, HOOK_CALL).install()->quick(); } FileSystem::~FileSystem() diff --git a/src/Components/Modules/FileSystem.hpp b/src/Components/Modules/FileSystem.hpp index e589648b..09afa221 100644 --- a/src/Components/Modules/FileSystem.hpp +++ b/src/Components/Modules/FileSystem.hpp @@ -115,5 +115,7 @@ namespace Components static void FsRestartSync(int a1, int a2); static void DelayLoadImagesSync(); static int LoadTextureSync(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image); + + static void IwdFreeStub(Game::iwd_t* iwd); }; } diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index 96b5a5d8..3b6da2f9 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -3,19 +3,102 @@ namespace Components { + Maps::UserMapContainer Maps::UserMap; std::string Maps::CurrentMainZone; std::vector> Maps::DependencyList; std::vector Maps::CurrentDependencies; - bool Maps::IsSPMap; + bool Maps::SPMap; std::vector Maps::DlcPacks; std::vector Maps::EntryPool; + Maps::UserMapContainer* Maps::GetUserMap() + { + return &Maps::UserMap; + } + + void Maps::UserMapContainer::loadIwd() + { + if (this->isValid() && !this->searchPath.iwd) + { + std::string iwdName = Utils::String::VA("%s.iwd", this->mapname.data()); + std::string path = Utils::String::VA("%s\\usermaps\\%s\\%s", Dvar::Var("fs_basepath").get(), this->mapname.data(), iwdName.data()); + + this->searchPath.iwd = Game::FS_IsShippedIWD(path.data(), iwdName.data()); + + if (this->searchPath.iwd) + { + this->searchPath.bLocalized = false; + this->searchPath.ignore = 0; + this->searchPath.ignorePureCheck = 0; + this->searchPath.language = 0; + this->searchPath.dir = nullptr; + this->searchPath.next = *Game::fs_searchpaths; + *Game::fs_searchpaths = &this->searchPath; + } + } + } + + void Maps::UserMapContainer::reloadIwd() + { + if (this->isValid() && this->wasFreed) + { + this->wasFreed = false; + this->searchPath.iwd = nullptr; + this->loadIwd(); + } + } + + void Maps::UserMapContainer::handlePackfile(void* packfile) + { + if(this->isValid() && this->searchPath.iwd == packfile) + { + this->wasFreed = true; + } + } + + void Maps::UserMapContainer::freeIwd() + { + if(this->isValid() && this->searchPath.iwd && !this->wasFreed) + { + this->wasFreed = true; + + // Unchain our searchpath + for(Game::searchpath_t** pathPtr = Game::fs_searchpaths; *pathPtr; pathPtr = &(*pathPtr)->next) + { + if(*pathPtr == &this->searchPath) + { + *pathPtr = (*pathPtr)->next; + break; + } + } + + Game::unzClose(this->searchPath.iwd->handle); + + auto _free = Utils::Hook::Call(0x6B5CF2); + _free(this->searchPath.iwd->buildBuffer); + _free(this->searchPath.iwd); + + ZeroMemory(&this->searchPath, sizeof this->searchPath); + } + } + + void Maps::UnloadMapZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) + { + Game::DB_LoadXAssets(zoneInfo, zoneCount, sync); + + if (Maps::UserMap.isValid()) + { + Maps::UserMap.freeIwd(); + Maps::UserMap.clear(); + } + } + void Maps::LoadMapZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync) { if (!zoneInfo) return; - Maps::IsSPMap = false; + Maps::SPMap = false; Maps::CurrentMainZone = zoneInfo->name; Maps::CurrentDependencies.clear(); @@ -160,7 +243,7 @@ namespace Components Logger::Print("Waiting for database...\n"); while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(100ms); - if (!Utils::String::StartsWith(Maps::CurrentMainZone, "mp_") || Maps::IsSPMap) + if (!Utils::String::StartsWith(Maps::CurrentMainZone, "mp_") || Maps::SPMap) { return Game::DB_XAssetPool[Game::XAssetType::ASSET_TYPE_GAMEWORLD_SP].gameWorldSp[0].data; } @@ -217,7 +300,7 @@ namespace Components void Maps::HandleAsSPMap() { - Maps::IsSPMap = true; + Maps::SPMap = true; } void Maps::AddDependency(std::string expression, std::string zone) @@ -290,6 +373,83 @@ namespace Components return { team_axis, team_allies }; } + void Maps::PrepareUsermap(const char* mapname) + { + if(Maps::UserMap.isValid()) + { + Maps::UserMap.freeIwd(); + Maps::UserMap.clear(); + } + + if(Utils::IO::DirectoryExists(Utils::String::VA("usermaps/%s", mapname)) && Utils::IO::FileExists(Utils::String::VA("usermaps/%s/%s.ff", mapname, mapname))) + { + Maps::UserMap = Maps::UserMapContainer(mapname); + Maps::UserMap.loadIwd(); + } + else + { + Maps::UserMap.clear(); + } + } + + unsigned int Maps::GetUsermapHash(std::string map) + { + if (Utils::IO::DirectoryExists(Utils::String::VA("usermaps/%s", map.data()))) + { + std::string zoneHash; + std::string zonePath = Utils::String::VA("usermaps/%s/%s.ff", map.data(), map.data()); + if (Utils::IO::FileExists(zonePath)) + { + zoneHash = Utils::Cryptography::SHA256::Compute(Utils::IO::ReadFile(zonePath)); + } + + std::string iwdHash; + std::string iwdPath = Utils::String::VA("usermaps/%s/%s.iwd", map.data(), map.data()); + if(Utils::IO::FileExists(iwdPath)) + { + iwdHash = Utils::Cryptography::SHA256::Compute(Utils::IO::ReadFile(iwdPath)); + } + + return Utils::Cryptography::JenkinsOneAtATime::Compute(zoneHash + iwdHash); + } + + return 0; + } + + __declspec(naked) void Maps::SpawnServerStub() + { + __asm + { + pushad + + push [esp + 24h] + call Maps::PrepareUsermap + pop eax + + popad + + push 4A7120h // SV_SpawnServer + retn + } + } + + __declspec(naked) void Maps::LoadMapLoadscreenStub() + { + __asm + { + pushad + + push[esp + 24h] + call Maps::PrepareUsermap + pop eax + + popad + + push 4D8030h // LoadMapLoadscreen + retn + } + } + #if defined(DEBUG) && defined(ENABLE_DXSDK) // Credit to SE2Dev, as we shouldn't share the code, keep that in debug mode! void Maps::ExportMap(Game::GfxWorld* world) @@ -638,8 +798,9 @@ namespace Components // Intercept BSP name resolving Utils::Hook(0x4C5979, Maps::GetBSPName, HOOK_CALL).install()->quick(); - // Intercept map zone loading + // Intercept map zone loading/unloading Utils::Hook(0x42C2AF, Maps::LoadMapZones, HOOK_CALL).install()->quick(); + Utils::Hook(0x60B477, Maps::UnloadMapZones, HOOK_CALL).install()->quick(); // Ignore SP entities Utils::Hook(0x444810, Maps::IgnoreEntityStub, HOOK_JUMP).install()->quick(); @@ -650,6 +811,15 @@ namespace Components // Allow loading raw suns Utils::Hook(0x51B46A, Maps::LoadRawSun, HOOK_CALL).install()->quick(); + // Intercept map loading for usermap initialization + Utils::Hook(0x6245E3, Maps::SpawnServerStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x62493E, Maps::SpawnServerStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x42CF58, Maps::LoadMapLoadscreenStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x487CDD, Maps::LoadMapLoadscreenStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x4CA3E9, Maps::LoadMapLoadscreenStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x5A9D51, Maps::LoadMapLoadscreenStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x5B34DD, Maps::LoadMapLoadscreenStub, HOOK_CALL).install()->quick(); + Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_GAMEWORLD_SP, 1); Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_IMAGE, 7168); Game::ReallocateAssetPool(Game::XAssetType::ASSET_TYPE_LOADED_SOUND, 2700); diff --git a/src/Components/Modules/Maps.hpp b/src/Components/Modules/Maps.hpp index 0f90a629..8b052e02 100644 --- a/src/Components/Modules/Maps.hpp +++ b/src/Components/Modules/Maps.hpp @@ -5,6 +5,41 @@ namespace Components class Maps : public Component { public: + class UserMapContainer + { + public: + UserMapContainer() : wasFreed(false) { } + UserMapContainer(std::string _mapname) : wasFreed(false), mapname(_mapname) + { + ZeroMemory(&this->searchPath, sizeof this->searchPath); + this->hash = Maps::GetUsermapHash(this->mapname); + } + + ~UserMapContainer() + { + this->freeIwd(); + this->clear(); + } + + unsigned int getHash() { return this->hash; } + std::string getName() { return this->mapname; } + bool isValid() { return !this->mapname.empty(); } + void clear() { this->mapname.clear(); } + + void loadIwd(); + void freeIwd(); + + void reloadIwd(); + + void handlePackfile(void* packfile); + + private: + bool wasFreed; + unsigned int hash; + std::string mapname; + Game::searchpath_t searchPath; + }; + Maps(); ~Maps(); @@ -22,6 +57,9 @@ namespace Components static bool CheckMapInstalled(const char* mapname, bool error = false); + static UserMapContainer* GetUserMap(); + static unsigned int GetUsermapHash(std::string map); + private: class DLC { @@ -31,7 +69,8 @@ namespace Components std::vector maps; }; - static bool IsSPMap; + static bool SPMap; + static UserMapContainer UserMap; static std::vector DlcPacks; static std::vector EntryPool; @@ -41,6 +80,7 @@ namespace Components static void GetBSPName(char* buffer, size_t size, const char* format, const char* mapname); static void LoadAssetRestrict(Game::XAssetType type, Game::XAssetHeader asset, std::string name, bool* restrict); static void LoadMapZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync); + static void UnloadMapZones(Game::XZoneInfo *zoneInfo, unsigned int zoneCount, int sync); static void OverrideMapEnts(Game::MapEnts* ents); @@ -58,6 +98,12 @@ namespace Components static void ExportMap(Game::GfxWorld* world); #endif + static void PrepareUsermap(const char* mapname); + static void SpawnServerStub(); + static void LoadMapLoadscreenStub(); + + static void IwdFreeStub(Game::iwd_t* iwd); + void reallocateEntryPool(); }; } diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index 1111b22a..61285f3b 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -657,11 +657,6 @@ namespace Components Utils::IO::WriteFile("userraw/logs/matlog.txt", buffer); } - - if (type == Game::XAssetType::ASSET_TYPE_CLIPMAP_PVS) - { - OutputDebugStringA(""); - } }); Dvar::OnInit([] @@ -675,7 +670,7 @@ namespace Components float cyan[4] = { 0.0f, 0.5f, 0.5f, 1.0f }; - Game::clipMap_t* clipMap = *reinterpret_cast(0x7998E0); + //Game::clipMap_t* clipMap = *reinterpret_cast(0x7998E0); Game::GfxWorld* gameWorld = *reinterpret_cast(0x66DEE94); if (!gameWorld) return; diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 51edafc1..ef1ba1e3 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -109,6 +109,7 @@ namespace Game FS_Remove_t FS_Remove = FS_Remove_t(0x4660F0); FS_Restart_t FS_Restart = FS_Restart_t(0x461A50); FS_BuildPathToFile_t FS_BuildPathToFile = FS_BuildPathToFile_t(0x4702C0); + FS_IsShippedIWD_t FS_IsShippedIWD = FS_IsShippedIWD_t(0x642440); G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString = G_SpawnEntitiesFromString_t(0x4D8840); @@ -274,6 +275,8 @@ namespace Game Vec3UnpackUnitVec_t Vec3UnpackUnitVec = Vec3UnpackUnitVec_t(0x45CA90); + unzClose_t unzClose = unzClose_t(0x41BF20); + XAssetHeader* DB_XAssetPool = reinterpret_cast(0x7998A8); unsigned int* g_poolSize = reinterpret_cast(0x7995E8); @@ -301,7 +304,7 @@ namespace Game int* gameTypeCount = reinterpret_cast(0x62E50A0); gameTypeName_t* gameTypes = reinterpret_cast(0x62E50A4); - searchpath_t* fs_searchpaths = reinterpret_cast(0x63D96E0); + searchpath_t** fs_searchpaths = reinterpret_cast(0x63D96E0); XBlock** g_streamBlocks = reinterpret_cast(0x16E554C); int* g_streamPos = reinterpret_cast(0x16E5554); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 2459750a..79c480a4 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -282,6 +282,9 @@ namespace Game typedef int(__cdecl * FS_BuildPathToFile_t)(const char*, const char*, const char*, char**); extern FS_BuildPathToFile_t FS_BuildPathToFile; + typedef iwd_t*(__cdecl * FS_IsShippedIWD_t)(const char* fullpath, const char* iwd); + extern FS_IsShippedIWD_t FS_IsShippedIWD; + typedef void(__cdecl* G_SpawnEntitiesFromString_t)(); extern G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString; @@ -658,6 +661,9 @@ namespace Game typedef void (__cdecl * Vec3UnpackUnitVec_t)(PackedUnitVec, vec3_t *); extern Vec3UnpackUnitVec_t Vec3UnpackUnitVec; + typedef void(__cdecl * unzClose_t)(void* handle); + extern unzClose_t unzClose; + extern XAssetHeader* DB_XAssetPool; extern unsigned int* g_poolSize; @@ -685,7 +691,7 @@ namespace Game extern int* gameTypeCount; extern gameTypeName_t* gameTypes; - extern searchpath_t* fs_searchpaths; + extern searchpath_t** fs_searchpaths; extern XBlock** g_streamBlocks; extern int* g_streamPos;