spilt np_handler to multiple files based on it purpose. (#5333)

* spilt np_handler to multiple files based on it purpose . No functionality added

* qos
This commit is contained in:
georgemoralis
2026-10-09 16:51:03 -05:00
committed by GitHub
parent f1d5d5d63a
commit 81be949167
32 changed files with 3699 additions and 3643 deletions
+8 -2
View File
@@ -647,8 +647,14 @@ set(NP_LIBS src/core/libraries/np/np_error.h
src/core/libraries/np/np_partner.cpp
src/core/libraries/np/np_partner.h
src/core/libraries/np/object_manager.h
src/core/libraries/np/np_handler.cpp
src/core/libraries/np/np_handler.h
src/core/libraries/np/np_handler/np_handler.cpp
src/core/libraries/np/np_handler/np_handler.h
src/core/libraries/np/np_handler/np_handler_friends.cpp
src/core/libraries/np/np_handler/np_handler_invitation.cpp
src/core/libraries/np/np_handler/np_handler_score.cpp
src/core/libraries/np/np_handler/np_handler_trophy.cpp
src/core/libraries/np/np_handler/np_handler_tus.cpp
src/core/libraries/np/np_handler/np_handler_web_api.cpp
src/core/libraries/np/signaling_handler.cpp
src/core/libraries/np/signaling_handler.h
src/core/libraries/np/np_commerce/np_commerce.cpp
@@ -9,7 +9,7 @@
#include "core/libraries/error_codes.h"
#include "core/libraries/invitation_dialog/invitation_dialog.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "invitation_dialog_ui.h"
namespace Libraries::InvitationDialog {
@@ -14,7 +14,7 @@
#include "core/libraries/net/net.h"
#include "core/libraries/network/http.h"
#include "core/libraries/network/ssl.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_web_api/np_web_api.h"
#include "core/libraries/system/commondialog.h"
#include "imgui/imgui_std.h"
@@ -9,7 +9,7 @@
#include <vector>
#include "core/libraries/invitation_dialog/invitation_dialog.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/system/commondialog.h"
#include "imgui/imgui_layer.h"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,754 @@
// SPDX-FileCopyrightText: Copyright 2019-2026 rpcs3 Project
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <chrono>
#include <cstring>
#include <fmt/format.h>
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/net/net_p2p.h"
#include "core/libraries/net/net_upnp.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_matching2/np_matching2_internal.h"
#include "core/libraries/np/np_matching2/np_matching2_mm.h"
#include "core/user_settings.h"
#include "imgui/shadnet_notifications_layer.h"
#include "shadnet.pb.h"
#include "shadnet/server_probe.h"
namespace Libraries::Np {
NpHandler::NpHandler()
: m_matching2_contexts(
std::unique_ptr<NpMatching2::ContextManager>(new NpMatching2::ContextManager())),
m_matching2_state(std::make_unique<NpMatching2::NpMatching2State>()) {}
NpHandler::~NpHandler() = default;
NpHandler& NpHandler::GetInstance() {
static NpHandler s_instance;
return s_instance;
}
NpMatching2::ContextManager& NpHandler::GetMatching2ContextManager() {
return *m_matching2_contexts;
}
NpMatching2::NpMatching2State& NpHandler::GetMatching2State() {
return *m_matching2_state;
}
NpHandler::Matching2CacheGuard NpHandler::LockMatching2Cache(
NpMatching2::OrbisNpMatching2ContextId ctx_id) {
std::shared_ptr<NpMatching2::Matching2ContextCache> cache;
{
std::lock_guard lock(m_mutex_matching2_cache);
auto& entry = m_matching2_cache[ctx_id];
if (!entry) {
entry = std::make_shared<NpMatching2::Matching2ContextCache>();
}
cache = entry;
}
return Matching2CacheGuard(std::move(cache));
}
void NpHandler::ResetMatching2Cache(NpMatching2::OrbisNpMatching2ContextId ctx_id) {
std::lock_guard lock(m_mutex_matching2_cache);
m_matching2_cache.erase(ctx_id);
}
void NpHandler::ResetMatching2Caches() {
std::lock_guard lock(m_mutex_matching2_cache);
m_matching2_cache.clear();
}
std::pair<std::string, u16> NpHandler::ParseServerAddress() const {
const std::string server_str = EmulatorSettings.GetShadNetServer();
u16 port = 31313; // default port
if (server_str.empty()) {
LOG_ERROR(NpHandler,
"shadNet server address is empty,set the shadNet server field in settings. "
"Connection will fail.");
return {std::string{}, port};
}
std::string host = server_str;
const auto colon = server_str.rfind(':');
if (colon != std::string::npos) {
host = server_str.substr(0, colon);
const std::string port_str = server_str.substr(colon + 1);
try {
port = static_cast<u16>(std::stoi(port_str));
} catch (const std::exception&) {
LOG_WARNING(NpHandler,
"shadNet server port '{}' is not a valid number; using default {}",
port_str, port);
}
}
return {host, port};
}
bool NpHandler::ConnectUserById(s32 user_id) {
if (!EmulatorSettings.IsShadNetEnabled())
return false;
{
std::lock_guard lock(m_mutex_clients);
if (m_clients.find(user_id) != m_clients.end())
return false; // already connected
}
const User* u = UserManagement.GetUserByID(user_id);
if (!u)
return false;
if (!u->shadnet_enabled) {
LOG_DEBUG(NpHandler, "user_id={} ('{}') shadNet disabled,skipping", u->user_id,
u->user_name);
return false;
}
if (u->shadnet_npid.empty() || u->shadnet_password.empty()) {
LOG_WARNING(NpHandler, "user_id={} ('{}') shadNet enabled but credentials missing,skipping",
u->user_id, u->user_name);
return false;
}
const auto [host, port] = ParseServerAddress();
return ConnectUser(u->user_id, host, port, u->shadnet_npid, u->shadnet_password,
u->shadnet_token);
}
void NpHandler::StartWorker() {
bool expected = false;
if (m_worker_running.compare_exchange_strong(expected, true)) {
m_worker_thread = std::thread(&NpHandler::WorkerThread, this);
}
}
void NpHandler::Initialize() {
if (m_initialized.exchange(true)) {
LOG_WARNING(NpHandler, "Initialize called more than once");
return;
}
if (!EmulatorSettings.IsShadNetEnabled()) {
LOG_INFO(NpHandler, "shadNet disabled globally we are in offline mode");
return;
}
{
const auto [host, port] = ParseServerAddress();
const ShadNet::ProbeInfo probe = ShadNet::ProbeServer(host, port);
if (probe.result != ShadNet::ProbeResult::Ok) {
EmulatorSettings.SetShadNetSessionDisabled(true);
switch (probe.result) {
case ShadNet::ProbeResult::VersionMismatch:
LOG_WARNING(NpHandler,
"shadNet server {}:{} protocol version mismatch (server v{}, emulator "
"v{}); disabling shadNet for this run (saved setting unchanged)",
host, port, probe.server_version, ShadNet::SHAD_PROTOCOL_VERSION);
ImGui::ShadNetNotify::Push(
ImGui::ShadNetNotify::Kind::Info,
fmt::format("shadNet protocol version mismatch (server v{}, emulator v{}). "
"Please update shadPS4. Online features are disabled for this "
"session.",
probe.server_version, ShadNet::SHAD_PROTOCOL_VERSION));
break;
case ShadNet::ProbeResult::ProtocolError:
LOG_WARNING(NpHandler,
"shadNet server {}:{} sent an invalid ServerInfo handshake; disabling "
"shadNet for this run (saved setting unchanged)",
host, port);
ImGui::ShadNetNotify::Push(
ImGui::ShadNetNotify::Kind::Info,
fmt::format("shadNet server ({}:{}) uses an incompatible protocol. Online "
"features are disabled for this session.",
host, port));
break;
default: // Unreachable
LOG_WARNING(NpHandler,
"shadNet server {}:{} is offline/unreachable; disabling shadNet for "
"this run (saved setting unchanged)",
host, port);
ImGui::ShadNetNotify::Push(
ImGui::ShadNetNotify::Kind::Info,
fmt::format("shadNet server ({}:{}) is offline. Online features are disabled "
"for this session.",
host, port));
break;
}
return;
}
}
const auto logged_in = UserManagement.GetLoggedInUsers(); // get all login users
int connected_count = 0;
for (int i = 0; i < Libraries::UserService::ORBIS_USER_SERVICE_MAX_LOGIN_USERS; ++i) {
const User* u = logged_in[i];
if (!u)
continue;
if (ConnectUserById(u->user_id))
++connected_count;
}
if (connected_count == 0) {
LOG_WARNING(NpHandler, "no users connected to shadNet");
return;
}
StartWorker();
}
void NpHandler::Shutdown() {
if (!m_initialized.exchange(false))
return;
m_worker_running = false;
// Stop any pending reconnect retries.
{
std::lock_guard lock(m_mutex_clients);
m_reconnect.clear();
}
// Collect user IDs to disconnect (avoid holding m_mutex_clients during Stop)
std::vector<s32> ids;
{
std::lock_guard lock(m_mutex_clients);
for (auto& [uid, _] : m_clients)
ids.push_back(uid);
}
for (s32 uid : ids)
DisconnectUser(uid);
if (m_worker_thread.joinable())
m_worker_thread.join();
// P2P goes with NP: closes the UDP port and removes its UPnP forwarding.
Net::StopP2P();
LOG_INFO(NpHandler, "Shutdown complete");
}
bool NpHandler::ConnectUser(s32 user_id, const std::string& host, u16 port, const std::string& npid,
const std::string& password, const std::string& token) {
LOG_INFO(NpHandler, "Connecting user_id={} npid='{}' to {}:{} (timeout {}s)", user_id, npid,
host, port, ShadNet::SHAD_CONNECT_TIMEOUT_MS / 1000);
auto client = std::make_shared<ShadNet::ShadNetClient>();
// Wire per-user notification callbacks
client->onFriendQuery = [this, user_id](const ShadNet::NotifyFriendQuery& n) {
OnFriendQuery(user_id, n);
};
client->onFriendNew = [this, user_id](const ShadNet::NotifyFriendNew& n) {
OnFriendNew(user_id, n);
};
client->onFriendLost = [this, user_id](const ShadNet::NotifyFriendLost& n) {
OnFriendLost(user_id, n);
};
client->onFriendStatus = [this, user_id](const ShadNet::NotifyFriendStatus& n) {
OnFriendStatus(user_id, n);
};
client->onWebApiPushEvent = [this, user_id](const ShadNet::NotifyWebApiPushEvent& n) {
OnWebApiPushEvent(user_id, n);
};
client->onAsyncReply = [this, user_id](ShadNet::CommandType cmd, u64 pkt_id,
ShadNet::ErrorType err, const std::vector<u8>& body) {
OnAsyncReply(user_id, cmd, pkt_id, err, body);
};
client->onLoginResult = [this, user_id](const ShadNet::LoginResult& res) {
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);
// Shared handling for an incompatible-protocol failure (version mismatch or
// corrupt/unparseable stream): tell the user and disable shadNet for this run,
// since reconnect attempts against an incompatible server are pointless.
const auto handle_protocol_mismatch = [&client](ShadNet::ShadNetState st) {
if (st != ShadNet::ShadNetState::FailureProtocol)
return;
const u32 server_ver = client->GetServerProtocolVersion();
EmulatorSettings.SetShadNetSessionDisabled(true);
if (server_ver != 0) {
LOG_ERROR(NpHandler,
"shadNet protocol version mismatch (server v{}, emulator v{}); disabling "
"shadNet for this run (saved setting unchanged)",
server_ver, ShadNet::SHAD_PROTOCOL_VERSION);
ImGui::ShadNetNotify::Push(
ImGui::ShadNetNotify::Kind::Info,
fmt::format("shadNet protocol version mismatch (server v{}, emulator v{}). "
"Please update shadPS4. Online features are disabled for this "
"session.",
server_ver, ShadNet::SHAD_PROTOCOL_VERSION));
} else {
LOG_ERROR(NpHandler, "shadNet protocol error during handshake; disabling shadNet for "
"this run (saved setting unchanged)");
ImGui::ShadNetNotify::Push(
ImGui::ShadNetNotify::Kind::Info,
"shadNet server uses an incompatible protocol. Online features are disabled "
"for this session.");
}
};
const ShadNet::ShadNetState conn_state = client->WaitForConnection();
if (conn_state != ShadNet::ShadNetState::Ok) {
LOG_ERROR(NpHandler, "user_id={} connection failed (state={})", user_id,
static_cast<int>(conn_state));
handle_protocol_mismatch(conn_state);
client->Stop();
return false;
}
const ShadNet::ShadNetState auth_state = client->WaitForAuthenticated();
if (auth_state != ShadNet::ShadNetState::Ok) {
LOG_ERROR(NpHandler, "user_id={} authentication failed (state={})", user_id,
static_cast<int>(auth_state));
handle_protocol_mismatch(auth_state);
client->Stop();
return false;
}
LOG_INFO(NpHandler, "user_id={} signed in npid='{}' accountId={}", user_id, npid,
client->GetUserId());
Net::UPnPClient::Instance().SetP2PFeaturesEnabled(client->IsMatching2Enabled());
if (client->IsMatching2Enabled() && EmulatorSettings.IsUPnPEnabled()) {
Net::UPnPClient::Instance().Start();
}
NpMatching2::SetMmShadNetClient(client, host, port);
// Build OrbisNpId
{
OrbisNpId np_id{};
SetNpId(np_id, npid);
std::lock_guard lock(m_mutex_clients);
m_np_ids[user_id] = np_id;
m_clients[user_id] = std::move(client);
}
FireStateCallback(user_id, NpManager::OrbisNpState::SignedIn);
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::FailPendingRequests(s32 user_id, s32 error_code) {
size_t failed = 0;
{
std::lock_guard lock(m_mutex_pending_tus);
for (auto it = m_pending_tus.begin(); it != m_pending_tus.end();) {
if (it->second.user_id == user_id) {
if (it->second.req) {
it->second.req->SetResult(error_code);
}
it = m_pending_tus.erase(it);
++failed;
} else {
++it;
}
}
}
{
std::lock_guard lock(m_mutex_pending_score);
for (auto it = m_pending_score.begin(); it != m_pending_score.end();) {
if (it->second.user_id == user_id) {
if (it->second.req) {
it->second.req->SetResult(error_code);
}
it = m_pending_score.erase(it);
++failed;
} else {
++it;
}
}
}
{
std::lock_guard lock(m_mutex_pending_lookup);
for (auto it = m_pending_lookup.begin(); it != m_pending_lookup.end();) {
if (it->second.user_id == user_id) {
if (it->second.on_result) {
it->second.on_result(error_code, 0, {});
}
it = m_pending_lookup.erase(it);
++failed;
} else {
++it;
}
}
}
if (failed > 0) {
LOG_WARNING(NpHandler, "user_id={} failed {} pending request(s) with {:#x}", user_id,
failed, static_cast<u32>(error_code));
}
}
void NpHandler::DisconnectUser(s32 user_id) {
std::shared_ptr<ShadNet::ShadNetClient> client;
{
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end())
return;
client = std::move(it->second);
m_clients.erase(it);
}
{
std::lock_guard lock(m_mutex_friend_state);
m_friend_state.erase(user_id);
}
client->Stop();
// The reader thread is joined, so no reply can race us: complete every
// in-flight request now, or PollAsync spins and WaitAsync blocks forever.
FailPendingRequests(user_id, ORBIS_NP_ERROR_SIGNED_OUT);
FireStateCallback(user_id, NpManager::OrbisNpState::SignedOut);
LOG_INFO(NpHandler, "user_id={} disconnected", user_id);
}
void NpHandler::OnUserLoggedIn(s32 user_id) {
if (ConnectUserById(user_id))
StartWorker();
}
void NpHandler::OnUserLoggedOut(s32 user_id) {
{
std::lock_guard lock(m_mutex_clients);
m_reconnect.erase(user_id); // do not auto-reconnect
}
DisconnectUser(user_id);
}
void NpHandler::WorkerThread() {
constexpr auto INTERVAL = std::chrono::milliseconds(500);
while (m_worker_running) {
std::this_thread::sleep_for(INTERVAL);
// Collect ids of dropped clients
std::vector<s32> dropped;
{
std::lock_guard lock(m_mutex_clients);
for (auto& [uid, client] : m_clients) {
if (!client->IsConnected())
dropped.push_back(uid);
}
}
for (s32 uid : dropped) {
LOG_WARNING(NpHandler, "user_id={} connection dropped (network); will retry", uid);
DisconnectUser(uid); // reports SignedOut + stops/removes the dead client
MarkForReconnect(uid); // schedule transparent reconnect (network drop, not logout)
}
TryReconnect();
}
}
void NpHandler::MarkForReconnect(s32 user_id) {
if (!EmulatorSettings.IsShadNetEnabled())
return; // offline mode: nothing to reconnect to
constexpr auto kInitialBackoff = std::chrono::milliseconds(2000);
std::lock_guard lock(m_mutex_clients);
auto& st = m_reconnect[user_id];
st.backoff = kInitialBackoff;
st.next_attempt = std::chrono::steady_clock::now() + st.backoff;
}
void NpHandler::TryReconnect() {
constexpr auto kMaxBackoff = std::chrono::milliseconds(30000);
const auto now = std::chrono::steady_clock::now();
std::vector<s32> due;
{
std::lock_guard lock(m_mutex_clients);
for (auto& [uid, st] : m_reconnect) {
if (m_clients.count(uid) || now >= st.next_attempt)
due.push_back(uid);
}
}
for (s32 uid : due) {
if (!m_worker_running)
return;
if (!EmulatorSettings.IsShadNetEnabled()) {
std::lock_guard lock(m_mutex_clients);
m_reconnect.erase(uid);
continue;
}
// ConnectUserById locks m_mutex_clients internally; call it unlocked.
const bool ok = ConnectUserById(uid);
std::lock_guard lock(m_mutex_clients);
if (ok || m_clients.count(uid)) {
m_reconnect.erase(uid);
LOG_INFO(NpHandler, "user_id={} reconnected to shadNet", uid);
} else if (auto it = m_reconnect.find(uid); it != m_reconnect.end()) {
it->second.backoff = std::min(it->second.backoff * 2, kMaxBackoff);
it->second.next_attempt = std::chrono::steady_clock::now() + it->second.backoff;
LOG_DEBUG(NpHandler, "user_id={} reconnect failed; next attempt in {}ms", uid,
it->second.backoff.count());
}
}
}
bool NpHandler::IsPsnSignedIn(s32 user_id) const {
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
return it != m_clients.end() && it->second->IsAuthenticated();
}
bool NpHandler::IsAnySignedIn() const {
std::lock_guard lock(m_mutex_clients);
for (auto& [_, client] : m_clients)
if (client->IsAuthenticated())
return true;
return false;
}
OrbisNpId NpHandler::GetNpId(s32 user_id) const {
std::lock_guard lock(m_mutex_clients);
auto it = m_np_ids.find(user_id);
return it != m_np_ids.end() ? it->second : OrbisNpId{};
}
OrbisNpOnlineId NpHandler::GetOnlineId(s32 user_id) const {
return GetNpId(user_id).handle;
}
std::string NpHandler::GetAvatarUrl(s32 user_id) const {
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
return it != m_clients.end() ? it->second->GetAvatarUrl() : std::string{};
}
OrbisNpAccountId NpHandler::GetAccountId(s32 user_id) const {
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
return it != m_clients.end() ? static_cast<OrbisNpAccountId>(it->second->GetUserId()) : 0;
}
std::string NpHandler::GetBearerToken(s32 user_id) const {
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
return it != m_clients.end() ? it->second->GetBearerToken() : std::string{};
}
u32 NpHandler::GetLocalIpAddr(s32 user_id) const {
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
return it != m_clients.end() ? it->second->GetAddrLocal() : 0;
}
s32 NpHandler::GetUserIdByAccountId(u64 account_id) const {
std::lock_guard lock(m_mutex_clients);
for (auto& [uid, client] : m_clients) {
if (static_cast<u64>(client->GetUserId()) == account_id)
return uid;
}
return -1;
}
s32 NpHandler::GetUserIdByOnlineId(const OrbisNpOnlineId& online_id) const {
std::lock_guard lock(m_mutex_clients);
for (auto& [uid, np_id] : m_np_ids) {
if (strncmp(np_id.handle.data, online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH) == 0)
return uid;
}
return -1;
}
std::vector<s32> NpHandler::GetConnectedUsers() const {
std::vector<s32> out;
std::lock_guard lock(m_mutex_clients);
out.reserve(m_clients.size());
for (const auto& [uid, c] : m_clients) {
out.push_back(uid);
}
return out;
}
// state callbacks
s32 NpHandler::RegisterStateCallback(StateCallback cb, void* userdata) {
std::lock_guard lock(m_mutex_cbs);
const s32 h = m_next_handle++;
m_state_cbs.push_back({h, std::move(cb), userdata});
return h;
}
void NpHandler::UnregisterStateCallback(s32 handle) {
std::lock_guard lock(m_mutex_cbs);
m_state_cbs.erase(std::remove_if(m_state_cbs.begin(), m_state_cbs.end(),
[handle](const CbEntry& e) { return e.handle == handle; }),
m_state_cbs.end());
}
void NpHandler::FireStateCallback(s32 user_id, NpManager::OrbisNpState state) {
std::lock_guard lock(m_mutex_cbs);
for (const auto& e : m_state_cbs) {
if (e.cb)
e.cb(user_id, state);
}
}
// A usable NP Communication ID is the 12-char "NPWRxxxxx_00" form. An empty or
// all-NUL buffer means npbind.dat was missing or unparsed,treat it as invalid
// so callers reject the request instead of sending a blank comm id on the wire.
bool IsValidNpCommId(const std::string& id) {
return !id.empty() && std::any_of(id.begin(), id.end(), [](char c) { return c != '\0'; });
}
std::string NpHandler::GetNpCommId(s32 service_label) const {
// TODO complete guess of how commid is mapping to service_label.
constexpr size_t COM_ID_LEN = 12;
const auto& ids = Common::ElfInfo::Instance().GetNpCommIds();
std::string com_id;
if (!ids.empty()) {
const size_t idx = (service_label >= 0 && static_cast<size_t>(service_label) < ids.size())
? static_cast<size_t>(service_label)
: 0;
com_id = ids[idx];
if (static_cast<size_t>(service_label) >= ids.size()) {
LOG_WARNING(NpHandler,
"service_label={} >= npbind entry count {} — falling back "
"to index 0",
service_label, ids.size());
}
}
if (com_id.size() < COM_ID_LEN) {
com_id.resize(COM_ID_LEN, '\0');
} else if (com_id.size() > COM_ID_LEN) {
com_id.resize(COM_ID_LEN);
}
return com_id;
}
void NpHandler::OnAsyncReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
ShadNet::ErrorType error, const std::vector<u8>& body) {
const auto cmd_val = static_cast<u16>(cmd);
if (cmd_val >= 100 && cmd_val <= 200) {
NpMatching2::OnMatchingReply(cmd, pkt_id, error, body);
} else if (cmd_val >= 201 && cmd_val <= 300) {
OnTusReply(user_id, cmd, pkt_id, error, body);
} else if (cmd_val >= 301 && cmd_val <= 400) {
OnTrophyReply(user_id, cmd, pkt_id, error, body);
} else if (cmd == ShadNet::CommandType::LookupOnlineId) {
OnLookupReply(user_id, cmd, pkt_id, error, body);
} else {
OnScoreReply(user_id, cmd, pkt_id, error, body);
}
}
void NpHandler::ResolveOnlineId(
s32 user_id, const std::string& online_id,
std::function<void(s32 result, u64 account_id, const std::string& canonical_online_id)>
on_result) {
std::shared_ptr<ShadNet::ShadNetClient> client;
{
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_WARNING(NpHandler, "No shadNet session for user_id={}", user_id);
if (on_result) {
on_result(ORBIS_NP_ERROR_SIGNED_OUT, 0, {});
}
return;
}
client = it->second;
}
if (!client || !client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "user_id={} not authenticated", user_id);
if (on_result) {
on_result(ORBIS_NP_COMMUNITY_ERROR_NO_LOGIN, 0, {});
}
return;
}
if (online_id.empty()) {
if (on_result) {
on_result(ORBIS_NP_COMMUNITY_SERVER_ERROR_NO_SUCH_USER_NPID, 0, {});
}
return;
}
const u64 pkt_id = client->LookupOnlineId(online_id);
if (on_result) {
std::lock_guard lock(m_mutex_pending_lookup);
PendingLookupRequest pending;
pending.user_id = user_id;
pending.on_result = std::move(on_result);
m_pending_lookup.emplace(pkt_id, std::move(pending));
}
LOG_INFO(NpHandler, "user_id={} onlineId='{}' pkt_id={}", user_id, online_id, pkt_id);
}
void NpHandler::OnLookupReply(s32 user_id, ShadNet::CommandType /*cmd*/, u64 pkt_id,
ShadNet::ErrorType error, const std::vector<u8>& body) {
std::function<void(s32, u64, const std::string&)> on_result;
{
std::lock_guard lock(m_mutex_pending_lookup);
auto it = m_pending_lookup.find(pkt_id);
if (it == m_pending_lookup.end()) {
return; // no waiter (already flushed by a disconnect, or a stray reply)
}
on_result = std::move(it->second.on_result);
m_pending_lookup.erase(it);
}
if (!on_result) {
return;
}
if (error == ShadNet::ErrorType::NotFound) {
LOG_INFO(NpHandler, "LookupOnlineId: user_id={} pkt_id={} -> not found", user_id, pkt_id);
on_result(ORBIS_NP_COMMUNITY_SERVER_ERROR_NO_SUCH_USER_NPID, 0, {});
return;
}
if (error == ShadNet::ErrorType::InvalidInput) {
on_result(ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT, 0, {});
return;
}
if (error != ShadNet::ErrorType::NoError) {
LOG_WARNING(NpHandler, "LookupOnlineId failed: error={}", static_cast<int>(error));
on_result(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE, 0, {});
return;
}
// Reply body is u32 LE blob size followed by the proto.
if (body.size() < 4) {
LOG_WARNING(NpHandler, "LookupOnlineId: truncated reply");
on_result(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE, 0, {});
return;
}
const u32 blob_sz = static_cast<u32>(body[0]) | (static_cast<u32>(body[1]) << 8) |
(static_cast<u32>(body[2]) << 16) | (static_cast<u32>(body[3]) << 24);
if (body.size() < 4 + blob_sz) {
LOG_WARNING(NpHandler, "LookupOnlineId: reply shorter than declared blob");
on_result(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE, 0, {});
return;
}
shadnet::LookupOnlineIdReply pb;
if (!pb.ParseFromArray(body.data() + 4, static_cast<int>(blob_sz))) {
LOG_WARNING(NpHandler, "LookupOnlineId: could not parse reply");
on_result(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE, 0, {});
return;
}
LOG_INFO(NpHandler, "LookupOnlineId: user_id={} pkt_id={} -> accountId={} npid='{}'", user_id,
pkt_id, pb.account_id(), pb.npid());
on_result(ORBIS_OK, pb.account_id(), pb.npid());
}
} // namespace Libraries::Np
@@ -37,6 +37,8 @@ class ContextManager;
struct NpMatching2State;
} // namespace NpMatching2
bool IsValidNpCommId(const std::string& id);
class NpHandler {
public:
class Matching2CacheGuard {
@@ -0,0 +1,232 @@
// SPDX-FileCopyrightText: Copyright 2019-2026 rpcs3 Project
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "common/logging/log.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "imgui/shadnet_notifications_layer.h"
namespace Libraries::Np {
// friends calls
u32 NpHandler::GetNumFriends(s32 user_id) const {
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
return it != m_clients.end() ? it->second->GetNumFriends() : 0;
}
std::optional<std::string> NpHandler::GetFriendNpid(s32 user_id, u32 index) const {
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
return it != m_clients.end() ? it->second->GetFriendNpid(index) : std::nullopt;
}
void NpHandler::OnFriendQuery(s32 user_id, const ShadNet::NotifyFriendQuery& n) {
LOG_INFO(NpHandler, "user_id={} FriendQuery from '{}'", user_id, n.fromNpid);
ImGui::ShadNetNotify::Push(ImGui::ShadNetNotify::Kind::FriendRequest,
"Friend request from " + n.fromNpid);
std::lock_guard lock(m_mutex_friend_state);
auto& st = m_friend_state[user_id];
if (std::find(st.requests_received.begin(), st.requests_received.end(), n.fromNpid) ==
st.requests_received.end()) {
st.requests_received.push_back(n.fromNpid);
}
}
void NpHandler::OnFriendNew(s32 user_id, const ShadNet::NotifyFriendNew& n) {
LOG_INFO(NpHandler, "user_id={} FriendNew '{}' ({})", user_id, n.npid,
n.online ? "online" : "offline");
ImGui::ShadNetNotify::Push(ImGui::ShadNetNotify::Kind::FriendNew,
n.npid + " is now your friend");
std::lock_guard lock(m_mutex_friend_state);
auto& st = m_friend_state[user_id];
auto it = std::find_if(st.friends.begin(), st.friends.end(),
[&](const FriendInfo& f) { return f.npid == n.npid; });
if (it == st.friends.end()) {
st.friends.push_back({n.npid, n.online});
} else {
it->online = n.online;
}
const auto drop = [&](std::vector<std::string>& v) {
v.erase(std::remove(v.begin(), v.end(), n.npid), v.end());
};
drop(st.requests_received);
drop(st.requests_sent);
}
void NpHandler::OnFriendLost(s32 user_id, const ShadNet::NotifyFriendLost& n) {
LOG_INFO(NpHandler, "user_id={} FriendLost '{}'", user_id, n.npid);
ImGui::ShadNetNotify::Push(ImGui::ShadNetNotify::Kind::FriendLost,
n.npid + " removed you as a friend");
std::lock_guard lock(m_mutex_friend_state);
auto& st = m_friend_state[user_id];
st.friends.erase(std::remove_if(st.friends.begin(), st.friends.end(),
[&](const FriendInfo& f) { return f.npid == n.npid; }),
st.friends.end());
}
void NpHandler::OnFriendStatus(s32 user_id, const ShadNet::NotifyFriendStatus& n) {
LOG_DEBUG(NpHandler, "user_id={} FriendStatus '{}' is {}", user_id, n.npid,
n.online ? "online" : "offline");
if (n.online) {
ImGui::ShadNetNotify::Push(ImGui::ShadNetNotify::Kind::Online, n.npid + " is online");
}
std::lock_guard lock(m_mutex_friend_state);
auto& st = m_friend_state[user_id];
auto it = std::find_if(st.friends.begin(), st.friends.end(),
[&](const FriendInfo& f) { return f.npid == n.npid; });
if (it != st.friends.end()) {
it->online = n.online;
} else {
st.friends.push_back({n.npid, n.online});
}
}
void NpHandler::OnLoginResult(s32 user_id, const ShadNet::LoginResult& res) {
if (res.error != ShadNet::ErrorType::NoError) {
return;
}
FriendListSnapshot snap;
snap.friends.reserve(res.friends.size());
for (const auto& f : res.friends) {
snap.friends.push_back({f.npid, f.online});
}
snap.requests_sent = res.requestsSent;
snap.requests_received = res.requestsReceived;
snap.blocked = res.blocked;
{
std::lock_guard lock(m_mutex_friend_state);
m_friend_state[user_id] = std::move(snap);
}
LOG_INFO(NpHandler,
"user_id={} friend state seeded: {} friends, {} received, {} sent, {} blocked",
user_id, res.friends.size(), res.requestsReceived.size(), res.requestsSent.size(),
res.blocked.size());
// Surface friend requests that arrived while offline (no live notification fires for these).
if (!res.requestsReceived.empty()) {
ImGui::ShadNetNotify::Push(ImGui::ShadNetNotify::Kind::FriendRequest,
std::to_string(res.requestsReceived.size()) +
" pending friend request(s)");
}
}
NpHandler::FriendListSnapshot NpHandler::GetFriendList(s32 user_id) const {
std::lock_guard lock(m_mutex_friend_state);
const auto it = m_friend_state.find(user_id);
return it == m_friend_state.end() ? FriendListSnapshot{} : it->second;
}
s32 NpHandler::SendFriendRequest(s32 user_id, const std::string& npid) {
std::shared_ptr<ShadNet::ShadNetClient> client;
{
std::lock_guard lock(m_mutex_clients);
const auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
if (!client->IsAuthenticated()) {
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client->AddFriend(npid);
LOG_INFO(NpHandler, "user_id={} AddFriend '{}'", user_id, npid);
std::lock_guard lock(m_mutex_friend_state);
auto& st = m_friend_state[user_id];
const bool accepting = std::find(st.requests_received.begin(), st.requests_received.end(),
npid) != st.requests_received.end();
if (!accepting && std::find(st.requests_sent.begin(), st.requests_sent.end(), npid) ==
st.requests_sent.end()) {
st.requests_sent.push_back(npid);
}
return ORBIS_OK;
}
s32 NpHandler::RemoveFriend(s32 user_id, const std::string& npid) {
std::shared_ptr<ShadNet::ShadNetClient> client;
{
std::lock_guard lock(m_mutex_clients);
const auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
if (!client->IsAuthenticated()) {
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client->RemoveFriend(npid);
LOG_INFO(NpHandler, "user_id={} RemoveFriend '{}'", user_id, npid);
std::lock_guard lock(m_mutex_friend_state);
auto& st = m_friend_state[user_id];
st.friends.erase(std::remove_if(st.friends.begin(), st.friends.end(),
[&](const FriendInfo& f) { return f.npid == npid; }),
st.friends.end());
const auto drop = [&](std::vector<std::string>& v) {
v.erase(std::remove(v.begin(), v.end(), npid), v.end());
};
drop(st.requests_received);
drop(st.requests_sent);
return ORBIS_OK;
}
s32 NpHandler::BlockUser(s32 user_id, const std::string& npid) {
std::shared_ptr<ShadNet::ShadNetClient> client;
{
std::lock_guard lock(m_mutex_clients);
const auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
if (!client->IsAuthenticated()) {
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client->AddBlock(npid);
LOG_INFO(NpHandler, "user_id={} AddBlock '{}'", user_id, npid);
std::lock_guard lock(m_mutex_friend_state);
auto& st = m_friend_state[user_id];
if (std::find(st.blocked.begin(), st.blocked.end(), npid) == st.blocked.end()) {
st.blocked.push_back(npid);
}
st.friends.erase(std::remove_if(st.friends.begin(), st.friends.end(),
[&](const FriendInfo& f) { return f.npid == npid; }),
st.friends.end());
const auto drop = [&](std::vector<std::string>& v) {
v.erase(std::remove(v.begin(), v.end(), npid), v.end());
};
drop(st.requests_received);
drop(st.requests_sent);
return ORBIS_OK;
}
s32 NpHandler::UnblockUser(s32 user_id, const std::string& npid) {
std::shared_ptr<ShadNet::ShadNetClient> client;
{
std::lock_guard lock(m_mutex_clients);
const auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
if (!client->IsAuthenticated()) {
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client->RemoveBlock(npid);
LOG_INFO(NpHandler, "user_id={} RemoveBlock '{}'", user_id, npid);
std::lock_guard lock(m_mutex_friend_state);
auto& st = m_friend_state[user_id];
st.blocked.erase(std::remove(st.blocked.begin(), st.blocked.end(), npid), st.blocked.end());
return ORBIS_OK;
}
} // namespace Libraries::Np
@@ -0,0 +1,221 @@
// SPDX-FileCopyrightText: Copyright 2019-2026 rpcs3 Project
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <chrono>
#include <cstring>
#include <httplib.h>
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#include "core/libraries/invitation_dialog/invitation_dialog.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/system/systemservice.h"
#include "imgui/invitation_prompt_layer.h"
namespace Libraries::Np {
// Minimal JSON string escaper for the invitation-request body (npids are safe but the user message
// can contain quotes/backslashes/control chars).
std::string JsonEscape(const std::string& in) {
std::string out;
out.reserve(in.size() + 8);
for (const char c : in) {
switch (c) {
case '"':
out += "\\\"";
break;
case '\\':
out += "\\\\";
break;
case '\n':
out += "\\n";
break;
case '\r':
out += "\\r";
break;
case '\t':
out += "\\t";
break;
default:
if (static_cast<unsigned char>(c) < 0x20) {
char buf[8];
std::snprintf(buf, sizeof(buf), "\\u%04x", c);
out += buf;
} else {
out += c;
}
}
}
return out;
}
bool NpHandler::SendSessionInvitation(s32 user_id, const std::string& session_id,
const std::vector<std::string>& to,
const std::string& message) {
if (session_id.empty() || to.empty()) {
LOG_ERROR(NpHandler, "empty session_id or recipient list");
return false;
}
const std::string base_url = EmulatorSettings.GetShadNetWebApiServer();
if (base_url.empty()) {
LOG_ERROR(NpHandler, "WebAPI server address is empty");
return false;
}
const std::string token = GetBearerToken(user_id);
if (token.empty()) {
LOG_ERROR(NpHandler, "no bearer token for user_id={}", user_id);
return false;
}
// invitation-request JSON: {"to":[...],"message":"..."}
std::string json = "{\"to\":[";
for (size_t i = 0; i < to.size(); ++i) {
if (i != 0) {
json += ',';
}
json += '"';
json += JsonEscape(to[i]);
json += '"';
}
json += ']';
if (!message.empty()) {
json += ",\"message\":\"";
json += JsonEscape(message);
json += '"';
}
json += '}';
// multipart/mixed with a single JSON part; shadNet keys on the Content-Description.
const std::string boundary = "shadps4invite" + std::to_string(user_id);
std::string body;
body += "--" + boundary + "\r\n";
body += "Content-Type: application/json; charset=utf-8\r\n";
body += "Content-Description: invitation-request\r\n\r\n";
body += json;
body += "\r\n--" + boundary + "--\r\n";
const std::string path = "/v1/sessions/" + session_id + "/invitations";
const std::string content_type = "multipart/mixed; boundary=" + boundary;
httplib::Client cli(base_url);
cli.set_connection_timeout(5);
cli.set_read_timeout(10);
const httplib::Headers headers = {{"Authorization", "Bearer " + token}};
const auto res = cli.Post(path.c_str(), headers, body, content_type.c_str());
if (!res) {
LOG_ERROR(NpHandler, "POST {} failed (no response)", path);
return false;
}
if (res->status != 200 && res->status != 204) {
LOG_ERROR(NpHandler, "POST {} -> HTTP {}", path, res->status);
return false;
}
LOG_INFO(NpHandler, "sent invite to {} recipient(s) for session '{}'", to.size(), session_id);
return true;
}
void NpHandler::PostSessionInvitationEvent(s32 user_id, const std::string& session_id,
const std::string& invitation_id,
const std::string& accepter_online_id,
const std::string& inviter_online_id,
OrbisNpAccountId inviter_account_id) {
using Libraries::InvitationDialog::ORBIS_NP_SESSION_INVITATION_EVENT_FLAG_INVITATION;
using Libraries::InvitationDialog::OrbisNpSessionInvitationEventParam;
Libraries::SystemService::OrbisSystemServiceEvent event{};
event.event_type = Libraries::SystemService::OrbisSystemServiceEventType::SessionInvitation;
auto* param = reinterpret_cast<OrbisNpSessionInvitationEventParam*>(event.param);
std::memset(param, 0, sizeof(*param));
std::strncpy(param->sessionId.data, session_id.c_str(), sizeof(param->sessionId.data) - 1);
if (!invitation_id.empty()) {
std::strncpy(param->invitationId.data, invitation_id.c_str(),
sizeof(param->invitationId.data) - 1);
param->flag = ORBIS_NP_SESSION_INVITATION_EVENT_FLAG_INVITATION;
} else {
param->flag = 0; // join from session info (no invitation id in the push)
}
// data[16] is followed by its own 'term' byte, so a full 16-char online id must not be cut.
std::strncpy(param->onlineId.data, accepter_online_id.c_str(), sizeof(param->onlineId.data));
param->userId = user_id;
std::strncpy(param->referralOnlineId.data, inviter_online_id.c_str(),
sizeof(param->referralOnlineId.data));
param->referralAccountId = inviter_account_id;
Libraries::SystemService::PushSystemServiceEvent(event);
LOG_INFO(NpHandler,
"Posted SESSION_INVITATION user_id={} session='{}' invitation='{}' flag={} "
"onlineId='{}' referral='{}'({})",
user_id, session_id, invitation_id, param->flag, accepter_online_id, inviter_online_id,
inviter_account_id);
}
std::vector<NpHandler::PendingInvitation> NpHandler::GetPendingInvitations(s32 user_id) const {
std::lock_guard lk(m_mutex_pending_invites);
const auto it = m_pending_invites.find(user_id);
if (it == m_pending_invites.end()) {
return {};
}
const auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
std::vector<PendingInvitation> out;
out.reserve(it->second.size());
for (const auto& p : it->second) {
if (p.valid_until == 0 || now <= p.valid_until) {
out.push_back(p);
}
}
return out;
}
bool NpHandler::AcceptSessionInvitation(s32 user_id, const std::string& invitation_id) {
// Pull the matching stashed invite (and drop it).
PendingInvitation inv;
bool found = false;
{
std::lock_guard lk(m_mutex_pending_invites);
const auto mit = m_pending_invites.find(user_id);
if (mit != m_pending_invites.end()) {
auto& v = mit->second;
for (auto it = v.begin(); it != v.end(); ++it) {
if (it->invitation_id == invitation_id) {
inv = *it;
v.erase(it);
found = true;
break;
}
}
}
}
// Whatever surface handled it (RECV dialog or the emulator prompt), retire the other one.
ImGui::InvitationPrompt::Dismiss(invitation_id);
if (!found) {
LOG_ERROR(NpHandler, "no pending invite '{}' for user_id={}", invitation_id, user_id);
return false;
}
// Raise the join event now that the user has explicitly accepted (via the RECV dialog or the
// emulator's system-UI equivalent).
PostSessionInvitationEvent(user_id, inv.session_id, invitation_id, inv.to_npid, inv.from_npid,
inv.from_account_id);
LOG_INFO(NpHandler, "accepted '{}' session='{}'", invitation_id, inv.session_id);
return true;
}
void NpHandler::DeclineSessionInvitation(s32 user_id, const std::string& invitation_id) {
ImGui::InvitationPrompt::Dismiss(invitation_id);
std::lock_guard lock(m_mutex_pending_invites);
auto uit = m_pending_invites.find(user_id);
if (uit == m_pending_invites.end()) {
return;
}
auto& v = uit->second;
v.erase(std::remove_if(
v.begin(), v.end(),
[&](const PendingInvitation& p) { return p.invitation_id == invitation_id; }),
v.end());
LOG_INFO(NpHandler, "dismissed '{}' for user_id={}", invitation_id, user_id);
}
} // namespace Libraries::Np
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,149 @@
// SPDX-FileCopyrightText: Copyright 2019-2026 rpcs3 Project
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "shadnet.pb.h"
namespace Libraries::Np {
void NpHandler::ReportTrophyUnlock(s32 user_id, s32 service_label, s32 trophy_id, u64 timestamp) {
std::shared_ptr<ShadNet::ShadNetClient> client;
{
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_DEBUG(NpHandler, "no shadNet session for user_id={}; skipping", user_id);
return;
}
client = it->second;
}
if (!client) {
LOG_DEBUG(NpHandler, "null client for user_id={}; skipping", user_id);
return;
}
if (!client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "not authenticated; trophy {} not mirrored", trophy_id);
return;
}
if (!client->IsTrophiesEnabled()) {
LOG_WARNING(NpHandler,
"server has not advertised trophy support; trophy {} "
"not mirrored",
trophy_id);
return;
}
const std::string com_id = GetNpCommId(service_label);
if (!IsValidNpCommId(com_id)) {
LOG_WARNING(NpHandler,
"no valid NP Communication ID for service_label={}; "
"skipping trophy {}",
service_label, trophy_id);
return;
}
const u64 pkt_id = client->UnlockTrophy(com_id, trophy_id, timestamp);
LOG_INFO(NpHandler, "user_id={} trophy={} com_id='{}' pkt_id={}", user_id, trophy_id, com_id,
pkt_id);
}
void NpHandler::SyncTrophies(
s32 user_id, s32 service_label, const std::vector<std::pair<s32, u64>>& local_trophies,
std::function<void(const std::vector<std::pair<s32, u64>>&)> on_merged) {
std::shared_ptr<ShadNet::ShadNetClient> client;
{
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
LOG_DEBUG(NpHandler, "no shadNet session for user_id={}; skipping", user_id);
return;
}
client = it->second;
}
if (!client) {
LOG_DEBUG(NpHandler, "null client for user_id={}; skipping", user_id);
return;
}
if (!client->IsAuthenticated()) {
LOG_WARNING(NpHandler, "not authenticated; skipping sync");
return;
}
if (!client->IsTrophiesEnabled()) {
LOG_WARNING(NpHandler, "server has not advertised trophy support; "
"skipping sync");
return;
}
const std::string com_id = GetNpCommId(service_label);
if (!IsValidNpCommId(com_id)) {
LOG_WARNING(NpHandler, "no valid NP Communication ID for service_label={}; skipping",
service_label);
return;
}
const u64 pkt_id = client->SyncTrophies(com_id, local_trophies);
if (on_merged) {
std::lock_guard lock(m_mutex_pending_trophy);
m_pending_trophy.emplace(pkt_id, std::move(on_merged));
}
LOG_INFO(NpHandler, "user_id={} uploading {} com_id='{}' pkt_id={}", user_id,
local_trophies.size(), com_id, pkt_id);
}
void NpHandler::OnTrophyReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
ShadNet::ErrorType error, const std::vector<u8>& body) {
if (cmd == ShadNet::CommandType::UnlockTrophy) {
if (error != ShadNet::ErrorType::NoError) {
LOG_WARNING(NpHandler, "UnlockTrophy rejected by server: error={}",
static_cast<int>(error));
}
return;
}
if (cmd != ShadNet::CommandType::SyncTrophies)
return;
std::function<void(const std::vector<std::pair<s32, u64>>&)> on_merged;
{
std::lock_guard lock(m_mutex_pending_trophy);
auto it = m_pending_trophy.find(pkt_id);
if (it == m_pending_trophy.end())
return;
on_merged = std::move(it->second);
m_pending_trophy.erase(it);
}
if (error != ShadNet::ErrorType::NoError) {
LOG_WARNING(NpHandler, "SyncTrophies failed: error={}", static_cast<int>(error));
return; // leave local trophies exactly as they were
}
// Reply body is u32 LE blob size followed by the proto.
if (body.size() < 4) {
LOG_WARNING(NpHandler, "SyncTrophies: truncated reply");
return;
}
const u32 blob_sz = static_cast<u32>(body[0]) | (static_cast<u32>(body[1]) << 8) |
(static_cast<u32>(body[2]) << 16) | (static_cast<u32>(body[3]) << 24);
if (body.size() < 4 + blob_sz) {
LOG_WARNING(NpHandler, "SyncTrophies: reply shorter than declared blob");
return;
}
shadnet::SyncTrophiesReply pb;
if (!pb.ParseFromArray(body.data() + 4, static_cast<int>(blob_sz))) {
LOG_WARNING(NpHandler, "could not parse reply");
return;
}
std::vector<std::pair<s32, u64>> merged;
merged.reserve(pb.trophies_size());
for (const auto& t : pb.trophies())
merged.emplace_back(t.trophyid(), t.timestamp());
LOG_INFO(NpHandler, "server holds {} trophies for user_id={}", merged.size(), user_id);
on_merged(merged);
}
} // namespace Libraries::Np
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: Copyright 2019-2026 rpcs3 Project
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <cstring>
#include "common/logging/log.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_web_api/np_web_api.h"
#include "imgui/invitation_prompt_layer.h"
namespace Libraries::Np {
void NpHandler::OnWebApiPushEvent(s32 user_id, const ShadNet::NotifyWebApiPushEvent& n) {
LOG_INFO(NpHandler, "user_id={} WebApiPushEvent svc='{}' type='{}' bytes={}", user_id,
n.npServiceName, n.dataType, n.data.size());
NpWebApi::PushEventInput ev;
ev.targetUserId = user_id;
ev.npServiceName = n.npServiceName;
ev.npServiceLabel = n.npServiceLabel;
ev.dataType = n.dataType;
ev.data = n.data;
if (!n.fromNpid.empty()) {
ev.hasFrom = true;
SetNpOnlineId(ev.fromOnlineId, n.fromNpid);
}
if (!n.toNpid.empty()) {
ev.hasTo = true;
SetNpOnlineId(ev.toOnlineId, n.toNpid);
}
ev.extdData = n.extdData; // extended-data (key,value) pairs -> dispatched as pExtdData
// Account ids for pFrom/pTo.
ev.toAccountId = n.toAccountId != 0 ? n.toAccountId : GetAccountId(user_id);
ev.fromAccountId = n.fromAccountId;
if (ev.fromAccountId == 0) {
for (const auto& kv : n.extdData) {
if (kv.first == "fromAccountId") {
ev.fromAccountId = std::strtoull(kv.second.c_str(), nullptr, 10);
break;
}
}
}
if (ev.toAccountId != 0) {
ev.hasTo = true;
if (n.toNpid.empty()) {
// e.g. friendlist events carry no toNpid; the recipient is still us.
std::lock_guard lock(m_mutex_clients);
if (const auto it = m_np_ids.find(user_id); it != m_np_ids.end()) {
std::memcpy(ev.toOnlineId.data, it->second.handle.data, sizeof(ev.toOnlineId.data));
}
}
}
NpWebApi::EnqueuePushEvent(ev);
// Also surface a SESSION_INVITATION system-service event for titles that watch it instead of
// (or in addition to) the WebAPI push callback
if (n.npServiceName == "sessionInvitation") {
std::string session_id, invitation_id;
int64_t valid_until = 0;
for (const auto& kv : n.extdData) {
if (kv.first == "sessionId") {
session_id = kv.second;
} else if (kv.first == "invitationId") {
invitation_id = kv.second;
} else if (kv.first == "validUntil") {
valid_until = std::strtoll(kv.second.c_str(), nullptr, 10);
}
}
if (!session_id.empty()) {
{
std::lock_guard lk(m_mutex_pending_invites);
auto& v = m_pending_invites[user_id];
v.erase(std::remove_if(v.begin(), v.end(),
[&](const PendingInvitation& p) {
return p.invitation_id == invitation_id;
}),
v.end());
PendingInvitation inv{session_id, invitation_id, n.fromNpid, n.toNpid, valid_until};
inv.from_account_id = ev.fromAccountId;
v.push_back(std::move(inv));
}
ImGui::InvitationPrompt::Push(user_id, invitation_id, session_id, n.fromNpid);
}
}
}
} // namespace Libraries::Np
+1 -1
View File
@@ -15,11 +15,11 @@
#include "core/libraries/kernel/process.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_manager.h"
#include "core/tls.h"
#include "core/user_manager.h"
#include "core/user_settings.h"
#include "np_handler.h"
namespace Libraries::Np::NpManager {
@@ -11,7 +11,7 @@
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/net/net.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_matching2/np_matching2.h"
#include "core/libraries/np/np_matching2/np_matching2_internal.h"
@@ -14,7 +14,7 @@
#include "core/libraries/net/net.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_matching2/np_matching2_internal.h"
#include "shadnet.pb.h"
@@ -19,7 +19,7 @@
#include "core/libraries/net/net_p2p.h"
#include "core/libraries/net/net_upnp.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_matching2/np_matching2_internal.h"
#include "core/libraries/np/np_matching2/np_matching2_mm.h"
#include "core/libraries/np/np_matching2/np_matching2_signaling.h"
@@ -14,7 +14,7 @@
#include "core/libraries/net/net_util.h"
#include "core/libraries/network/netctl.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_matching2/np_matching2_internal.h"
#include "core/libraries/np/np_matching2/np_matching2_mm.h"
#include "core/libraries/np/np_matching2/np_matching2_signaling.h"
+1 -1
View File
@@ -12,7 +12,7 @@
#include "core/libraries/kernel/process.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_score/np_score.h"
#include "core/libraries/np/np_score/np_score_ctx.h"
@@ -12,7 +12,7 @@
#include "core/libraries/net/net_util.h"
#include "core/libraries/network/netctl.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_signaling/np_signaling.h"
#include "core/libraries/np/np_signaling/np_signaling_helpers.h"
@@ -20,7 +20,7 @@
#include "core/libraries/net/net_util.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_signaling/np_signaling.h"
#include "core/libraries/np/np_signaling/np_signaling_helpers.h"
#include "core/libraries/np/np_signaling/np_signaling_state.h"
@@ -17,7 +17,7 @@
#include "core/libraries/net/net.h"
#include "core/libraries/net/net_util.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_signaling/np_signaling_state.h"
#include "core/libraries/np/np_signaling/np_signaling_transport.h"
+1 -1
View File
@@ -13,7 +13,7 @@
#include "core/emulator_settings.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_trophy.h"
#include "core/libraries/np/trophy_ui.h"
#include "core/libraries/system/userservice.h"
+1 -1
View File
@@ -9,7 +9,7 @@
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_tus/np_tus.h"
#include "core/libraries/np/np_types.h"
@@ -12,7 +12,7 @@
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_utility/np_utility.h"
#include "core/libraries/np/np_utility/np_utility_ctx.h"
@@ -15,7 +15,7 @@
#include "core/libraries/kernel/time.h"
#include "core/libraries/network/http.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "np_web_api_internal.h"
namespace Libraries::Np::NpWebApi {
@@ -6,7 +6,7 @@
#include "core/libraries/network/http2.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_web_api2/np_web_api2_context.h"
namespace Libraries::Np::NpWebApi2 {
+1 -1
View File
@@ -18,7 +18,7 @@
#include "core/libraries/net/net.h"
#include "core/libraries/net/net_util.h"
#include "core/libraries/np/np_error.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_matching2/np_matching2_internal.h"
#include "core/libraries/np/np_matching2/np_matching2_signaling.h"
#include "core/libraries/np/np_signaling/np_signaling_state.h"
+1 -1
View File
@@ -10,7 +10,7 @@
#include "common/singleton.h"
#include "core/emulator_settings.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/system/userservice.h"
#include "core/libraries/system/userservice_error.h"
+1 -1
View File
@@ -38,7 +38,7 @@
#include "core/file_sys/fs.h"
#include "core/libraries/kernel/kernel.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "core/libraries/np/np_trophy.h"
#include "core/libraries/save_data/save_backup.h"
#include "core/linker.h"
+1 -1
View File
@@ -8,7 +8,7 @@
#include <imgui.h>
#include "common/types.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "imgui/friends_layer.h"
#include "imgui/imgui_layer.h"
#include "imgui/shadnet_notifications_layer.h"
+1 -1
View File
@@ -11,7 +11,7 @@
#include <imgui.h>
#include "common/types.h"
#include "core/libraries/np/np_handler.h"
#include "core/libraries/np/np_handler/np_handler.h"
#include "imgui/imgui_layer.h"
#include "imgui/invitation_prompt_layer.h"
#include "imgui/renderer/imgui_core.h"