diff --git a/src/game/engine/scoped_critical_section.cpp b/src/game/engine/scoped_critical_section.cpp new file mode 100644 index 0000000..8388d63 --- /dev/null +++ b/src/game/engine/scoped_critical_section.cpp @@ -0,0 +1,84 @@ +#include +#include "game/game.hpp" + +#include "scoped_critical_section.hpp" + +namespace game::engine +{ + scoped_critical_section::scoped_critical_section(const native::CriticalSection s, const native::ScopedCriticalSectionType type) + : s_(s), is_scoped_release_(false) + { + if (type == native::SCOPED_CRITSECT_NORMAL) + { + Sys_EnterCriticalSection(this->s_); + this->has_ownership_ = true; + } + else + { + if (type == native::SCOPED_CRITSECT_TRY) + { + this->has_ownership_ = Sys_TryEnterCriticalSection(this->s_); + } + else + { + if (type == native::SCOPED_CRITSECT_RELEASE) + { + Sys_LeaveCriticalSection(this->s_); + this->is_scoped_release_ = true; + } + + this->has_ownership_ = false; + } + } + } + + scoped_critical_section::~scoped_critical_section() + { + if (!this->has_ownership_ || this->is_scoped_release_) + { + if (!this->has_ownership_ && this->is_scoped_release_) + { + Sys_EnterCriticalSection(this->s_); + } + } + else + { + Sys_LeaveCriticalSection(this->s_); + } + } + + void scoped_critical_section::enter_crit_sect() + { + assert(!this->has_ownership_); + + this->has_ownership_ = true; + Sys_EnterCriticalSection(this->s_); + } + + void scoped_critical_section::leave_crit_sect() + { + assert(this->has_ownership_); + + this->has_ownership_ = false; + Sys_LeaveCriticalSection(this->s_); + } + + bool scoped_critical_section::try_enter_crit_sect() + { + assert(!this->has_ownership_); + + const auto result = Sys_TryEnterCriticalSection(this->s_); + this->has_ownership_ = result; + return result; + } + + bool scoped_critical_section::has_ownership() const + { + return this->has_ownership_; + } + + bool scoped_critical_section::is_scoped_release() const + { + return this->is_scoped_release_; + } +} diff --git a/src/game/engine/scoped_critical_section.hpp b/src/game/engine/scoped_critical_section.hpp new file mode 100644 index 0000000..2ab6926 --- /dev/null +++ b/src/game/engine/scoped_critical_section.hpp @@ -0,0 +1,23 @@ +#pragma once + +namespace game::engine +{ + class scoped_critical_section + { + public: + scoped_critical_section(native::CriticalSection s, native::ScopedCriticalSectionType type); + ~scoped_critical_section(); + + void enter_crit_sect(); + void leave_crit_sect(); + bool try_enter_crit_sect(); + + [[nodiscard]] bool has_ownership() const; + [[nodiscard]] bool is_scoped_release() const; + + private: + native::CriticalSection s_; + bool has_ownership_; + bool is_scoped_release_; + }; +} diff --git a/src/game/game.cpp b/src/game/game.cpp index 194109d..79d145f 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -13,7 +13,6 @@ namespace game DB_LoadXAssets_t DB_LoadXAssets; Dvar_RegisterBool_t Dvar_RegisterBool; - Dvar_RegisterInt_t Dvar_RegisterInt; Dvar_RegisterString_t Dvar_RegisterString; Dvar_SetIntByName_t Dvar_SetIntByName; @@ -39,7 +38,7 @@ namespace game Sys_ShowConsole_t Sys_ShowConsole; Sys_Error_t Sys_Error; - Sys_IsServerThread_t Sys_IsServerThread; + Sys_Milliseconds_t Sys_Milliseconds; VM_Notify_t VM_Notify; @@ -73,6 +72,8 @@ namespace game Com_Quit_f_t Com_Quit_f; + FS_Printf_t FS_Printf; + player_die_t player_die; decltype(longjmp)* _longjmp; @@ -107,6 +108,22 @@ namespace game float* com_codeTimeScale; + RTL_CRITICAL_SECTION* s_criticalSection; + + int* logfile; + + searchpath_s** fs_searchpaths; + char* fs_gamedir; + fileHandleData_t* fsh; + int* com_fileAccessed; + + // DS does not have MJPEG thread + unsigned int (*threadId)[THREAD_CONTEXT_COUNT]; + + int* initialized_0; + int* sys_timeBase; + unsigned __int64* sys_counterBase; + namespace mp { SV_GetGuid_t SV_GetGuid; @@ -193,11 +210,9 @@ namespace game { return find_variable_dedicated(parentId, name); } - else - { - return reinterpret_cast // - (SELECT_VALUE(0x4C4E70, 0x5651F0, 0x0))(parentId, name); - } + + return reinterpret_cast // + (SELECT_VALUE(0x4C4E70, 0x5651F0, 0x0))(parentId, name); } __declspec(naked) VariableValue get_entity_field_value_dedicated(unsigned int classnum, int entnum, int _offset) @@ -221,11 +236,9 @@ namespace game { return get_entity_field_value_dedicated(classnum, entnum, offset); } - else - { - return reinterpret_cast // - (SELECT_VALUE(0x530E30, 0x56AF20, 0x0))(classnum, entnum, offset); - } + + return reinterpret_cast // + (SELECT_VALUE(0x530E30, 0x56AF20, 0x0))(classnum, entnum, offset); } void* MT_Alloc(const int numBytes, const int type) @@ -312,6 +325,27 @@ namespace game flags, dvar_value, domain, description); } + const dvar_t* Dvar_RegisterInt(const char* dvarName, int value, + int min, int max, unsigned __int16 flags, const char* description) + { + if (!is_dedi()) + { + return reinterpret_cast + (SELECT_VALUE(0x48CD40, 0x5BEA40, 0x0))(dvarName, value, min, max, flags, description); + } + + DvarLimits domain; + DvarValue dvar_value; + + domain.integer.min = min; + domain.integer.max = max; + + dvar_value.integer = value; + + return dvar_register_variant_dedicated(dvarName, DVAR_TYPE_INT, + flags, dvar_value, domain, description); + } + void IncInParam() { Scr_ClearOutParams(); @@ -368,10 +402,8 @@ namespace game { return scr_instanceFunctions[index]; } - else - { - return scr_globalFunctions[index]; - } + + return scr_globalFunctions[index]; } __declspec(naked) void scr_notify_id_multiplayer(unsigned int id, unsigned int string_value, @@ -444,11 +476,9 @@ namespace game { return scr_set_object_field_dedicated(classnum, entnum, offset); } - else - { - return reinterpret_cast // - (SELECT_VALUE(0x42CAD0, 0x52BCC0, 0x0))(classnum, entnum, offset); - } + + return reinterpret_cast // + (SELECT_VALUE(0x42CAD0, 0x52BCC0, 0x0))(classnum, entnum, offset); } __declspec(naked) void scr_add_string_dedicated(const char* value) @@ -529,7 +559,8 @@ namespace game { return dedi::svs_clients[clientNum].bIsTestClient; } - else if (is_mp()) + + if (is_mp()) { return mp::svs_clients[clientNum].bIsTestClient; } @@ -667,6 +698,238 @@ namespace game reinterpret_cast(0x4228A0)(localClientNum, msg, flags); } } + + void Sys_EnterCriticalSection(CriticalSection critSect) + { + assert(static_cast(critSect) < + static_cast(CRITSECT_COUNT)); + + EnterCriticalSection(&s_criticalSection[critSect]); + } + + void Sys_LeaveCriticalSection(CriticalSection critSect) + { + assert(static_cast(critSect) < + static_cast(CRITSECT_COUNT)); + + LeaveCriticalSection(&s_criticalSection[critSect]); + } + + bool Sys_TryEnterCriticalSection(CriticalSection critSect) + { + assert(static_cast(critSect) < + static_cast(CRITSECT_COUNT)); + + return TryEnterCriticalSection(&s_criticalSection[critSect]) != FALSE; + } + + bool Sys_IsMainThread() + { + const auto id = GetCurrentThreadId(); + assert(id); + return id == *threadId[THREAD_CONTEXT_MAIN]; + } + + bool Sys_IsDatabaseThread() + { + const auto id = GetCurrentThreadId(); + assert(id); + return id == *threadId[THREAD_CONTEXT_DATABASE]; + } + + bool Sys_IsStreamThread() + { + const auto id = GetCurrentThreadId(); + assert(id); + return id == *threadId[THREAD_CONTEXT_STREAM]; + } + + bool Sys_IsRenderThread() + { + const auto id = GetCurrentThreadId(); + assert(id); + return id == *threadId[THREAD_CONTEXT_BACKEND]; + } + + bool Sys_IsServerThread() + { + const auto id = GetCurrentThreadId(); + assert(id); + return id == *threadId[THREAD_CONTEXT_SERVER]; + } + + void fs_fclose_file_dedicated(int h) + { + static DWORD func = 0x524910; + + __asm + { + pushad + mov eax, h + call func + popad + } + } + + void FS_FCloseFile(int h) + { + if (is_dedi()) + { + fs_fclose_file_dedicated(h); + } + else + { + reinterpret_cast + (SELECT_VALUE(0x415160, 0x5AF170, 0x0))(h); + } + } + + bool FS_Initialized() + { + return (*fs_searchpaths != nullptr); + } + + void* fs_handle_for_file_dedicated(FsThread thread) + { + static DWORD func = 0x5245F0; + void* result = nullptr; + + __asm + { + pushad + mov edi, thread + call func + mov result, eax + popad + } + + return result; + } + + void* FS_HandleForFile(FsThread thread) + { + if (is_dedi()) + { + return fs_handle_for_file_dedicated(thread); + } + + return reinterpret_cast + (SELECT_VALUE(0x46B1C0, 0x5AEE50, 0x0))(thread); + } + + int fs_fopen_file_read_for_thread_singleplayer(const char* filename, int* file, FsThread thread) + { + static DWORD func = 0x627F20; + auto result = 0; + + __asm + { + pushad + + mov eax, file + mov edx, filename + push thread + call func + add esp, 0x4 + mov result, eax + + popad + } + + return result; + } + + int fs_fopen_file_read_for_thread_multiplayer(const char* filename, int* file, FsThread thread) + { + static DWORD func = 0x5B1990; + auto result = 0; + + __asm + { + pushad + + mov edx, filename + push thread + push file + call func + add esp, 0x8 + mov result, eax + + popad + } + + return result; + } + + int fs_fopen_file_read_for_thread_dedicated(const char* filename, int* file, FsThread thread) + { + static DWORD func = 0x524E30; + auto result = 0; + + __asm + { + pushad + + mov edx, filename + push thread + push file + call func + add esp, 0x8 + mov result, eax + + popad + } + + return result; + } + + int FS_FOpenFileReadForThread(const char* filename, int* file, FsThread thread) + { + if (is_sp()) + { + return fs_fopen_file_read_for_thread_singleplayer(filename, file, thread); + } + + if (is_mp()) + { + return fs_fopen_file_read_for_thread_multiplayer(filename, file, thread); + } + + return fs_fopen_file_read_for_thread_dedicated(filename, file, thread); + } + + int fs_create_path_directory(char* OSPath) + { + static DWORD func = 0x5247D0; + auto result = 0; + + __asm + { + pushad + mov edi, OSPath + call func + mov result, eax + popad + } + + return result; + } + + int FS_CreatePath(char* OSPath) + { + if (is_dedi()) + { + return fs_create_path_directory(OSPath); + } + + return reinterpret_cast + (SELECT_VALUE(0x4F5AB0, 0x5AF060, 0x0))(OSPath); + } + + void FS_CheckFileSystemStarted() + { + assert(*fs_searchpaths); + } } launcher::mode mode = launcher::mode::none; @@ -708,7 +971,6 @@ namespace game native::DB_LoadXAssets = native::DB_LoadXAssets_t(SELECT_VALUE(0x48A8E0, 0x4CD020, 0x44F770)); native::Dvar_RegisterBool = native::Dvar_RegisterBool_t(SELECT_VALUE(0x4914D0, 0x5BE9F0, 0x0)); - native::Dvar_RegisterInt = native::Dvar_RegisterInt_t(SELECT_VALUE(0x48CD40, 0x5BEA40, 0x0)); native::Dvar_RegisterString = native::Dvar_RegisterString_t(SELECT_VALUE(0x5197F0, 0x5BEC90, 0x0)); native::Dvar_SetIntByName = native::Dvar_SetIntByName_t(SELECT_VALUE(0x5396B0, 0x5BF560, 0x0)); @@ -735,8 +997,8 @@ namespace game native::Scr_GetString = native::Scr_GetString_t(SELECT_VALUE(0x497530, 0x56A3D0, 0x0)); native::Sys_ShowConsole = native::Sys_ShowConsole_t(SELECT_VALUE(0x470AF0, 0x5CF590, 0)); - native::Sys_Error = native::Sys_Error_t(SELECT_VALUE(0x490D90, 0x5CC3B0, 0x539590)); + native::Sys_Milliseconds = native::Sys_Milliseconds_t(SELECT_VALUE(0x4A1610, 0x5CE740, 0x53B900)); native::VM_Notify = native::VM_Notify_t(SELECT_VALUE(0x610200, 0x569720, 0x4EF450)); @@ -756,8 +1018,6 @@ namespace game native::SV_SendServerCommand = native::SV_SendServerCommand_t(SELECT_VALUE(0x4F6990, 0x575DE0, 0x4FD5A0)); native::mp::SV_GetGuid = native::mp::SV_GetGuid_t(0x573990); - native::Sys_IsServerThread = native::Sys_IsServerThread_t(SELECT_VALUE(0x4CC5A0, 0x55F9A0, 0x0)); - native::sp::IsServerRunning = native::sp::IsServerRunning_t(0x45D310); native::XUIDToString = native::XUIDToString_t(SELECT_VALUE(0x4FAA30, 0x55CC20, 0x0)); @@ -781,6 +1041,8 @@ namespace game native::Com_Quit_f = native::Com_Quit_f_t(SELECT_VALUE(0x4F48B0, 0x5556B0, 0x4D95B0)); + native::FS_Printf = native::FS_Printf_t(SELECT_VALUE(0x421E90, 0x5AF7C0, 0x5255A0)); + native::player_die = native::player_die_t(SELECT_VALUE(0x0, 0x503460, 0x47F4D0)); native::_longjmp = reinterpret_cast(SELECT_VALUE(0x73AC20, 0x7363BC, 0x655558)); @@ -823,5 +1085,20 @@ namespace game native::deferredQueue = reinterpret_cast(SELECT_VALUE(0x0, 0x1D55438, 0x0)); native::com_codeTimeScale = reinterpret_cast(SELECT_VALUE(0x1769F1C, 0x1CEF554, 0x1B9CEC0)); + + native::s_criticalSection = reinterpret_cast(SELECT_VALUE(0x1CD5638, 0x5A91048, 0x593FF98)); + + native::logfile = reinterpret_cast(SELECT_VALUE(0x176B534, 0x1CF0B78, 0x1B9E4C8)); + + native::fs_searchpaths = reinterpret_cast(SELECT_VALUE(0x1C2FE78, 0x59BA858, 0x62F4F60)); + native::fs_gamedir = reinterpret_cast(SELECT_VALUE(0x1C2B220, 0x59A98F8, 0x585A4D8)); + native::fsh = reinterpret_cast(SELECT_VALUE(0x1C2B540, 0x59B5F20, 0x5866AF8)); + native::com_fileAccessed = reinterpret_cast(SELECT_VALUE(0x1C2B328, 0x59A9A04, 0x585A5E0)); + + native::threadId = reinterpret_cast(SELECT_VALUE(0x18576C8, 0x1D6E448, 0x1C14BDC)); + + native::initialized_0 = reinterpret_cast(SELECT_VALUE(0x1CE1CA0, 0x5AA3058, 0x62F4F9C)); + native::sys_timeBase = reinterpret_cast(SELECT_VALUE(0x1CE1C98, 0x5AA3050, 0x5950CE4)); + native::sys_counterBase = reinterpret_cast(SELECT_VALUE(0x1CE1C90, 0x5AA3048, 0x5950CE8)); } } diff --git a/src/game/game.hpp b/src/game/game.hpp index 1917f9e..c94ca58 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -27,10 +27,6 @@ namespace game unsigned __int16 flags, const char* description); extern Dvar_RegisterBool_t Dvar_RegisterBool; - typedef const dvar_t* (*Dvar_RegisterInt_t)(const char* dvarName, int value, - int min, int max, unsigned __int16 flags, const char* description); - extern Dvar_RegisterInt_t Dvar_RegisterInt; - typedef const dvar_t* (*Dvar_RegisterString_t)(const char* dvarName, const char* value, unsigned __int16 flags, const char* description); extern Dvar_RegisterString_t Dvar_RegisterString; @@ -83,8 +79,8 @@ namespace game typedef void (*Sys_Error_t)(const char* error, ...); extern Sys_Error_t Sys_Error; - typedef bool (*Sys_IsServerThread_t)(); - extern Sys_IsServerThread_t Sys_IsServerThread; + typedef int (*Sys_Milliseconds_t)(); + extern Sys_Milliseconds_t Sys_Milliseconds; typedef void (*VM_Notify_t)(unsigned int notifyListOwnerId, unsigned int stringValue, VariableValue* top); extern VM_Notify_t VM_Notify; @@ -150,6 +146,9 @@ namespace game typedef void (*Com_Quit_f_t)(); extern Com_Quit_f_t Com_Quit_f; + typedef void (*FS_Printf_t)(int h, const char* fmt, ...); + extern FS_Printf_t FS_Printf; + 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; @@ -188,6 +187,21 @@ namespace game extern float* com_codeTimeScale; + extern RTL_CRITICAL_SECTION* s_criticalSection; + + extern int* logfile; + + extern searchpath_s** fs_searchpaths; + extern char* fs_gamedir; + extern fileHandleData_t* fsh; + extern int* com_fileAccessed; + + extern unsigned int(*threadId)[THREAD_CONTEXT_COUNT]; + + extern int* initialized_0; + extern int* sys_timeBase; + extern unsigned __int64* sys_counterBase; + // PM Global Definitions & Functions constexpr auto JUMP_LAND_SLOWDOWN_TIME = 1800; @@ -230,6 +244,7 @@ namespace game dvar_t* Dvar_FindVar(const char* dvarName); const dvar_t* Dvar_RegisterFloat(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description); + const dvar_t* Dvar_RegisterInt(const char* dvarName, int value, int min, int max, unsigned __int16 flags, const char* description); const float* Scr_AllocVector(const float* v); void Scr_ClearOutParams(); @@ -259,6 +274,22 @@ namespace game void TeleportPlayer(gentity_s* player, float* origin, float* angles); void CG_GameMessage(LocalClientNum_t localClientNum, const char* msg, int flags = 0); + + void Sys_EnterCriticalSection(CriticalSection critSect); + void Sys_LeaveCriticalSection(CriticalSection critSect); + bool Sys_TryEnterCriticalSection(CriticalSection critSect); + bool Sys_IsMainThread(); + bool Sys_IsDatabaseThread(); + bool Sys_IsStreamThread(); + bool Sys_IsRenderThread(); + bool Sys_IsServerThread(); + + void FS_FCloseFile(int h); + bool FS_Initialized(); + void* FS_HandleForFile(FsThread thread); + int FS_FOpenFileReadForThread(const char* filename, int* file, FsThread thread); + int FS_CreatePath(char* OSPath); + void FS_CheckFileSystemStarted(); } bool is_mp(); diff --git a/src/game/structs.hpp b/src/game/structs.hpp index 3035749..c49fc88 100644 --- a/src/game/structs.hpp +++ b/src/game/structs.hpp @@ -1197,6 +1197,112 @@ namespace game static_assert(sizeof(level_locals_t) == 0x3080); + enum CriticalSection + { + CRITSECT_CONSOLE = 0x0, + CRITSECT_CBUF = 0x1F, + CRITSECT_COUNT = 0x27, + }; + + enum ScopedCriticalSectionType + { + SCOPED_CRITSECT_NORMAL = 0x0, + SCOPED_CRITSECT_DISABLED = 0x1, + SCOPED_CRITSECT_RELEASE = 0x2, + SCOPED_CRITSECT_TRY = 0x3, + }; + + struct fileInIwd_s + { + unsigned int pos; + char* name; + fileInIwd_s* next; + }; + + struct iwd_t + { + char iwdFilename[256]; + char iwdBasename[256]; + char iwdGamename[256]; + char* handle; + int checksum; + int pure_checksum; + volatile int hasOpenFile; + int numfiles; + char referenced; + unsigned int hashSize; + fileInIwd_s** hashTable; + fileInIwd_s* buildBuffer; + }; + + struct directory_t + { + char path[256]; + char gamedir[256]; + }; + + struct searchpath_s + { + searchpath_s* next; + iwd_t* iwd; + directory_t* dir; + int bLocalized; + int ignore; + int ignorePureCheck; + int language; + }; + + enum + { + THREAD_CONTEXT_MAIN, + THREAD_CONTEXT_BACKEND, + THREAD_CONTEXT_WORKER0, + THREAD_CONTEXT_WORKER1, + THREAD_CONTEXT_SERVER, + THREAD_CONTEXT_CINEMATIC, + THREAD_CONTEXT_DATABASE, + THREAD_CONTEXT_STREAM, + THREAD_CONTEXT_STATS_WRITE, + THREAD_CONTEXT_MJPEG, + THREAD_CONTEXT_COUNT, + }; + + enum FsThread + { + FS_THREAD_MAIN = 0x0, + FS_THREAD_STREAM = 0x1, + FS_THREAD_DATABASE = 0x2, + FS_THREAD_BACKEND = 0x3, + FS_THREAD_SERVER = 0x4, + FS_THREAD_COUNT = 0x5, + FS_THREAD_INVALID = 0x6, + }; + + union qfile_gus + { + _iobuf* o; + char* z; + }; + + struct qfile_us + { + qfile_gus file; + int iwdIsClone; + }; + + struct fileHandleData_t + { + qfile_us handleFiles; + int handleSync; + int fileSize; + int zipFilePos; + iwd_t* zipFile; + int streamed; + char name[256]; + }; + + static_assert(sizeof(fileHandleData_t) == 0x11C); + namespace mp { struct client_t diff --git a/src/loader/module_loader.hpp b/src/loader/module_loader.hpp index f59cadd..d1db377 100644 --- a/src/loader/module_loader.hpp +++ b/src/loader/module_loader.hpp @@ -57,5 +57,5 @@ private: #define REGISTER_MODULE(name) \ namespace \ { \ - static module_loader::installer $_##name; \ + static module_loader::installer __module; \ } diff --git a/src/module/chat.cpp b/src/module/chat.cpp index 526a2f5..89edd63 100644 --- a/src/module/chat.cpp +++ b/src/module/chat.cpp @@ -5,7 +5,7 @@ #include -#include "log_file.hpp" +#include "game_log.hpp" static void notify_on_say(game::native::gentity_s* ent, int mode, const char* message) { @@ -22,11 +22,11 @@ static void notify_on_say(game::native::gentity_s* ent, int mode, const char* me if (mode == 0) { - log_file::g_log_printf("say;%s;%d;%s;%s\n", guid, ent_num, name, message + 1); + game_log::g_log_printf("say;%s;%d;%s;%s\n", guid, ent_num, name, message + 1); } else { - log_file::g_log_printf("sayteam;%s;%d;%s;%s\n", guid, ent_num, name, message + 1); + game_log::g_log_printf("sayteam;%s;%d;%s;%s\n", guid, ent_num, name, message + 1); } } diff --git a/src/module/console.cpp b/src/module/console.cpp index eccb500..074897a 100644 --- a/src/module/console.cpp +++ b/src/module/console.cpp @@ -2,7 +2,9 @@ #include #include "game/game.hpp" + #include "scheduler.hpp" +#include "log_file.hpp" class console final : public module { @@ -11,9 +13,9 @@ public: { ShowWindow(GetConsoleWindow(), SW_HIDE); - _pipe(this->handles_, 1024, _O_TEXT); - _dup2(this->handles_[1], 1); - _dup2(this->handles_[1], 2); + (void)_pipe(this->handles_, 1024, _O_TEXT); + (void)_dup2(this->handles_[1], 1); + (void)_dup2(this->handles_[1], 2); //setvbuf(stdout, nullptr, _IONBF, 0); //setvbuf(stderr, nullptr, _IONBF, 0); @@ -89,6 +91,7 @@ private: static void log_message(const std::string& message) { OutputDebugStringA(message.data()); + log_file::info("%s", message.data()); game::native::Conbuf_AppendText(message.data()); } diff --git a/src/module/file_system.cpp b/src/module/file_system.cpp new file mode 100644 index 0000000..22bb5c5 --- /dev/null +++ b/src/module/file_system.cpp @@ -0,0 +1,273 @@ +#include +#include + +#include + +#include "game/game.hpp" + +#include "file_system.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(int f) +{ + assert(!game::native::fsh[f].zipFile); + assert(game::native::fsh[f].handleFiles.file.o); + + return game::native::fsh[f].handleFiles.file.o; +} + +static unsigned int file_write(const void* ptr, unsigned int len, FILE* stream) +{ + return std::fwrite(ptr, 1, len, stream); +} + +static FILE* file_open_append_text(const char* filename) +{ + FILE* file; + const auto err = fopen_s(&file, filename, "at"); + if (err == 0) + { + return file; + } + + printf("Couldn't open file: %s\n", filename); + return nullptr; +} + +static void replace_separators(char* path) +{ + char* src, * dst; + + bool was_sep = false; + + for (src = path, dst = path; *src; ++src) + { + if (*src == '/' || *src == '\\') + { + if (!was_sep) + { + was_sep = true; + *dst++ = '\\'; + } + } + else + { + was_sep = false; + *dst++ = *src; + } + } + *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); + + 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 >= 256) + { + 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); +} + +static 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; +} + +static void* handle_for_file_current_thread() +{ + return game::native::FS_HandleForFile(get_current_thread()); +} + +static int open_file_append(const char* filename) +{ + char ospath[MAX_PATH]{}; + + 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) + { + printf("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 = reinterpret_cast(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 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) +{ + auto r = 6969; + auto sync = 0; + + switch (mode) + { + case game::native::FS_READ: + *game::native::com_fileAccessed = TRUE; + r = game::native::FS_FOpenFileReadForThread(qpath, f, game::native::FS_THREAD_MAIN); + break; + case game::native::FS_APPEND_SYNC: + sync = 1; + case game::native::FS_APPEND: + *f = open_file_append(qpath); + r = 0; + if (!*f ) + { + r = -1; + } + break; + default: + game::native::Com_Error(game::native::ERR_FATAL, "\x15" "FSH_FOpenFile: bad mode"); + break; + } + + if (!f) + { + return r; + } + + if (*f) + { + game::native::fsh[*f].fileSize = r; + game::native::fsh[*f].streamed = 0; + } + + game::native::fsh[*f].handleSync = sync; + return r; +} + +int file_system::write(const char* buffer, int len, int h) +{ + game::native::FS_CheckFileSystemStarted(); + if (!h) + { + return 0; + } + + auto* f = file_for_handle(h); + auto* buf = const_cast(buffer); + auto remaining = len; + auto tries = 0; + while (remaining) + { + auto block = remaining; + auto written = static_cast(file_write(buf, block, f)); + if (!written) + { + if (tries) + { + return 0; + } + tries = 1; + } + + if (written == -1) + { + return 0; + } + + remaining -= written; + buf += written; + } + + if (game::native::fsh[h].handleSync) + { + std::fflush(f); + } + + return len; +} + +void file_system::post_load() +{ + fs_homepath = reinterpret_cast( + SELECT_VALUE(0x1C2B538, 0x59ADD18, 0x585E8F0)); + fs_debug = reinterpret_cast( + SELECT_VALUE(0x1C2B32C, 0x59A9A08, 0x585A5E4)); + + // Make open-iw5 work outside of the game directory + sys_default_install_path_hook.create(SELECT_VALUE(0x487E50, 0x5C4A80, 0x535F80), &sys_default_install_path_stub); +} + +REGISTER_MODULE(file_system) diff --git a/src/module/file_system.hpp b/src/module/file_system.hpp new file mode 100644 index 0000000..2cc5f0a --- /dev/null +++ b/src/module/file_system.hpp @@ -0,0 +1,10 @@ +#pragma once + +class file_system final : public module +{ +public: + void post_load() override; + + static int open_file_by_mode(const char* qpath, int* f, game::native::fsMode_t mode); + static int write(const char* buffer, int len, int h); +}; diff --git a/src/module/game_log.cpp b/src/module/game_log.cpp new file mode 100644 index 0000000..2034531 --- /dev/null +++ b/src/module/game_log.cpp @@ -0,0 +1,138 @@ +#include +#include + +#include + +#include "game/game.hpp" + +#include "game_log.hpp" +#include "scheduler.hpp" +#include "file_system.hpp" + +const game::native::dvar_t* game_log::g_log; +const game::native::dvar_t* game_log::g_logSync; + +int game_log::log_file = 0; + +void game_log::g_log_printf(const char* fmt, ...) +{ + char buf[1024] = {0}; + char out[1024] = {0}; + + va_list va; + va_start(va, fmt); + vsnprintf_s(buf, _TRUNCATE, fmt, va); + va_end(va); + + if (!log_file) + { + return; + } + + const auto time = game::native::level->time / 1000; + const auto len = sprintf_s(out, "%3i:%i%i %s", time / 60, time % 60 / 10, time % 60 % 10, buf); + + file_system::write(out, len, log_file); +} + +void game_log::gscr_log_print() +{ + char buf[1024] = {0}; + std::size_t out_chars = 0; + + for (std::size_t i = 0; i < game::native::Scr_GetNumParam(); ++i) + { + const auto* value = game::native::Scr_GetString(i); + const auto len = std::strlen(value); + + out_chars += len; + if (out_chars >= sizeof(buf)) + { + // Do not overflow the buffer + break; + } + + strncat_s(buf, value, _TRUNCATE); + } + + g_log_printf("%s", buf); +} + +void game_log::g_init_game_stub() +{ + printf("------- Game Initialization -------\n"); + printf("gamename: %s\n", reinterpret_cast(0x7FFC68)); + printf("gamedate: %s\n", __DATE__); + + const auto* log = g_log->current.string; + + if (*log == '\0') + { + printf("Not logging to disk.\n"); + } + else + { + file_system::open_file_by_mode(log, &log_file, game::native::FS_APPEND_SYNC); + + if (!log_file) + { + printf("WARNING: Couldn't open logfile: %s\n", log); + } + else + { + printf("Logging to disk: '%s'.\n", log); + g_log_printf("------------------------------------------------------------\n"); + g_log_printf("InitGame\n"); + } + } + + utils::hook::invoke(0x5C2800); +} + +void game_log::g_shutdown_game_stub(int free_scripts) +{ + printf("==== ShutdownGame (%d) ====\n", free_scripts); + + if (log_file) + { + g_log_printf("ShutdownGame:\n"); + g_log_printf("------------------------------------------------------------\n"); + + game::native::FS_FCloseFile(log_file); + log_file = 0; + } + + utils::hook::invoke(0x50C100, free_scripts); +} + +void game_log::exit_level_stub() +{ + g_log_printf("ExitLevel: executed\n"); +} + +void game_log::post_load() +{ + if (!game::is_mp()) + { + return; + } + + utils::hook::set(0x8AC858, gscr_log_print); + + utils::hook(0x50D135, g_init_game_stub, HOOK_CALL).install()->quick(); + + utils::hook(0x573C82, g_shutdown_game_stub, HOOK_CALL).install()->quick(); + utils::hook(0x573D3A, g_shutdown_game_stub, HOOK_CALL).install()->quick(); + + utils::hook(0x50D5F4, exit_level_stub, HOOK_JUMP).install()->quick(); + + scheduler::once([] + { + g_log = game::native::Dvar_RegisterString("g_log", "games_mp.log", + game::native::DVAR_ARCHIVE, "Log file name"); + g_logSync = game::native::Dvar_RegisterBool("g_logSync", false, + game::native::DVAR_NONE, "Enable synchronous logging"); + }, scheduler::pipeline::main); +} + +REGISTER_MODULE(game_log) diff --git a/src/module/game_log.hpp b/src/module/game_log.hpp new file mode 100644 index 0000000..7cff0e0 --- /dev/null +++ b/src/module/game_log.hpp @@ -0,0 +1,25 @@ +#pragma once + +class game_log final : public module +{ +public: + static_assert(offsetof(game::native::level_locals_t, time) == 0x4A8); + + void post_load() override; + + static void g_log_printf(const char* fmt, ...); + +private: + static const game::native::dvar_t* g_log; + static const game::native::dvar_t* g_logSync; + + static int log_file; + + static void gscr_log_print(); + + static void g_init_game_stub(); + + static void g_shutdown_game_stub(int free_scripts); + + static void exit_level_stub(); +}; diff --git a/src/module/log_file.cpp b/src/module/log_file.cpp index 272bf4b..78a96b7 100644 --- a/src/module/log_file.cpp +++ b/src/module/log_file.cpp @@ -1,140 +1,94 @@ #include #include -#include - #include "game/game.hpp" +#include "game/engine/scoped_critical_section.hpp" + #include "log_file.hpp" -#include "scheduler.hpp" +#include "file_system.hpp" -const game::native::dvar_t* log_file::g_log; -const game::native::dvar_t* log_file::g_logSync; +const char* log_file::log_file_name; -FILE* log_file::game_log_fsh = nullptr; +int log_file::opening_qconsole = 0; +int log_file::com_console_log_open_failed = 0; -void log_file::g_log_printf(const char* fmt, ...) +const game::native::dvar_t* log_file::com_logfile; + +void log_file::com_open_log_file() { - char buf[1024] = {0}; - char out[1024] = {0}; + time_t aclock; + char time_buffer[32]{}; - va_list va; - va_start(va, fmt); - vsnprintf_s(buf, _TRUNCATE, fmt, va); - va_end(va); + if (game::native::Sys_IsMainThread() && !opening_qconsole) + { + opening_qconsole = 1; + tm new_time{}; - if (game_log_fsh == nullptr) + _time64(&aclock); + _localtime64_s(&new_time, &aclock); + + file_system::open_file_by_mode(log_file_name, game::native::logfile, game::native::FS_APPEND_SYNC); + + asctime_s(time_buffer, sizeof(time_buffer), &new_time); + info("logfile opened on %s\n", time_buffer); + opening_qconsole = 0; + com_console_log_open_failed = *game::native::logfile == 0; + } +} + +void log_file::com_log_print_message(const char* msg) +{ + char print_buffer[0x40]{}; + + game::engine::scoped_critical_section crit_sect_lock(game::native::CRITSECT_CONSOLE, game::native::SCOPED_CRITSECT_NORMAL); + + if (!game::native::FS_Initialized()) { return; } - _snprintf_s(out, _TRUNCATE, "%3i:%i%i %s", - game::native::level->time / 1000 / 60, - game::native::level->time / 1000 % 60 / 10, - game::native::level->time / 1000 % 60 % 10, - buf); - - fprintf(game_log_fsh, "%s", out); - fflush(game_log_fsh); -} - -void log_file::gscr_log_print() -{ - char buf[1024] = {0}; - std::size_t out_chars = 0; - - for (std::size_t i = 0; i < game::native::Scr_GetNumParam(); ++i) + if (!*game::native::logfile) { - const auto* value = game::native::Scr_GetString(i); - const auto len = std::strlen(value); - - out_chars += len; - if (out_chars >= sizeof(buf)) - { - // Do not overflow the buffer - break; - } - - strncat_s(buf, value, _TRUNCATE); + com_open_log_file(); } - g_log_printf("%s", buf); -} - -void log_file::g_init_game_stub() -{ - printf("------- Game Initialization -------\n"); - printf("gamename: %s\n", reinterpret_cast(0x7FFC68)); - printf("gamedate: %s\n", __DATE__); - - const auto* log = g_log->current.string; - - if (*log == '\0') + if (*game::native::logfile) { - printf("Not logging to disk.\n"); - } - else - { - game_log_fsh = _fsopen(log, "a", _SH_DENYWR); - - if (game_log_fsh == nullptr) + static auto log_next_time_stamp = true; + if (log_next_time_stamp) { - printf("WARNING: Couldn't open logfile: %s\n", log); + const auto len = sprintf_s(print_buffer, "[%10i] ", game::native::Sys_Milliseconds()); + file_system::write(print_buffer, len, *game::native::logfile); } - else - { - printf("Logging to disk: '%s'.\n", log); - g_log_printf("------------------------------------------------------------\n"); - g_log_printf("InitGame\n"); - } - } - utils::hook::invoke(0x5C2800); -} - -void log_file::g_shutdown_game_stub(int free_scripts) -{ - printf("==== ShutdownGame (%d) ====\n", free_scripts); - - if (game_log_fsh != nullptr) - { - g_log_printf("ShutdownGame:\n"); - g_log_printf("------------------------------------------------------------\n"); - - fclose(game_log_fsh); - game_log_fsh = nullptr; + log_next_time_stamp = std::strchr(msg, 10) != nullptr; + file_system::write(msg, static_cast(std::strlen(msg)), *game::native::logfile); } - - utils::hook::invoke(0x50C100, free_scripts); } -void log_file::exit_level_stub() +void log_file::info(const char* fmt, ...) { - g_log_printf("ExitLevel: executed\n"); + char msg[0x1000]{}; + va_list argptr; + + va_start(argptr, fmt); + vsnprintf_s(msg, _TRUNCATE, fmt, argptr); + va_end(argptr); + + if (com_logfile && com_logfile->current.integer) + { + com_log_print_message(msg); + } } void log_file::post_load() { - if (!game::is_mp()) - { - return; - } + // The game closes the logfile handle in Com_Quit_f - utils::hook::set(0x8AC858, gscr_log_print); + com_logfile = game::native::Dvar_RegisterInt("logfile", 1, + 0, 2, 0, "Write to log file - 0 = disabled, 1 = async file write, 2 = Sync every write"); - utils::hook(0x50D135, g_init_game_stub, HOOK_CALL).install()->quick(); - - utils::hook(0x573C82, g_shutdown_game_stub, HOOK_CALL).install()->quick(); - utils::hook(0x573D3A, g_shutdown_game_stub, HOOK_CALL).install()->quick(); - - utils::hook(0x50D5F4, exit_level_stub, HOOK_JUMP).install()->quick(); - - scheduler::once([] - { - g_log = game::native::Dvar_RegisterString("g_log", "games_mp.log", - game::native::DVAR_ARCHIVE, "Log file name"); - g_logSync = game::native::Dvar_RegisterBool("g_logSync", false, - game::native::DVAR_NONE, "Enable synchronous logging"); - }, scheduler::pipeline::main); + log_file_name = SELECT_VALUE("console_sp.log", "console_mp.log", "console_mp_dedicated.log"); } REGISTER_MODULE(log_file) diff --git a/src/module/log_file.hpp b/src/module/log_file.hpp index 41401dc..5a30072 100644 --- a/src/module/log_file.hpp +++ b/src/module/log_file.hpp @@ -3,23 +3,19 @@ class log_file final : public module { public: - static_assert(offsetof(game::native::level_locals_t, time) == 0x4A8); - void post_load() override; - static void g_log_printf(const char* fmt, ...); + static void info(const char* fmt, ...); private: - static const game::native::dvar_t* g_log; - static const game::native::dvar_t* g_logSync; + static const char* log_file_name; - static FILE* game_log_fsh; + static int opening_qconsole; + static int com_console_log_open_failed; - static void gscr_log_print(); + static const game::native::dvar_t* com_logfile; - static void g_init_game_stub(); + static void com_open_log_file(); - static void g_shutdown_game_stub(int free_scripts); - - static void exit_level_stub(); + static void com_log_print_message(const char* msg); };