From 00a8c4611562e274081f62a28a8dd396f142e332 Mon Sep 17 00:00:00 2001 From: LIBERGOD <37589902+libergod@users.noreply.github.com> Date: Fri, 9 Jan 2026 08:23:38 -0800 Subject: [PATCH 1/4] Update game_forge.lua --- modules/game_forge/game_forge.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/game_forge/game_forge.lua b/modules/game_forge/game_forge.lua index e3a4796da2..1259909f2d 100644 --- a/modules/game_forge/game_forge.lua +++ b/modules/game_forge/game_forge.lua @@ -130,6 +130,10 @@ function ForgeController:hide() if ForgeButton then ForgeButton:setOn(false) end + if self.ui and not self.ui:isDestroyed() then + self.ui:destroy() + end + self.ui = nil end function ForgeController:toggle() From f56833ff8bae79029416f1aa992523849d44e556 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 18 Feb 2026 11:09:32 -0300 Subject: [PATCH 2/4] feat: use spdlog for logging across project Add spdlog dependency and migrate console/file logging to spdlog. - Add find_package(spdlog) and link spdlog::spdlog_header_only in CMakeLists and add spdlog to vcpkg.json. - Integrate spdlog in logger.cpp: include spdlog headers, create a default colored stdout logger, define console/file patterns (debug vs release), map Fw::LogLevel to spdlog levels, route Logger::log through spdlog (with flush on errors) and implement setLogFile to manage a basic_file_sink. - Replace direct std::cout/std::clog printing with g_logger (or spdlog calls) in httplogin, proxy_client, soundeffect, main, datdump and stdext/cast; add/remove includes accordingly and remove unused iostream includes from pch and other files. - Preserve legacy file-stream fallback when spdlog is not available or file sink setup fails. --- src/CMakeLists.txt | 5 + src/framework/core/consoleapplication.cpp | 2 - src/framework/core/logger.cpp | 118 +++++++++++++++++++++- src/framework/net/httplogin.cpp | 53 +++++----- src/framework/pch.h | 1 - src/framework/proxy/proxy_client.cpp | 53 +++++----- src/framework/sound/soundeffect.cpp | 8 +- src/framework/stdext/cast.h | 3 +- src/main.cpp | 19 ++-- src/tools/datdump.cpp | 6 +- vcpkg.json | 3 +- 11 files changed, 194 insertions(+), 77 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 910ca8f3e1..68ff23a81c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -131,6 +131,7 @@ find_package(pugixml CONFIG REQUIRED) find_package(ZLIB REQUIRED) find_package(httplib CONFIG REQUIRED) find_package(fmt CONFIG REQUIRED) +find_package(spdlog CONFIG REQUIRED) find_package(utf8cpp REQUIRED) find_package(unofficial-inih CONFIG REQUIRED) find_package(Freetype REQUIRED) @@ -558,6 +559,7 @@ if(MSVC) winmm.lib pugixml::pugixml fmt::fmt-header-only + spdlog::spdlog_header_only utf8cpp::utf8cpp Freetype::Freetype ) @@ -609,6 +611,7 @@ elseif(ANDROID) log pugixml::pugixml fmt::fmt-header-only + spdlog::spdlog_header_only utf8cpp::utf8cpp ) @@ -673,6 +676,7 @@ elseif(WASM) OpenSSL::Crypto httplib::httplib fmt::fmt + spdlog::spdlog_header_only utf8cpp::utf8cpp Ogg::ogg Vorbis::vorbisfile @@ -790,6 +794,7 @@ else() # Linux OpenSSL::Crypto httplib::httplib fmt::fmt-header-only + spdlog::spdlog_header_only utf8cpp::utf8cpp Ogg::ogg Vorbis::vorbisfile diff --git a/src/framework/core/consoleapplication.cpp b/src/framework/core/consoleapplication.cpp index 98efbb76de..4e710175a1 100644 --- a/src/framework/core/consoleapplication.cpp +++ b/src/framework/core/consoleapplication.cpp @@ -28,8 +28,6 @@ #include #include -#include - #ifdef FW_NET #include #endif diff --git a/src/framework/core/logger.cpp b/src/framework/core/logger.cpp index de76ff8d7a..f1e7c1996e 100644 --- a/src/framework/core/logger.cpp +++ b/src/framework/core/logger.cpp @@ -25,6 +25,12 @@ #include "eventdispatcher.h" #include "framework/platform/platform.h" +#include +#include +#include +#include +#include + #ifdef FRAMEWORK_GRAPHICS #include #endif @@ -38,11 +44,83 @@ Logger g_logger; namespace { constexpr std::string_view s_logPrefixes[] = { "", "", "", "WARNING: ", "ERROR: ", "FATAL ERROR: " }; + constexpr std::string_view s_spdConsolePattern = "[%Y-%d-%m %H:%M:%S.%e] [%^%l%$] %v"; + constexpr std::string_view s_spdConsolePatternDebug = "[%Y-%d-%m %H:%M:%S.%e] [thread %t] [%^%l%$] %v"; + constexpr std::string_view s_spdFilePattern = "[%Y-%d-%m %H:%M:%S.%e] [%l] %v"; + constexpr std::string_view s_spdFilePatternDebug = "[%Y-%d-%m %H:%M:%S.%e] [thread %t] [%l] %v"; #if ENABLE_ENCRYPTION == 1 bool s_ignoreLogs = true; #else bool s_ignoreLogs = false; #endif + + std::string_view getConsolePattern() + { +#ifdef DEBUG_LOG + return s_spdConsolePatternDebug; +#else + return s_spdConsolePattern; +#endif + } + + std::string_view getFilePattern() + { +#ifdef DEBUG_LOG + return s_spdFilePatternDebug; +#else + return s_spdFilePattern; +#endif + } + + spdlog::level::level_enum toSpdLogLevel(const Fw::LogLevel level) + { + switch (level) { + case Fw::LogFine: + return spdlog::level::trace; + case Fw::LogDebug: + return spdlog::level::debug; + case Fw::LogInfo: + return spdlog::level::info; + case Fw::LogWarning: + return spdlog::level::warn; + case Fw::LogError: + return spdlog::level::err; + case Fw::LogFatal: + return spdlog::level::critical; + default: + return spdlog::level::info; + } + } + + std::shared_ptr createSpdLogger() + { + try { + auto sink = std::make_shared(); + sink->set_color_mode(spdlog::color_mode::always); + sink->set_pattern(std::string{ getConsolePattern() }); + + auto logger = std::make_shared("otclient", sink); + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::warn); + spdlog::set_default_logger(logger); + + return logger; + } catch (...) { + return nullptr; + } + } + + std::shared_ptr& getSpdLogger() + { + static std::shared_ptr s_logger = createSpdLogger(); + return s_logger; + } + + std::shared_ptr& getSpdLogFileSink() + { + static std::shared_ptr s_fileSink; + return s_fileSink; + } } void Logger::log(Fw::LogLevel level, const std::string_view message) @@ -65,14 +143,27 @@ void Logger::log(Fw::LogLevel level, const std::string_view message) return; } - std::string outmsg{ s_logPrefixes[level] }; + std::string outmsg{ s_logPrefixes[static_cast(level)] }; outmsg.append(message); #ifdef ANDROID __android_log_print(ANDROID_LOG_INFO, "OTClientMobile", "%s", outmsg.c_str()); #endif // ANDROID - std::cout << outmsg << std::endl; + auto& spdLogger = getSpdLogger(); + if (spdLogger) { + spdLogger->log(toSpdLogLevel(level), "{}", message); + if (level >= Fw::LogError) { + spdLogger->flush(); + } + } else { + if (const auto fallbackLogger = spdlog::default_logger(); fallbackLogger) { + fallbackLogger->log(toSpdLogLevel(level), "{}", message); + if (level >= Fw::LogError) { + fallbackLogger->flush(); + } + } + } if (m_outFile.good()) { m_outFile << outmsg << std::endl; @@ -138,10 +229,31 @@ void Logger::fireOldMessages() void Logger::setLogFile(const std::string_view file) { + auto& spdLogger = getSpdLogger(); + if (spdLogger) { + try { + auto fileSink = std::make_shared(stdext::utf8_to_latin1(file), true); + fileSink->set_pattern(std::string{ getFilePattern() }); + + auto& currentLogFileSink = getSpdLogFileSink(); + auto& sinks = spdLogger->sinks(); + if (currentLogFileSink) { + std::erase(sinks, currentLogFileSink); + } + + currentLogFileSink = fileSink; + sinks.push_back(currentLogFileSink); + spdLogger->flush(); + return; + } catch (const spdlog::spdlog_ex& e) { + g_logger.error("Unable to save log to '{}' using spdlog: {}", file, e.what()); + } + } + m_outFile.open(stdext::utf8_to_latin1(file), std::ios::out | std::ios::app); if (!m_outFile.is_open() || !m_outFile.good()) { g_logger.error("Unable to save log to '{}'", file); return; } m_outFile.flush(); -} \ No newline at end of file +} diff --git a/src/framework/net/httplogin.cpp b/src/framework/net/httplogin.cpp index 0727b8e549..1512d6ba77 100644 --- a/src/framework/net/httplogin.cpp +++ b/src/framework/net/httplogin.cpp @@ -23,6 +23,7 @@ #include "httplogin.h" #include #include +#include #include #ifdef __EMSCRIPTEN__ @@ -44,27 +45,27 @@ void LoginHttp::cancel() { } void LoginHttp::Logger(const auto& req, const auto& res) { - std::cout << "======= LOG ======= " << std::endl; - std::cout << "-- REQUEST --" << std::endl; - std::cout << req.method << std::endl; - std::cout << req.path << std::endl; - std::cout << req.body << std::endl; + g_logger.debug("======= HTTP LOG ======="); + g_logger.debug("-- REQUEST --"); + g_logger.debug("{}", req.method); + g_logger.debug("{}", req.path); + g_logger.debug("{}", req.body); for (auto itr = req.headers.begin(); itr != req.headers.end(); ++itr) { - std::cout << itr->first << '\t' << itr->second << '\n'; + g_logger.debug("{}\t{}", itr->first, itr->second); } - std::cout << "-- RESPONSE --" << std::endl; - std::cout << res.version << std::endl; - std::cout << res.status << std::endl; - std::cout << res.reason << std::endl; - std::cout << res.body << std::endl; - std::cout << res.location << std::endl; + g_logger.debug("-- RESPONSE --"); + g_logger.debug("{}", res.version); + g_logger.debug("{}", res.status); + g_logger.debug("{}", res.reason); + g_logger.debug("{}", res.body); + g_logger.debug("{}", res.location); for (auto itr = res.headers.begin(); itr != res.headers.end(); ++itr) { - std::cout << itr->first << '\t' << itr->second << '\n'; + g_logger.debug("{}\t{}", itr->first, itr->second); } - std::cout << "========= " << std::endl; + g_logger.debug("========="); } void LoginHttp::startHttpLogin(const std::string& host, const std::string& path, @@ -81,13 +82,13 @@ void LoginHttp::startHttpLogin(const std::string& host, const std::string& path, if (auto res = cli.Post(path, headers, body.dump(1), "application/json")) { if (res->status == 200) { const json bodyResponse = json::parse(res->body); - std::cout << bodyResponse.dump() << std::endl; + g_logger.debug("{}", bodyResponse.dump()); - std::cout << std::boolalpha << json::accept(res->body) << std::endl; + g_logger.debug("json::accept={}", json::accept(res->body)); } } else { const auto err = res.error(); - std::cout << "HTTP error: " << to_string(err) << std::endl; + g_logger.error("HTTP error: {}", to_string(err)); } } @@ -278,17 +279,15 @@ httplib::Result LoginHttp::loginHttpsJson(const std::string& host, client.Post(path, headers, body.dump(), "application/json"); if (!response) { this->errorMessage = "Failed to connect to server (HTTPS). Check the address and port."; - std::cout << "HTTPS error: unknown" << std::endl; + g_logger.warning("HTTPS error: {}", to_string(response.error())); } else if (response->status != Success) { this->errorMessage = "HTTP " + std::to_string(response->status); if (!response->reason.empty()) { this->errorMessage += " - " + response->reason; } - std::cout << "HTTPS error: " << to_string(response.error()) - << std::endl; + g_logger.warning("HTTPS request returned HTTP {} {}", response->status, response->reason); } else { - std::cout << "HTTPS status: " << to_string(response.error()) - << std::endl; + g_logger.debug("HTTPS status: HTTP {}", response->status); } return response; @@ -320,17 +319,15 @@ httplib::Result LoginHttp::loginHttpJson(const std::string& host, client.Post(path, headers, body.dump(), "application/json"); if (!response) { this->errorMessage = "Failed to connect to server (HTTP). Check the address and port."; - std::cout << "HTTP error: unknown" << std::endl; + g_logger.warning("HTTP error: {}", to_string(response.error())); } else if (response->status != Success) { this->errorMessage = "HTTP " + std::to_string(response->status); if (!response->reason.empty()) { this->errorMessage += " - " + response->reason; } - std::cout << "HTTP error: " << to_string(response.error()) - << std::endl; + g_logger.warning("HTTP request returned HTTP {} {}", response->status, response->reason); } else { - std::cout << "HTTP status: " << to_string(response.error()) - << std::endl; + g_logger.debug("HTTP status: HTTP {}", response->status); } if (response && response->status == Success && !parseJsonResponse(response->body)) { return response; @@ -373,4 +370,4 @@ bool LoginHttp::parseJsonResponse(const std::string& body) { this->worlds = to_string(playdata["worlds"]); return true; -} \ No newline at end of file +} diff --git a/src/framework/pch.h b/src/framework/pch.h index 27f979727d..7980e53f36 100644 --- a/src/framework/pch.h +++ b/src/framework/pch.h @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include diff --git a/src/framework/proxy/proxy_client.cpp b/src/framework/proxy/proxy_client.cpp index f74f078abc..1889e8cf95 100644 --- a/src/framework/proxy/proxy_client.cpp +++ b/src/framework/proxy/proxy_client.cpp @@ -24,6 +24,8 @@ #include "proxy_client.h" +#include + std::map> g_sessions; std::set> g_proxies; uint32_t UID = (std::chrono::high_resolution_clock::now().time_since_epoch().count()) & 0xFFFFFFFF; @@ -31,7 +33,7 @@ uint32_t UID = (std::chrono::high_resolution_clock::now().time_since_epoch().cou void Proxy::start() { #ifdef PROXY_DEBUG - std::clog << "[Proxy " << m_host << "] start" << std::endl; + g_logger.debug("[Proxy {}] start", m_host); #endif auto self(shared_from_this()); post(m_io, [&, self] { @@ -48,7 +50,7 @@ void Proxy::terminate() m_terminated = true; #ifdef PROXY_DEBUG - std::clog << "[Proxy " << m_host << "] terminate" << std::endl; + g_logger.debug("[Proxy {}] terminate", m_host); #endif auto self(shared_from_this()); @@ -86,7 +88,7 @@ void Proxy::check(const std::error_code& ec) if (m_waitingForPing) { if (lastPing + 50 > CHECK_INTERVAL * (m_state == STATE_CONNECTING_WAIT_FOR_PING ? 5 : 3)) { #ifdef PROXY_DEBUG - std::clog << "[Proxy " << m_host << "] ping timeout" << std::endl; + g_logger.debug("[Proxy {}] ping timeout", m_host); #endif disconnect(); } @@ -103,7 +105,7 @@ void Proxy::check(const std::error_code& ec) void Proxy::connect() { #ifdef PROXY_DEBUG - std::clog << "[Proxy " << m_host << "] connecting to " << m_host << ":" << m_port << std::endl; + g_logger.debug("[Proxy {}] connecting to {}:{}", m_host, m_host, m_port); #endif m_sendQueue.clear(); m_waitingForPing = false; @@ -117,7 +119,7 @@ void Proxy::connect() auto endpoint = asio::ip::tcp::endpoint(); if (ec || results.empty()) { #ifdef PROXY_DEBUG - std::clog << "[Proxy " << self->m_host << "] resolve error: " << ec.message() << std::endl; + g_logger.debug("[Proxy {}] resolve error: {}", self->m_host, ec.message()); #endif std::error_code ecc; const auto address = asio::ip::make_address_v4(self->m_host, ecc); @@ -144,7 +146,7 @@ void Proxy::connect() self->m_socket.set_option(asio::socket_base::receive_buffer_size(65536), ecc); if (ecc) { #ifdef PROXY_DEBUG - std::clog << "[Proxy " << self->m_host << "] connect error: " << ecc.message() << std::endl; + g_logger.debug("[Proxy {}] connect error: {}", self->m_host, ecc.message()); #endif } @@ -152,7 +154,7 @@ void Proxy::connect() self->readHeader(); self->ping(); #ifdef PROXY_DEBUG - std::clog << "[Proxy " << self->m_host << "] connected " << std::endl; + g_logger.debug("[Proxy {}] connected", self->m_host); #endif }); }); @@ -219,7 +221,7 @@ void Proxy::onHeader(const std::error_code& ec, const std::size_t bytes_transfer { if (ec || bytes_transferred != 2) { #ifdef PROXY_DEBUG - std::clog << "[Proxy " << m_host << "] onHeader error " << ec.message() << std::endl; + g_logger.debug("[Proxy {}] onHeader error {}", m_host, ec.message()); #endif return disconnect(); } @@ -230,7 +232,7 @@ void Proxy::onHeader(const std::error_code& ec, const std::size_t bytes_transfer uint16_t packetSize = *(uint16_t*)m_buffer; if (packetSize < 12 || packetSize > BUFFER_SIZE) { #ifdef PROXY_DEBUG - std::clog << "[Proxy " << m_host << "] onHeader wrong packet size " << packetSize << std::endl; + g_logger.debug("[Proxy {}] onHeader wrong packet size {}", m_host, packetSize); #endif return disconnect(); } @@ -244,7 +246,7 @@ void Proxy::onPacket(const std::error_code& ec, const std::size_t bytes_transfer { if (ec || bytes_transferred < 12) { #ifdef PROXY_DEBUG - std::clog << "[Proxy " << m_host << "] onPacket error " << ec.message() << std::endl; + g_logger.debug("[Proxy {}] onPacket error {}", m_host, ec.message()); #endif return disconnect(); } @@ -260,7 +262,7 @@ void Proxy::onPacket(const std::error_code& ec, const std::size_t bytes_transfer } if (packetId == 0xFFFFFFFFu) { #ifdef PROXY_DEBUG - std::clog << "[Proxy " << m_host << "] onPacket, session end: " << sessionId << std::endl; + g_logger.debug("[Proxy {}] onPacket, session end: {}", m_host, sessionId); #endif const auto it = g_sessions.find(sessionId); if (it != g_sessions.end()) { @@ -275,7 +277,7 @@ void Proxy::onPacket(const std::error_code& ec, const std::size_t bytes_transfer const uint16_t packetSize = *(uint16_t*)(&m_buffer[12]); #ifdef PROXY_DEBUG - //std::clog << "[Proxy " << m_host << "] onPacket, session: " << sessionId << " packetId: " << packetId << " lastRecivedPacket: " << lastRecivedPacketId << " size: " << packetSize << std::endl; + // g_logger.debug("[Proxy {}] onPacket, session: {} packetId: {} lastRecivedPacket: {} size: {}", m_host, sessionId, packetId, lastRecivedPacketId, packetSize); #endif const auto packet = std::make_shared(m_buffer + 12, m_buffer + 14 + packetSize); @@ -304,7 +306,7 @@ void Proxy::onSent(const std::error_code& ec, const std::size_t bytes_transferre { if (ec) { #ifdef PROXY_DEBUG - std::clog << "[Proxy " << m_host << "] onSent error " << ec.message() << std::endl; + g_logger.debug("[Proxy {}] onSent error {}", m_host, ec.message()); #endif return disconnect(); } @@ -322,7 +324,7 @@ void Proxy::onSent(const std::error_code& ec, const std::size_t bytes_transferre void Session::start(const int maxConnections) { #ifdef PROXY_DEBUG - std::clog << "[Session " << m_id << "] start" << std::endl; + g_logger.debug("[Session {}] start", m_id); #endif m_maxConnections = maxConnections; auto self(shared_from_this()); @@ -343,7 +345,7 @@ void Session::terminate(std::error_code ec) m_terminated = true; #ifdef PROXY_DEBUG - std::clog << "[Session " << m_id << "] terminate" << std::endl; + g_logger.debug("[Session {}] terminate", m_id); #endif auto self(shared_from_this()); @@ -413,7 +415,7 @@ void Session::selectProxies() const bool disconnectWorst = worst_ping && worst_ping != best_ping && worst_ping->getPing() > candidate_proxy->getPing() + 20; if (m_proxies.size() != m_maxConnections || disconnectWorst) { #ifdef PROXY_DEBUG - std::clog << "[Session " << m_id << "] new proxy: " << candidate_proxy->getHost() << std::endl; + g_logger.debug("[Session {}] new proxy: {}", m_id, candidate_proxy->getHost()); #endif candidate_proxy->addSession(m_id, m_port); m_proxies.insert(candidate_proxy); @@ -423,7 +425,7 @@ void Session::selectProxies() } if (m_proxies.size() > m_maxConnections) { #ifdef PROXY_DEBUG - std::clog << "[Session " << m_id << "] remove proxy: " << worst_ping->getHost() << std::endl; + g_logger.debug("[Session {}] remove proxy: {}", m_id, worst_ping->getHost()); #endif worst_ping->removeSession(m_id); m_proxies.erase(worst_ping); @@ -434,8 +436,7 @@ void Session::selectProxies() void Session::onProxyPacket(uint32_t packetId, uint32_t lastRecivedPacketId, const ProxyPacketPtr& packet) { #ifdef PROXY_DEBUG - std::clog << "[Session " << m_id << "] onProxyPacket, id: " << packetId << " (" << m_inputPacketId << ") last: " << lastRecivedPacketId << - " (" << m_outputPacketId << ") size: " << packet->size() << std::endl; + g_logger.debug("[Session {}] onProxyPacket, id: {} ({}) last: {} ({}) size: {}", m_id, packetId, m_inputPacketId, lastRecivedPacketId, m_outputPacketId, packet->size()); #endif if (packetId < m_inputPacketId) { return; // old packet, ignore @@ -480,7 +481,7 @@ void Session::readTibia12Header() } if (self->m_buffer[0] == 0x0A) { #ifdef PROXY_DEBUG - std::clog << "[Session " << self->m_id << "] Tibia 12 read header finished" << std::endl; + g_logger.debug("[Session {}] Tibia 12 read header finished", self->m_id); #endif return self->readHeader(); } @@ -500,7 +501,7 @@ void Session::onHeader(const std::error_code& ec, std::size_t /*bytes_transferre { if (ec) { #ifdef PROXY_DEBUG - std::clog << "[Session " << m_id << "] onHeader error: " << ec.message() << std::endl; + g_logger.debug("[Session {}] onHeader error: {}", m_id, ec.message()); #endif return terminate(); } @@ -512,7 +513,7 @@ void Session::onHeader(const std::error_code& ec, std::size_t /*bytes_transferre if (packetSize == 0 || packetSize + 16 > BUFFER_SIZE) { #ifdef PROXY_DEBUG - std::clog << "[Session " << m_id << "] onHeader invalid packet size: " << packetSize << std::endl; + g_logger.debug("[Session {}] onHeader invalid packet size: {}", m_id, packetSize); #endif return terminate(); } @@ -527,7 +528,7 @@ void Session::onBody(const std::error_code& ec, const std::size_t bytes_transfer { if (ec) { #ifdef PROXY_DEBUG - std::clog << "[Session " << m_id << "] onBody error: " << ec.message() << std::endl; + g_logger.debug("[Session {}] onBody error: {}", m_id, ec.message()); #endif return terminate(); } @@ -542,7 +543,7 @@ void Session::onPacket(const ProxyPacketPtr& packet) { if (!packet || packet->empty() || packet->size() + 14 > BUFFER_SIZE) { #ifdef PROXY_DEBUG - std::clog << "[Session " << m_id << "] onPacket error: missing packet or wrong size" << std::endl; + g_logger.debug("[Session {}] onPacket error: missing packet or wrong size", m_id); #endif return terminate(); } @@ -569,7 +570,7 @@ void Session::onSent(const std::error_code& ec, std::size_t /*bytes_transferred* { if (ec) { #ifdef PROXY_DEBUG - std::clog << "[Session " << m_id << "] onSent error: " << ec.message() << std::endl; + g_logger.debug("[Session {}] onSent error: {}", m_id, ec.message()); #endif return terminate(); } @@ -582,4 +583,4 @@ void Session::onSent(const std::error_code& ec, std::size_t /*bytes_transferred* capture0->onSent(std::forward(PH1), std::forward(PH2)); }); } -} \ No newline at end of file +} diff --git a/src/framework/sound/soundeffect.cpp b/src/framework/sound/soundeffect.cpp index e0ad95f266..7f8fd5ba3b 100644 --- a/src/framework/sound/soundeffect.cpp +++ b/src/framework/sound/soundeffect.cpp @@ -23,6 +23,8 @@ #include "soundeffect.h" #include "soundmanager.h" +#include + #include #include #include @@ -266,7 +268,7 @@ SoundEffect::~SoundEffect() void SoundEffect::loadPreset(const EFXEAXREVERBPROPERTIES& preset) { if (g_sounds.isEaxEnabled()) { - std::cout << "Using EAX Reverb!\n"; + g_logger.debug("Using EAX Reverb"); /* EAX Reverb is available. Set the EAX effect type then load the * reverb properties. */ @@ -296,7 +298,7 @@ void SoundEffect::loadPreset(const EFXEAXREVERBPROPERTIES& preset) alEffectf(m_effectId, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, preset.flRoomRolloffFactor); alEffecti(m_effectId, AL_EAXREVERB_DECAY_HFLIMIT, preset.iDecayHFLimit); } else { - std::cout << "Using Standard Reverb!\n"; + g_logger.debug("Using Standard Reverb"); /* No EAX Reverb. Set the standard reverb effect type then load the * available reverb properties. */ @@ -414,4 +416,4 @@ void SoundEffect::setReverbReflectionsDelay(const float reflectionsDelay) const alEffectf(m_effectId, AL_REVERB_REFLECTIONS_DELAY, reflectionsDelay); } alAuxiliaryEffectSloti(m_effectSlot, AL_EFFECTSLOT_EFFECT, static_cast(m_effectId)); -} \ No newline at end of file +} diff --git a/src/framework/stdext/cast.h b/src/framework/stdext/cast.h index ffe44df210..b212250b6f 100644 --- a/src/framework/stdext/cast.h +++ b/src/framework/stdext/cast.h @@ -24,6 +24,7 @@ #include "demangle.h" #include "exception.h" +#include namespace stdext { @@ -167,7 +168,7 @@ namespace stdext try { return safe_cast(t); } catch (const cast_exception& e) { - std::cerr << "CAST ERROR: " << e.what() << std::endl; + spdlog::error("CAST ERROR: {}", e.what()); return def; } } diff --git a/src/main.cpp b/src/main.cpp index eeb8288a08..30577c185c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,14 +59,15 @@ bool shouldShowHelp(const std::vector& args) void printHelp(const std::string& executableName) { - std::cout << "Usage: " << executableName << " [options]\n\n" - "General options:\n" - " --help, -h, /? Show this help message and exit\n" - " --encrypt Encrypt assets (requires ENABLE_ENCRYPTION == 1 && ENABLE_ENCRYPTION_BUILDER == 1 build)\n\n" - "DAT debugging:\n" - " --dump-dat-to-json= Dump the specified Tibia DAT file or version as JSON (requires FRAMEWORK_EDITOR build)\n" - " --dump-dat-output= Write JSON to file instead of stdout\n" - " --dump-dat-compact Emit compact (single-line) JSON\n"; + g_logger.info("Usage: {} [options]\n\n" + "General options:\n" + " --help, -h, /? Show this help message and exit\n" + " --encrypt Encrypt assets (requires ENABLE_ENCRYPTION == 1 && ENABLE_ENCRYPTION_BUILDER == 1 build)\n\n" + "DAT debugging:\n" + " --dump-dat-to-json= Dump the specified Tibia DAT file or version as JSON (requires FRAMEWORK_EDITOR build)\n" + " --dump-dat-output= Write JSON to file instead of stdout\n" + " --dump-dat-compact Emit compact (single-line) JSON", + executableName); } } // namespace @@ -91,7 +92,7 @@ void printHelp(const std::string& executableName) if (std::find(args.begin(), args.end(), "--encrypt") != args.end()) { g_lua.init(); g_resources.runEncryption(args.size() >= 3 ? args[2] : std::string(ENCRYPTION_PASSWORD)); - std::cout << "Encryption complete" << std::endl; + g_logger.info("Encryption complete"); #ifdef WIN32 MessageBoxA(NULL, "Encryption complete", "Success", 0); #endif diff --git a/src/tools/datdump.cpp b/src/tools/datdump.cpp index 67996b51e4..eda269cb5f 100644 --- a/src/tools/datdump.cpp +++ b/src/tools/datdump.cpp @@ -25,13 +25,13 @@ #include "client/game.h" #include "client/thingtype.h" #include "client/thingtypemanager.h" +#include "framework/core/logger.h" #include "framework/luaengine/luainterface.h" #include #include #include -#include #include #include #include @@ -245,7 +245,7 @@ namespace datdump { bool success = true; if (request.outputPath.empty()) { - std::cout << payload << std::endl; + g_logger.info("{}", payload); } else { std::ofstream out(request.outputPath, std::ios::out | std::ios::trunc); if (!out) { @@ -259,4 +259,4 @@ namespace datdump { g_lua.terminate(); return success; } -} // namespace datdump \ No newline at end of file +} // namespace datdump diff --git a/vcpkg.json b/vcpkg.json index 3923884161..8b5e636a93 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -22,6 +22,7 @@ "zlib", "bshoshany-thread-pool", "fmt", + "spdlog", "utfcpp", "gtest", "freetype", @@ -49,4 +50,4 @@ } ], "builtin-baseline": "b322364f06308bdd24823f9d8f03fe0cc86fd46f" -} \ No newline at end of file +} From 9cb4a0e4530cf6ffc99a04d86d1af1b75b93ef05 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Wed, 18 Feb 2026 12:45:24 -0300 Subject: [PATCH 3/4] Sanitize HTTP logs and adjust logging/output Add HTTP log sanitization and redaction for sensitive headers, JSON request/response fields, and token-like values; include helper functions (toLowerCopy, sanitizeJson, sanitizeHttpBody, etc.). Fix spdlog time patterns (use %Y-%m-%d) and simplify fallback logger handling. Change an HTTP error log to a warning and remove a json::accept debug line. Remove direct spdlog usage in stdext::cast (stop logging cast exceptions) and replace some g_logger uses with std::cout for help and datdump output; add required includes (cctype, iostream) and adjust datdump to write to stdout when no output path is provided. --- src/framework/core/logger.cpp | 17 +++-- src/framework/net/httplogin.cpp | 112 ++++++++++++++++++++++++++++++-- src/framework/stdext/cast.h | 4 +- src/main.cpp | 18 ++--- src/tools/datdump.cpp | 4 +- 5 files changed, 125 insertions(+), 30 deletions(-) diff --git a/src/framework/core/logger.cpp b/src/framework/core/logger.cpp index f1e7c1996e..e8d64b14fa 100644 --- a/src/framework/core/logger.cpp +++ b/src/framework/core/logger.cpp @@ -44,10 +44,10 @@ Logger g_logger; namespace { constexpr std::string_view s_logPrefixes[] = { "", "", "", "WARNING: ", "ERROR: ", "FATAL ERROR: " }; - constexpr std::string_view s_spdConsolePattern = "[%Y-%d-%m %H:%M:%S.%e] [%^%l%$] %v"; - constexpr std::string_view s_spdConsolePatternDebug = "[%Y-%d-%m %H:%M:%S.%e] [thread %t] [%^%l%$] %v"; - constexpr std::string_view s_spdFilePattern = "[%Y-%d-%m %H:%M:%S.%e] [%l] %v"; - constexpr std::string_view s_spdFilePatternDebug = "[%Y-%d-%m %H:%M:%S.%e] [thread %t] [%l] %v"; + constexpr std::string_view s_spdConsolePattern = "[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] %v"; + constexpr std::string_view s_spdConsolePatternDebug = "[%Y-%m-%d %H:%M:%S.%e] [thread %t] [%^%l%$] %v"; + constexpr std::string_view s_spdFilePattern = "[%Y-%m-%d %H:%M:%S.%e] [%l] %v"; + constexpr std::string_view s_spdFilePatternDebug = "[%Y-%m-%d %H:%M:%S.%e] [thread %t] [%l] %v"; #if ENABLE_ENCRYPTION == 1 bool s_ignoreLogs = true; #else @@ -157,11 +157,10 @@ void Logger::log(Fw::LogLevel level, const std::string_view message) spdLogger->flush(); } } else { - if (const auto fallbackLogger = spdlog::default_logger(); fallbackLogger) { - fallbackLogger->log(toSpdLogLevel(level), "{}", message); - if (level >= Fw::LogError) { - fallbackLogger->flush(); - } + auto fallbackLogger = spdlog::default_logger(); + fallbackLogger->log(toSpdLogLevel(level), "{}", message); + if (level >= Fw::LogError) { + fallbackLogger->flush(); } } diff --git a/src/framework/net/httplogin.cpp b/src/framework/net/httplogin.cpp index 1512d6ba77..955838dd9f 100644 --- a/src/framework/net/httplogin.cpp +++ b/src/framework/net/httplogin.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #ifdef __EMSCRIPTEN__ #include @@ -32,6 +33,102 @@ using json = nlohmann::json; +namespace { + constexpr std::string_view s_redacted = "***"; + + std::string toLowerCopy(const std::string_view value) + { + std::string out; + out.reserve(value.size()); + for (const auto c : value) { + out.push_back(static_cast(std::tolower(static_cast(c)))); + } + return out; + } + + std::string redactTokenLikeValue(const std::string& value) + { + if (value.empty() || value.size() <= 8) { + return std::string{ s_redacted }; + } + return value.substr(0, 4) + "..." + value.substr(value.size() - 4); + } + + bool isSensitiveRequestField(const std::string_view key) + { + const auto lowered = toLowerCopy(key); + return lowered.find("password") != std::string::npos + || lowered.find("token") != std::string::npos + || lowered.find("session") != std::string::npos; + } + + bool isSensitiveResponseField(const std::string_view key) + { + const auto lowered = toLowerCopy(key); + return lowered.find("token") != std::string::npos + || lowered.find("session") != std::string::npos + || lowered.find("cookie") != std::string::npos; + } + + bool isSensitiveHeaderKey(const std::string_view key) + { + const auto lowered = toLowerCopy(key); + return lowered == "authorization" + || lowered == "proxy-authorization" + || lowered == "cookie" + || lowered == "set-cookie" + || lowered == "x-auth-token"; + } + + std::string sanitizeHeaderValue(const std::string_view key, const std::string_view value) + { + if (isSensitiveHeaderKey(key)) { + return std::string{ s_redacted }; + } + return std::string{ value }; + } + + void sanitizeJson(json& value, const bool requestBody) + { + if (value.is_object()) { + for (auto it = value.begin(); it != value.end(); ++it) { + const bool sensitiveField = requestBody ? isSensitiveRequestField(it.key()) : isSensitiveResponseField(it.key()); + if (sensitiveField) { + if (!requestBody && it.value().is_string()) { + it.value() = redactTokenLikeValue(it.value().get()); + } else { + it.value() = std::string{ s_redacted }; + } + continue; + } + sanitizeJson(it.value(), requestBody); + } + return; + } + + if (value.is_array()) { + for (auto& item : value) { + sanitizeJson(item, requestBody); + } + } + } + + std::string sanitizeHttpBody(const std::string& body, const bool requestBody) + { + if (body.empty()) { + return {}; + } + + try { + auto parsed = json::parse(body); + sanitizeJson(parsed, requestBody); + return parsed.dump(); + } catch (...) { + return "[redacted-unparsed-body]"; + } + } +} + LoginHttp::LoginHttp() { this->characters.clear(); this->worlds.clear(); @@ -45,24 +142,27 @@ void LoginHttp::cancel() { } void LoginHttp::Logger(const auto& req, const auto& res) { + const auto sanitizedRequestBody = sanitizeHttpBody(req.body, true); + const auto sanitizedResponseBody = sanitizeHttpBody(res.body, false); + g_logger.debug("======= HTTP LOG ======="); g_logger.debug("-- REQUEST --"); g_logger.debug("{}", req.method); g_logger.debug("{}", req.path); - g_logger.debug("{}", req.body); + g_logger.debug("{}", sanitizedRequestBody); for (auto itr = req.headers.begin(); itr != req.headers.end(); ++itr) { - g_logger.debug("{}\t{}", itr->first, itr->second); + g_logger.debug("{}\t{}", itr->first, sanitizeHeaderValue(itr->first, itr->second)); } g_logger.debug("-- RESPONSE --"); g_logger.debug("{}", res.version); g_logger.debug("{}", res.status); g_logger.debug("{}", res.reason); - g_logger.debug("{}", res.body); + g_logger.debug("{}", sanitizedResponseBody); g_logger.debug("{}", res.location); for (auto itr = res.headers.begin(); itr != res.headers.end(); ++itr) { - g_logger.debug("{}\t{}", itr->first, itr->second); + g_logger.debug("{}\t{}", itr->first, sanitizeHeaderValue(itr->first, itr->second)); } g_logger.debug("========="); @@ -83,12 +183,10 @@ void LoginHttp::startHttpLogin(const std::string& host, const std::string& path, if (res->status == 200) { const json bodyResponse = json::parse(res->body); g_logger.debug("{}", bodyResponse.dump()); - - g_logger.debug("json::accept={}", json::accept(res->body)); } } else { const auto err = res.error(); - g_logger.error("HTTP error: {}", to_string(err)); + g_logger.warning("HTTP error: {}", to_string(err)); } } diff --git a/src/framework/stdext/cast.h b/src/framework/stdext/cast.h index b212250b6f..827f95f51a 100644 --- a/src/framework/stdext/cast.h +++ b/src/framework/stdext/cast.h @@ -24,7 +24,6 @@ #include "demangle.h" #include "exception.h" -#include namespace stdext { @@ -167,8 +166,7 @@ namespace stdext { try { return safe_cast(t); - } catch (const cast_exception& e) { - spdlog::error("CAST ERROR: {}", e.what()); + } catch (const cast_exception&) { return def; } } diff --git a/src/main.cpp b/src/main.cpp index 30577c185c..f2b00e88a5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,6 +29,7 @@ #ifdef FRAMEWORK_EDITOR #include "tools/datdump.h" #endif +#include #ifndef ANDROID #if ENABLE_DISCORD_RPC == 1 @@ -59,15 +60,14 @@ bool shouldShowHelp(const std::vector& args) void printHelp(const std::string& executableName) { - g_logger.info("Usage: {} [options]\n\n" - "General options:\n" - " --help, -h, /? Show this help message and exit\n" - " --encrypt Encrypt assets (requires ENABLE_ENCRYPTION == 1 && ENABLE_ENCRYPTION_BUILDER == 1 build)\n\n" - "DAT debugging:\n" - " --dump-dat-to-json= Dump the specified Tibia DAT file or version as JSON (requires FRAMEWORK_EDITOR build)\n" - " --dump-dat-output= Write JSON to file instead of stdout\n" - " --dump-dat-compact Emit compact (single-line) JSON", - executableName); + std::cout << "Usage: " << executableName << " [options]\n\n" + "General options:\n" + " --help, -h, /? Show this help message and exit\n" + " --encrypt Encrypt assets (requires ENABLE_ENCRYPTION == 1 && ENABLE_ENCRYPTION_BUILDER == 1 build)\n\n" + "DAT debugging:\n" + " --dump-dat-to-json= Dump the specified Tibia DAT file or version as JSON (requires FRAMEWORK_EDITOR build)\n" + " --dump-dat-output= Write JSON to file instead of stdout\n" + " --dump-dat-compact Emit compact (single-line) JSON\n"; } } // namespace diff --git a/src/tools/datdump.cpp b/src/tools/datdump.cpp index eda269cb5f..91f432456e 100644 --- a/src/tools/datdump.cpp +++ b/src/tools/datdump.cpp @@ -25,13 +25,13 @@ #include "client/game.h" #include "client/thingtype.h" #include "client/thingtypemanager.h" -#include "framework/core/logger.h" #include "framework/luaengine/luainterface.h" #include #include #include +#include #include #include #include @@ -245,7 +245,7 @@ namespace datdump { bool success = true; if (request.outputPath.empty()) { - g_logger.info("{}", payload); + std::cout << payload << '\n'; } else { std::ofstream out(request.outputPath, std::ios::out | std::ios::trunc); if (!out) { From 47856009808da5fca6d919440a0fbb85ed8d7002 Mon Sep 17 00:00:00 2001 From: LIBERGOD <37589902+libergod@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:35:35 -0800 Subject: [PATCH 4/4] Fix docker compilation --- Dockerfile.browser | 7 +++++-- browser/triplets/wasm32-emscripten-pthread.cmake | 12 ++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Dockerfile.browser b/Dockerfile.browser index 83d1c41431..4a9bac7cf1 100644 --- a/Dockerfile.browser +++ b/Dockerfile.browser @@ -62,12 +62,15 @@ RUN source /opt/emsdk/emsdk_env.sh \ -DTOGGLE_BOT_PROTECTION=OFF \ -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ -DCMAKE_CXX_STANDARD=20 \ - -DCMAKE_C_FLAGS="-pthread -matomics -mbulk-memory -fno-lto" \ - -DCMAKE_CXX_FLAGS="-pthread -matomics -mbulk-memory -fno-lto -std=c++20 -D_LIBCPP_DISABLE_AVAILABILITY -fexperimental-library" \ + -DCMAKE_C_FLAGS="-pthread -matomics -mbulk-memory -fno-lto -DFMT_USE_NONTYPE_TEMPLATE_ARGS=0 -DFMT_USE_CONSTEVAL=0" \ + -DCMAKE_CXX_FLAGS="-pthread -matomics -mbulk-memory -fno-lto -std=c++20 -D_LIBCPP_DISABLE_AVAILABILITY -fexperimental-library -DFMT_USE_NONTYPE_TEMPLATE_ARGS=0 -DFMT_USE_CONSTEVAL=0" \ -DCMAKE_EXE_LINKER_FLAGS="-pthread -matomics -mbulk-memory -s ALLOW_MEMORY_GROWTH=1 -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=4 -s WASM=1 -s NO_EXIT_RUNTIME=1 -fno-lto" \ -DCMAKE_CXX_FLAGS_RELEASE="-O2 -DNDEBUG -fno-lto" \ -DCMAKE_CXX_FLAGS_DEBUG="-O0 -g -fno-lto" \ -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF \ + && sed -i \ + 's/define SPDLOG_FMT_STRING(format_string) FMT_STRING(format_string)/define SPDLOG_FMT_STRING(format_string) (format_string)/' \ + ${BUILD_DIR}/vcpkg_installed/wasm32-emscripten-pthread/include/spdlog/common.h \ && cmake --build ${BUILD_DIR} --target otclient -j"$(nproc)" # NOSONAR: minimal BusyBox stage used only to package static artifacts, root default is acceptable diff --git a/browser/triplets/wasm32-emscripten-pthread.cmake b/browser/triplets/wasm32-emscripten-pthread.cmake index f12608c860..55cf61a54f 100644 --- a/browser/triplets/wasm32-emscripten-pthread.cmake +++ b/browser/triplets/wasm32-emscripten-pthread.cmake @@ -7,12 +7,16 @@ set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE "$ENV{EMSDK}/upstream/emscripten/cmake/Module # Force pthread support with atomics and bulk-memory for all packages # These flags are required for shared memory support in WebAssembly -set(VCPKG_C_FLAGS "-pthread -matomics -mbulk-memory") -set(VCPKG_CXX_FLAGS "-pthread -matomics -mbulk-memory") +# FMT_USE_CONSTEVAL=0: disables fmt's consteval format-string constructor which +# emscripten's Clang cannot satisfy inside catch blocks (SPDLOG_LOGGER_CATCH). +# FMT_USE_NONTYPE_TEMPLATE_ARGS=0: disables C++20 non-type template args in fmt +# which are not reliably supported by the emscripten toolchain. +set(VCPKG_C_FLAGS "-pthread -matomics -mbulk-memory -DFMT_USE_CONSTEVAL=0 -DFMT_USE_NONTYPE_TEMPLATE_ARGS=0") +set(VCPKG_CXX_FLAGS "-pthread -matomics -mbulk-memory -DFMT_USE_CONSTEVAL=0 -DFMT_USE_NONTYPE_TEMPLATE_ARGS=0") set(VCPKG_LINKER_FLAGS "-pthread -matomics -mbulk-memory") # Also set as CMAKE_*_FLAGS to ensure they're applied universally set(VCPKG_CMAKE_CONFIGURE_OPTIONS - "-DCMAKE_C_FLAGS=-pthread -matomics -mbulk-memory" - "-DCMAKE_CXX_FLAGS=-pthread -matomics -mbulk-memory" + "-DCMAKE_C_FLAGS=-pthread -matomics -mbulk-memory -DFMT_USE_CONSTEVAL=0 -DFMT_USE_NONTYPE_TEMPLATE_ARGS=0" + "-DCMAKE_CXX_FLAGS=-pthread -matomics -mbulk-memory -DFMT_USE_CONSTEVAL=0 -DFMT_USE_NONTYPE_TEMPLATE_ARGS=0" )