#include "pch.h" #include "logging.h" #include "core/convar/convar.h" #include "core/convar/concommand.h" #include "config/profile.h" #include "core/tier0.h" #include "spdlog/sinks/basic_file_sink.h" #include <iomanip> #include <sstream> AUTOHOOK_INIT() std::vector<std::shared_ptr<ColoredLogger>> loggers {}; namespace NS::log { std::shared_ptr<ColoredLogger> SCRIPT_UI; std::shared_ptr<ColoredLogger> SCRIPT_CL; std::shared_ptr<ColoredLogger> SCRIPT_SV; std::shared_ptr<ColoredLogger> NATIVE_UI; std::shared_ptr<ColoredLogger> NATIVE_CL; std::shared_ptr<ColoredLogger> NATIVE_SV; std::shared_ptr<ColoredLogger> NATIVE_EN; std::shared_ptr<ColoredLogger> fs; std::shared_ptr<ColoredLogger> rpak; std::shared_ptr<ColoredLogger> echo; std::shared_ptr<ColoredLogger> NORTHSTAR; }; // namespace NS::log // 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 { try { // 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()); auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(stream.str(), false); sink->set_pattern("[%H:%M:%S] [%n] [%l] %v"); for (auto& logger : loggers) { logger->sinks().push_back(sink); } spdlog::flush_on(spdlog::level::info); } catch (...) { spdlog::error("Failed creating log file!"); MessageBoxA( 0, "Failed creating log file! Make sure the profile directory is writable.", "Northstar Warning", MB_ICONWARNING | MB_OK); } } } void ExternalConsoleSink::sink_it_(const spdlog::details::log_msg& msg) { throw std::runtime_error("sink_it_ called on SourceConsoleSink with pure log_msg. This is an error!"); } void ExternalConsoleSink::custom_sink_it_(const custom_log_msg& msg) { spdlog::memory_buf_t formatted; spdlog::sinks::base_sink<std::mutex>::formatter_->format(msg, formatted); std::string out = ""; // if ansi colour is turned off, just use WriteConsoleA and return if (!g_bSpdLog_UseAnsiColor) { out += fmt::to_string(formatted); } // print to the console with colours else { // get message string std::string str = fmt::to_string(formatted); std::string levelColor = m_LogColours[msg.level]; std::string name {msg.logger_name.begin(), msg.logger_name.end()}; std::string name_str = "[NAME]"; int name_pos = str.find(name_str); str.replace(name_pos, name_str.length(), msg.origin->ANSIColor + "[" + name + "]" + default_color); std::string level_str = "[LVL]"; int level_pos = str.find(level_str); str.replace(level_pos, level_str.length(), levelColor + "[" + std::string(level_names[msg.level]) + "]" + default_color); out += str; } // print the string to the console - this is definitely bad i think HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); auto ignored = WriteConsoleA(handle, out.c_str(), std::strlen(out.c_str()), nullptr, nullptr); (void)ignored; } void ExternalConsoleSink::flush_() { std::cout << std::flush; } void CustomSink::custom_log(const custom_log_msg& msg) { std::lock_guard<std::mutex> lock(mutex_); custom_sink_it_(msg); } void InitialiseConsole() { if (AllocConsole() == FALSE) { std::cout << "[*] Failed to create a console window, maybe a console already exists?" << std::endl; } else { freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); } // this if statement is adapted from r5sdk if (!strstr(GetCommandLineA(), "-noansiclr")) { g_bSpdLog_UseAnsiColor = true; DWORD dwMode = NULL; HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleMode(hOutput, &dwMode); dwMode |= ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING; if (!SetConsoleMode(hOutput, dwMode)) // Some editions of Windows have 'VirtualTerminalLevel' disabled by default. { // If 'VirtualTerminalLevel' can't be set, just disable ANSI color, since it wouldnt work anyway. spdlog::warn("could not set VirtualTerminalLevel. Disabling color output"); g_bSpdLog_UseAnsiColor = false; } } } void RegisterCustomSink(std::shared_ptr<CustomSink> sink) { for (auto& logger : loggers) { logger->custom_sinks_.push_back(sink); } }; void InitialiseLogging() { // create a logger, and set it to default NS::log::NORTHSTAR = std::make_shared<ColoredLogger>("NORTHSTAR", NS::Colors::NORTHSTAR, true); NS::log::NORTHSTAR->sinks().clear(); loggers.push_back(NS::log::NORTHSTAR); spdlog::set_default_logger(NS::log::NORTHSTAR); // create our console sink auto sink = std::make_shared<ExternalConsoleSink>(); // set the pattern if (g_bSpdLog_UseAnsiColor) // dont put the log level in the pattern if we are using colours, as the colour will show the log level sink->set_pattern("[%H:%M:%S] [NAME] [LVL] %v"); else sink->set_pattern("[%H:%M:%S] [%n] [%l] %v"); // add our sink to the logger NS::log::NORTHSTAR->custom_sinks_.push_back(sink); NS::log::SCRIPT_UI = std::make_shared<ColoredLogger>("SCRIPT UI", NS::Colors::SCRIPT_UI); NS::log::SCRIPT_CL = std::make_shared<ColoredLogger>("SCRIPT CL", NS::Colors::SCRIPT_CL); NS::log::SCRIPT_SV = std::make_shared<ColoredLogger>("SCRIPT SV", NS::Colors::SCRIPT_SV); NS::log::NATIVE_UI = std::make_shared<ColoredLogger>("NATIVE UI", NS::Colors::NATIVE_UI); NS::log::NATIVE_CL = std::make_shared<ColoredLogger>("NATIVE CL", NS::Colors::NATIVE_CL); NS::log::NATIVE_SV = std::make_shared<ColoredLogger>("NATIVE SV", NS::Colors::NATIVE_SV); NS::log::NATIVE_EN = std::make_shared<ColoredLogger>("NATIVE EN", NS::Colors::NATIVE_ENGINE); NS::log::fs = std::make_shared<ColoredLogger>("FILESYSTM", NS::Colors::FILESYSTEM); NS::log::rpak = std::make_shared<ColoredLogger>("RPAK_FSYS", NS::Colors::RPAK); NS::log::echo = std::make_shared<ColoredLogger>("ECHO", NS::Colors::ECHO); loggers.push_back(NS::log::SCRIPT_UI); loggers.push_back(NS::log::SCRIPT_CL); loggers.push_back(NS::log::SCRIPT_SV); loggers.push_back(NS::log::NATIVE_UI); loggers.push_back(NS::log::NATIVE_CL); loggers.push_back(NS::log::NATIVE_SV); loggers.push_back(NS::log::NATIVE_EN); loggers.push_back(NS::log::fs); loggers.push_back(NS::log::rpak); loggers.push_back(NS::log::echo); } void NS::log::FlushLoggers() { for (auto& logger : loggers) logger->flush(); spdlog::default_logger()->flush(); }