diff --git a/src/game/game.cpp b/src/game/game.cpp index 4d277bc..f666e36 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -6,7 +6,6 @@ namespace game namespace native { Cmd_AddCommand_t Cmd_AddCommand; - Cmd_RemoveCommand_t Cmd_RemoveCommand; Com_Error_t Com_Error; @@ -16,8 +15,10 @@ namespace game Dvar_RegisterBool_t Dvar_RegisterBool; Dvar_RegisterInt_t Dvar_RegisterInt; Dvar_RegisterString_t Dvar_RegisterString; + Dvar_SetIntByName_t Dvar_SetIntByName; Dvar_SetFromStringByName_t Dvar_SetFromStringByName; + Dvar_SetString_t Dvar_SetString; G_RunFrame_t G_RunFrame; G_GetWeaponForName_t G_GetWeaponForName; @@ -31,48 +32,37 @@ namespace game SL_GetStringOfSize_t SL_GetStringOfSize; Scr_AddEntityNum_t Scr_AddEntityNum; - Scr_Notify_t Scr_Notify; Scr_NotifyLevel_t Scr_NotifyLevel; Scr_GetNumParam_t Scr_GetNumParam; Scr_GetString_t Scr_GetString; Sys_ShowConsole_t Sys_ShowConsole; - Sys_Error_t Sys_Error; + Sys_IsServerThread_t Sys_IsServerThread; VM_Notify_t VM_Notify; BG_NetDataChecksum_t BG_NetDataChecksum; LiveStorage_GetPersistentDataDefVersion_t LiveStorage_GetPersistentDataDefVersion; - LiveStorage_GetPersistentDataDefFormatChecksum_t LiveStorage_GetPersistentDataDefFormatChecksum; SV_DirectConnect_t SV_DirectConnect; - SV_ClientEnterWorld_t SV_ClientEnterWorld; - SV_Cmd_TokenizeString_t SV_Cmd_TokenizeString; - SV_Cmd_EndTokenizedString_t SV_Cmd_EndTokenizedString; - SV_GameSendServerCommand_t SV_GameSendServerCommand; - SV_SendServerCommand_t SV_SendServerCommand; - Sys_IsServerThread_t Sys_IsServerThread; - XUIDToString_t XUIDToString; SEH_LocalizeTextMessage_t SEH_LocalizeTextMessage; - PM_WeaponUseAmmo_t PM_WeaponUseAmmo; - CM_TransformedCapsuleTrace_t CM_TransformedCapsuleTrace; + PM_WeaponUseAmmo_t PM_WeaponUseAmmo; PM_playerTrace_t PM_playerTrace; - PM_trace_t PM_trace; Jump_ClearState_t Jump_ClearState; @@ -111,10 +101,14 @@ namespace game gentity_s* g_entities; + level_locals_t* level; + DeferredQueue* deferredQueue; namespace mp { + SV_GetGuid_t SV_GetGuid; + client_t* svs_clients; } @@ -705,7 +699,6 @@ namespace game mode = _mode; native::Cmd_AddCommand = native::Cmd_AddCommand_t(SELECT_VALUE(0x558820, 0x545DF0, 0)); - native::Cmd_RemoveCommand = native::Cmd_RemoveCommand_t(SELECT_VALUE(0x443A30, 0x545E20, 0x4CC060)); native::Com_Error = native::Com_Error_t(SELECT_VALUE(0x425540, 0x555450, 0x4D93F0)); @@ -717,9 +710,9 @@ namespace game native::Dvar_RegisterString = native::Dvar_RegisterString_t(SELECT_VALUE(0x5197F0, 0x5BEC90, 0x0)); native::Dvar_SetIntByName = native::Dvar_SetIntByName_t(SELECT_VALUE(0x5396B0, 0x5BF560, 0x0)); - native::Dvar_SetFromStringByName = native::Dvar_SetFromStringByName_t( SELECT_VALUE(0x4DD090, 0x5BF740, 0x518DF0)); + native::Dvar_SetString = native::Dvar_SetString_t(SELECT_VALUE(0x540570, 0x5BF3E0, 0x0)); native::G_RunFrame = native::G_RunFrame_t(SELECT_VALUE(0x52EAA0, 0x50CB70, 0x48AD60)); native::G_GetWeaponForName = native::G_GetWeaponForName_t(SELECT_VALUE(0x495E40, 0x531070, 0x0)); @@ -754,16 +747,12 @@ namespace game SELECT_VALUE(0x0, 0x548D80, 0x4D03D0)); native::SV_DirectConnect = native::SV_DirectConnect_t(SELECT_VALUE(0x0, 0x572750, 0x4F74C0)); - native::SV_ClientEnterWorld = native::SV_ClientEnterWorld_t(SELECT_VALUE(0x0, 0x571100, 0x0)); - native::SV_Cmd_TokenizeString = native::SV_Cmd_TokenizeString_t(SELECT_VALUE(0x0, 0x545D40, 0x0)); - native::SV_Cmd_EndTokenizedString = native::SV_Cmd_EndTokenizedString_t(SELECT_VALUE(0x0, 0x545D70, 0x0)); - native::SV_GameSendServerCommand = native::SV_GameSendServerCommand_t(SELECT_VALUE(0x402130, 0x573220, 0x0)); - 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)); @@ -774,13 +763,11 @@ namespace game native::SEH_LocalizeTextMessage = native::SEH_LocalizeTextMessage_t( SELECT_VALUE(0x41EA20, 0x57E240, 0x0)); - native::PM_WeaponUseAmmo = native::PM_WeaponUseAmmo_t(SELECT_VALUE(0x463F80, 0x42E930, 0x0)); - native::CM_TransformedCapsuleTrace = native::CM_TransformedCapsuleTrace_t( SELECT_VALUE(0x4F9B80, 0x541340, 0x0)); + native::PM_WeaponUseAmmo = native::PM_WeaponUseAmmo_t(SELECT_VALUE(0x463F80, 0x42E930, 0x0)); native::PM_playerTrace = native::PM_playerTrace_t(SELECT_VALUE(0x4CE600, 0x421F00, 0x0)); - native::PM_trace = native::PM_trace_t(SELECT_VALUE(0x544BF0, 0x41CEB0, 0x0)); native::Jump_ClearState = native::Jump_ClearState_t(SELECT_VALUE(0x514CE0, 0x4160F0, 0x0)); @@ -829,6 +816,8 @@ namespace game native::sp::g_clients = reinterpret_cast(0x1381D48); + native::level = reinterpret_cast(SELECT_VALUE(0x0, 0x1C6D4D8, 0x1B21A20)); + native::deferredQueue = reinterpret_cast(SELECT_VALUE(0x0, 0x1D55438, 0x0)); } } diff --git a/src/game/game.hpp b/src/game/game.hpp index 67e1c0a..5e3f0c8 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -41,6 +41,9 @@ namespace game typedef void (*Dvar_SetFromStringByName_t)(const char* dvarName, const char* string); extern Dvar_SetFromStringByName_t Dvar_SetFromStringByName; + typedef void (*Dvar_SetString_t)(const dvar_t* dvar, const char* value); + extern Dvar_SetString_t Dvar_SetString; + typedef int (*G_RunFrame_t)(int, int); extern G_RunFrame_t G_RunFrame; @@ -80,6 +83,9 @@ 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 void (*VM_Notify_t)(unsigned int notifyListOwnerId, unsigned int stringValue, VariableValue* top); extern VM_Notify_t VM_Notify; @@ -110,23 +116,20 @@ namespace game typedef void (*SV_SendServerCommand_t)(dedi::client_t* cl, svscmd_type type, const char* fmt, ...); extern SV_SendServerCommand_t SV_SendServerCommand; - typedef bool (*Sys_IsServerThread_t)(); - extern Sys_IsServerThread_t Sys_IsServerThread; - typedef void (*XUIDToString_t)(const unsigned __int64* xuid, char* str); extern XUIDToString_t XUIDToString; typedef char* (*SEH_LocalizeTextMessage_t)(const char* pszInputBuffer, const char* pszMessageType, msgLocErrType_t errType); extern SEH_LocalizeTextMessage_t SEH_LocalizeTextMessage; - typedef void (*PM_WeaponUseAmmo_t)(playerState_s* ps, const Weapon weapon, bool isAlternate, int amount, PlayerHandIndex hand); - extern PM_WeaponUseAmmo_t PM_WeaponUseAmmo; - 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); extern CM_TransformedCapsuleTrace_t CM_TransformedCapsuleTrace; + typedef void (*PM_WeaponUseAmmo_t)(playerState_s* ps, const Weapon weapon, bool isAlternate, int amount, PlayerHandIndex hand); + extern PM_WeaponUseAmmo_t PM_WeaponUseAmmo; + typedef void (*PM_playerTrace_t)(pmove_t* pm, trace_t* results, const float* start, const float* end, const Bounds* bounds, int passEntityNum, int contentMask); extern PM_playerTrace_t PM_playerTrace; @@ -179,6 +182,8 @@ namespace game constexpr auto ENTITYNUM_NONE = MAX_GENTITIES - 1u; extern gentity_s* g_entities; + extern level_locals_t* level; + extern DeferredQueue* deferredQueue; // PM Global Definitions & Functions @@ -190,6 +195,9 @@ namespace game namespace mp { + typedef char* (*SV_GetGuid_t)(int clientNum); + extern SV_GetGuid_t SV_GetGuid; + extern client_t* svs_clients; } diff --git a/src/game/structs.hpp b/src/game/structs.hpp index 45ee4d9..8536b94 100644 --- a/src/game/structs.hpp +++ b/src/game/structs.hpp @@ -400,7 +400,14 @@ namespace game LOCAL_CLIENT_3 = 3, LOCAL_CLIENT_LAST = 3, LOCAL_CLIENT_COUNT = 4, - LOCAL_CLIENT_INVALID = -1, + }; + + enum fsMode_t + { + FS_READ, + FS_WRITE, + FS_APPEND, + FS_APPEND_SYNC, }; enum msgLocErrType_t @@ -597,6 +604,7 @@ namespace game enum dvar_flags : std::uint16_t { + DVAR_NONE = 0, DVAR_ARCHIVE = 1 << 0, DVAR_LATCH = 1 << 1, DVAR_CHEAT = 1 << 2, @@ -1145,6 +1153,49 @@ namespace game static_assert(sizeof(clientHeader_t) == 0x66C); + enum objectiveState_t + { + OBJST_EMPTY = 0x0, + OBJST_ACTIVE = 0x1, + OBJST_INVISIBLE = 0x2, + OBJST_DONE = 0x3, + OBJST_CURRENT = 0x4, + OBJST_FAILED = 0x5, + OBJST_NUMSTATES = 0x6, + }; + + struct objective_t + { + objectiveState_t state; + float origin[3]; + int entNum; + int teamNum; + int clientNum; + int invertVisibilityByClientNum; + int icon; + }; + + static_assert(sizeof(objective_t) == 0x24); + + struct level_locals_t + { + gclient_s* clients; + gentity_s* gentities; + int num_entities; + gentity_s* firstFreeEnt; + gentity_s* lastFreeEnt; + void* turrets; + int initializing; + int clientIsSpawning; + objective_t objectives[32]; + int maxclients; + int framenum; + int time; + unsigned char __pad0[0x2BD4]; + }; + + static_assert(sizeof(level_locals_t) == 0x3080); + namespace mp { struct client_t @@ -1154,14 +1205,15 @@ namespace game char userinfo[1024]; // 0x670 unsigned char __pad0[0x209B8]; gentity_s* gentity; // 0x21428 - unsigned char __pad1[0x20886]; + char name[16]; // 0x2142C + unsigned char __pad1[0x20876]; unsigned __int16 scriptId; // 0x41CB2 int bIsTestClient; // 0x41CB4 int serverId; // 0x41CB8 unsigned char __pad2[0x369DC]; }; - static_assert(sizeof(mp::client_t) == 0x78698); + static_assert(sizeof(client_t) == 0x78698); } namespace dedi @@ -1260,11 +1312,11 @@ namespace game struct entityShared_t { - unsigned __int8 isLinked; - unsigned __int8 modelType; - unsigned __int8 svFlags; - unsigned __int8 eventType; - unsigned __int8 isInUse; + unsigned char isLinked; + unsigned char modelType; + unsigned char svFlags; + unsigned char eventType; + unsigned char isInUse; Bounds box; int contents; Bounds absBox; @@ -1278,9 +1330,9 @@ namespace game struct gentity_s { - sp::entityState_s s; - sp::entityShared_t r; - sp::gclient_s* client; // 0x10C + entityState_s s; + entityShared_t r; + gclient_s* client; // 0x10C unsigned char __pad0[0x2C]; int flags; int clipmask; diff --git a/src/module/branding.cpp b/src/module/branding.cpp new file mode 100644 index 0000000..2a7a823 --- /dev/null +++ b/src/module/branding.cpp @@ -0,0 +1,47 @@ +#include +#include + +#include + +#include "game/game.hpp" + +static char* com_get_build_version_stub() +{ + static char buf[128]; + + const auto version_number = SELECT_VALUE(0x1CD, 0x5EC0E, 0x5EC0E); + + _snprintf_s(buf, _TRUNCATE, "%d %s", version_number, __DATE__); + + return buf; +} + +static int com_get_build_version_dedi_stub(char* buf, const char* fmt, int version_number, const char* /*date*/) +{ + return _snprintf_s(buf, 0x80, _TRUNCATE, fmt, version_number, __DATE__); +} + +class branding final : public module +{ +public: + void post_load() override + { + if (game::is_dedi()) this->patch_dedi(); + else this->add_branding(); + + // gamedate dvar + utils::hook::set(SELECT_VALUE(0x5C223B, 0x50B0F4, 0x48844F), __DATE__); + } + + static void patch_dedi() + { + utils::hook(0x4DAB99, com_get_build_version_dedi_stub, HOOK_CALL).install()->quick(); + } + + static void add_branding() + { + utils::hook(SELECT_VALUE(0x50BBD0, 0x53B4B0, 0x0), com_get_build_version_stub, HOOK_JUMP).install()->quick(); + } +}; + +REGISTER_MODULE(branding) diff --git a/src/module/chat.cpp b/src/module/chat.cpp index 2572cdb..a93e630 100644 --- a/src/module/chat.cpp +++ b/src/module/chat.cpp @@ -5,13 +5,29 @@ #include +#include "log_file.hpp" + static void notify_on_say(game::native::gentity_s* ent, int mode, const char* message) { + const auto ent_num = ent->s.number; + game::native::Scr_AddString(message + 1); // First character has nothing to do with actual message game::native::Scr_AddInt(mode); - game::native::Scr_AddEntityNum(ent->s.number, 0); + game::native::Scr_AddEntityNum(ent_num, 0); game::native::Scr_NotifyLevel(game::native::SL_GetString("say", 0), 3); + + const auto* guid = game::native::mp::SV_GetGuid(ent_num); + const auto* name = game::native::mp::svs_clients[ent_num].name; + + if (mode == 0) + { + log_file::g_log_printf("say;%d;%d;%s;%s\n", guid, ent_num, name, message + 1); + } + else + { + log_file::g_log_printf("sayteam;%d;%d;%s;%s\n", guid, ent_num, name, message + 1); + } } static __declspec(naked) void g_say_stub() @@ -36,6 +52,8 @@ static __declspec(naked) void g_say_stub() class chat final : public module { public: + static_assert(offsetof(game::native::mp::client_t, name) == 0x2142C); + void post_load() override { if (game::is_mp()) diff --git a/src/module/log_file.cpp b/src/module/log_file.cpp new file mode 100644 index 0000000..d69d617 --- /dev/null +++ b/src/module/log_file.cpp @@ -0,0 +1,140 @@ +#include +#include + +#include + +#include "game/game.hpp" +#include "log_file.hpp" +#include "scheduler.hpp" + +const game::native::dvar_t* log_file::g_log; +const game::native::dvar_t* log_file::g_logSync; + +FILE* log_file::log_fsh = nullptr; + +void log_file::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_fsh == nullptr) + { + 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(log_fsh, "%s", out); + fflush(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) + { + 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 log_file::g_init_game_stub() +{ + printf("------- Game Initialization -------\n"); + printf("gamename: %s", 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 + { + log_fsh = _fsopen(log, "a", _SH_DENYWR); + + if (log_fsh == nullptr) + { + 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 log_file::g_shutdown_game_stub(int free_scripts) +{ + printf("==== ShutdownGame (%d) ====\n", free_scripts); + + if (log_fsh != nullptr) + { + g_log_printf("ShutdownGame:\n"); + g_log_printf("------------------------------------------------------------\n"); + + fclose(log_fsh); + log_fsh = nullptr; + } + + utils::hook::invoke(0x50C100, free_scripts); +} + +void log_file::exit_level_stub() +{ + printf("ExitLevel: executed\n"); +} + +void log_file::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(log_file) diff --git a/src/module/log_file.hpp b/src/module/log_file.hpp new file mode 100644 index 0000000..70d218b --- /dev/null +++ b/src/module/log_file.hpp @@ -0,0 +1,25 @@ +#pragma once + +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, ...); + +private: + static const game::native::dvar_t* g_log; + static const game::native::dvar_t* g_logSync; + + static FILE* log_fsh; + + 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(); +};