#include "pch.h" #include "logging.h" #include "convar.h" #include "concommand.h" #include "nsprefix.h" #include "bitbuf.h" #include "tier0.h" #include "spdlog/sinks/basic_file_sink.h" #include #include AUTOHOOK_INIT() ConVar* Cvar_spewlog_enable; enum class SpewType_t { SPEW_MESSAGE = 0, SPEW_WARNING, SPEW_ASSERT, SPEW_ERROR, SPEW_LOG, SPEW_TYPE_COUNT }; const std::unordered_map PrintSpewTypes = { {SpewType_t::SPEW_MESSAGE, "SPEW_MESSAGE"}, {SpewType_t::SPEW_WARNING, "SPEW_WARNING"}, {SpewType_t::SPEW_ASSERT, "SPEW_ASSERT"}, {SpewType_t::SPEW_ERROR, "SPEW_ERROR"}, {SpewType_t::SPEW_LOG, "SPEW_LOG"} }; // clang-format off AUTOHOOK(EngineSpewFunc, engine.dll + 0x11CA80, void,, (void* pEngineServer, SpewType_t type, const char* format, va_list args)) // clang-format on { if (!Cvar_spewlog_enable->GetBool()) return; const char* typeStr = PrintSpewTypes.at(type); char formatted[2048] = {0}; bool bShouldFormat = true; // because titanfall 2 is quite possibly the worst thing to yet exist, it sometimes gives invalid specifiers which will crash // ttf2sdk had a way to prevent them from crashing but it doesnt work in debug builds // so we use this instead for (int i = 0; format[i]; i++) { if (format[i] == '%') { switch (format[i + 1]) { // this is fucking awful lol case 'd': case 'i': case 'u': case 'x': case 'X': case 'f': case 'F': case 'g': case 'G': case 'a': case 'A': case 'c': case 's': case 'p': case 'n': case '%': case '-': case '+': case ' ': case '#': case '*': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': break; default: { bShouldFormat = false; break; } } } } if (bShouldFormat) vsnprintf(formatted, sizeof(formatted), format, args); else spdlog::warn("Failed to format {} \"{}\"", typeStr, format); auto endpos = strlen(formatted); if (formatted[endpos - 1] == '\n') formatted[endpos - 1] = '\0'; // cut off repeated newline spdlog::info("[SERVER {}] {}", typeStr, formatted); } // used for printing the output of status // clang-format off AUTOHOOK(Status_ConMsg, engine.dll + 0x15ABD0, void,, (const char* text, ...)) // clang-format on { char formatted[2048]; va_list list; va_start(list, text); vsprintf_s(formatted, text, list); va_end(list); auto endpos = strlen(formatted); if (formatted[endpos - 1] == '\n') formatted[endpos - 1] = '\0'; // cut off repeated newline spdlog::info(formatted); } // clang-format off AUTOHOOK(CClientState_ProcessPrint, engine.dll + 0x1A1530, bool,, (void* thisptr, uintptr_t msg)) // clang-format on { char* text = *(char**)(msg + 0x20); auto endpos = strlen(text); if (text[endpos - 1] == '\n') text[endpos - 1] = '\0'; // cut off repeated newline spdlog::info(text); return true; } ConVar* Cvar_cl_showtextmsg; class ICenterPrint { public: virtual void ctor() = 0; virtual void Clear(void) = 0; virtual void ColorPrint(int r, int g, int b, int a, wchar_t* text) = 0; virtual void ColorPrint(int r, int g, int b, int a, char* text) = 0; virtual void Print(wchar_t* text) = 0; virtual void Print(char* text) = 0; virtual void SetTextColor(int r, int g, int b, int a) = 0; }; ICenterPrint* pInternalCenterPrint = NULL; enum class TextMsgPrintType_t { HUD_PRINTNOTIFY = 1, HUD_PRINTCONSOLE, HUD_PRINTTALK, HUD_PRINTCENTER }; // clang-format off AUTOHOOK(TextMsg, client.dll + 0x198710, void,, (BFRead* msg)) // clang-format on { TextMsgPrintType_t msg_dest = (TextMsgPrintType_t)msg->ReadByte(); char text[256]; msg->ReadString(text, sizeof(text)); if (!Cvar_cl_showtextmsg->GetBool()) return; switch (msg_dest) { case TextMsgPrintType_t::HUD_PRINTCENTER: pInternalCenterPrint->Print(text); break; default: spdlog::warn("Unimplemented TextMsg type {}! printing to console", msg_dest); [[fallthrough]]; case TextMsgPrintType_t::HUD_PRINTCONSOLE: auto endpos = strlen(text); if (text[endpos - 1] == '\n') text[endpos - 1] = '\0'; // cut off repeated newline spdlog::info(text); break; } } // clang-format off AUTOHOOK(ConCommand_echo, engine.dll + 0x123680, void,, (const CCommand& arg)) // clang-format on { if (arg.ArgC() >= 2) spdlog::info("[echo] {}", arg.ArgS()); } // This needs to be called after hooks are loaded so we can access the command line args void CreateLogFiles() { if (strstr(GetCommandLineA(), "-disablelogs")) { spdlog::default_logger()->set_level(spdlog::level::off); } else { // todo: might be good to delete logs that are too old time_t time = std::time(nullptr); tm currentTime = *std::localtime(&time); std::stringstream stream; stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nslog%Y-%m-%d %H-%M-%S.txt").c_str()); spdlog::default_logger()->sinks().push_back(std::make_shared(stream.str(), false)); spdlog::flush_on(spdlog::level::info); } } void InitialiseLogging() { AllocConsole(); // Bind stdout to receive console output. // these two lines are responsible for stuff to not show up in the console sometimes, from talking about it on discord // apparently they were meant to make logging work when using -northstar, however from testing it seems that it doesnt // work regardless of these two lines // freopen("CONOUT$", "w", stdout); // freopen("CONOUT$", "w", stderr); spdlog::default_logger()->set_pattern("[%H:%M:%S] [%^%l%$] %v"); } ON_DLL_LOAD_CLIENT_RELIESON("engine.dll", EngineSpewFuncHooks, ConVar, (CModule module)) { AUTOHOOK_DISPATCH_MODULE(engine.dll) Cvar_spewlog_enable = new ConVar("spewlog_enable", "1", FCVAR_NONE, "Enables/disables whether the engine spewfunc should be logged"); } ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ClientPrintHooks, ConVar, (CModule module)) { AUTOHOOK_DISPATCH_MODULE(client.dll) Cvar_cl_showtextmsg = new ConVar("cl_showtextmsg", "1", FCVAR_NONE, "Enable/disable text messages printing on the screen."); pInternalCenterPrint = module.Offset(0x216E940).As(); }