Matching2 P7: signaling additions, movement to separate signaling files, corrections to payloads, corrections to structs, corrections to shadnet. (#4656)

This commit is contained in:
metr1k-91 2026-07-02 01:21:45 -05:00 committed by GitHub
parent 9756843860
commit 0a51093d28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1310 additions and 687 deletions

View File

@ -636,6 +636,8 @@ set(NP_LIBS src/core/libraries/np/np_error.h
src/core/libraries/np/np_matching2/np_matching2_internal.h
src/core/libraries/np/np_matching2/np_matching2_mm.cpp
src/core/libraries/np/np_matching2/np_matching2_mm.h
src/core/libraries/np/np_matching2/np_matching2_signaling.cpp
src/core/libraries/np/np_matching2/np_matching2_signaling.h
src/core/libraries/np/np_matching2/np_matching2_types.h
src/core/libraries/np/np_trophy.cpp
src/core/libraries/np/np_trophy.h

View File

@ -14,20 +14,19 @@
#include "core/libraries/np/np_matching2/np_matching2.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"
#include "core/libraries/np/np_matching2/np_matching2_types.h"
#include "core/libraries/np/np_types.h"
#include "core/libraries/system/userservice.h"
namespace Libraries::Np::NpMatching2 {
static bool g_initialized = false;
int PS4_SYSV_ABI sceNpMatching2CreateContext(const OrbisNpMatching2CreateContextParameter* param,
OrbisNpMatching2ContextId* ctxId) {
LOG_INFO(Lib_NpMatching2, "called, npId = {}, serviceLabel = {}, size = {}",
param->npId->handle.data, param->serviceLabel, param->size);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -44,7 +43,7 @@ int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContex
LOG_INFO(Lib_NpMatching2, "called, userId = {}, serviceLabel = {}, size = {}", param->userId,
param->serviceLabel, param->size);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -59,191 +58,12 @@ int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContex
return ContextManager::Instance().CreateContext(&np_id, param->serviceLabel, ctxId);
}
static void StoreRequestCallback(ContextObject* ctx,
const OrbisNpMatching2RequestOptParam* requestOpt) {
if (requestOpt && requestOpt->callback) {
ctx->default_request_callback = requestOpt->callback;
ctx->default_request_callback_arg = requestOpt->arg;
}
}
static u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId) {
const auto room_it = ctx.room_cache.find(roomId);
if (room_it == ctx.room_cache.end()) {
return 0;
}
u64 total_ping = 0;
u32 ping_count = 0;
for (const auto& [member_id, member] : room_it->second.members) {
if (member_id == ctx.my_member_id) {
continue;
}
const auto peer_it = ctx.peers.find(member_id);
if (peer_it == ctx.peers.end() || peer_it->second.ping_us == 0) {
continue;
}
total_ping += peer_it->second.ping_us;
++ping_count;
}
return ping_count == 0 ? 0 : static_cast<u32>(total_ping / ping_count);
}
static void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, OrbisNpMatching2RoomId roomId) {
CallbackPayload& p = ctx.request_payload;
p.Reset();
const auto room_it = ctx.room_cache.find(roomId);
p.ping_info_response = std::make_unique<OrbisNpMatching2SignalingGetPingInfoResponse>();
auto& out = *p.ping_info_response;
out = {};
if (room_it != ctx.room_cache.end()) {
out.serverId = room_it->second.server_id;
out.worldId = room_it->second.world_id;
out.roomId = room_it->second.room_id;
} else {
out.serverId = ctx.server_id;
out.worldId = ctx.world_id;
out.roomId = roomId;
}
out.rtt = GetRoomPingUs(ctx, roomId);
p.request_data = p.ping_info_response.get();
return p.request_data;
}
static Libraries::Np::OrbisNpId MakePeerNpId(const PeerInfo* peer, const MemberCache* member) {
Libraries::Np::OrbisNpId npid{};
if (member) {
npid = member->np_id;
} else if (peer) {
npid.handle = peer->online_id;
}
return npid;
}
static u64 StableAccountIdFromNpId(const Libraries::Np::OrbisNpId& npid) {
u64 hash = 1469598103934665603ULL;
for (const char c : npid.handle.data) {
if (c == '\0') {
break;
}
hash ^= static_cast<u8>(c);
hash *= 1099511628211ULL;
}
return hash == 0 ? 1 : hash;
}
static Libraries::Np::OrbisNpPeerAddressA MakePeerAddressA(const PeerInfo* peer,
const MemberCache* member) {
const Libraries::Np::OrbisNpId npid = MakePeerNpId(peer, member);
Libraries::Np::OrbisNpPeerAddressA addr{};
addr.accountId = StableAccountIdFromNpId(npid);
addr.platform = Libraries::Np::OrbisNpPlatformType::PS4;
return addr;
}
static s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId roomId,
OrbisNpMatching2RoomMemberId memberId, u32 infoType,
void* connInfo, bool a_variant) {
if (!connInfo) {
LOG_ERROR(Lib_NpMatching2, "connInfo null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
const auto room_it = ctx.room_cache.find(roomId);
if (room_it == ctx.room_cache.end()) {
LOG_INFO(Lib_NpMatching2, "room={} not cached for connection info", roomId);
if (a_variant) {
*static_cast<OrbisNpMatching2SignalingConnectionInfoA*>(connInfo) = {};
} else {
*static_cast<OrbisNpMatching2SignalingConnectionInfo*>(connInfo) = {};
}
return ORBIS_OK;
}
const auto member_it = room_it->second.members.find(memberId);
const MemberCache* member =
member_it != room_it->second.members.end() ? &member_it->second : nullptr;
const auto peer_it = ctx.peers.find(memberId);
const PeerInfo* peer = peer_it != ctx.peers.end() ? &peer_it->second : nullptr;
if (a_variant) {
auto* out = static_cast<OrbisNpMatching2SignalingConnectionInfoA*>(connInfo);
*out = {};
switch (infoType) {
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_RTT:
out->rtt = peer ? peer->ping_us : 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_BANDWIDTH:
out->bandwidth = 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR:
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_MAPPED_ADDR:
if (peer) {
out->address.addr = peer->addr;
out->address.port = peer->port;
} else if (member) {
out->address.addr = member->addr;
out->address.port = member->port;
}
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PACKET_LOSS:
out->packetLoss = 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDRESS_A:
out->peerAddrA = MakePeerAddressA(peer, member);
break;
default:
LOG_WARNING(Lib_NpMatching2, "unsupported connection info A type={}", infoType);
return ORBIS_NP_MATCHING2_SIGNALING_ERROR_INVALID_ARGUMENT;
}
} else {
auto* out = static_cast<OrbisNpMatching2SignalingConnectionInfo*>(connInfo);
*out = {};
switch (infoType) {
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_RTT:
out->rtt = peer ? peer->ping_us : 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_BANDWIDTH:
out->bandwidth = 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_NP_ID:
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_NPID:
out->npId = MakePeerNpId(peer, member);
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR:
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_MAPPED_ADDR:
if (peer) {
out->address.addr = peer->addr;
out->address.port = peer->port;
} else if (member) {
out->address.addr = member->addr;
out->address.port = member->port;
}
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PACKET_LOSS:
out->packetLoss = 0;
break;
default:
LOG_WARNING(Lib_NpMatching2, "unsupported connection info type={}", infoType);
return ORBIS_NP_MATCHING2_SIGNALING_ERROR_INVALID_ARGUMENT;
}
}
LOG_INFO(Lib_NpMatching2, "connection info{}: ctx={} room={} member={} type={} rtt={}",
a_variant ? "A" : "", ctx.ctx_id, roomId, memberId, infoType,
peer ? peer->ping_us : 0);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2CreateJoinRoom(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2CreateJoinRoomRequest* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -269,7 +89,7 @@ int PS4_SYSV_ABI sceNpMatching2JoinRoom(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -296,7 +116,7 @@ int PS4_SYSV_ABI sceNpMatching2CreateJoinRoomA(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -314,7 +134,7 @@ int PS4_SYSV_ABI sceNpMatching2CreateJoinRoomA(OrbisNpMatching2ContextId ctxId,
StoreRequestCallback(ctx, requestOpt);
const OrbisNpMatching2RequestId reqId = AllocRequestId();
*requestId = reqId;
LOG_WARNING(Lib_NpMatching2, "not implemented");
MmCreateJoinRoomA(ctxId, reqId, *request);
return ORBIS_OK;
}
@ -322,7 +142,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterContextCallback(OrbisNpMatching2ContextCa
void* userdata) {
LOG_INFO(Lib_NpMatching2, "called, userdata = {}", userdata);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -335,7 +155,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterLobbyEventCallback(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2LobbyEventCallback callback, void* userdata) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -355,7 +175,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterRoomEventCallback(OrbisNpMatching2Context
void* userdata) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -375,7 +195,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterSignalingCallback(OrbisNpMatching2Context
void* userdata) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -393,7 +213,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterSignalingCallback(OrbisNpMatching2Context
int PS4_SYSV_ABI sceNpMatching2ContextStart(OrbisNpMatching2ContextId ctxId, u64 timeout) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, timeout = {}", ctxId, timeout);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -416,7 +236,7 @@ int PS4_SYSV_ABI sceNpMatching2ContextStart(OrbisNpMatching2ContextId ctxId, u64
int PS4_SYSV_ABI sceNpMatching2ContextStop(OrbisNpMatching2ContextId ctxId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -433,7 +253,7 @@ int PS4_SYSV_ABI sceNpMatching2ContextStop(OrbisNpMatching2ContextId ctxId) {
int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* param) {
LOG_INFO(Lib_NpMatching2, "called");
if (g_initialized) {
if (IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "already initialized");
return ORBIS_NP_MATCHING2_ERROR_ALREADY_INITIALIZED;
}
@ -451,7 +271,7 @@ int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* p
InitEventDispatcher();
g_initialized = true;
SetInitialized(true);
g_state.initialized.store(true);
return ORBIS_OK;
@ -460,12 +280,12 @@ int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* p
int PS4_SYSV_ABI sceNpMatching2Terminate() {
LOG_INFO(Lib_NpMatching2, "called");
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
g_initialized = false;
SetInitialized(false);
g_state.initialized.store(false);
TermEventDispatcher();
@ -478,7 +298,7 @@ int PS4_SYSV_ABI sceNpMatching2SetDefaultRequestOptParam(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RequestOptParam* requestOpt) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -502,7 +322,7 @@ int PS4_SYSV_ABI sceNpMatching2GetServerId(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2ServerId* serverId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -523,7 +343,7 @@ int PS4_SYSV_ABI sceNpMatching2GetWorldInfoList(OrbisNpMatching2ContextId ctxId,
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, request.serverId = {}, requestOpt = {}", ctxId,
request ? request->serverId : 0xFFFF, fmt::ptr(requestOpt));
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -551,7 +371,7 @@ int PS4_SYSV_ABI sceNpMatching2LeaveRoom(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -578,7 +398,7 @@ int PS4_SYSV_ABI sceNpMatching2SearchRoom(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -606,7 +426,7 @@ int PS4_SYSV_ABI sceNpMatching2SetUserInfo(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -632,7 +452,7 @@ int PS4_SYSV_ABI sceNpMatching2SendRoomMessage(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -658,7 +478,7 @@ int PS4_SYSV_ABI sceNpMatching2SetRoomDataExternal(
OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -685,7 +505,7 @@ int PS4_SYSV_ABI sceNpMatching2SetRoomDataInternal(
OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -715,7 +535,7 @@ s32 PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionStatus(OrbisNpMatching2Cont
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, roomId = {}, memberId = {}", ctxId, roomId,
memberId);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -780,7 +600,7 @@ int PS4_SYSV_ABI sceNpMatching2AbortContextStart(OrbisNpMatching2ContextId ctxId
int PS4_SYSV_ABI sceNpMatching2DestroyContext(OrbisNpMatching2ContextId ctxId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -796,7 +616,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterLobbyMessageCallback(
void* userdata) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -815,7 +635,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterRoomMessageCallback(
OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RoomMessageCallback callback, void* userdata) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -880,7 +700,7 @@ int PS4_SYSV_ABI sceNpMatching2GetRoomDataInternal(
OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -947,9 +767,29 @@ int PS4_SYSV_ABI sceNpMatching2JoinLobby(OrbisNpMatching2ContextId ctxId, void*
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpMatching2JoinRoomA(OrbisNpMatching2ContextId ctxId, void* reqParam,
void* optParam, s32* reqId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId);
int PS4_SYSV_ABI sceNpMatching2JoinRoomA(OrbisNpMatching2ContextId ctxId,
OrbisNpMatching2JoinRoomRequestA* request,
OrbisNpMatching2RequestOptParam* requestOpt,
OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
if (!request || !requestId) {
LOG_ERROR(Lib_NpMatching2, "request or requestId null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
ContextObject* ctx = ContextManager::Instance().Get(ctxId);
if (!ctx) {
LOG_ERROR(Lib_NpMatching2, "invalid context id");
return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
}
StoreRequestCallback(ctx, requestOpt);
const OrbisNpMatching2RequestId reqId = AllocRequestId();
*requestId = reqId;
MmJoinRoomA(ctxId, reqId, *request);
return ORBIS_OK;
}
@ -959,7 +799,7 @@ int PS4_SYSV_ABI sceNpMatching2KickoutRoomMember(OrbisNpMatching2ContextId ctxId
OrbisNpMatching2RequestId* requestId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt));
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -1021,7 +861,7 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetPingInfo(OrbisNpMatching2ContextId ct
const void* reqParam, const void* optParam,
s32* reqId) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -1070,7 +910,7 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionInfoA(OrbisNpMatching2Conte
u32 infoType, void* connInfo) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, roomId = {}, memberId = {}", ctxId, roomId,
memberId);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}
@ -1089,7 +929,7 @@ int PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionInfo(OrbisNpMatching2Contex
u32 infoType, void* connInfo) {
LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, roomId = {}, memberId = {}", ctxId, roomId,
memberId);
if (!g_initialized) {
if (!IsInitialized()) {
LOG_ERROR(Lib_NpMatching2, "not initialized");
return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}

View File

@ -14,6 +14,7 @@ namespace Libraries::Np::NpMatching2 {
using OrbisNpMatching2AttributeId = u16;
using OrbisNpMatching2BlockKickFlag = u8;
using OrbisNpMatching2ContextId = u16;
using OrbisNpMatching2FlagAttr = u32;
using OrbisNpMatching2Flags = u32;
using OrbisNpMatching2LobbyId = u64;
using OrbisNpMatching2LobbyMemberId = u16;
@ -59,6 +60,7 @@ enum OrbisNpMatching2Event : u16 {
ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED = 0x6F02,
ORBIS_NP_MATCHING2_CONTEXT_EVENT_STOPPED = 0x6F03,
ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A = 0x7101,
ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM_A = 0x7102,
ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM_A = 0x7106,
ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED_A = 0x8101,
ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_LEFT_A = 0x8102,

View File

@ -4,6 +4,7 @@
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <utility>
#include "common/logging/log.h"
#include "common/thread.h"
@ -29,10 +30,9 @@ u32 IpStringToAddr(std::string_view ip) {
} // namespace
void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp) {
void BuildCreateJoinRoomPayloadCommon(ContextObject& ctx,
const shadnet::CreateJoinRoomResponse& resp) {
CallbackPayload& p = ctx.request_payload;
p.Reset();
const auto& rd = resp.room_data();
p.room_groups.resize(rd.groups_size());
@ -56,11 +56,12 @@ void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRo
auto& buf = p.bin_buffers.back();
auto& dst = p.room_bin_attrs[i];
dst = {};
dst.lastUpdate.tick = a.update_date();
dst.memberId = static_cast<OrbisNpMatching2RoomMemberId>(a.update_member_id());
dst.binAttr.id = static_cast<OrbisNpMatching2AttributeId>(a.attr_id());
dst.binAttr.data = buf.empty() ? nullptr : buf.data();
dst.binAttr.dataSize = buf.size();
}
p.room_data = std::make_unique<OrbisNpMatching2RoomDataInternal>();
auto& room = *p.room_data;
room = {};
@ -81,38 +82,6 @@ void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRo
room.internalBinAttr = p.room_bin_attrs.empty() ? nullptr : p.room_bin_attrs.data();
room.internalBinAttrs = p.room_bin_attrs.size();
const int member_count = resp.members_size();
p.member_data.resize(member_count);
p.member_bin_attrs.clear();
for (int i = 0; i < member_count; ++i) {
const auto& m = resp.members(i);
auto& dst = p.member_data[i];
dst = {};
dst.next = (i + 1 < member_count) ? &p.member_data[i + 1] : nullptr;
std::strncpy(dst.npId.handle.data, m.npid().c_str(), sizeof(dst.npId.handle.data) - 1);
dst.memberId = static_cast<OrbisNpMatching2RoomMemberId>(m.member_id());
dst.teamId = static_cast<OrbisNpMatching2TeamId>(m.team_id());
if (m.is_owner()) {
dst.flagAttr |= ORBIS_NP_MATCHING2_ROOMMEMBER_FLAG_ATTR_OWNER;
}
}
p.create_join_response = std::make_unique<OrbisNpMatching2CreateJoinRoomResponse>();
auto& out = *p.create_join_response;
out.roomData = p.room_data.get();
out.members.members = p.member_data.empty() ? nullptr : p.member_data.data();
out.members.membersNum = p.member_data.size();
out.members.me = nullptr;
out.members.owner = nullptr;
for (auto& md : p.member_data) {
if (md.memberId == static_cast<OrbisNpMatching2RoomMemberId>(resp.me_member_id())) {
out.members.me = &md;
}
if (md.memberId == static_cast<OrbisNpMatching2RoomMemberId>(resp.owner_member_id())) {
out.members.owner = &md;
}
}
if (ctx.room_id != 0 && ctx.room_id != room.roomId) {
ctx.room_cache.erase(ctx.room_id);
}
@ -137,12 +106,32 @@ void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRo
rc.owner = resp.me_member_id() == resp.owner_member_id();
rc.signaling_type = ORBIS_NP_MATCHING2_SIGNALING_TYPE_MESH;
rc.signaling_main_member = static_cast<OrbisNpMatching2RoomMemberId>(resp.owner_member_id());
rc.bin_attrs_internal.resize(p.room_bin_attrs.size());
rc.bin_buffers.clear();
rc.bin_buffers.reserve(p.room_bin_attrs.size());
for (size_t i = 0; i < p.room_bin_attrs.size(); ++i) {
const auto& a = p.room_bin_attrs[i].binAttr;
if (a.data && a.dataSize > 0) {
rc.bin_buffers.emplace_back(a.data, a.data + a.dataSize);
} else {
rc.bin_buffers.emplace_back();
}
auto& buf = rc.bin_buffers.back();
auto& dst = rc.bin_attrs_internal[i];
dst = OrbisNpMatching2RoomBinAttrInternal{};
dst.lastUpdate = p.room_bin_attrs[i].lastUpdate;
dst.memberId = p.room_bin_attrs[i].memberId;
dst.binAttr.id = a.id;
dst.binAttr.data = buf.empty() ? nullptr : buf.data();
dst.binAttr.dataSize = buf.size();
}
ctx.room_id = room.roomId;
ctx.my_member_id = static_cast<OrbisNpMatching2RoomMemberId>(resp.me_member_id());
ctx.is_room_owner = rc.owner;
for (const auto& g : p.room_groups) {
rc.groups[g.id] = g;
}
const int member_count = resp.members_size();
for (int i = 0; i < member_count; ++i) {
const auto& m = resp.members(i);
MemberCache& mc = rc.members[static_cast<OrbisNpMatching2RoomMemberId>(m.member_id())];
@ -156,9 +145,12 @@ void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRo
mc.addr = IpStringToAddr(m.addr());
mc.port = Libraries::Net::sceNetHtons(static_cast<u16>(m.port()));
std::strncpy(mc.np_id.handle.data, m.npid().c_str(), sizeof(mc.np_id.handle.data) - 1);
mc.account_id = static_cast<Libraries::Np::OrbisNpAccountId>(m.account_id());
mc.platform = static_cast<Libraries::Np::OrbisNpPlatformType>(m.platform());
for (const auto& a : m.bin_attrs_internal()) {
MemberBinCache& b = mc.bins[a.attr_id()];
b.id = static_cast<OrbisNpMatching2AttributeId>(a.attr_id());
b.update_date = a.update_date();
b.data.assign(a.data().begin(), a.data().end());
}
if (mc.member_id != ctx.my_member_id) {
@ -170,11 +162,137 @@ void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRo
ctx.peers[mc.member_id] = peer;
}
}
}
OrbisNpMatching2RoomGroup* FindPayloadGroup(CallbackPayload& p, OrbisNpMatching2RoomGroupId id) {
for (auto& group : p.room_groups) {
if (group.id == id) {
return &group;
}
}
return nullptr;
}
void ReserveMemberBinAttrs(CallbackPayload& p, const shadnet::CreateJoinRoomResponse& resp) {
size_t total = 0;
for (const auto& member : resp.members()) {
total += static_cast<size_t>(member.bin_attrs_internal_size());
}
p.member_bin_attrs.reserve(total);
}
OrbisNpMatching2RoomMemberBinAttrInternal* AppendMemberBinAttrs(
CallbackPayload& p, const shadnet::MatchingRoomMemberData& member, u64& count) {
const size_t first = p.member_bin_attrs.size();
count = static_cast<u64>(member.bin_attrs_internal_size());
for (const auto& a : member.bin_attrs_internal()) {
p.bin_buffers.emplace_back(a.data().begin(), a.data().end());
auto& buf = p.bin_buffers.back();
auto& dst = p.member_bin_attrs.emplace_back();
dst = OrbisNpMatching2RoomMemberBinAttrInternal{};
dst.lastUpdate.tick = a.update_date();
dst.binAttr.id = static_cast<OrbisNpMatching2AttributeId>(a.attr_id());
dst.binAttr.data = buf.empty() ? nullptr : buf.data();
dst.binAttr.dataSize = buf.size();
}
return count == 0 ? nullptr : &p.member_bin_attrs[first];
}
void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp) {
CallbackPayload& p = ctx.request_payload;
p.Reset();
BuildCreateJoinRoomPayloadCommon(ctx, resp);
const int member_count = resp.members_size();
p.member_data.resize(member_count);
ReserveMemberBinAttrs(p, resp);
for (int i = 0; i < member_count; ++i) {
const auto& m = resp.members(i);
auto& dst = p.member_data[i];
dst = OrbisNpMatching2RoomMemberDataInternal{};
dst.next = (i + 1 < member_count) ? &p.member_data[i + 1] : nullptr;
dst.joinDate = m.join_date();
std::strncpy(dst.npId.handle.data, m.npid().c_str(), sizeof(dst.npId.handle.data) - 1);
dst.memberId = static_cast<OrbisNpMatching2RoomMemberId>(m.member_id());
dst.teamId = static_cast<OrbisNpMatching2TeamId>(m.team_id());
dst.natType = static_cast<OrbisNpMatching2NatType>(m.nat_type());
dst.flagAttr = m.flag_attr();
if (m.is_owner()) {
dst.flagAttr |= ORBIS_NP_MATCHING2_ROOMMEMBER_FLAG_ATTR_OWNER;
}
dst.roomGroup = FindPayloadGroup(p, static_cast<OrbisNpMatching2RoomGroupId>(m.group_id()));
dst.roomMemberBinAttrInternal =
AppendMemberBinAttrs(p, m, dst.roomMemberBinAttrInternalNum);
}
p.create_join_response = std::make_unique<OrbisNpMatching2CreateJoinRoomResponse>();
auto& out = *p.create_join_response;
out.roomData = p.room_data.get();
out.members.members = p.member_data.empty() ? nullptr : p.member_data.data();
out.members.membersNum = p.member_data.size();
out.members.me = nullptr;
out.members.owner = nullptr;
for (auto& md : p.member_data) {
if (md.memberId == static_cast<OrbisNpMatching2RoomMemberId>(resp.me_member_id())) {
out.members.me = &md;
}
if (md.memberId == static_cast<OrbisNpMatching2RoomMemberId>(resp.owner_member_id())) {
out.members.owner = &md;
}
}
p.request_data = p.create_join_response.get();
return p.request_data;
}
void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp) {
CallbackPayload& p = ctx.request_payload;
p.Reset();
BuildCreateJoinRoomPayloadCommon(ctx, resp);
const int member_count = resp.members_size();
p.member_data_a.resize(member_count);
ReserveMemberBinAttrs(p, resp);
for (int i = 0; i < member_count; ++i) {
const auto& m = resp.members(i);
auto& dst = p.member_data_a[i];
dst = OrbisNpMatching2RoomMemberDataInternalA{};
dst.next = (i + 1 < member_count) ? &p.member_data_a[i + 1] : nullptr;
dst.joinDateTicks.tick = m.join_date();
dst.user.accountId = static_cast<Libraries::Np::OrbisNpAccountId>(m.account_id());
dst.user.platform = static_cast<Libraries::Np::OrbisNpPlatformType>(m.platform());
std::strncpy(dst.onlineId.data, m.npid().c_str(), sizeof(dst.onlineId.data) - 1);
dst.memberId = static_cast<OrbisNpMatching2RoomMemberId>(m.member_id());
dst.teamId = static_cast<OrbisNpMatching2TeamId>(m.team_id());
dst.natType = static_cast<OrbisNpMatching2NatType>(m.nat_type());
dst.flags = m.flag_attr();
if (m.is_owner()) {
dst.flags |= ORBIS_NP_MATCHING2_ROOMMEMBER_FLAG_ATTR_OWNER;
}
dst.roomGroup = FindPayloadGroup(p, static_cast<OrbisNpMatching2RoomGroupId>(m.group_id()));
dst.roomMemberInternalBinAttr = AppendMemberBinAttrs(p, m, dst.roomMemberInternalBinAttrs);
}
p.create_join_response_a = std::make_unique<OrbisNpMatching2CreateJoinRoomResponseA>();
auto& out = *p.create_join_response_a;
out.roomData = p.room_data.get();
out.members.members = p.member_data_a.empty() ? nullptr : p.member_data_a.data();
out.members.membersNum = p.member_data_a.size();
out.members.me = nullptr;
out.members.owner = nullptr;
for (auto& md : p.member_data_a) {
if (md.memberId == static_cast<OrbisNpMatching2RoomMemberId>(resp.me_member_id())) {
out.members.me = &md;
}
if (md.memberId == static_cast<OrbisNpMatching2RoomMemberId>(resp.owner_member_id())) {
out.members.owner = &md;
}
}
p.request_data = p.create_join_response_a.get();
return p.request_data;
}
void* BuildLeaveRoomPayload(ContextObject& ctx, const shadnet::LeaveRoomReply& resp) {
CallbackPayload& p = ctx.request_payload;
p.Reset();
@ -343,8 +461,23 @@ void* BuildGetRoomDataInternalPayload(ContextObject& ctx, OrbisNpMatching2RoomId
}
p.room_bin_attrs.resize(rc.bin_attrs_internal.size());
p.bin_buffers.clear();
p.bin_buffers.reserve(rc.bin_attrs_internal.size());
for (size_t i = 0; i < rc.bin_attrs_internal.size(); ++i) {
p.room_bin_attrs[i] = rc.bin_attrs_internal[i];
const auto& src = rc.bin_attrs_internal[i];
const auto* data = static_cast<const u8*>(src.binAttr.data);
if (data && src.binAttr.dataSize > 0) {
p.bin_buffers.emplace_back(data, data + src.binAttr.dataSize);
} else {
p.bin_buffers.emplace_back();
}
auto& buf = p.bin_buffers.back();
auto& dst = p.room_bin_attrs[i];
dst = OrbisNpMatching2RoomBinAttrInternal{};
dst.lastUpdate = src.lastUpdate;
dst.binAttr.id = src.binAttr.id;
dst.binAttr.data = buf.empty() ? nullptr : buf.data();
dst.binAttr.dataSize = buf.size();
}
p.room_data = std::make_unique<OrbisNpMatching2RoomDataInternal>();
@ -367,7 +500,38 @@ void* BuildGetRoomDataInternalPayload(ContextObject& ctx, OrbisNpMatching2RoomId
room.internalBinAttr = p.room_bin_attrs.empty() ? nullptr : p.room_bin_attrs.data();
room.internalBinAttrs = p.room_bin_attrs.size();
p.request_data = p.room_data.get();
p.member_data.clear();
p.member_data.reserve(rc.members.size());
for (const auto& [member_id, mc] : rc.members) {
auto& dst = p.member_data.emplace_back();
dst = OrbisNpMatching2RoomMemberDataInternal{};
dst.joinDate = mc.join_date;
dst.npId = mc.np_id;
dst.memberId = mc.member_id;
dst.teamId = mc.team_id;
dst.natType = mc.nat_type;
dst.flagAttr = mc.flag_attr;
}
for (size_t i = 0; i < p.member_data.size(); ++i) {
p.member_data[i].next = (i + 1 < p.member_data.size()) ? &p.member_data[i + 1] : nullptr;
}
p.create_join_response = std::make_unique<OrbisNpMatching2CreateJoinRoomResponse>();
auto& out = *p.create_join_response;
out = OrbisNpMatching2CreateJoinRoomResponse{};
out.roomData = p.room_data.get();
out.members.members = p.member_data.empty() ? nullptr : p.member_data.data();
out.members.membersNum = p.member_data.size();
for (auto& md : p.member_data) {
if (md.memberId == ctx.my_member_id) {
out.members.me = &md;
}
if (md.flagAttr & ORBIS_NP_MATCHING2_ROOMMEMBER_FLAG_ATTR_OWNER) {
out.members.owner = &md;
}
}
p.request_data = p.create_join_response.get();
return p.request_data;
}
@ -504,6 +668,23 @@ OrbisNpMatching2RequestId AllocRequestId() {
return id;
}
static bool g_initialized = false;
bool IsInitialized() {
return g_initialized;
}
void SetInitialized(bool initialized) {
g_initialized = initialized;
}
void StoreRequestCallback(ContextObject* ctx, const OrbisNpMatching2RequestOptParam* requestOpt) {
if (requestOpt && requestOpt->callback) {
ctx->default_request_callback = requestOpt->callback;
ctx->default_request_callback_arg = requestOpt->arg;
}
}
namespace {
void FireEvent(const PendingEvent& ev) {

View File

@ -53,6 +53,8 @@ struct MemberBinCache {
struct MemberCache {
Libraries::Np::OrbisNpId np_id{};
Libraries::Np::OrbisNpAccountId account_id = 0;
Libraries::Np::OrbisNpPlatformType platform = Libraries::Np::OrbisNpPlatformType::None;
u64 join_date = 0;
OrbisNpMatching2RoomMemberId member_id = 0;
OrbisNpMatching2TeamId team_id = 0;
@ -91,14 +93,18 @@ struct RoomCache {
struct CallbackPayload {
std::unique_ptr<OrbisNpMatching2RoomDataInternal> room_data;
std::unique_ptr<OrbisNpMatching2CreateJoinRoomResponse> create_join_response;
std::unique_ptr<OrbisNpMatching2CreateJoinRoomResponseA> create_join_response_a;
std::unique_ptr<OrbisNpMatching2LeaveRoomResponse> leave_room_response;
std::unique_ptr<OrbisNpMatching2SearchRoomResponse> search_room_response;
std::unique_ptr<OrbisNpMatching2GetWorldInfoListResponse> world_info_response;
std::unique_ptr<OrbisNpMatching2SignalingGetPingInfoResponse> ping_info_response;
std::vector<OrbisNpMatching2World> world_list;
std::vector<OrbisNpMatching2RoomMemberDataInternal> member_data;
std::vector<OrbisNpMatching2RoomMemberDataInternalA> member_data_a;
std::vector<OrbisNpMatching2RoomGroup> room_groups;
std::vector<OrbisNpMatching2RoomGroup*> room_group_ptrs;
std::vector<OrbisNpMatching2RoomBinAttrInternal> room_bin_attrs;
std::vector<OrbisNpMatching2RoomBinAttrInternal*> room_bin_attr_ptrs;
std::vector<OrbisNpMatching2RoomMemberBinAttrInternal> member_bin_attrs;
std::vector<OrbisNpMatching2RoomDataExternal> room_data_external;
std::vector<std::vector<u8>> bin_buffers;
@ -110,6 +116,11 @@ struct CallbackPayload {
std::unique_ptr<OrbisNpMatching2RoomMemberUpdate> room_member_update;
std::unique_ptr<OrbisNpMatching2RoomUpdate> room_update;
std::unique_ptr<OrbisNpMatching2RoomDataInternalUpdate> room_data_internal_update;
std::unique_ptr<OrbisNpMatching2FlagAttr> event_chg_flag_attr;
std::unique_ptr<OrbisNpMatching2FlagAttr> event_prev_flag_attr;
std::unique_ptr<OrbisNpMatching2RoomPasswordSlotMask> event_chg_passwd_slot_mask;
std::unique_ptr<OrbisNpMatching2RoomPasswordSlotMask> event_prev_passwd_slot_mask;
std::unique_ptr<OrbisNpMatching2RoomMemberDataInternal> event_member;
void* room_event_data = nullptr;
@ -122,14 +133,18 @@ struct CallbackPayload {
void Reset() {
room_data.reset();
create_join_response.reset();
create_join_response_a.reset();
leave_room_response.reset();
search_room_response.reset();
world_info_response.reset();
ping_info_response.reset();
world_list.clear();
member_data.clear();
member_data_a.clear();
room_groups.clear();
room_group_ptrs.clear();
room_bin_attrs.clear();
room_bin_attr_ptrs.clear();
member_bin_attrs.clear();
room_data_external.clear();
bin_buffers.clear();
@ -140,6 +155,11 @@ struct CallbackPayload {
request_data = nullptr;
room_member_update.reset();
room_update.reset();
room_data_internal_update.reset();
event_chg_flag_attr.reset();
event_prev_flag_attr.reset();
event_chg_passwd_slot_mask.reset();
event_prev_passwd_slot_mask.reset();
event_member.reset();
room_event_data = nullptr;
}
@ -322,7 +342,12 @@ extern NpMatching2State g_state;
OrbisNpMatching2RequestId AllocRequestId();
bool IsInitialized();
void SetInitialized(bool initialized);
void StoreRequestCallback(ContextObject* ctx, const OrbisNpMatching2RequestOptParam* requestOpt);
void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp);
void* BuildCreateJoinRoomPayloadA(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp);
void* BuildLeaveRoomPayload(ContextObject& ctx, const shadnet::LeaveRoomReply& resp);
void* BuildGetWorldInfoListPayload(ContextObject& ctx, const shadnet::GetWorldInfoListReply& resp);
void* BuildSearchRoomPayload(ContextObject& ctx, const shadnet::SearchRoomReply& resp);

View File

@ -4,11 +4,10 @@
#include <chrono>
#include <condition_variable>
#include <cstring>
#include <limits>
#include <map>
#include <memory>
#include <mutex>
#include <thread>
#include <string>
#include <vector>
#include "common/logging/log.h"
@ -17,6 +16,7 @@
#include "core/libraries/np/np_error.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"
#include "core/libraries/np/np_signaling/np_signaling_stubs.h"
#include "shadnet.pb.h"
#include "shadnet/client.h"
@ -29,6 +29,7 @@ struct PendingRequest {
OrbisNpMatching2ContextId ctx_id = 0;
OrbisNpMatching2RequestId req_id = 0;
OrbisNpMatching2Event req_event{};
bool a_variant = false;
};
struct MmClientState {
@ -70,6 +71,12 @@ std::vector<u8> MakeProtoPayload(const T& msg) {
return out;
}
std::string OnlineIdToString(const Libraries::Np::OrbisNpOnlineId& online_id) {
char buf[ORBIS_NP_ONLINEID_MAX_LENGTH + 1]{};
std::memcpy(buf, online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
return std::string(buf);
}
void AppendBinAttr(shadnet::MatchingBinAttr* dst, const OrbisNpMatching2BinAttr& src) {
dst->set_attr_id(src.id);
if (src.data && src.dataSize > 0) {
@ -82,6 +89,40 @@ void AppendIntAttr(shadnet::MatchingIntAttr* dst, const OrbisNpMatching2IntAttr&
dst->set_attr_value(src.num);
}
void AppendRoomGroupConfig(shadnet::MatchingRoomGroupConfig* dst,
const OrbisNpMatching2RoomGroupConfig& src) {
dst->set_slot_count(src.slots);
dst->set_has_label(src.hasLabel);
dst->set_has_passwd(src.hasPassword);
if (src.hasLabel) {
dst->set_label(src.label.data, sizeof(src.label.data));
}
}
void AppendCreateJoinRoomCommon(shadnet::CreateRoomRequest& req, const void* room_passwd,
const OrbisNpMatching2RoomPasswordSlotMask* passwd_slot_mask,
const void* group_config, u64 group_configs,
const void* join_group_label) {
if (room_passwd) {
const auto* password = static_cast<const OrbisNpMatching2SessionPassword*>(room_passwd);
req.set_room_password(password->data, sizeof(password->data));
}
if (passwd_slot_mask) {
req.set_has_passwd_slot_mask(true);
req.set_passwd_slot_mask(*passwd_slot_mask);
}
if (group_config) {
const auto* groups = static_cast<const OrbisNpMatching2RoomGroupConfig*>(group_config);
for (u64 i = 0; i < group_configs; ++i) {
AppendRoomGroupConfig(req.add_group_configs(), groups[i]);
}
}
if (join_group_label) {
const auto* label = static_cast<const OrbisNpMatching2GroupLabel*>(join_group_label);
req.set_join_group_label(label->data, sizeof(label->data));
}
}
std::string ExtractProtoBytes(const std::vector<u8>& payload, size_t offset = 0) {
if (payload.size() < offset + 4) {
return {};
@ -96,391 +137,6 @@ std::string ExtractProtoBytes(const std::vector<u8>& payload, size_t offset = 0)
return std::string(reinterpret_cast<const char*>(payload.data() + offset + 4), len);
}
constexpr s32 kMatching2ConnInactive = 0;
constexpr s32 kMatching2ConnPending = 1;
constexpr s32 kMatching2ConnActive = 2;
constexpr auto kMatching2HandshakeRetry = std::chrono::milliseconds(350);
constexpr auto kMatching2HandshakeTimeout = std::chrono::seconds(10);
std::atomic<bool> g_matching2_stop{false};
std::mutex g_matching2_thread_mutex;
std::thread g_matching2_thread;
enum class Matching2HandshakeKind : u8 {
Offer = 1,
Accept = 2,
Check = 3,
CheckAck = 4,
};
#pragma pack(push, 1)
struct Matching2HandshakePacket {
u8 magic[4] = {'S', 'H', 'A', 'D'};
u8 type = 0x21;
u8 kind = 0;
u64 room_id = 0;
u16 from_member_id = 0;
u16 to_member_id = 0;
u8 online_id_from[16]{};
u32 mapped_addr = 0;
u16 mapped_port = 0;
u16 reserved = 0;
u64 nonce = 0;
};
#pragma pack(pop)
static_assert(sizeof(Matching2HandshakePacket) == 0x32);
bool HasMatching2Magic(const Matching2HandshakePacket& pkt) {
return pkt.magic[0] == 'S' && pkt.magic[1] == 'H' && pkt.magic[2] == 'A' &&
pkt.magic[3] == 'D' && pkt.type == 0x21;
}
std::string OnlineIdToString(const Libraries::Np::OrbisNpOnlineId& online_id) {
char buf[ORBIS_NP_ONLINEID_MAX_LENGTH + 1]{};
std::memcpy(buf, online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
return std::string(buf);
}
bool ShouldConnectToPeer(const RoomCache& room, OrbisNpMatching2RoomMemberId self,
OrbisNpMatching2RoomMemberId peer) {
if (peer == 0 || peer == self) {
return false;
}
if (room.signaling_type == ORBIS_NP_MATCHING2_SIGNALING_TYPE_NONE) {
return false;
}
if (room.signaling_type == ORBIS_NP_MATCHING2_SIGNALING_TYPE_STAR &&
room.signaling_main_member != 0) {
return self == room.signaling_main_member || peer == room.signaling_main_member;
}
return true;
}
void QueueMatching2SignalingEvent(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id,
OrbisNpMatching2Event event, s32 error_code) {
PendingEvent ev{};
ev.type = PendingEvent::SIGNALING_CB;
ev.ctx_id = ctx.ctx_id;
ev.fire_at = std::chrono::steady_clock::now();
ev.room_id = room_id;
ev.member_id = member_id;
ev.sig_event = event;
ev.error_code = error_code;
ScheduleEvent(std::move(ev));
}
void QueueMatching2DeadForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
s32 error_code) {
auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end()) {
return;
}
for (const auto& [member_id, member] : room_it->second.members) {
if (member_id == 0 || member_id == ctx.my_member_id) {
continue;
}
auto peer_it = ctx.peers.find(member_id);
if (peer_it != ctx.peers.end()) {
peer_it->second.status = kMatching2ConnInactive;
peer_it->second.handshake_started = false;
peer_it->second.sent_check = false;
}
QueueMatching2SignalingEvent(ctx, room_id, member_id,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD, error_code);
LOG_INFO(Lib_NpMatching2, "Matching2 signaling dead: ctx={} room={} member={} reason={:#x}",
ctx.ctx_id, room_id, member_id, static_cast<u32>(error_code));
}
}
bool ResolvePeerEndpoint(const MemberCache& member, PeerInfo& peer) {
if (peer.addr != 0 && peer.port != 0) {
return true;
}
if (member.addr != 0 && member.port != 0) {
peer.addr = member.addr;
peer.port = member.port;
if (peer.online_id.data[0] == 0) {
std::strncpy(peer.online_id.data, member.np_id.handle.data,
sizeof(peer.online_id.data) - 1);
}
return true;
}
const std::string online_id(member.np_id.handle.data);
u32 resolved_addr = 0;
u16 resolved_port = 0;
if (!online_id.empty() && RequestSignalingInfos(online_id, &resolved_addr, &resolved_port)) {
peer.addr = resolved_addr;
peer.port = resolved_port;
}
if (peer.online_id.data[0] == 0) {
std::strncpy(peer.online_id.data, member.np_id.handle.data,
sizeof(peer.online_id.data) - 1);
}
return peer.addr != 0 && peer.port != 0;
}
void MarkMatching2PeerActive(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id, u32 addr, u16 port) {
if (member_id == 0 || member_id == ctx.my_member_id) {
return;
}
PeerInfo& peer = ctx.peers[member_id];
peer.member_id = member_id;
if (addr != 0) {
peer.addr = addr;
}
if (port != 0) {
peer.port = port;
}
const bool first_active = peer.status != kMatching2ConnActive;
peer.status = kMatching2ConnActive;
peer.handshake_started = true;
if (first_active && !peer.sent_established) {
peer.sent_established = true;
QueueMatching2SignalingEvent(ctx, room_id, member_id,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_ESTABLISHED, ORBIS_OK);
LOG_INFO(Lib_NpMatching2,
"Matching2 signaling established: ctx={} room={} member={} addr={:#x}:{}",
ctx.ctx_id, room_id, member_id, peer.addr, Libraries::Net::sceNetNtohs(peer.port));
}
}
bool SendMatching2Handshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id, Matching2HandshakeKind kind,
u64 nonce) {
auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end()) {
return false;
}
auto member_it = room_it->second.members.find(member_id);
if (member_it == room_it->second.members.end()) {
return false;
}
PeerInfo& peer = ctx.peers[member_id];
peer.member_id = member_id;
if (!ResolvePeerEndpoint(member_it->second, peer)) {
LOG_WARNING(Lib_NpMatching2, "Matching2 signaling: unresolved endpoint room={} member={}",
room_id, member_id);
return false;
}
Matching2HandshakePacket pkt{};
pkt.kind = static_cast<u8>(kind);
pkt.room_id = room_id;
pkt.from_member_id = ctx.my_member_id;
pkt.to_member_id = member_id;
std::memcpy(pkt.online_id_from, ctx.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
pkt.mapped_addr = Net::GetP2PAdvertisedAddr();
pkt.mapped_port = Net::GetP2PConfiguredPort() != 0
? Libraries::Net::sceNetHtons(Net::GetP2PConfiguredPort())
: 0;
pkt.nonce = nonce;
const int rc = Net::P2PMatching2SendTo(&pkt, sizeof(pkt), peer.addr, peer.port);
const auto now = std::chrono::steady_clock::now();
peer.last_send = now;
if (kind == Matching2HandshakeKind::Check) {
peer.last_check_send = now;
}
LOG_DEBUG(Lib_NpMatching2,
"Matching2 handshake send kind={} ctx={} room={} {}->{} dst={:#x}:{} rc={}",
static_cast<u8>(kind), ctx.ctx_id, room_id, ctx.my_member_id, member_id, peer.addr,
Libraries::Net::sceNetNtohs(peer.port), rc);
return rc >= 0;
}
void StartMatching2PeerHandshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id) {
auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end() ||
!ShouldConnectToPeer(room_it->second, ctx.my_member_id, member_id)) {
return;
}
auto member_it = room_it->second.members.find(member_id);
if (member_it == room_it->second.members.end()) {
return;
}
Net::EnsureP2PTransport();
PeerInfo& peer = ctx.peers[member_id];
peer.member_id = member_id;
if (peer.status == kMatching2ConnActive) {
return;
}
ResolvePeerEndpoint(member_it->second, peer);
peer.status = kMatching2ConnPending;
peer.handshake_started = true;
peer.sent_check = false;
if (peer.nonce == 0) {
peer.nonce = static_cast<u64>(std::chrono::steady_clock::now().time_since_epoch().count() ^
(static_cast<u64>(ctx.ctx_id) << 48) ^
(static_cast<u64>(member_id) << 16));
}
SendMatching2Handshake(ctx, room_id, member_id, Matching2HandshakeKind::Offer, 0);
}
void StartMatching2SignalingForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id) {
const auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end()) {
return;
}
for (const auto& [member_id, member] : room_it->second.members) {
StartMatching2PeerHandshake(ctx, room_id, member_id);
}
}
ContextObject* FindContextForMatching2Packet(const Matching2HandshakePacket& pkt) {
for (u32 id = 1; id <= ContextManager::kMaxContexts; ++id) {
ContextObject* ctx =
ContextManager::Instance().Get(static_cast<OrbisNpMatching2ContextId>(id));
if (!ctx || ctx->room_id != pkt.room_id || ctx->my_member_id != pkt.to_member_id) {
continue;
}
if (ctx->room_cache.find(static_cast<OrbisNpMatching2RoomId>(pkt.room_id)) !=
ctx->room_cache.end()) {
return ctx;
}
}
return nullptr;
}
void HandleMatching2HandshakePacket(u32 from_addr, u16 from_port,
const Matching2HandshakePacket& pkt) {
if (!HasMatching2Magic(pkt)) {
return;
}
ContextObject* ctx = FindContextForMatching2Packet(pkt);
if (!ctx) {
LOG_DEBUG(Lib_NpMatching2, "Matching2 handshake: no ctx for room={} to_member={}",
pkt.room_id, pkt.to_member_id);
return;
}
const auto room_id = static_cast<OrbisNpMatching2RoomId>(pkt.room_id);
const auto member_id = static_cast<OrbisNpMatching2RoomMemberId>(pkt.from_member_id);
auto room_it = ctx->room_cache.find(room_id);
if (room_it == ctx->room_cache.end() ||
!ShouldConnectToPeer(room_it->second, ctx->my_member_id, member_id)) {
return;
}
PeerInfo& peer = ctx->peers[member_id];
peer.member_id = member_id;
peer.addr = pkt.mapped_addr != 0 ? pkt.mapped_addr : from_addr;
peer.port = pkt.mapped_port != 0 ? pkt.mapped_port : from_port;
peer.status =
peer.status == kMatching2ConnActive ? kMatching2ConnActive : kMatching2ConnPending;
peer.handshake_started = true;
std::memcpy(peer.online_id.data, pkt.online_id_from, ORBIS_NP_ONLINEID_MAX_LENGTH);
const auto kind = static_cast<Matching2HandshakeKind>(pkt.kind);
switch (kind) {
case Matching2HandshakeKind::Offer:
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::Accept, 0);
break;
case Matching2HandshakeKind::Accept:
peer.sent_check = true;
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::Check, peer.nonce);
break;
case Matching2HandshakeKind::Check:
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::CheckAck,
pkt.nonce);
MarkMatching2PeerActive(*ctx, room_id, member_id, peer.addr, peer.port);
break;
case Matching2HandshakeKind::CheckAck:
if (pkt.nonce == 0 || pkt.nonce == peer.nonce) {
if (peer.last_check_send.time_since_epoch().count() != 0) {
const auto rtt = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - peer.last_check_send);
peer.ping_us =
static_cast<u32>(std::min<u64>(static_cast<u64>(std::max<s64>(0, rtt.count())),
std::numeric_limits<u32>::max()));
peer.last_check_send = {};
}
MarkMatching2PeerActive(*ctx, room_id, member_id, peer.addr, peer.port);
}
break;
default:
break;
}
}
void Matching2HandshakeThreadMain() {
while (!g_matching2_stop.load(std::memory_order_relaxed)) {
Matching2HandshakePacket pkt{};
u32 from_addr = 0;
u16 from_port = 0;
const int rc = Net::P2PMatching2RecvFrom(&pkt, sizeof(pkt), &from_addr, &from_port);
if (rc == sizeof(pkt)) {
HandleMatching2HandshakePacket(from_addr, from_port, pkt);
}
const auto now = std::chrono::steady_clock::now();
for (u32 id = 1; id <= ContextManager::kMaxContexts; ++id) {
ContextObject* ctx =
ContextManager::Instance().Get(static_cast<OrbisNpMatching2ContextId>(id));
if (!ctx || ctx->room_id == 0) {
continue;
}
auto room_it = ctx->room_cache.find(ctx->room_id);
if (room_it == ctx->room_cache.end()) {
continue;
}
for (auto& [member_id, peer] : ctx->peers) {
if (peer.status != kMatching2ConnPending || !peer.handshake_started) {
continue;
}
if (peer.last_send.time_since_epoch().count() != 0 &&
now - peer.last_send > kMatching2HandshakeTimeout) {
peer.status = kMatching2ConnInactive;
QueueMatching2SignalingEvent(*ctx, ctx->room_id, member_id,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_NETINFO_ERROR,
ORBIS_NP_MATCHING2_SIGNALING_ERROR_TIMEOUT);
continue;
}
if (peer.last_send.time_since_epoch().count() == 0 ||
now - peer.last_send >= kMatching2HandshakeRetry) {
SendMatching2Handshake(*ctx, ctx->room_id, member_id,
peer.sent_check ? Matching2HandshakeKind::Check
: Matching2HandshakeKind::Offer,
peer.sent_check ? peer.nonce : 0);
}
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
}
void StartMatching2HandshakeThread() {
std::lock_guard lock(g_matching2_thread_mutex);
if (g_matching2_thread.joinable()) {
return;
}
g_matching2_stop.store(false, std::memory_order_relaxed);
g_matching2_thread = std::thread(Matching2HandshakeThreadMain);
}
void StopMatching2HandshakeThread() {
{
std::lock_guard lock(g_matching2_thread_mutex);
g_matching2_stop.store(true, std::memory_order_relaxed);
}
if (g_matching2_thread.joinable()) {
g_matching2_thread.join();
}
}
void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error,
const std::vector<u8>& body) {
ContextObject* ctx = ContextManager::Instance().Get(pr.ctx_id);
@ -507,15 +163,19 @@ void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error,
if (error_code == 0) {
const std::string proto = ExtractProtoBytes(body);
if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM) {
if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM ||
pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A) {
shadnet::CreateRoomReply reply;
if (reply.ParseFromString(proto) && reply.has_details()) {
request_data = BuildCreateJoinRoomPayload(*ctx, reply.details());
request_data = pr.a_variant ? BuildCreateJoinRoomPayloadA(*ctx, reply.details())
: BuildCreateJoinRoomPayload(*ctx, reply.details());
}
} else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM) {
} else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM ||
pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM_A) {
shadnet::JoinRoomReply reply;
if (reply.ParseFromString(proto) && reply.has_details()) {
request_data = BuildCreateJoinRoomPayload(*ctx, reply.details());
request_data = pr.a_variant ? BuildCreateJoinRoomPayloadA(*ctx, reply.details())
: BuildCreateJoinRoomPayload(*ctx, reply.details());
}
} else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_LEAVE_ROOM) {
shadnet::LeaveRoomReply reply;
@ -551,7 +211,9 @@ void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error,
ScheduleEvent(std::move(ev));
if (error_code == 0 && (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM ||
pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM)) {
pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A ||
pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM ||
pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM_A)) {
StartMatching2SignalingForRoomPeers(*ctx, ctx->room_id);
}
}
@ -631,6 +293,8 @@ void HandleRoomEvent(const ShadNet::NotifyRoomEvent& n) {
mc.addr = IpStringToAddr(n.member_addr);
mc.port = Libraries::Net::sceNetHtons(static_cast<u16>(n.member_port));
std::strncpy(mc.np_id.handle.data, n.member_npid.c_str(), sizeof(mc.np_id.handle.data) - 1);
mc.account_id = static_cast<Libraries::Np::OrbisNpAccountId>(n.member_account_id);
mc.platform = static_cast<Libraries::Np::OrbisNpPlatformType>(n.member_platform);
for (const auto& a : n.member_bin_attrs) {
MemberBinCache& b = mc.bins[a.attr_id];
b.id = static_cast<OrbisNpMatching2AttributeId>(a.attr_id);
@ -673,6 +337,124 @@ void HandleRoomEvent(const ShadNet::NotifyRoomEvent& n) {
p.room_event_data = p.room_update.get();
break;
}
case ORBIS_NP_MATCHING2_ROOM_EVENT_UPDATED_ROOM_DATA_INTERNAL: {
auto room_it = ctx->room_cache.find(room_id);
if (room_it == ctx->room_cache.end()) {
return;
}
RoomCache& rc = room_it->second;
const auto prev_flags = static_cast<OrbisNpMatching2FlagAttr>(rc.flags);
const auto prev_passwd_slot_mask = rc.passwd_slot_mask;
rc.flags = n.flags;
if (n.has_passwd_mask) {
rc.passwd_slot_mask = n.passwd_slot_mask;
}
std::vector<OrbisNpMatching2RoomBinAttrInternal> changed_attrs;
changed_attrs.reserve(n.bin_attrs.size());
for (const auto& a : n.bin_attrs) {
rc.bin_buffers.emplace_back(a.data.begin(), a.data.end());
auto& buf = rc.bin_buffers.back();
OrbisNpMatching2RoomBinAttrInternal updated{};
updated.binAttr.id = static_cast<OrbisNpMatching2AttributeId>(a.attr_id);
updated.binAttr.data = buf.empty() ? nullptr : buf.data();
updated.binAttr.dataSize = buf.size();
updated.lastUpdate.tick = a.update_date;
updated.memberId = static_cast<OrbisNpMatching2RoomMemberId>(a.update_member_id);
bool replaced = false;
for (auto& existing : rc.bin_attrs_internal) {
if (existing.binAttr.id == updated.binAttr.id) {
existing = updated;
replaced = true;
break;
}
}
if (!replaced) {
rc.bin_attrs_internal.push_back(updated);
}
changed_attrs.push_back(updated);
}
p.room_groups.reserve(rc.groups.size());
for (const auto& [gid, group] : rc.groups) {
p.room_groups.push_back(group);
}
p.room_group_ptrs.reserve(p.room_groups.size());
for (auto& group : p.room_groups) {
p.room_group_ptrs.push_back(&group);
}
p.room_bin_attrs.resize(changed_attrs.size());
p.bin_buffers.clear();
p.bin_buffers.reserve(changed_attrs.size());
for (size_t i = 0; i < changed_attrs.size(); ++i) {
const auto& src = changed_attrs[i];
const auto* data = static_cast<const u8*>(src.binAttr.data);
if (data && src.binAttr.dataSize > 0) {
p.bin_buffers.emplace_back(data, data + src.binAttr.dataSize);
} else {
p.bin_buffers.emplace_back();
}
auto& buf = p.bin_buffers.back();
auto& dst = p.room_bin_attrs[i];
dst = OrbisNpMatching2RoomBinAttrInternal{};
dst.lastUpdate = src.lastUpdate;
dst.memberId = src.memberId;
dst.binAttr.id = src.binAttr.id;
dst.binAttr.data = buf.empty() ? nullptr : buf.data();
dst.binAttr.dataSize = buf.size();
}
p.room_bin_attr_ptrs.reserve(p.room_bin_attrs.size());
for (auto& attr : p.room_bin_attrs) {
p.room_bin_attr_ptrs.push_back(&attr);
}
p.room_data = std::make_unique<OrbisNpMatching2RoomDataInternal>();
auto& room = *p.room_data;
room = {};
room.publicSlots = rc.public_slots;
room.privateSlots = rc.private_slots;
room.openPublicSlots = rc.open_public_slots;
room.openPrivateSlots = rc.open_private_slots;
room.maxSlot = rc.max_slot;
room.serverId = rc.server_id;
room.worldId = rc.world_id;
room.lobbyId = rc.lobby_id;
room.roomId = rc.room_id;
room.passwdSlotMask = rc.passwd_slot_mask;
room.joinedSlotMask = rc.joined_slot_mask;
room.roomGroup = p.room_groups.empty() ? nullptr : p.room_groups.data();
room.roomGroups = p.room_groups.size();
room.flags = rc.flags;
room.internalBinAttr = p.room_bin_attrs.empty() ? nullptr : p.room_bin_attrs.data();
room.internalBinAttrs = p.room_bin_attrs.size();
p.event_chg_flag_attr = std::make_unique<OrbisNpMatching2FlagAttr>(
static_cast<OrbisNpMatching2FlagAttr>(rc.flags));
p.event_prev_flag_attr = std::make_unique<OrbisNpMatching2FlagAttr>(prev_flags);
p.event_chg_passwd_slot_mask =
std::make_unique<OrbisNpMatching2RoomPasswordSlotMask>(rc.passwd_slot_mask);
p.event_prev_passwd_slot_mask =
std::make_unique<OrbisNpMatching2RoomPasswordSlotMask>(prev_passwd_slot_mask);
p.room_data_internal_update = std::make_unique<OrbisNpMatching2RoomDataInternalUpdate>();
auto& u = *p.room_data_internal_update;
u = {};
u.chgRoomDataInternal = p.room_data.get();
u.chgFlagAttr = p.event_chg_flag_attr.get();
u.prevFlagAttr = p.event_prev_flag_attr.get();
u.chgRoomPasswordSlotMask = p.event_chg_passwd_slot_mask.get();
u.prevRoomPasswordSlotMask = p.event_prev_passwd_slot_mask.get();
u.chgRoomGroup = p.room_group_ptrs.empty() ? nullptr : p.room_group_ptrs.data();
u.chgRoomGroupNum = p.room_group_ptrs.size();
u.chgRoomBinAttrInternal =
p.room_bin_attr_ptrs.empty() ? nullptr : p.room_bin_attr_ptrs.data();
u.chgRoomBinAttrInternalNum = p.room_bin_attr_ptrs.size();
p.room_event_data = p.room_data_internal_update.get();
break;
}
default:
LOG_WARNING(Lib_NpMatching2, "unhandled room event {:#x}", n.event);
return;
@ -795,6 +577,9 @@ void MmContextStart(OrbisNpMatching2ContextId ctx_id) {
req.set_ctx_id(ctx_id);
MmSubmitRequest(ctx_id, 0, ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED, MmCommand::ContextStart,
MakeProtoPayload(req));
if (ContextObject* ctx = ContextManager::Instance().Get(ctx_id)) {
SendMatching2StunPing(*ctx);
}
}
void MmContextStop(OrbisNpMatching2ContextId ctx_id) {
@ -805,8 +590,8 @@ void MmContextStop(OrbisNpMatching2ContextId ctx_id) {
}
s32 MmSubmitRequest(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
OrbisNpMatching2Event req_event, MmCommand cmd,
const std::vector<u8>& payload) {
OrbisNpMatching2Event req_event, MmCommand cmd, const std::vector<u8>& payload,
bool a_variant) {
std::shared_ptr<ShadNet::ShadNetClient> client;
{
std::lock_guard lock(g_mm.mutex);
@ -820,7 +605,7 @@ s32 MmSubmitRequest(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId
const u64 pkt_id = client->SubmitRequest(static_cast<ShadNet::CommandType>(cmd), payload);
{
std::lock_guard lock(g_mm.pending_mutex);
g_mm.pending[pkt_id] = {ctx_id, req_id, req_event};
g_mm.pending[pkt_id] = {ctx_id, req_id, req_event, a_variant};
}
LOG_DEBUG(Lib_NpMatching2, "submit cmd={} pkt_id={} ctx={} reqId={}", static_cast<u16>(cmd),
pkt_id, ctx_id, req_id);
@ -840,6 +625,22 @@ s32 MmCreateJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId
req.set_allowed_user_count(static_cast<u32>(request.allowedUsers));
req.set_blocked_user_count(static_cast<u32>(request.blockedUsers));
req.set_internal_bin_attr_count(static_cast<u32>(request.internalBinAttrs));
AppendCreateJoinRoomCommon(req, request.roomPasswd, request.passwdSlotMask, request.groupConfig,
request.groupConfigs, request.joinGroupLabel);
req.set_user_id_kind(shadnet::MATCHING_USER_ID_ONLINE_ID);
if (request.allowedUser) {
for (u64 i = 0; i < request.allowedUsers; ++i) {
req.add_allowed_online_ids(OnlineIdToString(request.allowedUser[i]));
}
}
if (request.blockedUser) {
for (u64 i = 0; i < request.blockedUsers; ++i) {
req.add_blocked_online_ids(OnlineIdToString(request.blockedUser[i]));
}
}
for (u64 i = 0; i < request.internalBinAttrs; ++i) {
AppendBinAttr(req.add_internal_bin_attrs(), request.internalBinAttr[i]);
}
for (u64 i = 0; i < request.externalSearchIntAttrs; ++i) {
AppendIntAttr(req.add_external_search_int_attrs(), request.externalSearchIntAttr[i]);
}
@ -863,6 +664,58 @@ s32 MmCreateJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId
MmCommand::CreateRoom, MakeProtoPayload(req));
}
s32 MmCreateJoinRoomA(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2CreateJoinRoomRequestA& request) {
shadnet::CreateRoomRequest req;
req.set_req_id(req_id);
req.set_max_slots(request.maxSlot);
req.set_team_id(request.teamId);
req.set_world_id(request.worldId);
req.set_lobby_id(static_cast<u32>(request.lobbyId));
req.set_flags(request.flags);
req.set_group_config_count(static_cast<u32>(request.groupConfigs));
req.set_allowed_user_count(static_cast<u32>(request.allowedUsers));
req.set_blocked_user_count(static_cast<u32>(request.blockedUsers));
req.set_internal_bin_attr_count(static_cast<u32>(request.internalBinAttrs));
AppendCreateJoinRoomCommon(req, request.roomPasswd, request.passwdSlotMask, request.groupConfig,
request.groupConfigs, request.joinGroupLabel);
req.set_user_id_kind(shadnet::MATCHING_USER_ID_ACCOUNT_ID);
if (request.allowedUser) {
for (u64 i = 0; i < request.allowedUsers; ++i) {
req.add_allowed_account_ids(request.allowedUser[i]);
}
}
if (request.blockedUser) {
for (u64 i = 0; i < request.blockedUsers; ++i) {
req.add_blocked_account_ids(request.blockedUser[i]);
}
}
for (u64 i = 0; i < request.internalBinAttrs; ++i) {
AppendBinAttr(req.add_internal_bin_attrs(), request.internalBinAttr[i]);
}
for (u64 i = 0; i < request.externalSearchIntAttrs; ++i) {
AppendIntAttr(req.add_external_search_int_attrs(), request.externalSearchIntAttr[i]);
}
for (u64 i = 0; i < request.externalSearchBinAttrs; ++i) {
AppendBinAttr(req.add_external_search_bin_attrs(), request.externalSearchBinAttr[i]);
}
for (u64 i = 0; i < request.externalBinAttrs; ++i) {
AppendBinAttr(req.add_external_bin_attrs(), request.externalBinAttr[i]);
}
for (u64 i = 0; i < request.memberInternalBinAttrs; ++i) {
AppendBinAttr(req.add_member_bin_attrs(), request.memberInternalBinAttr[i]);
}
req.set_join_group_label_present(request.joinGroupLabel != nullptr);
req.set_room_password_present(request.roomPasswd != nullptr);
if (request.signalingParam) {
req.set_sig_type(request.signalingParam->type);
req.set_sig_flag(request.signalingParam->flag);
req.set_sig_main_member(request.signalingParam->memberId);
}
return MmSubmitRequest(ctx_id, req_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A,
MmCommand::CreateRoom, MakeProtoPayload(req), true);
}
s32 MmJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2JoinRoomRequest& request) {
shadnet::JoinRoomRequest req;
@ -871,6 +724,12 @@ s32 MmJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_i
req.set_team_id(request.teamId);
req.set_join_flags(request.flags);
req.set_blocked_user_count(static_cast<u32>(request.blockedUsers));
req.set_user_id_kind(shadnet::MATCHING_USER_ID_ONLINE_ID);
if (request.blockedUser) {
for (u64 i = 0; i < request.blockedUsers; ++i) {
req.add_blocked_online_ids(OnlineIdToString(request.blockedUser[i]));
}
}
for (u64 i = 0; i < request.roomMemberBinInternalAttrNum; ++i) {
AppendBinAttr(req.add_member_bin_attrs(), request.roomMemberBinInternalAttr[i]);
}
@ -880,6 +739,29 @@ s32 MmJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_i
MmCommand::JoinRoom, MakeProtoPayload(req));
}
s32 MmJoinRoomA(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2JoinRoomRequestA& request) {
shadnet::JoinRoomRequest req;
req.set_room_id(request.roomId);
req.set_req_id(req_id);
req.set_team_id(request.teamId);
req.set_join_flags(request.flags);
req.set_blocked_user_count(static_cast<u32>(request.blockedUsers));
req.set_user_id_kind(shadnet::MATCHING_USER_ID_ACCOUNT_ID);
if (request.blockedUser) {
for (u64 i = 0; i < request.blockedUsers; ++i) {
req.add_blocked_account_ids(request.blockedUser[i]);
}
}
for (u64 i = 0; i < request.roomMemberBinInternalAttrNum; ++i) {
AppendBinAttr(req.add_member_bin_attrs(), request.roomMemberBinInternalAttr[i]);
}
req.set_room_password_present(request.roomPasswd != nullptr);
req.set_join_group_label_present(request.joinGroupLabel != nullptr);
return MmSubmitRequest(ctx_id, req_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM_A,
MmCommand::JoinRoom, MakeProtoPayload(req), true);
}
s32 MmLeaveRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2LeaveRoomRequest& request) {
shadnet::LeaveRoomRequest req;

View File

@ -45,12 +45,17 @@ void MmContextStart(OrbisNpMatching2ContextId ctx_id);
void MmContextStop(OrbisNpMatching2ContextId ctx_id);
s32 MmSubmitRequest(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
OrbisNpMatching2Event req_event, MmCommand cmd, const std::vector<u8>& payload);
OrbisNpMatching2Event req_event, MmCommand cmd, const std::vector<u8>& payload,
bool a_variant = false);
s32 MmCreateJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2CreateJoinRoomRequest& request);
s32 MmCreateJoinRoomA(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2CreateJoinRoomRequestA& request);
s32 MmJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2JoinRoomRequest& request);
s32 MmJoinRoomA(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2JoinRoomRequestA& request);
s32 MmLeaveRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,
const OrbisNpMatching2LeaveRoomRequest& request);
s32 MmGetWorldInfoList(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id,

View File

@ -0,0 +1,609 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstring>
#include <limits>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include "common/logging/log.h"
#include "core/libraries/network/net.h"
#include "core/libraries/network/sockets.h"
#include "core/libraries/np/np_error.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"
#include "core/libraries/np/np_signaling/np_signaling_stubs.h"
namespace Libraries::Np::NpMatching2 {
namespace {
constexpr s32 kMatching2ConnInactive = 0;
constexpr s32 kMatching2ConnPending = 1;
constexpr s32 kMatching2ConnActive = 2;
constexpr auto kMatching2HandshakeRetry = std::chrono::milliseconds(350);
constexpr auto kMatching2HandshakeTimeout = std::chrono::seconds(10);
constexpr auto kMatching2StunPingInterval = std::chrono::seconds(5);
std::atomic<bool> g_matching2_stop{false};
std::mutex g_matching2_thread_mutex;
std::thread g_matching2_thread;
enum class Matching2HandshakeKind : u8 {
Offer = 1,
Accept = 2,
Check = 3,
CheckAck = 4,
};
#pragma pack(push, 1)
struct Matching2HandshakePacket {
u8 magic[4] = {'S', 'H', 'A', 'D'};
u8 type = 0x21;
u8 kind = 0;
u64 room_id = 0;
u16 from_member_id = 0;
u16 to_member_id = 0;
u8 online_id_from[16]{};
u32 mapped_addr = 0;
u16 mapped_port = 0;
u16 reserved = 0;
u64 nonce = 0;
};
#pragma pack(pop)
static_assert(sizeof(Matching2HandshakePacket) == 0x32);
#pragma pack(push, 1)
struct Matching2StunPing {
u8 cmd = 0x01;
u8 online_id[ORBIS_NP_ONLINEID_MAX_LENGTH]{};
u32 local_ip = 0;
};
#pragma pack(pop)
static_assert(sizeof(Matching2StunPing) == 21);
bool HasMatching2Magic(const Matching2HandshakePacket& pkt) {
return pkt.magic[0] == 'S' && pkt.magic[1] == 'H' && pkt.magic[2] == 'A' &&
pkt.magic[3] == 'D' && pkt.type == 0x21;
}
std::string OnlineIdToString(const Libraries::Np::OrbisNpOnlineId& online_id) {
char buf[ORBIS_NP_ONLINEID_MAX_LENGTH + 1]{};
std::memcpy(buf, online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
return std::string(buf);
}
bool ShouldConnectToPeer(const RoomCache& room, OrbisNpMatching2RoomMemberId self,
OrbisNpMatching2RoomMemberId peer) {
if (peer == 0 || peer == self) {
return false;
}
if (room.signaling_type == ORBIS_NP_MATCHING2_SIGNALING_TYPE_NONE) {
return false;
}
if (room.signaling_type == ORBIS_NP_MATCHING2_SIGNALING_TYPE_STAR &&
room.signaling_main_member != 0) {
return self == room.signaling_main_member || peer == room.signaling_main_member;
}
return true;
}
bool ResolvePeerEndpoint(const MemberCache& member, PeerInfo& peer) {
if (peer.addr != 0 && peer.port != 0) {
return true;
}
if (member.addr != 0 && member.port != 0) {
peer.addr = member.addr;
peer.port = member.port;
if (peer.online_id.data[0] == 0) {
std::strncpy(peer.online_id.data, member.np_id.handle.data,
sizeof(peer.online_id.data) - 1);
}
return true;
}
const std::string online_id(member.np_id.handle.data);
u32 resolved_addr = 0;
u16 resolved_port = 0;
if (!online_id.empty() && RequestSignalingInfos(online_id, &resolved_addr, &resolved_port)) {
peer.addr = resolved_addr;
peer.port = resolved_port;
}
if (peer.online_id.data[0] == 0) {
std::strncpy(peer.online_id.data, member.np_id.handle.data,
sizeof(peer.online_id.data) - 1);
}
return peer.addr != 0 && peer.port != 0;
}
void MarkMatching2PeerActive(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id, u32 addr, u16 port) {
if (member_id == 0 || member_id == ctx.my_member_id) {
return;
}
PeerInfo& peer = ctx.peers[member_id];
peer.member_id = member_id;
if (addr != 0) {
peer.addr = addr;
}
if (port != 0) {
peer.port = port;
}
const bool first_active = peer.status != kMatching2ConnActive;
peer.status = kMatching2ConnActive;
peer.handshake_started = true;
if (first_active && !peer.sent_established) {
peer.sent_established = true;
QueueMatching2SignalingEvent(ctx, room_id, member_id,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_ESTABLISHED, ORBIS_OK);
LOG_INFO(Lib_NpMatching2,
"Matching2 signaling established: ctx={} room={} member={} addr={:#x}:{}",
ctx.ctx_id, room_id, member_id, peer.addr, Libraries::Net::sceNetNtohs(peer.port));
}
}
bool SendMatching2Handshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id, Matching2HandshakeKind kind,
u64 nonce) {
auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end()) {
return false;
}
auto member_it = room_it->second.members.find(member_id);
if (member_it == room_it->second.members.end()) {
return false;
}
PeerInfo& peer = ctx.peers[member_id];
peer.member_id = member_id;
if (!ResolvePeerEndpoint(member_it->second, peer)) {
LOG_WARNING(Lib_NpMatching2, "Matching2 signaling: unresolved endpoint room={} member={}",
room_id, member_id);
return false;
}
Matching2HandshakePacket pkt{};
pkt.kind = static_cast<u8>(kind);
pkt.room_id = room_id;
pkt.from_member_id = ctx.my_member_id;
pkt.to_member_id = member_id;
std::memcpy(pkt.online_id_from, ctx.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
pkt.mapped_addr = Net::GetP2PAdvertisedAddr();
pkt.mapped_port = Net::GetP2PConfiguredPort() != 0
? Libraries::Net::sceNetHtons(Net::GetP2PConfiguredPort())
: 0;
pkt.nonce = nonce;
const int rc = Net::P2PMatching2SendTo(&pkt, sizeof(pkt), peer.addr, peer.port);
const auto now = std::chrono::steady_clock::now();
peer.last_send = now;
if (kind == Matching2HandshakeKind::Check) {
peer.last_check_send = now;
}
LOG_DEBUG(Lib_NpMatching2,
"Matching2 handshake send kind={} ctx={} room={} {}->{} dst={:#x}:{} rc={}",
static_cast<u8>(kind), ctx.ctx_id, room_id, ctx.my_member_id, member_id, peer.addr,
Libraries::Net::sceNetNtohs(peer.port), rc);
return rc >= 0;
}
ContextObject* FindContextForMatching2Packet(const Matching2HandshakePacket& pkt) {
for (u32 id = 1; id <= ContextManager::kMaxContexts; ++id) {
ContextObject* ctx =
ContextManager::Instance().Get(static_cast<OrbisNpMatching2ContextId>(id));
if (!ctx || ctx->room_id != pkt.room_id || ctx->my_member_id != pkt.to_member_id) {
continue;
}
if (ctx->room_cache.find(static_cast<OrbisNpMatching2RoomId>(pkt.room_id)) !=
ctx->room_cache.end()) {
return ctx;
}
}
return nullptr;
}
void HandleMatching2HandshakePacket(u32 from_addr, u16 from_port,
const Matching2HandshakePacket& pkt) {
if (!HasMatching2Magic(pkt)) {
return;
}
ContextObject* ctx = FindContextForMatching2Packet(pkt);
if (!ctx) {
LOG_DEBUG(Lib_NpMatching2, "Matching2 handshake: no ctx for room={} to_member={}",
pkt.room_id, pkt.to_member_id);
return;
}
const auto room_id = static_cast<OrbisNpMatching2RoomId>(pkt.room_id);
const auto member_id = static_cast<OrbisNpMatching2RoomMemberId>(pkt.from_member_id);
auto room_it = ctx->room_cache.find(room_id);
if (room_it == ctx->room_cache.end() ||
!ShouldConnectToPeer(room_it->second, ctx->my_member_id, member_id)) {
return;
}
PeerInfo& peer = ctx->peers[member_id];
peer.member_id = member_id;
peer.addr = pkt.mapped_addr != 0 ? pkt.mapped_addr : from_addr;
peer.port = pkt.mapped_port != 0 ? pkt.mapped_port : from_port;
peer.status =
peer.status == kMatching2ConnActive ? kMatching2ConnActive : kMatching2ConnPending;
peer.handshake_started = true;
std::memcpy(peer.online_id.data, pkt.online_id_from, ORBIS_NP_ONLINEID_MAX_LENGTH);
const auto kind = static_cast<Matching2HandshakeKind>(pkt.kind);
switch (kind) {
case Matching2HandshakeKind::Offer:
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::Accept, 0);
break;
case Matching2HandshakeKind::Accept:
peer.sent_check = true;
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::Check, peer.nonce);
break;
case Matching2HandshakeKind::Check:
SendMatching2Handshake(*ctx, room_id, member_id, Matching2HandshakeKind::CheckAck,
pkt.nonce);
MarkMatching2PeerActive(*ctx, room_id, member_id, peer.addr, peer.port);
break;
case Matching2HandshakeKind::CheckAck:
if (pkt.nonce == 0 || pkt.nonce == peer.nonce) {
if (peer.last_check_send.time_since_epoch().count() != 0) {
const auto rtt = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - peer.last_check_send);
peer.ping_us =
static_cast<u32>(std::min<u64>(static_cast<u64>(std::max<s64>(0, rtt.count())),
std::numeric_limits<u32>::max()));
peer.last_check_send = {};
}
MarkMatching2PeerActive(*ctx, room_id, member_id, peer.addr, peer.port);
}
break;
default:
break;
}
}
void Matching2HandshakeThreadMain() {
auto last_stun_ping = std::chrono::steady_clock::time_point{};
while (!g_matching2_stop.load(std::memory_order_relaxed)) {
Matching2HandshakePacket pkt{};
u32 from_addr = 0;
u16 from_port = 0;
const int rc = Net::P2PMatching2RecvFrom(&pkt, sizeof(pkt), &from_addr, &from_port);
if (rc == sizeof(pkt)) {
HandleMatching2HandshakePacket(from_addr, from_port, pkt);
}
const auto now = std::chrono::steady_clock::now();
const bool should_stun_ping = last_stun_ping.time_since_epoch().count() == 0 ||
now - last_stun_ping >= kMatching2StunPingInterval;
for (u32 id = 1; id <= ContextManager::kMaxContexts; ++id) {
ContextObject* ctx =
ContextManager::Instance().Get(static_cast<OrbisNpMatching2ContextId>(id));
if (!ctx) {
continue;
}
if (should_stun_ping) {
SendMatching2StunPing(*ctx);
}
if (ctx->room_id == 0) {
continue;
}
auto room_it = ctx->room_cache.find(ctx->room_id);
if (room_it == ctx->room_cache.end()) {
continue;
}
for (auto& [member_id, peer] : ctx->peers) {
if (peer.status != kMatching2ConnPending || !peer.handshake_started) {
continue;
}
if (peer.last_send.time_since_epoch().count() != 0 &&
now - peer.last_send > kMatching2HandshakeTimeout) {
peer.status = kMatching2ConnInactive;
QueueMatching2SignalingEvent(*ctx, ctx->room_id, member_id,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_NETINFO_ERROR,
ORBIS_NP_MATCHING2_SIGNALING_ERROR_TIMEOUT);
continue;
}
if (peer.last_send.time_since_epoch().count() == 0 ||
now - peer.last_send >= kMatching2HandshakeRetry) {
SendMatching2Handshake(*ctx, ctx->room_id, member_id,
peer.sent_check ? Matching2HandshakeKind::Check
: Matching2HandshakeKind::Offer,
peer.sent_check ? peer.nonce : 0);
}
}
}
if (should_stun_ping) {
last_stun_ping = now;
}
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
}
} // namespace
bool SendMatching2StunPing(const ContextObject& ctx) {
if (!ctx.started || ctx.online_id.data[0] == '\0') {
return false;
}
if (!NpSignaling::Stubs::EnsureTransport()) {
return false;
}
const u32 server_addr = NpSignaling::Stubs::MmServerAddr();
const u16 server_udp = NpSignaling::Stubs::MmServerUdpPort();
if (server_addr == 0 || server_udp == 0) {
return false;
}
Matching2StunPing ping{};
ping.cmd = 0x01;
std::memcpy(ping.online_id, ctx.online_id.data, ORBIS_NP_ONLINEID_MAX_LENGTH);
ping.local_ip = NpSignaling::Stubs::AdvertisedAddr();
const int rc =
NpSignaling::Stubs::SignalingSendTo(&ping, sizeof(ping), server_addr, server_udp);
LOG_DEBUG(Lib_NpMatching2,
"Matching2 STUN ping: ctx={} online_id='{}' server={:#x}:{} local_ip={:#x} rc={}",
ctx.ctx_id, OnlineIdToString(ctx.online_id), server_addr,
Libraries::Net::sceNetNtohs(server_udp), ping.local_ip, rc);
return rc >= 0;
}
void QueueMatching2SignalingEvent(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id,
OrbisNpMatching2Event event, s32 error_code) {
PendingEvent ev{};
ev.type = PendingEvent::SIGNALING_CB;
ev.ctx_id = ctx.ctx_id;
ev.fire_at = std::chrono::steady_clock::now();
ev.room_id = room_id;
ev.member_id = member_id;
ev.sig_event = event;
ev.error_code = error_code;
ScheduleEvent(std::move(ev));
}
void StartMatching2PeerHandshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id) {
auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end() ||
!ShouldConnectToPeer(room_it->second, ctx.my_member_id, member_id)) {
return;
}
auto member_it = room_it->second.members.find(member_id);
if (member_it == room_it->second.members.end()) {
return;
}
Net::EnsureP2PTransport();
PeerInfo& peer = ctx.peers[member_id];
peer.member_id = member_id;
if (peer.status == kMatching2ConnActive) {
return;
}
ResolvePeerEndpoint(member_it->second, peer);
peer.status = kMatching2ConnPending;
peer.handshake_started = true;
peer.sent_check = false;
if (peer.nonce == 0) {
peer.nonce = static_cast<u64>(std::chrono::steady_clock::now().time_since_epoch().count() ^
(static_cast<u64>(ctx.ctx_id) << 48) ^
(static_cast<u64>(member_id) << 16));
}
SendMatching2Handshake(ctx, room_id, member_id, Matching2HandshakeKind::Offer, 0);
}
void StartMatching2SignalingForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id) {
const auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end()) {
return;
}
for (const auto& [member_id, member] : room_it->second.members) {
StartMatching2PeerHandshake(ctx, room_id, member_id);
}
}
void QueueMatching2DeadForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
s32 error_code) {
auto room_it = ctx.room_cache.find(room_id);
if (room_it == ctx.room_cache.end()) {
return;
}
for (const auto& [member_id, member] : room_it->second.members) {
if (member_id == 0 || member_id == ctx.my_member_id) {
continue;
}
auto peer_it = ctx.peers.find(member_id);
if (peer_it != ctx.peers.end()) {
peer_it->second.status = kMatching2ConnInactive;
peer_it->second.handshake_started = false;
peer_it->second.sent_check = false;
}
QueueMatching2SignalingEvent(ctx, room_id, member_id,
ORBIS_NP_MATCHING2_SIGNALING_EVENT_DEAD, error_code);
LOG_INFO(Lib_NpMatching2, "Matching2 signaling dead: ctx={} room={} member={} reason={:#x}",
ctx.ctx_id, room_id, member_id, static_cast<u32>(error_code));
}
}
void StartMatching2HandshakeThread() {
std::lock_guard lock(g_matching2_thread_mutex);
if (g_matching2_thread.joinable()) {
return;
}
g_matching2_stop.store(false, std::memory_order_relaxed);
g_matching2_thread = std::thread(Matching2HandshakeThreadMain);
}
void StopMatching2HandshakeThread() {
{
std::lock_guard lock(g_matching2_thread_mutex);
g_matching2_stop.store(true, std::memory_order_relaxed);
}
if (g_matching2_thread.joinable()) {
g_matching2_thread.join();
}
}
u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId) {
const auto room_it = ctx.room_cache.find(roomId);
if (room_it == ctx.room_cache.end()) {
return 0;
}
u64 total_ping = 0;
u32 ping_count = 0;
for (const auto& [member_id, member] : room_it->second.members) {
if (member_id == ctx.my_member_id) {
continue;
}
const auto peer_it = ctx.peers.find(member_id);
if (peer_it == ctx.peers.end() || peer_it->second.ping_us == 0) {
continue;
}
total_ping += peer_it->second.ping_us;
++ping_count;
}
return ping_count == 0 ? 0 : static_cast<u32>(total_ping / ping_count);
}
void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, OrbisNpMatching2RoomId roomId) {
CallbackPayload& p = ctx.request_payload;
p.Reset();
const auto room_it = ctx.room_cache.find(roomId);
p.ping_info_response = std::make_unique<OrbisNpMatching2SignalingGetPingInfoResponse>();
auto& out = *p.ping_info_response;
out = {};
if (room_it != ctx.room_cache.end()) {
out.serverId = room_it->second.server_id;
out.worldId = room_it->second.world_id;
out.roomId = room_it->second.room_id;
} else {
out.serverId = ctx.server_id;
out.worldId = ctx.world_id;
out.roomId = roomId;
}
out.rtt = GetRoomPingUs(ctx, roomId);
p.request_data = p.ping_info_response.get();
return p.request_data;
}
s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId roomId,
OrbisNpMatching2RoomMemberId memberId, u32 infoType, void* connInfo,
bool a_variant) {
if (!connInfo) {
LOG_ERROR(Lib_NpMatching2, "connInfo null");
return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
}
const auto room_it = ctx.room_cache.find(roomId);
if (room_it == ctx.room_cache.end()) {
LOG_INFO(Lib_NpMatching2, "room={} not cached for connection info", roomId);
if (a_variant) {
*static_cast<OrbisNpMatching2SignalingConnectionInfoA*>(connInfo) = {};
} else {
*static_cast<OrbisNpMatching2SignalingConnectionInfo*>(connInfo) = {};
}
return ORBIS_OK;
}
const auto member_it = room_it->second.members.find(memberId);
const MemberCache* member =
member_it != room_it->second.members.end() ? &member_it->second : nullptr;
const auto peer_it = ctx.peers.find(memberId);
const PeerInfo* peer = peer_it != ctx.peers.end() ? &peer_it->second : nullptr;
if (a_variant) {
auto* out = static_cast<OrbisNpMatching2SignalingConnectionInfoA*>(connInfo);
*out = {};
switch (infoType) {
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_RTT:
out->rtt = peer ? peer->ping_us : 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_BANDWIDTH:
out->bandwidth = 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR:
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_MAPPED_ADDR:
if (peer) {
out->address.addr = peer->addr;
out->address.port = peer->port;
} else if (member) {
out->address.addr = member->addr;
out->address.port = member->port;
}
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PACKET_LOSS:
out->packetLoss = 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDRESS_A:
if (member) {
out->peerAddrA.accountId = member->account_id;
out->peerAddrA.platform = member->platform;
}
break;
default:
LOG_WARNING(Lib_NpMatching2, "unsupported connection info A type={}", infoType);
return ORBIS_NP_MATCHING2_SIGNALING_ERROR_INVALID_ARGUMENT;
}
} else {
auto* out = static_cast<OrbisNpMatching2SignalingConnectionInfo*>(connInfo);
*out = {};
switch (infoType) {
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_RTT:
out->rtt = peer ? peer->ping_us : 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_BANDWIDTH:
out->bandwidth = 0;
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_NP_ID:
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_NPID:
if (member) {
out->npId = member->np_id;
}
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PEER_ADDR:
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_MAPPED_ADDR:
if (peer) {
out->address.addr = peer->addr;
out->address.port = peer->port;
} else if (member) {
out->address.addr = member->addr;
out->address.port = member->port;
}
break;
case ORBIS_NP_MATCHING2_SIGNALING_CONN_INFO_PACKET_LOSS:
out->packetLoss = 0;
break;
default:
LOG_WARNING(Lib_NpMatching2, "unsupported connection info type={}", infoType);
return ORBIS_NP_MATCHING2_SIGNALING_ERROR_INVALID_ARGUMENT;
}
}
LOG_INFO(Lib_NpMatching2, "connection info{}: ctx={} room={} member={} type={} rtt={}",
a_variant ? "A" : "", ctx.ctx_id, roomId, memberId, infoType,
peer ? peer->ping_us : 0);
return ORBIS_OK;
}
} // namespace Libraries::Np::NpMatching2

View File

@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
#include "core/libraries/np/np_matching2/np_matching2.h"
namespace Libraries::Np::NpMatching2 {
struct ContextObject;
void StartMatching2HandshakeThread();
void StopMatching2HandshakeThread();
bool SendMatching2StunPing(const ContextObject& ctx);
void StartMatching2PeerHandshake(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id);
void StartMatching2SignalingForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id);
void QueueMatching2DeadForRoomPeers(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
s32 error_code);
void QueueMatching2SignalingEvent(ContextObject& ctx, OrbisNpMatching2RoomId room_id,
OrbisNpMatching2RoomMemberId member_id,
OrbisNpMatching2Event event, s32 error_code);
u32 GetRoomPingUs(const ContextObject& ctx, OrbisNpMatching2RoomId roomId);
void* BuildSignalingGetPingInfoPayload(ContextObject& ctx, OrbisNpMatching2RoomId roomId);
s32 FillMatching2ConnectionInfo(const ContextObject& ctx, OrbisNpMatching2RoomId roomId,
OrbisNpMatching2RoomMemberId memberId, u32 infoType, void* connInfo,
bool a_variant);
} // namespace Libraries::Np::NpMatching2

View File

@ -50,30 +50,6 @@ using OrbisNpMatching2SignalingCallback =
OrbisNpMatching2RoomMemberId roomMemberId, OrbisNpMatching2Event event,
int errorCode, void* userdata);
// internal - to be removed.
struct NpMatching2ContextEvent {
OrbisNpMatching2ContextId contextId;
OrbisNpMatching2Event event;
OrbisNpMatching2EventCause cause;
int errorCode;
};
// internal - to be removed.
struct NpMatching2LobbyEvent {
OrbisNpMatching2ContextId contextId;
OrbisNpMatching2LobbyId lobbyId;
OrbisNpMatching2Event event;
void* data;
};
// internal - to be removed.
struct NpMatching2RoomEvent {
OrbisNpMatching2ContextId contextId;
OrbisNpMatching2RoomId roomId;
OrbisNpMatching2Event event;
void* data;
};
struct OrbisNpMatching2BinAttr {
OrbisNpMatching2AttributeId id;
u8 pad[6];
@ -281,6 +257,18 @@ struct OrbisNpMatching2RoomUpdate {
OrbisNpMatching2PresenceOptionData optData;
};
struct OrbisNpMatching2RoomDataInternalUpdate {
OrbisNpMatching2RoomDataInternal* chgRoomDataInternal;
OrbisNpMatching2FlagAttr* chgFlagAttr;
OrbisNpMatching2FlagAttr* prevFlagAttr;
OrbisNpMatching2RoomPasswordSlotMask* chgRoomPasswordSlotMask;
OrbisNpMatching2RoomPasswordSlotMask* prevRoomPasswordSlotMask;
OrbisNpMatching2RoomGroup** chgRoomGroup;
u64 chgRoomGroupNum;
OrbisNpMatching2RoomBinAttrInternal** chgRoomBinAttrInternal;
u64 chgRoomBinAttrInternalNum;
};
struct OrbisNpMatching2JoinRoomRequest {
OrbisNpMatching2RoomId roomId;
OrbisNpMatching2SessionPassword* roomPasswd;
@ -300,6 +288,25 @@ struct OrbisNpMatching2JoinRoomRequest {
};
static_assert(sizeof(OrbisNpMatching2JoinRoomRequest) == 0x58);
struct OrbisNpMatching2JoinRoomRequestA {
OrbisNpMatching2RoomId roomId;
OrbisNpMatching2SessionPassword* roomPasswd;
OrbisNpMatching2GroupLabel* joinGroupLabel;
OrbisNpMatching2BinAttr* roomMemberBinInternalAttr;
u64 roomMemberBinInternalAttrNum;
OrbisNpMatching2PresenceOptionData optData;
OrbisNpMatching2TeamId teamId;
u8 pad[3];
OrbisNpMatching2Flags flags;
Libraries::Np::OrbisNpAccountId* blockedUser;
u64 blockedUsers;
int Validate() {
return 0;
}
};
static_assert(sizeof(OrbisNpMatching2JoinRoomRequestA) == 0x58);
struct OrbisNpMatching2KickoutRoomMemberRequest {
OrbisNpMatching2RoomId roomId;
OrbisNpMatching2RoomMemberId memberId;
@ -564,15 +571,6 @@ union OrbisNpMatching2SignalingConnectionInfoA {
};
static_assert(sizeof(OrbisNpMatching2SignalingConnectionInfoA) == 0x10);
// internal - to be removed.
struct OrbisNpMatching2SignalingEvent {
OrbisNpMatching2ContextId contextId;
OrbisNpMatching2RoomId roomId;
OrbisNpMatching2RoomMemberId roomMemberId;
OrbisNpMatching2Event event;
int errorCode;
};
struct OrbisNpMatching2SignalingGetPingInfoRequest {
OrbisNpMatching2RoomId roomId;
u8 pad[16];

View File

@ -756,9 +756,13 @@ void ShadNetClient::HandleNotification(u16 cmd_raw, const std::vector<u8>& paylo
n.event_cause = pb.event_cause();
n.error_code = pb.error_code();
n.flags = pb.flags();
n.has_passwd_mask = pb.has_passwd_mask();
n.passwd_slot_mask = pb.passwd_slot_mask();
if (pb.has_member()) {
const auto& m = pb.member();
n.member_npid = m.npid();
n.member_account_id = m.account_id();
n.member_platform = m.platform();
n.member_id = m.member_id();
n.member_team_id = m.team_id();
n.member_is_owner = m.is_owner();
@ -771,6 +775,8 @@ void ShadNetClient::HandleNotification(u16 cmd_raw, const std::vector<u8>& paylo
for (const auto& a : m.bin_attrs_internal()) {
MatchingBinAttr ba;
ba.attr_id = a.attr_id();
ba.update_date = a.update_date();
ba.update_member_id = a.update_member_id();
ba.data.assign(a.data().begin(), a.data().end());
n.member_bin_attrs.push_back(std::move(ba));
}
@ -778,6 +784,8 @@ void ShadNetClient::HandleNotification(u16 cmd_raw, const std::vector<u8>& paylo
for (const auto& a : pb.bin_attrs()) {
MatchingBinAttr ba;
ba.attr_id = a.attr_id();
ba.update_date = a.update_date();
ba.update_member_id = a.update_member_id();
ba.data.assign(a.data().begin(), a.data().end());
n.bin_attrs.push_back(std::move(ba));
}

View File

@ -199,6 +199,8 @@ struct NotifyWebApiPushEvent {
struct MatchingBinAttr {
u32 attr_id = 0;
u64 update_date = 0;
u32 update_member_id = 0;
std::vector<u8> data;
};
@ -209,8 +211,12 @@ struct NotifyRoomEvent {
u32 event_cause = 0;
s32 error_code = 0;
u32 flags = 0;
bool has_passwd_mask = false;
u64 passwd_slot_mask = 0;
std::string member_npid;
u64 member_account_id = 0;
u32 member_platform = 0;
u32 member_id = 0;
u32 member_team_id = 0;
bool member_is_owner = false;

View File

@ -8,6 +8,11 @@ package shadnet;
option optimize_for = LITE_RUNTIME;
enum MatchingUserIdKind {
MATCHING_USER_ID_ONLINE_ID = 0;
MATCHING_USER_ID_ACCOUNT_ID = 1;
}
// Account
message LoginRequest {
@ -192,8 +197,10 @@ message GetScoreResponse {
// Shared attribute types
message MatchingBinAttr {
uint32 attr_id = 1;
bytes data = 2;
uint32 attr_id = 1;
bytes data = 2;
uint64 update_date = 3;
uint32 update_member_id = 4;
}
message MatchingIntAttr {
@ -210,6 +217,13 @@ message MatchingRoomGroup {
uint32 num_members = 6;
}
message MatchingRoomGroupConfig {
uint32 slot_count = 1;
bool has_label = 2;
bytes label = 3;
bool has_passwd = 4;
}
// Room data structures
message MatchingRoomDataInternal {
@ -242,6 +256,8 @@ message MatchingRoomMemberData {
string addr = 10;
uint32 port = 11;
uint32 group_id = 12;
uint64 account_id = 13;
uint32 platform = 14;
}
message MatchingRoomDataExternal {
@ -302,6 +318,17 @@ message CreateRoomRequest {
uint32 sig_type = 17;
uint32 sig_flag = 18;
uint32 sig_main_member = 19;
repeated MatchingBinAttr internal_bin_attrs = 20;
MatchingUserIdKind user_id_kind = 21;
repeated string allowed_online_ids = 22;
repeated string blocked_online_ids = 23;
repeated uint64 allowed_account_ids = 24;
repeated uint64 blocked_account_ids = 25;
repeated MatchingRoomGroupConfig group_configs = 26;
bytes room_password = 27;
bool has_passwd_slot_mask = 28;
uint64 passwd_slot_mask = 29;
bytes join_group_label = 30;
}
message JoinRoomRequest {
@ -313,6 +340,9 @@ message JoinRoomRequest {
repeated MatchingBinAttr member_bin_attrs = 6;
bool room_password_present = 7;
bool join_group_label_present = 8;
MatchingUserIdKind user_id_kind = 9;
repeated string blocked_online_ids = 10;
repeated uint64 blocked_account_ids = 11;
}
message LeaveRoomRequest {
@ -449,4 +479,6 @@ message NotifyRoomEvent {
MatchingRoomMemberData member = 6;
repeated MatchingBinAttr bin_attrs = 7;
uint32 flags = 8;
bool has_passwd_mask = 9;
uint64 passwd_slot_mask = 10;
}