filesystem: reverse properly a lot of stuff

This commit is contained in:
Diavolo 2023-01-11 16:02:49 +01:00
parent cec72268b0
commit 395e27bbe4
No known key found for this signature in database
GPG Key ID: FA77F074E98D98A5
11 changed files with 732 additions and 286 deletions

View File

@ -8,10 +8,14 @@ namespace dvars
const game::native::dvar_t** sv_maxclients;
const game::native::dvar_t** loc_language;
void initialize()
{
com_sv_running = reinterpret_cast<const game::native::dvar_t**>(SELECT_VALUE(0x1769F50, 0x1CEF588));
sv_maxclients = reinterpret_cast<const game::native::dvar_t**>(SELECT_VALUE(0x0, 0x21223C0));
loc_language = reinterpret_cast<const game::native::dvar_t**>(SELECT_VALUE(0x1BF6938, 0x58D5A90));
}
}

View File

@ -6,5 +6,7 @@ namespace dvars
extern const game::native::dvar_t** sv_maxclients;
extern const game::native::dvar_t** loc_language;
void initialize();
}

View File

@ -6,7 +6,7 @@ namespace game
{
namespace native
{
Cmd_AddCommand_t Cmd_AddCommand;
Cmd_AddCommandInternal_t Cmd_AddCommandInternal;
Cmd_RemoveCommand_t Cmd_RemoveCommand;
Cbuf_AddText_t Cbuf_AddText;
@ -63,6 +63,8 @@ namespace game
Sys_Error_t Sys_Error;
Sys_Milliseconds_t Sys_Milliseconds;
Sys_Sleep_t Sys_Sleep;
Sys_FreeFileList_t Sys_FreeFileList;
Sys_MessageBox_t Sys_MessageBox;
PMem_AllocFromSource_NoDebug_t PMem_AllocFromSource_NoDebug;
@ -86,6 +88,7 @@ namespace game
XUIDToString_t XUIDToString;
SEH_LocalizeTextMessage_t SEH_LocalizeTextMessage;
SEH_GetLanguageName_t SEH_GetLanguageName;
CM_TransformedCapsuleTrace_t CM_TransformedCapsuleTrace;
@ -103,11 +106,17 @@ namespace game
FS_Printf_t FS_Printf;
FS_ReadFile_t FS_ReadFile;
FS_CreatePath_t FS_CreatePath;
FS_HandleForFile_t FS_HandleForFile;
FS_FCloseFile_t FS_FCloseFile;
FS_ListFilteredFiles_t FS_ListFilteredFiles;
player_die_t player_die;
LargeLocalResetToMark_t LargeLocalResetToMark;
Win_LocalizeRef_t Win_LocalizeRef;
decltype(longjmp)* _longjmp;
CmdArgs* sv_cmd_args;
@ -143,6 +152,9 @@ namespace game
searchpath_s** fs_searchpaths;
char* fs_gamedir;
fileHandleData_t* fsh;
int* fs_numServerIwds;
int* fs_serverIwds;
int* fs_iwdFileCount;
int* com_fileAccessed;
// DS does not have MJPEG thread
@ -186,6 +198,23 @@ namespace game
gclient_s* g_clients;
}
int Cmd_Argc()
{
assert(cmd_args->nesting < CMD_MAX_NESTING);
return cmd_args->argc[cmd_args->nesting];
}
const char* Cmd_Argv(int argIndex)
{
assert(cmd_args->nesting < CMD_MAX_NESTING);
assert(argIndex >= 0);
if (argIndex < cmd_args->argc[cmd_args->nesting])
{
return cmd_args->argv[cmd_args->nesting][argIndex];
}
return "";
}
void AddRefToValue(VariableValue* value)
{
if (value->type == VAR_POINTER)
@ -524,9 +553,16 @@ namespace game
InterlockedDecrement(&critSect->readCount);
}
void FS_FCloseFile(int h)
void Sys_OutOfMemErrorInternal(const char* filename, int line)
{
reinterpret_cast<void(*)(int)>(SELECT_VALUE(0x415160, 0x5AF170))(h);
Sys_EnterCriticalSection(CRITSECT_FATAL_ERROR);
printf("Out of memory: filename \'%s\', line %d\n", filename, line);
const auto* title = Win_LocalizeRef("WIN_OUT_OF_MEM_TITLE");
const auto* body = Win_LocalizeRef("WIN_OUT_OF_MEM_BODY");
Sys_MessageBox(body, title, MB_ICONERROR, 0);
std::exit(-1);
}
bool FS_Initialized()
@ -534,11 +570,6 @@ namespace game
return (*fs_searchpaths != nullptr);
}
int FS_HandleForFile(FsThread thread)
{
return reinterpret_cast<int(*)(FsThread)>(SELECT_VALUE(0x46B1C0, 0x5AEE50))(thread);
}
int fs_fopen_file_read_for_thread_singleplayer(const char* filename, int* file, FsThread thread)
{
static DWORD func = 0x627F20;
@ -593,11 +624,6 @@ namespace game
return fs_fopen_file_read_for_thread_multiplayer(filename, file, thread);
}
int FS_CreatePath(char* OSPath)
{
return reinterpret_cast<int(*)(char*)>(SELECT_VALUE(0x4F5AB0, 0x5AF060))(OSPath);
}
void FS_CheckFileSystemStarted()
{
assert(*fs_searchpaths);
@ -634,6 +660,34 @@ namespace game
Sys_UnlockRead(db_hashCritSect);
return asset_entry != nullptr;
}
int SEH_GetCurrentLanguage()
{
return (*dvars::loc_language)->current.integer;
}
void* Z_Malloc(std::size_t size)
{
void* buf = std::malloc(size);
if (buf)
{
std::memset(buf, 0, size);
return buf;
}
printf("Failed to Z_Malloc %u bytes\n", size);
Sys_OutOfMemError();
}
bool I_islower(int c)
{
return c >= 'a' && c <= 'z';
}
bool I_isupper(int c)
{
return c >= 'A' && c <= 'Z';
}
}
launcher::mode mode = launcher::mode::none;
@ -664,7 +718,7 @@ namespace game
dvars::initialize();
native::Cmd_AddCommand = native::Cmd_AddCommand_t(SELECT_VALUE(0x558820, 0x545DF0));
native::Cmd_AddCommandInternal = native::Cmd_AddCommandInternal_t(SELECT_VALUE(0x558820, 0x545DF0));
native::Cmd_RemoveCommand = native::Cmd_RemoveCommand_t(SELECT_VALUE(0x443A30, 0x545E20));
native::Cbuf_AddText = native::Cbuf_AddText_t(SELECT_VALUE(0x457C90, 0x545680));
@ -722,6 +776,8 @@ namespace game
native::Sys_Error = native::Sys_Error_t(SELECT_VALUE(0x490D90, 0x5CC3B0));
native::Sys_Milliseconds = native::Sys_Milliseconds_t(SELECT_VALUE(0x4A1610, 0x5CE740));
native::Sys_Sleep = native::Sys_Sleep_t(SELECT_VALUE(0x438600, 0x55F460));
native::Sys_FreeFileList = native::Sys_FreeFileList_t(SELECT_VALUE(0x486380, 0x5C4F90));
native::Sys_MessageBox = native::Sys_MessageBox_t(SELECT_VALUE(0x4664D0, 0x5CD180));
native::PMem_AllocFromSource_NoDebug = native::PMem_AllocFromSource_NoDebug_t(SELECT_VALUE(0x449E50, 0x5C15C0));
@ -753,6 +809,7 @@ namespace game
native::XUIDToString = native::XUIDToString_t(SELECT_VALUE(0x4FAA30, 0x55CC20));
native::SEH_LocalizeTextMessage = native::SEH_LocalizeTextMessage_t(SELECT_VALUE(0x41EA20, 0x57E240));
native::SEH_GetLanguageName = native::SEH_GetLanguageName_t(SELECT_VALUE(0x533650, 0x57E5A0));
native::CM_TransformedCapsuleTrace = native::CM_TransformedCapsuleTrace_t(SELECT_VALUE(0x4F9B80, 0x541340));
@ -770,11 +827,17 @@ namespace game
native::FS_Printf = native::FS_Printf_t(SELECT_VALUE(0x421E90, 0x5AF7C0));
native::FS_ReadFile = native::FS_ReadFile_t(SELECT_VALUE(0x4D8DF0, 0x5B1FB0));
native::FS_CreatePath = native::FS_CreatePath_t(SELECT_VALUE(0x4F5AB0, 0x5AF060));
native::FS_HandleForFile = native::FS_HandleForFile_t(SELECT_VALUE(0x46B1C0, 0x5AEE50));
native::FS_FCloseFile = native::FS_FCloseFile_t(SELECT_VALUE(0x415160, 0x5AF170));
native::FS_ListFilteredFiles = native::FS_ListFilteredFiles_t(SELECT_VALUE(0x41D910, 0x5AFF10));
native::player_die = native::player_die_t(SELECT_VALUE(0x0, 0x503460));
native::LargeLocalResetToMark = native::LargeLocalResetToMark_t(SELECT_VALUE(0x524350, 0x5B7150));
native::Win_LocalizeRef = native::Win_LocalizeRef_t(SELECT_VALUE(0x49D7E0, 0x5CBE90));
native::_longjmp = reinterpret_cast<decltype(longjmp)*>(SELECT_VALUE(0x73AC20, 0x7363BC));
native::sv_cmd_args = reinterpret_cast<native::CmdArgs*>(SELECT_VALUE(0x1757218, 0x1CAA998));
@ -817,9 +880,12 @@ namespace game
native::fs_searchpaths = reinterpret_cast<native::searchpath_s**>(SELECT_VALUE(0x1C2FE78, 0x59BA858));
native::fs_gamedir = reinterpret_cast<char*>(SELECT_VALUE(0x1C2B220, 0x59A98F8));
native::fsh = reinterpret_cast<native::fileHandleData_t*>(SELECT_VALUE(0x1C2B540, 0x59B5F20));
native::fs_numServerIwds = reinterpret_cast<int*>(SELECT_VALUE(0x1C2FE84, 0x59BA864));
native::fs_serverIwds = reinterpret_cast<int*>(SELECT_VALUE(0x0, 0x59AAB18));
native::fs_iwdFileCount = reinterpret_cast<int*>(SELECT_VALUE(0x1C2fE7C, 0x59BA85C));
native::com_fileAccessed = reinterpret_cast<int*>(SELECT_VALUE(0x1C2B328, 0x59A9A04));
native::threadId = reinterpret_cast<unsigned(*)[native::THREAD_CONTEXT_COUNT]>(SELECT_VALUE(0x18576C8, 0x1D6E448));
native::threadId = reinterpret_cast<unsigned int(*)[native::THREAD_CONTEXT_COUNT]>(SELECT_VALUE(0x18576C8, 0x1D6E448));
native::initialized_0 = reinterpret_cast<int*>(SELECT_VALUE(0x1CE1CA0, 0x5AA3058));
native::sys_timeBase = reinterpret_cast<int*>(SELECT_VALUE(0x1CE1C98, 0x5AA3050));

View File

@ -11,8 +11,8 @@ namespace game
{
namespace native
{
typedef void (*Cmd_AddCommand_t)(const char* cmdName, void (*function)(), cmd_function_t* allocedCmd);
extern Cmd_AddCommand_t Cmd_AddCommand;
typedef void (*Cmd_AddCommandInternal_t)(const char* cmdName, void (*function)(), cmd_function_s* allocedCmd);
extern Cmd_AddCommandInternal_t Cmd_AddCommandInternal;
typedef void (*Cmd_RemoveCommand_t)(const char* cmdName);
extern Cmd_RemoveCommand_t Cmd_RemoveCommand;
@ -137,6 +137,12 @@ namespace game
typedef void (*Sys_Sleep_t)(int msec);
extern Sys_Sleep_t Sys_Sleep;
typedef void (*Sys_FreeFileList_t)(char** list);
extern Sys_FreeFileList_t Sys_FreeFileList;
typedef int (*Sys_MessageBox_t)(const char* lpText, const char* lpCaption, unsigned int uType, int defaultValue);
extern Sys_MessageBox_t Sys_MessageBox;
typedef void* (*PMem_AllocFromSource_NoDebug_t)(unsigned int size, unsigned int alignment, unsigned int type, int source);
extern PMem_AllocFromSource_NoDebug_t PMem_AllocFromSource_NoDebug;
@ -182,6 +188,9 @@ namespace game
typedef char* (*SEH_LocalizeTextMessage_t)(const char* pszInputBuffer, const char* pszMessageType, msgLocErrType_t errType);
extern SEH_LocalizeTextMessage_t SEH_LocalizeTextMessage;
typedef const char* (*SEH_GetLanguageName_t)(int iLanguage);
extern SEH_GetLanguageName_t SEH_GetLanguageName;
typedef void (*CM_TransformedCapsuleTrace_t)(trace_t* results, const float* start, const float* end,
const Bounds* bounds, const Bounds* capsule, int contents,
const float* origin, const float* angles);
@ -216,12 +225,27 @@ namespace game
typedef int (*FS_ReadFile_t)(const char* qpath, char** buffer);
extern FS_ReadFile_t FS_ReadFile;
typedef int (*FS_CreatePath_t)(char* OSPath);
extern FS_CreatePath_t FS_CreatePath;
typedef int (*FS_HandleForFile_t)(FsThread thread);
extern FS_HandleForFile_t FS_HandleForFile;
typedef void (*FS_FCloseFile_t)(int h);
extern FS_FCloseFile_t FS_FCloseFile;
typedef char** (*FS_ListFilteredFiles_t)(searchpath_s* searchPath, const char* path, const char* extension, const char* filter, FsListBehavior_e behavior, int* numfiles, int allocTrackType);
extern FS_ListFilteredFiles_t FS_ListFilteredFiles;
typedef void (*player_die_t)(gentity_s* self, const gentity_s* inflictor, gentity_s* attacker, int damage, int meansOfDeath, const Weapon* iWeapon, bool isAlternate, const float* vDir, const hitLocation_t hitLoc, int psTimeOffset);
extern player_die_t player_die;
typedef void (*LargeLocalResetToMark_t)(int markPos);
extern LargeLocalResetToMark_t LargeLocalResetToMark;
typedef const char* (*Win_LocalizeRef_t)(const char* ref);
extern Win_LocalizeRef_t Win_LocalizeRer;
extern decltype(longjmp)* _longjmp;
constexpr auto CMD_MAX_NESTING = 8;
@ -261,6 +285,9 @@ namespace game
extern searchpath_s** fs_searchpaths;
extern char* fs_gamedir;
extern fileHandleData_t* fsh;
extern int* fs_numServerIwds;
extern int* fs_serverIwds;
extern int* fs_iwdFileCount;
extern int* com_fileAccessed;
extern unsigned int(*threadId)[THREAD_CONTEXT_COUNT];
@ -322,6 +349,9 @@ namespace game
extern gclient_s* g_clients;
}
int Cmd_Argc();
const char* Cmd_Argv(int argIndex);
void AddRefToValue(VariableValue* value);
void* MT_Alloc(int numBytes, int type);
@ -361,16 +391,23 @@ namespace game
bool Sys_IsStreamThread();
bool Sys_IsRenderThread();
bool Sys_IsServerThread();
void Sys_LockRead(FastCriticalSection* critSect);
void Sys_UnlockRead(FastCriticalSection* critSect);
void Sys_OutOfMemErrorInternal(const char* filename, int line);
void FS_FCloseFile(int h);
bool FS_Initialized();
int FS_HandleForFile(FsThread thread);
int FS_FOpenFileReadForThread(const char* filename, int* file, FsThread thread);
int FS_CreatePath(char* OSPath);
void FS_CheckFileSystemStarted();
XAssetEntry* DB_FindXAssetEntry(XAssetType type, const char* name);
int DB_XAssetExists(XAssetType type, const char* name);
int SEH_GetCurrentLanguage();
void* Z_Malloc(std::size_t size);
bool I_islower(int c);
bool I_isupper(int c);
}
bool is_mp();
@ -378,3 +415,5 @@ namespace game
void initialize(launcher::mode mode);
}
#define Sys_OutOfMemError() game::native::Sys_OutOfMemErrorInternal(__FILE__, __LINE__)

View File

@ -483,9 +483,15 @@ namespace game
PMEM_SOURCE_SCRIPT,
};
struct cmd_function_t
enum FsListBehavior_e
{
cmd_function_t* next;
FS_LIST_PURE_ONLY,
FS_LIST_ALL,
};
struct cmd_function_s
{
cmd_function_s* next;
const char* name;
const char* autoCompleteDir;
const char* autoCompleteExt;
@ -1289,6 +1295,7 @@ namespace game
enum CriticalSection
{
CRITSECT_CONSOLE = 0x0,
CRITSECT_FATAL_ERROR = 0x12,
CRITSECT_CBUF = 0x1F,
CRITSECT_COUNT = 0x27,
};

View File

@ -1,95 +1,94 @@
#include <std_include.hpp>
#include <loader/module_loader.hpp>
#include <utils/hook.hpp>
#include "game/game.hpp"
class ceg final : public module
#include "ceg.hpp"
#include <utils/hook.hpp>
void ceg::post_load()
{
public:
void post_load() override
{
// Only SP has CEG
// CEG in MP has accidentally been removed due to CVE-2018-10718
if (!game::is_sp()) return;
// Only SP has CEG
// CEG in MP has accidentally been removed due to CVE-2018-10718
if (!game::is_sp()) return;
utils::hook::signature signature(0x401000, 0x3E1000);
utils::hook::signature signature(0x401000, 0x3E1000);
signature.add({
"\x56\xE8\x00\x00\x00\x00\x8B\xF0\xE8\x00\x00\x00\x00\x50\x56\xE8", "xx????xxx????xxx", [](char* address)
{
utils::hook::set<DWORD>(address, 0xC301B0);
}
});
signature.add({
"\x56\xE8\x00\x00\x00\x00\x8B\xF0\xE8\x00\x00\x00\x00\x50\x56\xE8", "xx????xxx????xxx", [](char* address)
{
utils::hook::set<std::uint32_t>(address, 0xC301B0);
}
});
// Generic killer caller.
signature.add({
"\x55\x8B\xEC\x80\x7D\x08\x00\x75\x55", "xxxxxx?xx", [](char* address)
{
utils::hook::set<DWORD>(address, 0xC301B0);
}
});
// Generic killer caller.
signature.add({
"\x55\x8B\xEC\x80\x7D\x08\x00\x75\x55", "xxxxxx?xx", [](char* address)
{
utils::hook::set<std::uint32_t>(address, 0xC301B0);
}
});
// CEG initialization.
signature.add({
"\x55\x8B\xEC\x83\xEC\x18\x53\x56\x57\xE8\x00\x00\x00\x00", "xxxxxxxxxx????", [](char* address)
{
utils::hook::set<BYTE>(address, 0xC3);
}
});
// CEG initialization.
signature.add({
"\x55\x8B\xEC\x83\xEC\x18\x53\x56\x57\xE8\x00\x00\x00\x00", "xxxxxxxxxx????", [](char* address)
{
utils::hook::set<std::uint8_t>(address, 0xC3);
}
});
// Some odd trap.
signature.add({
"\x55\x8B\xEC\x81\xEC\x00\x00\x00\x00\x53\x56\x57\x8B\x3D", "xxxxx??xxxxxxx", [](char* address)
{
utils::hook::set<DWORD>(address, 0xC301B0);
}
});
// Some odd trap.
signature.add({
"\x55\x8B\xEC\x81\xEC\x00\x00\x00\x00\x53\x56\x57\x8B\x3D", "xxxxx??xxxxxxx", [](char* address)
{
utils::hook::set<std::uint32_t>(address, 0xC301B0);
}
});
// Custom shit
signature.add({
"\x55\x8B\xEC\x68\x00\x00\x00\x00\x68\x00\x00\x00\x00\x64\xFF\x35\x00\x00\x00\x00\x64\x89\x25\x00\x00\x00\x00\xE8",
"xxxx????x????xxx????xxx????x", [](char* address)
{
utils::hook::set<BYTE>(address, 0xC3);
}
});
// Custom shit
signature.add({
"\x55\x8B\xEC\x68\x00\x00\x00\x00\x68\x00\x00\x00\x00\x64\xFF\x35\x00\x00\x00\x00\x64\x89\x25\x00\x00\x00\x00\xE8",
"xxxx????x????xxx????xxx????x", [](char* address)
{
utils::hook::set<std::uint8_t>(address, 0xC3);
}
});
// hkcr guid check
signature.add({
"\x55\x8B\xEC\xB8\x00\x00\x00\x00\xE8\x00\x00\x00\x00\xE8\x00\x00\x00\x00\x84\xC0\x75\x06",
"xxxx????x????x????xxxx", [](char* address)
{
utils::hook::nop(address + 0xD, 5); // Call
utils::hook::nop(address + 0x14, 2); // Jump
}
});
// hkcr guid check
signature.add({
"\x55\x8B\xEC\xB8\x00\x00\x00\x00\xE8\x00\x00\x00\x00\xE8\x00\x00\x00\x00\x84\xC0\x75\x06",
"xxxx????x????x????xxxx", [](char* address)
{
utils::hook::nop(address + 0xD, 5); // Call
utils::hook::nop(address + 0x14, 2); // Jump
}
});
// hkcr guid check 2
signature.add({
"\x55\x8B\xEC\x81\xEC\x00\x00\x00\x00\xE8\x00\x00\x00\x00\x84\xC0\x75\x06", "xxxxx????x????xxxx", [
](char* address)
{
utils::hook::nop(address + 0x9, 5); // Call
utils::hook::nop(address + 0x10, 2); // Jump
}
});
// hkcr guid check 2
signature.add({
"\x55\x8B\xEC\x81\xEC\x00\x00\x00\x00\xE8\x00\x00\x00\x00\x84\xC0\x75\x06",
"xxxxx????x????xxxx", [](char* address)
{
utils::hook::nop(address + 0x9, 5); // Call
utils::hook::nop(address + 0x10, 2); // Jump
}
});
signature.process();
signature.process();
// Checks on startup
utils::hook::set<DWORD>(0x402ED0, 0xC301B0);
utils::hook::set<DWORD>(0x4b9280, 0xC301B0);
// Checks on startup
utils::hook::set<std::uint32_t>(0x402ED0, 0xC301B0);
utils::hook::set<std::uint32_t>(0x4b9280, 0xC301B0);
// Function fixup
utils::hook(0x4CA310, game::native::DB_LoadXAssets, HOOK_JUMP).install()->quick();
// Function fixup
utils::hook(0x4CA310, game::native::DB_LoadXAssets, HOOK_JUMP).install()->quick();
// Some value obfuscation
utils::hook(0x493B81, 0x493BFC, HOOK_JUMP).install()->quick();
// Some value obfuscation
utils::hook(0x493B81, 0x493BFC, HOOK_JUMP).install()->quick();
// CEG uninitialization
utils::hook::set<BYTE>(0x527110, 0xC3);
}
};
// CEG uninitialization
utils::hook::set<std::uint8_t>(0x527110, 0xC3);
}
REGISTER_MODULE(ceg)

7
src/module/ceg.hpp Normal file
View File

@ -0,0 +1,7 @@
#pragma once
class ceg final : public module
{
public:
void post_load() override;
};

View File

@ -85,7 +85,7 @@ std::string command::params_sv::join(const int index) const
void command::add_raw(const char* name, void (*callback)())
{
game::native::Cmd_AddCommand(name, callback, allocator.allocate<game::native::cmd_function_t>());
game::native::Cmd_AddCommandInternal(name, callback, allocator.allocate<game::native::cmd_function_s>());
}
void command::add(const char* name, const std::function<void(const params&)>& callback)

View File

@ -64,3 +64,9 @@ private:
static void add_sp_commands();
};
#define Cmd_AddCommand(cmd_name, function) \
{ \
static game::native::cmd_function_s function##_VAR; \
game::native::Cmd_AddCommandInternal(cmd_name, function, &function##_VAR); \
}

View File

@ -133,7 +133,7 @@ std::string console::format(va_list* ap, const char* message)
const auto count = vsnprintf_s(buffer, _TRUNCATE, message, *ap);
if (count < 0) return {};
return {buffer, static_cast<size_t>(count)};
return {buffer, static_cast<std::size_t>(count)};
}
REGISTER_MODULE(console)

View File

@ -4,234 +4,540 @@
#include <utils/hook.hpp>
#include "command.hpp"
#include "file_system.hpp"
#include "log_file.hpp"
static utils::hook::detour sys_default_install_path_hook;
static const game::native::dvar_t** fs_homepath;
static const game::native::dvar_t** fs_debug;
static FILE* file_for_handle(const int f)
namespace
{
assert(!game::native::fsh[f].zipFile);
assert(game::native::fsh[f].handleFiles.file.o);
utils::hook::detour sys_default_install_path_hook;
return game::native::fsh[f].handleFiles.file.o;
}
const game::native::dvar_t** fs_homepath;
const game::native::dvar_t** fs_debug;
const game::native::dvar_t** fs_ignoreLocalized;
static unsigned int file_write(const void* ptr, const unsigned int len, FILE* stream)
{
return std::fwrite(ptr, sizeof(char), len, stream);
}
static FILE* file_open_append_text(const char* filename)
{
errno = 0;
auto* file = std::fopen(filename, "at");
if (file)
FILE* file_for_handle(const int f)
{
return file;
assert(!game::native::fsh[f].zipFile);
assert(game::native::fsh[f].handleFiles.file.o);
return game::native::fsh[f].handleFiles.file.o;
}
log_file::info("Couldn't open file: %s %s\n", filename, std::strerror(errno));
return nullptr;
}
static FILE* file_open_write_binary(const char* filename)
{
errno = 0;
auto* file = std::fopen(filename, "wb");
if (file)
unsigned int file_write(const void* ptr, const unsigned int len, FILE* stream)
{
return file;
return std::fwrite(ptr, sizeof(char), len, stream);
}
log_file::info("Couldn't open file: %s %s\n", filename, std::strerror(errno));
return nullptr;
}
static void replace_separators(char* path)
{
char* src, * dst;
bool was_sep = false;
for (src = path, dst = path; *src; ++src)
FILE* file_open_append_text(const char* filename)
{
if (*src == '/' || *src == '\\')
errno = 0;
auto* file = std::fopen(filename, "at");
if (file)
{
if (!was_sep)
return file;
}
log_file::info("Couldn't open file: %s %s\n", filename, std::strerror(errno));
return nullptr;
}
FILE* file_open_write_binary(const char* filename)
{
errno = 0;
auto* file = std::fopen(filename, "wb");
if (file)
{
return file;
}
log_file::info("Couldn't open file: %s %s\n", filename, std::strerror(errno));
return nullptr;
}
void replace_separators(char* path)
{
char* src, * dst;
bool was_sep = false;
for (src = path, dst = path; *src; ++src)
{
if (*src == '/' || *src == '\\')
{
was_sep = true;
*dst++ = '\\';
if (!was_sep)
{
was_sep = true;
*dst++ = '\\';
}
}
else
{
was_sep = false;
*dst++ = *src;
}
}
else
*dst = 0;
}
void build_os_path_for_thread(const char* base, const char* game, const char* qpath, char* ospath, game::native::FsThread thread)
{
assert(base);
assert(qpath);
assert(ospath);
if (!game)
{
was_sep = false;
*dst++ = *src;
game = "";
}
else if (!game[0])
{
game = game::native::fs_gamedir;
}
auto len_base = std::strlen(base);
auto len_game = std::strlen(game);
auto len_qpath = std::strlen(qpath);
if (len_game + 1 + len_base + len_qpath + 1 >= game::native::MAX_OSPATH)
{
if (thread)
{
*ospath = '\0';
return;
}
game::native::Com_Error(game::native::ERR_FATAL, "\x15" "FS_BuildOSPath: os path length exceeded\n");
}
std::memcpy(ospath, base, len_base);
ospath[len_base] = '/';
std::memcpy(&ospath[len_base + 1], game, len_game);
ospath[len_base + 1 + len_game] = '/';
std::memcpy(ospath + len_base + 2 + len_game, qpath, len_qpath + 1);
replace_separators(ospath);
}
game::native::FsThread get_current_thread()
{
if (game::native::Sys_IsMainThread())
{
return game::native::FS_THREAD_MAIN;
}
if (game::native::Sys_IsDatabaseThread())
{
return game::native::FS_THREAD_DATABASE;
}
if (game::native::Sys_IsStreamThread())
{
return game::native::FS_THREAD_STREAM;
}
if (game::native::Sys_IsRenderThread())
{
return game::native::FS_THREAD_BACKEND;
}
if (game::native::Sys_IsServerThread())
{
return game::native::FS_THREAD_SERVER;
}
return game::native::FS_THREAD_INVALID;
}
int handle_for_file_current_thread()
{
return game::native::FS_HandleForFile(get_current_thread());
}
int open_file_append(const char* filename)
{
char ospath[game::native::MAX_OSPATH]{};
game::native::FS_CheckFileSystemStarted();
const auto* basepath = (*fs_homepath)->current.string;
build_os_path_for_thread(basepath, game::native::fs_gamedir, filename, ospath, game::native::FS_THREAD_MAIN);
if ((*fs_debug)->current.integer)
{
log_file::info("FS_FOpenFileAppend: %s\n", ospath);
}
if (game::native::FS_CreatePath(ospath))
{
return 0;
}
auto* f = file_open_append_text(ospath);
if (!f)
{
return 0;
}
auto h = handle_for_file_current_thread();
game::native::fsh[h].zipFile = nullptr;
strncpy_s(game::native::fsh[h].name, filename, _TRUNCATE);
game::native::fsh[h].handleFiles.file.o = f;
game::native::fsh[h].handleSync = 0;
if (!game::native::fsh[h].handleFiles.file.o)
{
game::native::FS_FCloseFile(h);
h = 0;
}
return h;
}
int get_handle_and_open_file(const char* filename, const char* ospath, game::native::FsThread thread)
{
auto* fp = file_open_write_binary(ospath);
if (!fp)
{
return 0;
}
const auto f = game::native::FS_HandleForFile(thread);
game::native::fsh[f].zipFile = nullptr;
game::native::fsh[f].handleFiles.file.o = fp;
strncpy_s(game::native::fsh[f].name, filename, _TRUNCATE);
game::native::fsh[f].handleSync = 0;
return f;
}
int open_file_write_to_dir_for_thread(const char* filename, const char* dir, const char* osbasepath, game::native::FsThread thread)
{
char ospath[game::native::MAX_OSPATH]{};
game::native::FS_CheckFileSystemStarted();
const char* basepath = (*fs_homepath)->current.string;
build_os_path_for_thread(basepath, dir, filename, ospath, game::native::FS_THREAD_MAIN);
if ((*fs_debug)->current.integer)
{
log_file::info("FS_FOpenFileWriteToDirForThread: %s\n", ospath);
}
if (game::native::FS_CreatePath(ospath))
{
return 0;
}
return get_handle_and_open_file(filename, ospath, thread);
}
int open_file_write(const char* filename)
{
return open_file_write_to_dir_for_thread(filename, game::native::fs_gamedir, "", game::native::FS_THREAD_MAIN);
}
void convert_path(char* s)
{
while (*s)
{
if (*s == '\\' || *s == ':')
{
*s = '/';
}
++s;
}
}
*dst = 0;
}
static void build_os_path_for_thread(const char* base, const char* game, const char* qpath, char* ospath, game::native::FsThread thread)
{
assert(base);
assert(qpath);
assert(ospath);
int path_cmp(const char* s1, const char* s2)
{
int c1;
if (!game)
{
game = "";
}
else if (!game[0])
{
game = game::native::fs_gamedir;
}
auto len_base = std::strlen(base);
auto len_game = std::strlen(game);
auto len_qpath = std::strlen(qpath);
if (len_game + 1 + len_base + len_qpath + 1 >= game::native::MAX_OSPATH)
{
if (thread)
do
{
*ospath = '\0';
c1 = *s1++;
int c2 = *s2++;
if (game::native::I_islower(c1))
{
c1 -= ('a' - 'A');
}
if (game::native::I_islower(c2))
{
c2 -= ('a' - 'A');
}
if (c1 == '\\' || c1 == ':')
{
c1 = '/';
}
if (c2 == '\\' || c2 == ':')
{
c2 = '/';
}
if (c1 < c2)
{
return -1; // strings not equal
}
if (c1 > c2)
{
return 1;
}
} while (c1);
return 0; // strings are equal
}
void sort_file_list(char** filelist, int numfiles)
{
int j;
const char** sortedlist = static_cast<const char**>(game::native::Z_Malloc((numfiles * sizeof(*sortedlist)) + 4));
sortedlist[0] = nullptr;
auto numsortedfiles = 0;
for (auto i = 0; i < numfiles; ++i)
{
for (j = 0; j < numsortedfiles; j++)
{
if (path_cmp(filelist[i], sortedlist[j]) < 0)
{
break;
}
}
for (auto k = numsortedfiles; k > j; --k)
{
sortedlist[k] = sortedlist[k - 1];
}
sortedlist[j] = filelist[i];
++numsortedfiles;
}
std::memcpy(filelist, sortedlist, numfiles * sizeof(*filelist));
std::free(sortedlist);
}
int use_search_path(game::native::searchpath_s* pSearch)
{
if (pSearch->bLocalized && (*fs_ignoreLocalized)->current.enabled)
{
return 0;
}
if (pSearch->bLocalized && pSearch->language != game::native::SEH_GetCurrentLanguage())
{
return 0;
}
return 1;
}
int iwd_is_pure(game::native::iwd_t* iwd)
{
if (*game::native::fs_numServerIwds)
{
for (auto i = 0; i < *game::native::fs_numServerIwds; ++i)
{
if (iwd->checksum == game::native::fs_serverIwds[i])
{
return 1;
}
}
return 0;
}
return 1;
}
char** list_files(const char* path, const char* extension, game::native::FsListBehavior_e behavior, int* numfiles, int allocTrackType)
{
return game::native::FS_ListFilteredFiles(*game::native::fs_searchpaths, path, extension, nullptr, behavior, numfiles, allocTrackType);
}
void display_path(bool b_language_cull)
{
auto i_language = game::native::SEH_GetCurrentLanguage();
const auto* psz_language_name = game::native::SEH_GetLanguageName(i_language);
log_file::info("Current language: %s\n", psz_language_name);
if ((*fs_ignoreLocalized)->current.enabled)
{
log_file::info(" localized assets are being ignored\n");
}
log_file::info("Current search path:\n");
for (auto* s = *game::native::fs_searchpaths; s; s = s->next)
{
if (b_language_cull && !use_search_path(s))
{
continue;
}
if (s->iwd)
{
log_file::info("%s (%i files)\n", s->iwd->iwdFilename, s->iwd->numfiles);
if (s->bLocalized)
{
log_file::info(" localized assets iwd file for %s\n", game::native::SEH_GetLanguageName(s->language));
}
if (*game::native::fs_numServerIwds)
{
if (iwd_is_pure(s->iwd))
{
log_file::info(" on the pure list\n");
}
else
{
log_file::info(" not on the pure list\n");
}
}
}
else
{
log_file::info("%s/%s\n", s->dir->path, s->dir->gamedir);
if (s->bLocalized)
{
log_file::info(" localized assets game folder for %s\n", game::native::SEH_GetLanguageName(s->language));
}
}
}
log_file::info("\nFile Handles:\n");
for (int i = 1; i < 64; ++i)
{
if (game::native::fsh[i].handleFiles.file.o)
{
log_file::info("handle %i: %s\n", i, game::native::fsh[i].name);
}
}
}
bool touch_file(const char* name)
{
*game::native::com_fileAccessed = 1;
auto ret = game::native::FS_FOpenFileReadForThread(name, nullptr, game::native::FS_THREAD_MAIN);
return ret != -1;
}
void path_f()
{
display_path(true);
}
void full_path_f()
{
display_path(false);
}
void dir_f()
{
const char* path;
const char* extension;
int ndirs;
if (game::native::Cmd_Argc() < 2 || game::native::Cmd_Argc() > 3)
{
log_file::info("usage: dir <directory> [extension]\n");
return;
}
game::native::Com_Error(game::native::ERR_FATAL, "\x15" "FS_BuildOSPath: os path length exceeded\n");
if (game::native::Cmd_Argc() == 2)
{
path = game::native::Cmd_Argv(1);
extension = "";
}
else
{
path = game::native::Cmd_Argv(1);
extension = game::native::Cmd_Argv(2);
}
log_file::info("Directory of %s %s\n", path, extension);
log_file::info("---------------\n");
auto** dirnames = list_files(path, extension, game::native::FS_LIST_PURE_ONLY, &ndirs, 3);
for (int i = 0; i < ndirs; ++i)
{
log_file::info("%s\n", dirnames[i]);
}
game::native::Sys_FreeFileList(dirnames);
}
std::memcpy(ospath, base, len_base);
ospath[len_base] = '/';
std::memcpy(&ospath[len_base + 1], game, len_game);
ospath[len_base + 1 + len_game] = '/';
std::memcpy(ospath + len_base + 2 + len_game, qpath, len_qpath + 1);
replace_separators(ospath);
}
static game::native::FsThread get_current_thread()
{
if (game::native::Sys_IsMainThread())
void new_dir_f()
{
return game::native::FS_THREAD_MAIN;
int ndirs;
if (game::native::Cmd_Argc() < 2)
{
log_file::info("usage: fdir <filter>\n");
log_file::info("example: fdir *q3dm*.bsp\n");
return;
}
const auto* filter = game::native::Cmd_Argv(1);
log_file::info("---------------\n");
auto** dirnames = game::native::FS_ListFilteredFiles(*game::native::fs_searchpaths, "", "", filter, game::native::FS_LIST_PURE_ONLY, &ndirs, 3);
sort_file_list(dirnames, ndirs);
for (auto i = 0; i < ndirs; ++i)
{
convert_path(dirnames[i]);
log_file::info("%s\n", dirnames[i]);
}
log_file::info("%d files listed\n", ndirs);
game::native::Sys_FreeFileList(dirnames);
}
if (game::native::Sys_IsDatabaseThread())
void touch_file_f()
{
return game::native::FS_THREAD_DATABASE;
if (game::native::Cmd_Argc() != 2)
{
log_file::info("Usage: touchFile <file>\n");
return;
}
touch_file(game::native::Cmd_Argv(1));
}
if (game::native::Sys_IsStreamThread())
void add_commands()
{
return game::native::FS_THREAD_STREAM;
Cmd_AddCommand("path", path_f);
Cmd_AddCommand("fullpath", full_path_f);
Cmd_AddCommand("dir", dir_f);
Cmd_AddCommand("fdir", new_dir_f);
Cmd_AddCommand("touchFile", touch_file_f);
}
if (game::native::Sys_IsRenderThread())
void fs_startup_stub(char* game_name)
{
return game::native::FS_THREAD_BACKEND;
log_file::info("----- FS_Startup -----\n");
utils::hook::invoke<void>(0x5B1070, game_name);
add_commands();
display_path(true);
log_file::info("----------------------\n");
log_file::info("%d files in iwd files\n", *game::native::fs_iwdFileCount);
}
if (game::native::Sys_IsServerThread())
void fs_shutdown_stub(int closemfp)
{
return game::native::FS_THREAD_SERVER;
utils::hook::invoke<void>(0x5B0D30, closemfp);
game::native::Cmd_RemoveCommand("path");
game::native::Cmd_RemoveCommand("fullpath");
game::native::Cmd_RemoveCommand("dir");
game::native::Cmd_RemoveCommand("fdir");
game::native::Cmd_RemoveCommand("touchFile");
}
return game::native::FS_THREAD_INVALID;
}
static int handle_for_file_current_thread()
{
return game::native::FS_HandleForFile(get_current_thread());
}
static int open_file_append(const char* filename)
{
char ospath[game::native::MAX_OSPATH]{};
game::native::FS_CheckFileSystemStarted();
const auto* basepath = (*fs_homepath)->current.string;
build_os_path_for_thread(basepath, game::native::fs_gamedir, filename, ospath, game::native::FS_THREAD_MAIN);
if ((*fs_debug)->current.integer)
const char* sys_default_install_path_stub()
{
log_file::info("FS_FOpenFileAppend: %s\n", ospath);
static auto current_path = std::filesystem::current_path().string();
return current_path.data();
}
if (game::native::FS_CreatePath(ospath))
{
return 0;
}
auto* f = file_open_append_text(ospath);
if (!f)
{
return 0;
}
auto h = handle_for_file_current_thread();
game::native::fsh[h].zipFile = nullptr;
strncpy_s(game::native::fsh[h].name, filename, _TRUNCATE);
game::native::fsh[h].handleFiles.file.o = f;
game::native::fsh[h].handleSync = 0;
if (!game::native::fsh[h].handleFiles.file.o)
{
game::native::FS_FCloseFile(h);
h = 0;
}
return h;
}
static int get_handle_and_open_file(const char* filename, const char* ospath, game::native::FsThread thread)
{
auto* fp = file_open_write_binary(ospath);
if (!fp)
{
return 0;
}
const auto f = game::native::FS_HandleForFile(thread);
game::native::fsh[f].zipFile = nullptr;
game::native::fsh[f].handleFiles.file.o = fp;
strncpy_s(game::native::fsh[f].name, filename, _TRUNCATE);
game::native::fsh[f].handleSync = 0;
return f;
}
static int open_file_write_to_dir_for_thread(const char* filename, const char* dir, const char* osbasepath, game::native::FsThread thread)
{
char ospath[game::native::MAX_OSPATH]{};
game::native::FS_CheckFileSystemStarted();
const char* basepath = (*fs_homepath)->current.string;
build_os_path_for_thread(basepath, dir, filename, ospath, game::native::FS_THREAD_MAIN);
if ((*fs_debug)->current.integer)
{
log_file::info("FS_FOpenFileWriteToDirForThread: %s\n", ospath);
}
if (game::native::FS_CreatePath(ospath))
{
return 0;
}
return get_handle_and_open_file(filename, ospath, thread);
}
static int open_file_write(const char* filename)
{
return open_file_write_to_dir_for_thread(filename, game::native::fs_gamedir, "", game::native::FS_THREAD_MAIN);
}
static const char* sys_default_install_path_stub()
{
static auto current_path = std::filesystem::current_path().string();
return current_path.data();
}
int file_system::open_file_by_mode(const char* qpath, int* f, game::native::fsMode_t mode)
@ -329,6 +635,16 @@ void file_system::post_load()
{
fs_homepath = reinterpret_cast<const game::native::dvar_t**>(SELECT_VALUE(0x1C2B538, 0x59ADD18));
fs_debug = reinterpret_cast<const game::native::dvar_t**>(SELECT_VALUE(0x1C2B32C, 0x59A9A08));
fs_ignoreLocalized = reinterpret_cast<const game::native::dvar_t**>(SELECT_VALUE(0x1C2B21C, 0x59A99F8));
if (game::is_mp())
{
utils::hook(0x5B20AA, fs_startup_stub, HOOK_CALL).install()->quick(); // FS_InitFilesystem
utils::hook(0x5B2148, fs_startup_stub, HOOK_CALL).install()->quick(); // FS_Restart
utils::hook(0x5557CC, fs_shutdown_stub, HOOK_CALL).install()->quick(); // Com_Quit_f
utils::hook(0x5B2115, fs_shutdown_stub, HOOK_CALL).install()->quick(); // FS_Restart
}
// Make open-iw5 work outside of the game directory
sys_default_install_path_hook.create(SELECT_VALUE(0x487E50, 0x5C4A80), &sys_default_install_path_stub);