diff --git a/src/core/libraries/invitation_dialog/invitation_dialog.h b/src/core/libraries/invitation_dialog/invitation_dialog.h index 103baefeb..73750ffb6 100644 --- a/src/core/libraries/invitation_dialog/invitation_dialog.h +++ b/src/core/libraries/invitation_dialog/invitation_dialog.h @@ -117,12 +117,18 @@ struct OrbisNpSessionInvitationEventParam { OrbisNpInvitationId invitationId; OrbisNpSessionInvitationEventFlag flag; char padding[4]; - Libraries::Np::OrbisNpOnlineId onlineId; + Libraries::Np::OrbisNpOnlineId onlineId; // accepting (local) user + Libraries::UserService::OrbisUserServiceUserId userId; // accepting (local) user + Libraries::Np::OrbisNpOnlineId referralOnlineId; // inviter + Libraries::Np::OrbisNpAccountId referralAccountId; // inviter }; -static_assert(sizeof(OrbisNpSessionInvitationEventParam) == 0x8c, - "SESSION_INVITATION param must be 140 bytes"); static_assert(offsetof(OrbisNpSessionInvitationEventParam, flag) == 112); static_assert(offsetof(OrbisNpSessionInvitationEventParam, onlineId) == 120); +static_assert(offsetof(OrbisNpSessionInvitationEventParam, userId) == 140); +static_assert(offsetof(OrbisNpSessionInvitationEventParam, referralOnlineId) == 144); +static_assert(offsetof(OrbisNpSessionInvitationEventParam, referralAccountId) == 168); +static_assert(sizeof(OrbisNpSessionInvitationEventParam) == 176, + "SESSION_INVITATION param must be 176 bytes"); union OrbisInvitationDialogDataParam { struct { diff --git a/src/core/libraries/np/np_handler.cpp b/src/core/libraries/np/np_handler.cpp index 0e15388c1..99fe06bc5 100644 --- a/src/core/libraries/np/np_handler.cpp +++ b/src/core/libraries/np/np_handler.cpp @@ -611,9 +611,11 @@ bool NpHandler::SendSessionInvitation(s32 user_id, const std::string& session_id return true; } -void NpHandler::PostSessionInvitationEvent(const std::string& session_id, +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& 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; @@ -630,12 +632,19 @@ void NpHandler::PostSessionInvitationEvent(const std::string& session_id, } else { param->flag = 0; // join from session info (no invitation id in the push) } - std::strncpy(param->onlineId.data, accepter_online_id.c_str(), - sizeof(param->onlineId.data) - 1); + // 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 session='{}' flag={} onlineId='{}'", session_id, - param->flag, accepter_online_id); + 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::GetPendingInvitations(s32 user_id) const { @@ -685,7 +694,8 @@ bool NpHandler::AcceptSessionInvitation(s32 user_id, const std::string& invitati } // Raise the join event now that the user has explicitly accepted (via the RECV dialog or the // emulator's system-UI equivalent). - PostSessionInvitationEvent(inv.session_id, invitation_id, inv.to_npid); + PostSessionInvitationEvent(user_id, inv.session_id, invitation_id, inv.to_npid, inv.from_npid, + inv.from_account_id); // Consume it server-side (PUT usedFlag=true). const std::string base_url = EmulatorSettings.GetShadNetWebApiServer(); const std::string token = GetBearerToken(user_id); @@ -845,6 +855,27 @@ void NpHandler::OnWebApiPushEvent(s32 user_id, const ShadNet::NotifyWebApiPushEv 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 @@ -870,7 +901,9 @@ void NpHandler::OnWebApiPushEvent(s32 user_id, const ShadNet::NotifyWebApiPushEv return p.invitation_id == invitation_id; }), v.end()); - v.push_back({session_id, invitation_id, n.fromNpid, n.toNpid, valid_until}); + PendingInvitation inv{session_id, invitation_id, n.fromNpid, n.toNpid, valid_until}; + inv.from_account_id = ev.fromAccountId; + v.push_back(std::move(inv)); } // ORBIS_SYSTEM_SERVICE_EVENT_SESSION_INVITATION is a *join* event: it // fires only after the user explicitly accepts, via the game-opened invitation dialog diff --git a/src/core/libraries/np/np_handler.h b/src/core/libraries/np/np_handler.h index 0f06dbace..bcef98b76 100644 --- a/src/core/libraries/np/np_handler.h +++ b/src/core/libraries/np/np_handler.h @@ -82,16 +82,20 @@ public: // Raises ORBIS_SYSTEM_SERVICE_EVENT_SESSION_INVITATION (0x10000002) so titles that watch the // system-service event learn about the invite. - void PostSessionInvitationEvent(const std::string& session_id, const std::string& invitation_id, - const std::string& accepter_online_id); + void 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); // A session invitation surfaced from a shadNet push, stashed until the user acts on it. struct PendingInvitation { std::string session_id; std::string invitation_id; - std::string from_npid; // sender (for display) - std::string to_npid; // local recipient / accepter - int64_t valid_until = 0; // ms since epoch; 0 = never expires + std::string from_npid; // sender (for display) + std::string to_npid; // local recipient / accepter + int64_t valid_until = 0; // ms since epoch; 0 = never expires + OrbisNpAccountId from_account_id = 0; // sender account id (0 = unknown) }; // Pending invitations stashed for a local user (newest last), populated on arrival. std::vector GetPendingInvitations(s32 user_id) const; diff --git a/src/core/libraries/np/np_web_api/np_web_api.h b/src/core/libraries/np/np_web_api/np_web_api.h index 057632818..fcd60aa79 100644 --- a/src/core/libraries/np/np_web_api/np_web_api.h +++ b/src/core/libraries/np/np_web_api/np_web_api.h @@ -142,6 +142,9 @@ struct PushEventInput { bool hasFrom = false; OrbisNpOnlineId toOnlineId{}; bool hasTo = false; + // Account ids for the OrbisNpPeerAddressA pFrom/pTo of extended push callbacks. + OrbisNpAccountId fromAccountId = 0; + OrbisNpAccountId toAccountId = 0; std::vector> extdData; // (key,value) for extd push }; void EnqueuePushEvent(const PushEventInput& ev); diff --git a/src/core/libraries/np/np_web_api/np_web_api_internal.cpp b/src/core/libraries/np/np_web_api/np_web_api_internal.cpp index ed2869d65..bbde526ea 100644 --- a/src/core/libraries/np/np_web_api/np_web_api_internal.cpp +++ b/src/core/libraries/np/np_web_api/np_web_api_internal.cpp @@ -1592,13 +1592,19 @@ s32 createExtendedPushEventFilterInternal( sizeof(OrbisNpWebApiExtdPushEventFilterParameter)); LOG_INFO(Lib_NpWebApi, " filterParam[{}] dataType='{}' extdKeys={}", param_idx, copy.dataType.val, copy.extdDataKeyNum); // debug + std::vector keys; if (copy.pExtdDataKey != nullptr) { for (u64 k = 0; k < copy.extdDataKeyNum; k++) { LOG_INFO(Lib_NpWebApi, " extdDataKey[{}]='{}'", k, copy.pExtdDataKey[k].val); // debug + keys.emplace_back( + copy.pExtdDataKey[k].val, + strnlen(copy.pExtdDataKey[k].val, sizeof(copy.pExtdDataKey[k].val))); } } + copy.pExtdDataKey = nullptr; // guest memory, see extdDataKeys filter->filterParams.emplace_back(copy); + filter->extdDataKeys.emplace_back(std::move(keys)); // TODO: Every parameter is registered with an extended data filter through // sceNpPushRegisterExtendedDataFilter } @@ -2128,6 +2134,14 @@ void DrainPushEvents() { std::snprintf(dt.val, sizeof(dt.val), "%s", ev.dataType.c_str()); const OrbisNpOnlineId* from_p = ev.hasFrom ? &ev.fromOnlineId : nullptr; const OrbisNpOnlineId* to_p = ev.hasTo ? &ev.toOnlineId : nullptr; + OrbisNpPeerAddressA to_addr{}; + OrbisNpPeerAddressA from_addr{}; + to_addr.accountId = ev.toAccountId; + to_addr.platform = OrbisNpPlatformType::PS4; + from_addr.accountId = ev.fromAccountId; + from_addr.platform = OrbisNpPlatformType::PS4; + const OrbisNpPeerAddressA* to_addr_p = ev.toAccountId != 0 ? &to_addr : nullptr; + const OrbisNpPeerAddressA* from_addr_p = ev.fromAccountId != 0 ? &from_addr : nullptr; std::scoped_lock gl{g_global_mutex}; for (auto& [libId, context] : g_contexts) { @@ -2162,9 +2176,29 @@ void DrainPushEvents() { if (!filterMatches(flt, ev.npServiceName, ev_has_service, ev.dataType)) { continue; } + // Only hand the title the extended-data keys its filter asked for for this + // dataType (SDK behaviour). This also keeps server-internal pairs such as + // fromAccountId/toAccountId/fromPlatform out of pExtdData. + const std::vector* wanted_keys = nullptr; + for (size_t i = 0; i < flt->filterParams.size(); ++i) { + if (ev.dataType == flt->filterParams[i].dataType.val) { + if (i < flt->extdDataKeys.size()) { + wanted_keys = &flt->extdDataKeys[i]; + } + break; + } + } + auto key_requested = [wanted_keys](const std::string& k) { + return wanted_keys != nullptr && + std::find(wanted_keys->begin(), wanted_keys->end(), k) != + wanted_keys->end(); + }; std::vector exarr; exarr.reserve(ev.extdData.size()); for (auto& [k, v] : ev.extdData) { + if (!key_requested(k)) { + continue; + } OrbisNpWebApiExtdPushEventExtdData e{}; std::snprintf(e.extdDataKey.val, sizeof(e.extdDataKey.val), "%s", k.c_str()); @@ -2186,8 +2220,9 @@ void DrainPushEvents() { title_user_ctx_id, cbId, ev.dataType); // debug confirm the listener callback fires. to be removed reinterpret_cast(raw)( - title_user_ctx_id, cbId, svc, flt->npServiceLabel, nullptr, to_p, nullptr, - from_p, &dt, ext_data, ev.data.size(), ext_arr, exarr.size(), cb->pUserArg); + title_user_ctx_id, cbId, svc, flt->npServiceLabel, to_addr_p, to_p, + from_addr_p, from_p, &dt, ext_data, ev.data.size(), ext_arr, exarr.size(), + cb->pUserArg); } // Service push diff --git a/src/core/libraries/np/np_web_api/np_web_api_internal.h b/src/core/libraries/np/np_web_api/np_web_api_internal.h index 6136f8702..eaa01966a 100644 --- a/src/core/libraries/np/np_web_api/np_web_api_internal.h +++ b/src/core/libraries/np/np_web_api/np_web_api_internal.h @@ -129,6 +129,7 @@ struct OrbisNpWebApiExtendedPushEventFilter { s32 filterId; bool internal; std::vector filterParams; + std::vector> extdDataKeys; std::string npServiceName; OrbisNpServiceLabel npServiceLabel; OrbisNpWebApiContext* parentContext; diff --git a/src/core/libraries/system/systemservice.cpp b/src/core/libraries/system/systemservice.cpp index 4c082a0c1..041471692 100644 --- a/src/core/libraries/system/systemservice.cpp +++ b/src/core/libraries/system/systemservice.cpp @@ -2004,6 +2004,8 @@ s32 PS4_SYSV_ABI sceSystemServiceReceiveEvent(OrbisSystemServiceEvent* event) { *event = g_event_queue.front(); g_event_queue.pop(); + LOG_DEBUG(Lib_SystemService, "delivered event type={:#x} ({} still queued)", + static_cast(event->event_type), g_event_queue.size()); return ORBIS_OK; } diff --git a/src/shadnet/client.cpp b/src/shadnet/client.cpp index 3d45281ea..4da11f720 100644 --- a/src/shadnet/client.cpp +++ b/src/shadnet/client.cpp @@ -978,20 +978,31 @@ void ShadNetClient::HandleNotification(u16 cmd_raw, const std::vector& paylo if (off + 4 <= static_cast(payload.size())) { const u32 count = GetLE32(payload.data() + off); off += 4; + bool extd_complete = count <= 256; for (u32 i = 0; i < count && i < 256; ++i) { - if (off + 4 > static_cast(payload.size())) + if (off + 4 > static_cast(payload.size())) { + extd_complete = false; break; + } std::string key = ExtractBlob(payload, off); off += 4 + static_cast(key.size()); - if (off + 4 > static_cast(payload.size())) + if (off + 4 > static_cast(payload.size())) { + extd_complete = false; break; + } std::string val = ExtractBlob(payload, off); off += 4 + static_cast(val.size()); n.extdData.emplace_back(std::move(key), std::move(val)); } + if (extd_complete && off + 16 <= static_cast(payload.size())) { + n.fromAccountId = GetLE64(payload.data() + off); + n.toAccountId = GetLE64(payload.data() + off + 8); + } } - LOG_INFO(ShadNet, "WebApiPushEvent svc='{}' type='{}' from='{}' bytes={} extd={}", - n.npServiceName, n.dataType, n.fromNpid, n.data.size(), n.extdData.size()); + LOG_INFO(ShadNet, + "WebApiPushEvent svc='{}' type='{}' from='{}'({}) to='{}'({}) bytes={} extd={}", + n.npServiceName, n.dataType, n.fromNpid, n.fromAccountId, n.toNpid, n.toAccountId, + n.data.size(), n.extdData.size()); if (onWebApiPushEvent) onWebApiPushEvent(n); break; diff --git a/src/shadnet/client.h b/src/shadnet/client.h index f65826991..9c8edcb21 100644 --- a/src/shadnet/client.h +++ b/src/shadnet/client.h @@ -222,6 +222,9 @@ struct NotifyWebApiPushEvent { // 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; + // Optional trailing account ids (server >= push-account-ids). 0 = unknown / not sent. + u64 fromAccountId = 0; + u64 toAccountId = 0; }; struct MatchingBinAttr {