From fbdf611fc420a55abb0bad8750b5e9a601cc1a0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 3 Sep 2026 10:07:01 -0600 Subject: [PATCH] Chat: keep more lines of history, and add timestamps The log was trimmed to 50 lines, which isn't enough to scroll back through a conversation - now 250. Chat entries also carry the time they arrived, shown as a dimmed HH:MM in front of the name, behind a new "Show timestamps in chat" setting. The timestamp is kept next to the text rather than baked into it, since the chat view finds the sender by splitting the line at the first colon. Fixes #15444 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq --- Core/Config.cpp | 1 + Core/Config.h | 1 + Core/HLE/proAdhoc.cpp | 26 ++++++++++++++++---------- Core/HLE/proAdhoc.h | 8 +++++++- UI/ChatScreen.cpp | 24 +++++++++++++++++++++--- UI/GameSettingsScreen.cpp | 1 + 6 files changed, 47 insertions(+), 14 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 5dca9c3f5d..2f7e0952f3 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1057,6 +1057,7 @@ static const ConfigSetting networkSettings[] = { ConfigSetting("EnableNetworkChat", SETTING(g_Config, bEnableNetworkChat), false, CfgFlag::PER_GAME), ConfigSetting("ChatButtonPosition", SETTING(g_Config, iChatButtonPosition), (int)ScreenEdgePosition::BOTTOM_LEFT, CfgFlag::PER_GAME), ConfigSetting("ChatScreenPosition", SETTING(g_Config, iChatScreenPosition), (int)ScreenEdgePosition::BOTTOM_LEFT, CfgFlag::PER_GAME), + ConfigSetting("ChatTimestamps", SETTING(g_Config, bChatTimestamps), true, CfgFlag::PER_GAME), ConfigSetting("EnableQuickChat", SETTING(g_Config, bEnableQuickChat), true, CfgFlag::PER_GAME), ConfigSetting("QuickChat1", SETTING(g_Config, sQuickChat[0]), "Quick Chat 1", CfgFlag::PER_GAME), ConfigSetting("QuickChat2", SETTING(g_Config, sQuickChat[1]), "Quick Chat 2", CfgFlag::PER_GAME), diff --git a/Core/Config.h b/Core/Config.h index c5b7ead9aa..cb4ebc95a6 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -611,6 +611,7 @@ public: bool bDontDownloadInfraJson; int iChatButtonPosition; int iChatScreenPosition; + bool bChatTimestamps; bool bEnableQuickChat; std::string sQuickChat[5]; diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 3c40adf583..9b55fb7d9a 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -120,7 +120,17 @@ sockaddr LocalIP; int defaultWlanChannel = PSP_SYSTEMPARAM_ADHOC_CHANNEL_11; // Don't put 0(Auto) here, it needed to be a valid/actual channel number static std::mutex chatLogLock; -static std::vector chatLog; +static std::vector chatLog; +// Enough to scroll back through a decent conversation without growing unbounded. +static const size_t MAX_CHAT_LOG_LINES = 250; + +// chatLogLock must be held. +static void AddChatLogEntry(std::string text) { + chatLog.push_back(ChatLogEntry{std::move(text), time(nullptr)}); + if (chatLog.size() > MAX_CHAT_LOG_LINES) { + chatLog.erase(chatLog.begin(), chatLog.begin() + (chatLog.size() - MAX_CHAT_LOG_LINES)); + } +} static int chatMessageGeneration = 0; static int chatMessageCount = 0; @@ -1322,25 +1332,21 @@ void sendChat(std::string_view chatString) { std::string name = g_Config.sNickName; std::lock_guard guard(chatLogLock); - chatLog.emplace_back(name.substr(0, 8) + ": " + chat.message); + AddChatLogEntry(name.substr(0, 8) + ": " + chat.message); chatMessageGeneration++; } } } else { std::lock_guard guard(chatLogLock); auto n = GetI18NCategory(I18NCat::NETWORKING); - chatLog.push_back(std::string(n->T("You're in Offline Mode, go to lobby or online hall"))); + AddChatLogEntry(std::string(n->T("You're in Offline Mode, go to lobby or online hall"))); INFO_LOG(Log::sceNet, "Offline. Would have sent: %.*s", STR_VIEW(chatString)); chatMessageGeneration++; } } -std::vector getChatLog() { +std::vector getChatLog() { std::lock_guard guard(chatLogLock); - // If the log gets large, trim it down. - if (chatLog.size() > 50) { - chatLog.erase(chatLog.begin(), chatLog.begin() + (chatLog.size() - 50)); - } return chatLog; } @@ -1555,7 +1561,7 @@ int friendFinder() { incoming.append((char*)packet->base.message); std::lock_guard guard(chatLogLock); - chatLog.push_back(incoming); + AddChatLogEntry(incoming); chatMessageGeneration++; chatMessageCount++; @@ -1626,7 +1632,7 @@ int friendFinder() { //joined.append((char *)packet->ip); std::lock_guard guard(chatLogLock); - chatLog.push_back(incoming); + AddChatLogEntry(incoming); chatMessageGeneration++; #ifdef LOCALHOST_AS_PEER diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index 4ca28ea360..969ba11afe 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -29,6 +29,7 @@ #include #include #include +#include #include "Common/Net/Resolve.h" #include "Common/Serialize/Serializer.h" @@ -884,7 +885,12 @@ void addFriend(SceNetAdhocctlConnectPacketS2C * packet); * @param std::string ChatString */ void sendChat(std::string_view chatString); -std::vector getChatLog(); + +struct ChatLogEntry { + std::string text; // "name: message", or an info line with no colon. + time_t timestamp; +}; +std::vector getChatLog(); int GetChatChangeID(); int GetChatMessageCount(); diff --git a/UI/ChatScreen.cpp b/UI/ChatScreen.cpp index 3bc2d0e10a..0fe79b99c2 100644 --- a/UI/ChatScreen.cpp +++ b/UI/ChatScreen.cpp @@ -10,6 +10,7 @@ #include "Common/UI/UI.h" #include "Common/Data/Text/I18n.h" +#include "Common/File/FileUtil.h" // For the localtime_r shim on Windows. #include "Common/Data/Encoding/Utf8.h" #include "Common/System/Request.h" #include "Core/Config.h" @@ -137,11 +138,13 @@ void ChatMenu::UpdateChat() { using namespace UI; if (chatVert_ != nullptr) { chatVert_->Clear(); //read Access violation is proadhoc.cpp use NULL_->Clear() pointer? - std::vector chatLog = getChatLog(); - for (auto i : chatLog) { + std::vector chatLog = getChatLog(); + for (const auto &entry : chatLog) { + const std::string &i = entry.text; uint32_t namecolor = 0x29B6F6; uint32_t textcolor = 0xFFFFFF; uint32_t infocolor = 0xFDD835; + uint32_t timecolor = 0x9E9E9E; std::string name = g_Config.sNickName; std::string displayname = i.substr(0, i.find(':')); @@ -150,12 +153,27 @@ void ChatMenu::UpdateChat() { namecolor = 0xE53935; } + std::string timestamp; + if (g_Config.bChatTimestamps) { + char buf[16]; + tm localTime; + localtime_r(&entry.timestamp, &localTime); + if (strftime(buf, sizeof(buf), "%H:%M ", &localTime)) { + timestamp = buf; + } + } + if (i.length() <= displayname.length() || i[displayname.length()] != ':') { - TextView *v = chatVert_->Add(new TextView(i, ALIGN_LEFT | FLAG_WRAP_TEXT, true, new LayoutParams(FILL_PARENT, WRAP_CONTENT))); + // Info line - no name to color separately, so the timestamp can just go in the text. + TextView *v = chatVert_->Add(new TextView(timestamp + i, ALIGN_LEFT | FLAG_WRAP_TEXT, true, new LayoutParams(FILL_PARENT, WRAP_CONTENT))); v->SetTextColor(0xFF000000 | infocolor); } else { LinearLayout *line = chatVert_->Add(new LinearLayout(ORIENT_HORIZONTAL, new LayoutParams(FILL_PARENT, FILL_PARENT))); line->SetSpacing(0.0f); + if (!timestamp.empty()) { + TextView *timeView = line->Add(new TextView(timestamp, ALIGN_LEFT, true, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 0.0f))); + timeView->SetTextColor(0xFF000000 | timecolor); + } TextView *nameView = line->Add(new TextView(displayname, ALIGN_LEFT, true, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 0.0f))); nameView->SetTextColor(0xFF000000 | namecolor); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index e5257a4ed3..9aed67a5c6 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1070,6 +1070,7 @@ void GameSettingsScreen::CreateNetworkingSettings(UI::ViewGroup *networkingSetti networkingSettings->Add(new PopupMultiChoice(&g_Config.iChatButtonPosition, n->T("Chat Button Position"), chatButtonPositions, 0, ARRAY_SIZE(chatButtonPositions), I18NCat::DIALOG, screenManager()))->SetEnabledPtr(&g_Config.bEnableNetworkChat); static const char *chatScreenPositions[] = { "Bottom Left", "Bottom Center", "Bottom Right", "Top Left", "Top Center", "Top Right" }; networkingSettings->Add(new PopupMultiChoice(&g_Config.iChatScreenPosition, n->T("Chat Screen Position"), chatScreenPositions, 0, ARRAY_SIZE(chatScreenPositions), I18NCat::DIALOG, screenManager()))->SetEnabledPtr(&g_Config.bEnableNetworkChat); + networkingSettings->Add(new CheckBox(&g_Config.bChatTimestamps, n->T("Show timestamps in chat")))->SetEnabledPtr(&g_Config.bEnableNetworkChat); networkingSettings->Add(new ItemHeader(n->T("Quick chat"))); CheckBox *qc = networkingSettings->Add(new CheckBox(&g_Config.bEnableQuickChat, n->T("Enable quick chat")));