diff --git a/src/Components/Modules/FastFiles.cpp b/src/Components/Modules/FastFiles.cpp index d2a35cbd..a25f5d22 100644 --- a/src/Components/Modules/FastFiles.cpp +++ b/src/Components/Modules/FastFiles.cpp @@ -245,8 +245,6 @@ namespace Components const char* FastFiles::GetZoneLocation(const char* file) { - const auto* dir = (*Game::fs_basepath)->current.string; - std::vector paths; const std::string fsGame = (*Game::fs_gameDirVar)->current.string; @@ -270,6 +268,7 @@ namespace Components Utils::String::Replace(zone, "_load", ""); } + // Only check for usermaps on our working directory if (Utils::IO::FileExists(std::format("usermaps\\{}\\{}.ff", zone, filename))) { return Utils::String::Format("usermaps\\{}\\", zone); @@ -506,11 +505,53 @@ namespace Components return Utils::Hook::Call(0x004925B0)(atStreamStart, surface->numsurfs); } + void FastFiles::DB_BuildOSPath_FromSource_Default(const char* zoneName, Game::FF_DIR source, unsigned int size, char* filename) + { + // TODO: this is where user map and mod.ff check should happen + if (source == Game::FFD_DEFAULT) + { + (void)sprintf_s(filename, size, "%s\\%s%s.ff", FileSystem::Sys_DefaultInstallPath_Hk(), GetZoneLocation(zoneName), zoneName); + } + } + + void FastFiles::DB_BuildOSPath_FromSource_Custom(const char* zoneName, Game::FF_DIR source, unsigned int size, char* filename) + { + // TODO: this is where user map and mod.ff check should happen + if (source == Game::FFD_DEFAULT) + { + (void)sprintf_s(filename, size, "%s\\%s%s.ff", FileSystem::Sys_HomePath_Hk(), GetZoneLocation(zoneName), zoneName); + } + } + + Game::Sys_File FastFiles::Sys_CreateFile_Stub(const char* dir, const char* filename) + { + static_assert(sizeof(Game::Sys_File) == 4); + + auto file = Game::Sys_CreateFile(dir, filename); + + static const std::filesystem::path home = FileSystem::Sys_HomePath_Hk(); + if (file.handle == INVALID_HANDLE_VALUE && !home.empty()) + { + file.handle = Game::Sys_OpenFileReliable(Utils::String::VA("%s\\%s%s", FileSystem::Sys_HomePath_Hk(), dir, filename)); + } + + if (file.handle == INVALID_HANDLE_VALUE && ZoneBuilder::IsEnabled()) + { + file = Game::Sys_CreateFile("zone\\zonebuilder\\", filename); + } + + return file; + } + FastFiles::FastFiles() { Dvar::Register("ui_zoneDebug", false, Game::DVAR_ARCHIVE, "Display current loaded zone."); g_loadingInitialZones = Dvar::Register("g_loadingInitialZones", true, Game::DVAR_NONE, "Is loading initial zones"); + Utils::Hook(0x5BC832, Sys_CreateFile_Stub, HOOK_CALL).install()->quick(); + + Utils::Hook(0x4CCDF0, DB_FileExists_Hk, HOOK_JUMP).install()->quick(); + // Fix XSurface assets Utils::Hook(0x0048E8A5, FastFiles::Load_XSurfaceArray, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/FastFiles.hpp b/src/Components/Modules/FastFiles.hpp index 24f0a03a..9677c040 100644 --- a/src/Components/Modules/FastFiles.hpp +++ b/src/Components/Modules/FastFiles.hpp @@ -70,5 +70,9 @@ namespace Components static void Load_XSurfaceArray(int atStreamStart, int count); + static void DB_BuildOSPath_FromSource_Default(const char* zoneName, Game::FF_DIR source, unsigned int size, char* filename); + static void DB_BuildOSPath_FromSource_Custom(const char* zoneName, Game::FF_DIR source, unsigned int size, char* filename); + static Game::Sys_File Sys_CreateFile_Stub(const char* dir, const char* filename); + static bool DB_FileExists_Hk(const char* zoneName, Game::FF_DIR source); }; } diff --git a/src/Components/Modules/FileSystem.cpp b/src/Components/Modules/FileSystem.cpp index 02b27006..eae77d1a 100644 --- a/src/Components/Modules/FileSystem.cpp +++ b/src/Components/Modules/FileSystem.cpp @@ -281,7 +281,7 @@ namespace Components int FileSystem::Cmd_Exec_f_Stub(const char* s0, [[maybe_unused]] const char* s1) { int f; - auto len = Game::FS_FOpenFileByMode(s0, &f, Game::FS_READ); + const auto len = Game::FS_FOpenFileByMode(s0, &f, Game::FS_READ); if (len < 0) { return 1; // Not found @@ -336,6 +336,39 @@ namespace Components return current_path.data(); } + FILE* FileSystem::FS_FileOpenReadText_Hk(const char* file) + { + const auto path = Utils::GetBaseFilesLocation(); + if (!path.empty() && Utils::IO::FileExists((path / file).string())) + { + return Game::FS_FileOpenReadText((path / file).string().data()); + } + + return Game::FS_FileOpenReadText(file); + } + + const char* FileSystem::Sys_DefaultCDPath_Hk() + { + return Sys_DefaultInstallPath_Hk(); + } + + const char* FileSystem::Sys_HomePath_Hk() + { + const auto path = Utils::GetBaseFilesLocation(); + if (!path.empty()) + { + static auto current_path = path.string(); + return current_path.data(); + } + + return ""; + } + + const char* FileSystem::Sys_Cwd_Hk() + { + return Sys_DefaultInstallPath_Hk(); + } + FileSystem::FileSystem() { // Thread safe file system interaction @@ -383,6 +416,21 @@ namespace Components // Set the working dir based on info from the Xlabs launcher Utils::Hook(0x4326E0, Sys_DefaultInstallPath_Hk, HOOK_JUMP).install()->quick(); + + // Make the exe run from a folder other than the game folder + Utils::Hook(0x406D26, FS_FileOpenReadText_Hk, HOOK_CALL).install()->quick(); + + // Make the exe run from a folder other than the game folder + Utils::Hook::Nop(0x4290D8, 5); // FS_IsBasePathValid + Utils::Hook::Set(0x4290DF, 0xEB); + // ^^ This check by the game above is super redundant, IW4x has other checks in place to make sure we + // are running from a properly installed directory. This only breaks the containerized patch and we don't need it + + // Patch FS dvar values + Utils::Hook(0x643194, Sys_DefaultCDPath_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x643232, Sys_HomePath_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x6431B6, Sys_Cwd_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x51C29A, Sys_Cwd_Hk, HOOK_CALL).install()->quick(); } FileSystem::~FileSystem() diff --git a/src/Components/Modules/FileSystem.hpp b/src/Components/Modules/FileSystem.hpp index cb721369..9d4e94cf 100644 --- a/src/Components/Modules/FileSystem.hpp +++ b/src/Components/Modules/FileSystem.hpp @@ -99,6 +99,16 @@ namespace Components static std::vector GetSysFileList(const std::string& path, const std::string& extension, bool folders = false); static bool _DeleteFile(const std::string& folder, const std::string& file); + /** + * The game will check in FS_Startup if homepath != default(base) path + * If they differ which will happen when IW4x is containerized it will register two brand new search paths: + * one for the container and one where the game files are. Pretty cool! + */ + static const char* Sys_DefaultInstallPath_Hk(); + static const char* Sys_DefaultCDPath_Hk(); + static const char* Sys_HomePath_Hk(); + static const char* Sys_Cwd_Hk(); + private: static std::mutex Mutex; static std::recursive_mutex FSMutex; @@ -122,6 +132,6 @@ namespace Components static void IwdFreeStub(Game::iwd_t* iwd); - static const char* Sys_DefaultInstallPath_Hk(); + static FILE* FS_FileOpenReadText_Hk(const char* file); }; } diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index d045a34a..b2fcce55 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -1094,18 +1094,6 @@ namespace Components sound->info.data_ptr = allocatedSpace; } - Game::Sys_File ZoneBuilder::Sys_CreateFile_Stub(const char* dir, const char* filename) - { - auto file = Game::Sys_CreateFile(dir, filename); - - if (file.handle == INVALID_HANDLE_VALUE) - { - file = Game::Sys_CreateFile("zone\\zonebuilder\\", filename); - } - - return file; - } - iw4of::params_t ZoneBuilder::GetExporterAPIParams() { iw4of::params_t params{}; @@ -1162,8 +1150,6 @@ namespace Components // Prevent loading textures (preserves loaddef) //Utils::Hook::Set(Game::Load_Texture, 0xC3); - Utils::Hook(0x5BC832, Sys_CreateFile_Stub, HOOK_CALL).install()->quick(); - // Store the loaddef Utils::Hook(Game::Load_Texture, StoreTexture, HOOK_JUMP).install()->quick(); diff --git a/src/Game/FileSystem.cpp b/src/Game/FileSystem.cpp index 4486f405..d700607f 100644 --- a/src/Game/FileSystem.cpp +++ b/src/Game/FileSystem.cpp @@ -10,6 +10,7 @@ namespace Game FS_FOpenFileAppend_t FS_FOpenFileAppend = FS_FOpenFileAppend_t(0x410BB0); FS_FOpenFileWrite_t FS_FOpenFileWrite = FS_FOpenFileWrite_t(0x4BA530); FS_FOpenTextFileWrite_t FS_FOpenTextFileWrite = FS_FOpenTextFileWrite_t(0x43FD90); + FS_FileOpenReadText_t FS_FileOpenReadText = FS_FileOpenReadText_t(0x446B60); FS_FOpenFileRead_t FS_FOpenFileRead = FS_FOpenFileRead_t(0x46CBF0); FS_FOpenFileReadDatabase_t FS_FOpenFileReadDatabase = FS_FOpenFileReadDatabase_t(0x42ECA0); FS_FOpenFileReadForThread_t FS_FOpenFileReadForThread = FS_FOpenFileReadForThread_t(0x643270); diff --git a/src/Game/FileSystem.hpp b/src/Game/FileSystem.hpp index 41a0029e..74c2f793 100644 --- a/src/Game/FileSystem.hpp +++ b/src/Game/FileSystem.hpp @@ -26,6 +26,9 @@ namespace Game typedef int(*FS_FOpenFileRead_t)(const char* filename, int* file); extern FS_FOpenFileRead_t FS_FOpenFileRead; + typedef FILE*(*FS_FileOpenReadText_t)(const char* filename); + extern FS_FileOpenReadText_t FS_FileOpenReadText; + typedef int(*FS_FOpenFileReadDatabase_t)(const char* filename, int* file); extern FS_FOpenFileReadDatabase_t FS_FOpenFileReadDatabase; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 21842c63..047504d7 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -11059,6 +11059,13 @@ namespace Game huff_t compressDecompress; }; + enum FF_DIR + { + FFD_DEFAULT = 0x0, + FFD_MOD_DIR = 0x1, + FFD_USER_MAP = 0x2, + }; + #pragma endregion #ifndef IDA diff --git a/src/Game/System.cpp b/src/Game/System.cpp index 50f9d3ab..9b34bda2 100644 --- a/src/Game/System.cpp +++ b/src/Game/System.cpp @@ -55,4 +55,10 @@ namespace Game return TryEnterCriticalSection(&s_criticalSection[critSect]) != FALSE; } + + HANDLE Sys_OpenFileReliable(const char* filename) + { + return CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, nullptr); + } } diff --git a/src/Game/System.hpp b/src/Game/System.hpp index 70b37c1c..d88b476c 100644 --- a/src/Game/System.hpp +++ b/src/Game/System.hpp @@ -81,6 +81,8 @@ namespace Game extern bool Sys_TryEnterCriticalSection(CriticalSection critSect); + extern HANDLE Sys_OpenFileReliable(const char* filename); + class Sys { public: diff --git a/src/Utils/Utils.cpp b/src/Utils/Utils.cpp index a1066804..daf68be9 100644 --- a/src/Utils/Utils.cpp +++ b/src/Utils/Utils.cpp @@ -25,8 +25,10 @@ namespace Utils void OutputDebugLastError() { +#ifdef DEBUG DWORD errorMessageID = ::GetLastError(); - OutputDebugStringA(Utils::String::VA("Last error code: 0x%08X (%s)\n", errorMessageID, GetLastWindowsError().data())); + OutputDebugStringA(String::VA("Last error code: 0x%08X (%s)\n", errorMessageID, GetLastWindowsError().data())); +#endif } std::string GetLastWindowsError() @@ -85,8 +87,7 @@ namespace Utils if (!ntdll) return nullptr; - static uint8_t ntQueryInformationThread[] = { 0xB1, 0x8B, 0xAE, 0x8A, 0x9A, 0x8D, 0x86, 0xB6, 0x91, 0x99, 0x90, 0x8D, 0x92, 0x9E, 0x8B, 0x96, 0x90, 0x91, 0xAB, 0x97, 0x8D, 0x9A, 0x9E, 0x9B }; // NtQueryInformationThread - NtQueryInformationThread_t NtQueryInformationThread = NtQueryInformationThread_t(GetProcAddress(ntdll, String::XOR(std::string(reinterpret_cast(ntQueryInformationThread), sizeof ntQueryInformationThread), -1).data())); + NtQueryInformationThread_t NtQueryInformationThread = NtQueryInformationThread_t(GetProcAddress(ntdll, "NtQueryInformationThread")); if (!NtQueryInformationThread) return nullptr; HANDLE dupHandle, currentProcess = GetCurrentProcess(); @@ -116,26 +117,54 @@ namespace Utils SetCurrentDirectoryW(binaryPath); } + /** + * IW4x_INSTALL should point to where the IW4x rawfiles/client files are + * or the current working dir + */ void SetEnvironment() { char* buffer{}; std::size_t size{}; - if (_dupenv_s(&buffer, &size, "MW2_INSTALL") != 0 || buffer == nullptr) + if (_dupenv_s(&buffer, &size, "IW4x_INSTALL") != 0 || buffer == nullptr) { SetLegacyEnvironment(); return; } - const auto _0 = gsl::finally([&] { std::free(buffer); }); - SetCurrentDirectoryA(buffer); SetDllDirectoryA(buffer); + std::free(buffer); + } + + /** + * Points to where the Modern Warfare 2 folder is + */ + std::filesystem::path GetBaseFilesLocation() + { + char* buffer{}; + std::size_t size{}; + if (_dupenv_s(&buffer, &size, "BASE_INSTALL") != 0 || buffer == nullptr) + { + return {}; + } + + try + { + std::filesystem::path result = buffer; + std::free(buffer); + return result; + } + catch (const std::exception& ex) + { + printf("Failed to convert '%s' to native file system path. Got error '%s'\n", buffer, ex.what()); + std::free(buffer); + return {}; + } } HMODULE GetNTDLL() { - static uint8_t ntdll[] = { 0x91, 0x8B, 0x9B, 0x93, 0x93, 0xD1, 0x9B, 0x93, 0x93 }; // ntdll.dll - return GetModuleHandleA(Utils::String::XOR(std::string(reinterpret_cast(ntdll), sizeof ntdll), -1).data()); + return GetModuleHandleA("ntdll.dll"); } void SafeShellExecute(HWND hwnd, LPCSTR lpOperation, LPCSTR lpFile, LPCSTR lpParameters, LPCSTR lpDirectory, INT nShowCmd) diff --git a/src/Utils/Utils.hpp b/src/Utils/Utils.hpp index eba34b59..f864a670 100644 --- a/src/Utils/Utils.hpp +++ b/src/Utils/Utils.hpp @@ -20,6 +20,7 @@ namespace Utils HMODULE GetNTDLL(); void SetEnvironment(); + std::filesystem::path GetBaseFilesLocation(); void OpenUrl(const std::string& url);