Merge pull request #83 from diamante0018/containers-are-lovely

feat: containers!
This commit is contained in:
Louve 2024-01-31 09:55:18 +01:00 committed by GitHub
commit 9adedb4062
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 185 additions and 24 deletions

View File

@ -245,8 +245,6 @@ namespace Components
const char* FastFiles::GetZoneLocation(const char* file)
{
const auto* dir = (*Game::fs_basepath)->current.string;
std::vector<std::string> 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);
@ -280,6 +279,7 @@ namespace Components
for (auto& path : paths)
{
const auto* dir = (*Game::fs_basepath)->current.string;
auto absoluteFile = std::format("{}\\{}{}", dir, path, file);
// No ".ff" appended, append it manually
@ -506,11 +506,74 @@ namespace Components
return Utils::Hook::Call<void(int, int)>(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);
}
}
bool FastFiles::DB_FileExists_Hk(const char* zoneName, Game::FF_DIR source)
{
char filename[256]{};
DB_BuildOSPath_FromSource_Default(zoneName, source, sizeof(filename), filename);
if (auto zoneFile = Game::Sys_OpenFileReliable(filename); zoneFile != INVALID_HANDLE_VALUE)
{
CloseHandle(zoneFile);
return true;
}
DB_BuildOSPath_FromSource_Custom(zoneName, source, sizeof(filename), filename);
if (auto zoneFile = Game::Sys_OpenFileReliable(filename); zoneFile != INVALID_HANDLE_VALUE)
{
CloseHandle(zoneFile);
return true;
}
return false;
}
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<bool>("ui_zoneDebug", false, Game::DVAR_ARCHIVE, "Display current loaded zone.");
g_loadingInitialZones = Dvar::Register<bool>("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();

View File

@ -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);
};
}

View File

@ -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<uint8_t>(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()

View File

@ -99,6 +99,16 @@ namespace Components
static std::vector<std::string> 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);
};
}

View File

@ -1115,18 +1115,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{};
@ -1345,8 +1333,6 @@ namespace Components
// Prevent loading textures (preserves loaddef)
//Utils::Hook::Set<BYTE>(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();

View File

@ -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);

View File

@ -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;

View File

@ -11079,6 +11079,13 @@ namespace Game
huff_t compressDecompress;
};
enum FF_DIR
{
FFD_DEFAULT = 0x0,
FFD_MOD_DIR = 0x1,
FFD_USER_MAP = 0x2,
};
#pragma endregion
#ifndef IDA

View File

@ -56,4 +56,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);
}
}

View File

@ -84,6 +84,8 @@ namespace Game
extern bool Sys_TryEnterCriticalSection(CriticalSection critSect);
extern HANDLE Sys_OpenFileReliable(const char* filename);
class Sys
{
public:

View File

@ -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<char*>(ntQueryInformationThread), sizeof ntQueryInformationThread), -1).data()));
NtQueryInformationThread_t NtQueryInformationThread = NtQueryInformationThread_t(GetProcAddress(ntdll, "NtQueryInformationThread"));
if (!NtQueryInformationThread) return nullptr;
HANDLE dupHandle, currentProcess = GetCurrentProcess();
@ -116,11 +117,15 @@ 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;
@ -132,10 +137,35 @@ namespace Utils
SetDllDirectoryA(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 {};
}
const auto _0 = gsl::finally([&] { std::free(buffer); });
try
{
std::filesystem::path result = 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());
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<char*>(ntdll), sizeof ntdll), -1).data());
return GetModuleHandleA("ntdll.dll");
}
void SafeShellExecute(HWND hwnd, LPCSTR lpOperation, LPCSTR lpFile, LPCSTR lpParameters, LPCSTR lpDirectory, INT nShowCmd)

View File

@ -20,6 +20,7 @@ namespace Utils
HMODULE GetNTDLL();
void SetEnvironment();
std::filesystem::path GetBaseFilesLocation();
void OpenUrl(const std::string& url);