From 1d5447b74cf83472f4c0a862702d34685fc39103 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 30 Jun 2026 19:05:16 +0300 Subject: [PATCH] ShadNet:Presence support (#4649) * added serial and title to shadnet login for reporting for presense support * more presensce support * more presnse * live appear online * added appear offline setting * clang.. --- src/core/libraries/np/np_handler.cpp | 13 ++++++++++ src/core/libraries/np/np_handler.h | 10 ++++++++ src/imgui/friends_layer.cpp | 7 ++++++ src/shadnet/client.cpp | 36 ++++++++++++++++++++++++++-- src/shadnet/client.h | 8 +++++++ src/shadnet/shadnet.proto | 15 ++++++++---- 6 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/core/libraries/np/np_handler.cpp b/src/core/libraries/np/np_handler.cpp index 1b210c9d2..0b23842a3 100644 --- a/src/core/libraries/np/np_handler.cpp +++ b/src/core/libraries/np/np_handler.cpp @@ -174,6 +174,9 @@ bool NpHandler::ConnectUser(s32 user_id, const std::string& host, u16 port, cons OnLoginResult(user_id, res); }; + // Seed the current Appear-Offline preference so the login packet carries it (the send + // is suppressed pre-auth; it just caches on the client). + client->SetAppearOffline(m_appear_offline.load()); client->Start(host, port, npid, password, token); const ShadNet::ShadNetState conn_state = client->WaitForConnection(); @@ -210,6 +213,15 @@ bool NpHandler::ConnectUser(s32 user_id, const std::string& host, u16 port, cons return true; } +void NpHandler::SetAppearOffline(bool enable) { + m_appear_offline = enable; + std::lock_guard lock(m_mutex_clients); + for (auto& [uid, client] : m_clients) { + if (client) + client->SetAppearOffline(enable); // sends the toggle to connected sessions + } +} + void NpHandler::DisconnectUser(s32 user_id) { std::shared_ptr client; { @@ -474,6 +486,7 @@ void NpHandler::OnWebApiPushEvent(s32 user_id, const ShadNet::NotifyWebApiPushEv ev.hasTo = true; std::strncpy(ev.toOnlineId.data, n.toNpid.c_str(), sizeof(ev.toOnlineId.data) - 1); } + ev.extdData = n.extdData; // extended-data (key,value) pairs -> dispatched as pExtdData NpWebApi::EnqueuePushEvent(ev); } diff --git a/src/core/libraries/np/np_handler.h b/src/core/libraries/np/np_handler.h index 5572a0a9e..94d95dc22 100644 --- a/src/core/libraries/np/np_handler.h +++ b/src/core/libraries/np/np_handler.h @@ -50,6 +50,13 @@ public: /// True if any user is currently signed in bool IsAnySignedIn() const; + // Set the Appear-Offline preference for all signed-in users (and future logins). While + // enabled, shadNet handles the user as offline for everyone else. Call from the UI/config. + void SetAppearOffline(bool enable); + bool IsAppearOffline() const { + return m_appear_offline.load(); + } + /// Full NP ID for this user, built once from shadnet_npid after login. OrbisNpId GetNpId(s32 user_id) const; @@ -226,6 +233,9 @@ private: // 12-byte NP Communication ID std::string GetNpCommId(s32 service_label) const; + // Appear-Offline preference, applied to every client at login and on change. + std::atomic m_appear_offline{false}; + // Per-user client map mutable std::mutex m_mutex_clients; std::map> m_clients; diff --git a/src/imgui/friends_layer.cpp b/src/imgui/friends_layer.cpp index 27aed77a9..7dc5d754a 100644 --- a/src/imgui/friends_layer.cpp +++ b/src/imgui/friends_layer.cpp @@ -53,6 +53,13 @@ void DrawFriendsTab() { const s32 user_id = users[g_user_index]; const Libraries::Np::NpHandler::FriendListSnapshot fl = np.GetFriendList(user_id); + bool appear_offline = np.IsAppearOffline(); + if (ImGui::Checkbox("Appear offline", &appear_offline)) { + np.SetAppearOffline(appear_offline); + } + ImGui::SameLine(); + ImGui::TextDisabled("(friends see you as offline)"); + ImGui::SeparatorText("Add friend"); ImGui::InputTextWithHint("##addnpid", "Online ID", g_add_buf, sizeof(g_add_buf)); ImGui::SameLine(); diff --git a/src/shadnet/client.cpp b/src/shadnet/client.cpp index 06eaa732e..5c3b59a60 100644 --- a/src/shadnet/client.cpp +++ b/src/shadnet/client.cpp @@ -194,6 +194,9 @@ void ShadNetClient::ConnectThread() { if (!m_token.empty()) req.set_token(m_token); req.set_title_id(std::string(Common::ElfInfo::Instance().GameSerial())); + req.set_title_name(std::string(Common::ElfInfo::Instance().Title())); + // Appear-Offline preference. shadNet handles us as offline for everyone else while set. + req.set_appear_offline(m_appear_offline); const u64 id = m_pkt_counter.fetch_add(1); if (!SendAll(BuildPacket(CommandType::Login, id, MakeProtoPayload(req)))) { @@ -506,6 +509,15 @@ u64 ShadNetClient::RemoveBlock(const std::string& npid) { return SubmitRequest(CommandType::RemoveBlock, MakeProtoPayload(req)); } +u64 ShadNetClient::SetAppearOffline(bool enable) { + m_appear_offline = enable; // cache so the (re)login packet carries the current state + if (!IsAuthenticated()) + return 0; // not connected yet -> login will carry the cached value + shadnet::SetAppearOfflineRequest req; + req.set_appear_offline(enable); + return SubmitRequest(CommandType::SetAppearOffline, MakeProtoPayload(req)); +} + // Packet dispatch void ShadNetClient::DispatchPacket(PacketType type, u16 cmd_raw, u64 pkt_id, @@ -794,8 +806,28 @@ void ShadNetClient::HandleNotification(u16 cmd_raw, const std::vector& paylo n.fromNpid = ExtractBlob(payload, off); off += 4 + static_cast(n.fromNpid.size()); n.toNpid = ExtractBlob(payload, off); - LOG_INFO(ShadNet, "WebApiPushEvent svc='{}' type='{}' from='{}' bytes={}", n.npServiceName, - n.dataType, n.fromNpid, n.data.size()); + off += 4 + static_cast(n.toNpid.size()); + // Optional extended-data section appended after toNpid: u32 LE count, then + // (blob key, blob value) per pair. Absent on older servers -> no bytes left -> + // zero pairs. Length-guarded throughout; count capped to avoid runaway on a + // malformed packet. + if (off + 4 <= static_cast(payload.size())) { + const u32 count = GetLE32(payload.data() + off); + off += 4; + for (u32 i = 0; i < count && i < 256; ++i) { + if (off + 4 > static_cast(payload.size())) + break; + std::string key = ExtractBlob(payload, off); + off += 4 + static_cast(key.size()); + if (off + 4 > static_cast(payload.size())) + break; + std::string val = ExtractBlob(payload, off); + off += 4 + static_cast(val.size()); + n.extdData.emplace_back(std::move(key), std::move(val)); + } + } + LOG_INFO(ShadNet, "WebApiPushEvent svc='{}' type='{}' from='{}' bytes={} extd={}", + n.npServiceName, n.dataType, n.fromNpid, n.data.size(), n.extdData.size()); if (onWebApiPushEvent) onWebApiPushEvent(n); break; diff --git a/src/shadnet/client.h b/src/shadnet/client.h index fc5c8d278..a8b6c5e31 100644 --- a/src/shadnet/client.h +++ b/src/shadnet/client.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "common/types.h" #ifdef _WIN32 @@ -88,6 +89,7 @@ enum class CommandType : u16 { GetScoreAccountId = 37, GetScoreGameDataByAccId = 38, GetToken = 39, + SetAppearOffline = 40, }; enum class NotificationType : u16 { @@ -190,6 +192,9 @@ struct NotifyWebApiPushEvent { std::string data; // raw event body (typically PSN-format JSON) std::string fromNpid; // may be empty std::string toNpid; // may be empty + // Optional extended-data (key,value) pairs (e.g. friendlist trigger/additionalTrigger, + // presence gameStatus/gameData). Empty when the server sends none / is older. + std::vector> extdData; }; struct MatchingBinAttr { @@ -275,6 +280,8 @@ public: u64 RemoveFriend(const std::string& npid); u64 AddBlock(const std::string& npid); u64 RemoveBlock(const std::string& npid); + // Set the Appear-Offline preference mid-session (server handles us as offline while set). + u64 SetAppearOffline(bool enable); private: void ConnectThread(); @@ -306,6 +313,7 @@ private: std::string m_npid; std::string m_password; std::string m_token; + bool m_appear_offline = false; // user's Appear-Offline preference (sent at login) std::atomic m_terminate{false}; std::atomic m_connected{false}; diff --git a/src/shadnet/shadnet.proto b/src/shadnet/shadnet.proto index 4508614b5..b39f8bdca 100644 --- a/src/shadnet/shadnet.proto +++ b/src/shadnet/shadnet.proto @@ -11,10 +11,17 @@ option optimize_for = LITE_RUNTIME; // Account message LoginRequest { - string npid = 1; - string password = 2; - string token = 3; - string title_id = 4; + string npid = 1; + string password = 2; + string token = 3; + string title_id = 4; + string title_name = 5; + bool appear_offline = 6; // user's Appear-Offline preference +} + +// Mid-session Appear-Offline toggle. +message SetAppearOfflineRequest { + bool appear_offline = 1; } message FriendEntry {