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..
This commit is contained in:
georgemoralis 2026-06-30 19:05:16 +03:00 committed by GitHub
parent dacd29077a
commit 1d5447b74c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 83 additions and 6 deletions

View File

@ -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<ShadNet::ShadNetClient> 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);
}

View File

@ -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<bool> m_appear_offline{false};
// Per-user client map
mutable std::mutex m_mutex_clients;
std::map<s32, std::shared_ptr<ShadNet::ShadNetClient>> m_clients;

View File

@ -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();

View File

@ -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<u8>& paylo
n.fromNpid = ExtractBlob(payload, off);
off += 4 + static_cast<int>(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<int>(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<int>(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<int>(payload.size()))
break;
std::string key = ExtractBlob(payload, off);
off += 4 + static_cast<int>(key.size());
if (off + 4 > static_cast<int>(payload.size()))
break;
std::string val = ExtractBlob(payload, off);
off += 4 + static_cast<int>(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;

View File

@ -11,6 +11,7 @@
#include <semaphore>
#include <string>
#include <thread>
#include <utility>
#include <vector>
#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<std::pair<std::string, std::string>> 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<bool> m_terminate{false};
std::atomic<bool> m_connected{false};

View File

@ -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 {