diff --git a/src/game/dvars.cpp b/src/game/dvars.cpp index 3d35623..5c680f9 100644 --- a/src/game/dvars.cpp +++ b/src/game/dvars.cpp @@ -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(SELECT_VALUE(0x1769F50, 0x1CEF588)); sv_maxclients = reinterpret_cast(SELECT_VALUE(0x0, 0x21223C0)); + + loc_language = reinterpret_cast(SELECT_VALUE(0x1BF6938, 0x58D5A90)); } } diff --git a/src/game/dvars.hpp b/src/game/dvars.hpp index 4cc5a94..d5c99a9 100644 --- a/src/game/dvars.hpp +++ b/src/game/dvars.hpp @@ -6,5 +6,7 @@ namespace dvars extern const game::native::dvar_t** sv_maxclients; + extern const game::native::dvar_t** loc_language; + void initialize(); } diff --git a/src/game/game.cpp b/src/game/game.cpp index f7df25c..b9e58a9 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -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(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(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(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(SELECT_VALUE(0x73AC20, 0x7363BC)); native::sv_cmd_args = reinterpret_cast(SELECT_VALUE(0x1757218, 0x1CAA998)); @@ -817,9 +880,12 @@ namespace game native::fs_searchpaths = reinterpret_cast(SELECT_VALUE(0x1C2FE78, 0x59BA858)); native::fs_gamedir = reinterpret_cast(SELECT_VALUE(0x1C2B220, 0x59A98F8)); native::fsh = reinterpret_cast(SELECT_VALUE(0x1C2B540, 0x59B5F20)); + native::fs_numServerIwds = reinterpret_cast(SELECT_VALUE(0x1C2FE84, 0x59BA864)); + native::fs_serverIwds = reinterpret_cast(SELECT_VALUE(0x0, 0x59AAB18)); + native::fs_iwdFileCount = reinterpret_cast(SELECT_VALUE(0x1C2fE7C, 0x59BA85C)); native::com_fileAccessed = reinterpret_cast(SELECT_VALUE(0x1C2B328, 0x59A9A04)); - native::threadId = reinterpret_cast(SELECT_VALUE(0x18576C8, 0x1D6E448)); + native::threadId = reinterpret_cast(SELECT_VALUE(0x18576C8, 0x1D6E448)); native::initialized_0 = reinterpret_cast(SELECT_VALUE(0x1CE1CA0, 0x5AA3058)); native::sys_timeBase = reinterpret_cast(SELECT_VALUE(0x1CE1C98, 0x5AA3050)); diff --git a/src/game/game.hpp b/src/game/game.hpp index 11a6f49..4eccf19 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -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__) diff --git a/src/game/structs.hpp b/src/game/structs.hpp index c5ca166..6fadebd 100644 --- a/src/game/structs.hpp +++ b/src/game/structs.hpp @@ -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, }; diff --git a/src/module/ceg.cpp b/src/module/ceg.cpp index 9a3c475..a5f4d6b 100644 --- a/src/module/ceg.cpp +++ b/src/module/ceg.cpp @@ -1,95 +1,94 @@ #include #include -#include - #include "game/game.hpp" -class ceg final : public module +#include "ceg.hpp" + +#include + + +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(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(address, 0xC301B0); + } + }); - // Generic killer caller. - signature.add({ - "\x55\x8B\xEC\x80\x7D\x08\x00\x75\x55", "xxxxxx?xx", [](char* address) - { - utils::hook::set(address, 0xC301B0); - } - }); + // Generic killer caller. + signature.add({ + "\x55\x8B\xEC\x80\x7D\x08\x00\x75\x55", "xxxxxx?xx", [](char* address) + { + utils::hook::set(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(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(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(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(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(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(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(0x402ED0, 0xC301B0); - utils::hook::set(0x4b9280, 0xC301B0); + // Checks on startup + utils::hook::set(0x402ED0, 0xC301B0); + utils::hook::set(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(0x527110, 0xC3); - } -}; + // CEG uninitialization + utils::hook::set(0x527110, 0xC3); +} REGISTER_MODULE(ceg) diff --git a/src/module/ceg.hpp b/src/module/ceg.hpp new file mode 100644 index 0000000..3dddebc --- /dev/null +++ b/src/module/ceg.hpp @@ -0,0 +1,7 @@ +#pragma once + +class ceg final : public module +{ +public: + void post_load() override; +}; diff --git a/src/module/command.cpp b/src/module/command.cpp index 6e949a3..1c5f50d 100644 --- a/src/module/command.cpp +++ b/src/module/command.cpp @@ -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_AddCommandInternal(name, callback, allocator.allocate()); } void command::add(const char* name, const std::function& callback) diff --git a/src/module/command.hpp b/src/module/command.hpp index 68bd992..3e49e33 100644 --- a/src/module/command.hpp +++ b/src/module/command.hpp @@ -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); \ +} diff --git a/src/module/console.cpp b/src/module/console.cpp index 4406ee2..9e88a39 100644 --- a/src/module/console.cpp +++ b/src/module/console.cpp @@ -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(count)}; + return {buffer, static_cast(count)}; } REGISTER_MODULE(console) diff --git a/src/module/file_system.cpp b/src/module/file_system.cpp index ce99372..18c0665 100644 --- a/src/module/file_system.cpp +++ b/src/module/file_system.cpp @@ -4,234 +4,540 @@ #include +#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(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 [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 \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 \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(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(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(SELECT_VALUE(0x1C2B538, 0x59ADD18)); fs_debug = reinterpret_cast(SELECT_VALUE(0x1C2B32C, 0x59A9A08)); + fs_ignoreLocalized = reinterpret_cast(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);