#include #include #include "game/game.hpp" #include #include "game_log.hpp" #include "scheduler.hpp" #include "file_system.hpp" #include "scripting.hpp" #include "console.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]{}; char out[1024]{}; 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::mp::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]{}; 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() { console::info("------- Game Initialization -------\n"); console::info("gamename: %s\n", reinterpret_cast(0x7FFC68)); console::info("gamedate: %s\n", __DATE__); const auto* log = g_log->current.string; if (!*log) { console::info("Not logging to disk.\n"); } else { file_system::open_file_by_mode(log, &log_file_, game::native::FS_APPEND_SYNC); if (!log_file_) { console::info("WARNING: Couldn't open logfile: %s\n", log); } else { console::info("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) { console::info("==== 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(); scripting::on_shutdown(g_shutdown_game_stub); 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)