diff --git a/CMakeLists.txt b/CMakeLists.txt index eeb14210d..5c91750da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/core/libraries/np/np_matching2/np_matching2.cpp b/src/core/libraries/np/np_matching2/np_matching2.cpp index 5708a36bc..4fd175ebe 100644 --- a/src/core/libraries/np/np_matching2/np_matching2.cpp +++ b/src/core/libraries/np/np_matching2/np_matching2.cpp @@ -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(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(); - 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(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(connInfo) = {}; - } else { - *static_cast(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(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(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; } diff --git a/src/core/libraries/np/np_matching2/np_matching2.h b/src/core/libraries/np/np_matching2/np_matching2.h index 3606eb6bd..f12e584d1 100644 --- a/src/core/libraries/np/np_matching2/np_matching2.h +++ b/src/core/libraries/np/np_matching2/np_matching2.h @@ -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, diff --git a/src/core/libraries/np/np_matching2/np_matching2_internal.cpp b/src/core/libraries/np/np_matching2/np_matching2_internal.cpp index 476548e24..b702e3112 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_internal.cpp +++ b/src/core/libraries/np/np_matching2/np_matching2_internal.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #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(a.update_member_id()); dst.binAttr.id = static_cast(a.attr_id()); dst.binAttr.data = buf.empty() ? nullptr : buf.data(); dst.binAttr.dataSize = buf.size(); } - p.room_data = std::make_unique(); 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(m.member_id()); - dst.teamId = static_cast(m.team_id()); - if (m.is_owner()) { - dst.flagAttr |= ORBIS_NP_MATCHING2_ROOMMEMBER_FLAG_ATTR_OWNER; - } - } - - p.create_join_response = std::make_unique(); - 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(resp.me_member_id())) { - out.members.me = &md; - } - if (md.memberId == static_cast(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(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(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(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(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(m.account_id()); + mc.platform = static_cast(m.platform()); for (const auto& a : m.bin_attrs_internal()) { MemberBinCache& b = mc.bins[a.attr_id()]; b.id = static_cast(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(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(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(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(m.member_id()); + dst.teamId = static_cast(m.team_id()); + dst.natType = static_cast(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(m.group_id())); + dst.roomMemberBinAttrInternal = + AppendMemberBinAttrs(p, m, dst.roomMemberBinAttrInternalNum); + } + + p.create_join_response = std::make_unique(); + 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(resp.me_member_id())) { + out.members.me = &md; + } + if (md.memberId == static_cast(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(m.account_id()); + dst.user.platform = static_cast(m.platform()); + std::strncpy(dst.onlineId.data, m.npid().c_str(), sizeof(dst.onlineId.data) - 1); + dst.memberId = static_cast(m.member_id()); + dst.teamId = static_cast(m.team_id()); + dst.natType = static_cast(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(m.group_id())); + dst.roomMemberInternalBinAttr = AppendMemberBinAttrs(p, m, dst.roomMemberInternalBinAttrs); + } + + p.create_join_response_a = std::make_unique(); + 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(resp.me_member_id())) { + out.members.me = &md; + } + if (md.memberId == static_cast(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(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(); @@ -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(); + 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) { diff --git a/src/core/libraries/np/np_matching2/np_matching2_internal.h b/src/core/libraries/np/np_matching2/np_matching2_internal.h index 62a01d9e7..ede416c3f 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_internal.h +++ b/src/core/libraries/np/np_matching2/np_matching2_internal.h @@ -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 room_data; std::unique_ptr create_join_response; + std::unique_ptr create_join_response_a; std::unique_ptr leave_room_response; std::unique_ptr search_room_response; std::unique_ptr world_info_response; std::unique_ptr ping_info_response; std::vector world_list; std::vector member_data; + std::vector member_data_a; std::vector room_groups; + std::vector room_group_ptrs; std::vector room_bin_attrs; + std::vector room_bin_attr_ptrs; std::vector member_bin_attrs; std::vector room_data_external; std::vector> bin_buffers; @@ -110,6 +116,11 @@ struct CallbackPayload { std::unique_ptr room_member_update; std::unique_ptr room_update; + std::unique_ptr room_data_internal_update; + std::unique_ptr event_chg_flag_attr; + std::unique_ptr event_prev_flag_attr; + std::unique_ptr event_chg_passwd_slot_mask; + std::unique_ptr event_prev_passwd_slot_mask; std::unique_ptr 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); diff --git a/src/core/libraries/np/np_matching2/np_matching2_mm.cpp b/src/core/libraries/np/np_matching2/np_matching2_mm.cpp index afa8ae461..5fa0c4c22 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_mm.cpp +++ b/src/core/libraries/np/np_matching2/np_matching2_mm.cpp @@ -4,11 +4,10 @@ #include #include #include -#include #include #include #include -#include +#include #include #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 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(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(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(join_group_label); + req.set_join_group_label(label->data, sizeof(label->data)); + } +} + std::string ExtractProtoBytes(const std::vector& payload, size_t offset = 0) { if (payload.size() < offset + 4) { return {}; @@ -96,391 +137,6 @@ std::string ExtractProtoBytes(const std::vector& payload, size_t offset = 0) return std::string(reinterpret_cast(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 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(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(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(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(std::chrono::steady_clock::now().time_since_epoch().count() ^ - (static_cast(ctx.ctx_id) << 48) ^ - (static_cast(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(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(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(pkt.room_id); - const auto member_id = static_cast(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(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::steady_clock::now() - peer.last_check_send); - peer.ping_us = - static_cast(std::min(static_cast(std::max(0, rtt.count())), - std::numeric_limits::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(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& 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(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(n.member_account_id); + mc.platform = static_cast(n.member_platform); for (const auto& a : n.member_bin_attrs) { MemberBinCache& b = mc.bins[a.attr_id]; b.id = static_cast(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(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 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(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(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(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(); + 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( + static_cast(rc.flags)); + p.event_prev_flag_attr = std::make_unique(prev_flags); + p.event_chg_passwd_slot_mask = + std::make_unique(rc.passwd_slot_mask); + p.event_prev_passwd_slot_mask = + std::make_unique(prev_passwd_slot_mask); + + p.room_data_internal_update = std::make_unique(); + 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& payload) { + OrbisNpMatching2Event req_event, MmCommand cmd, const std::vector& payload, + bool a_variant) { std::shared_ptr 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(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(cmd), pkt_id, ctx_id, req_id); @@ -840,6 +625,22 @@ s32 MmCreateJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req.set_allowed_user_count(static_cast(request.allowedUsers)); req.set_blocked_user_count(static_cast(request.blockedUsers)); req.set_internal_bin_attr_count(static_cast(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(request.lobbyId)); + req.set_flags(request.flags); + req.set_group_config_count(static_cast(request.groupConfigs)); + req.set_allowed_user_count(static_cast(request.allowedUsers)); + req.set_blocked_user_count(static_cast(request.blockedUsers)); + req.set_internal_bin_attr_count(static_cast(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(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(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; diff --git a/src/core/libraries/np/np_matching2/np_matching2_mm.h b/src/core/libraries/np/np_matching2/np_matching2_mm.h index 35abfd78b..91853a79b 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_mm.h +++ b/src/core/libraries/np/np_matching2/np_matching2_mm.h @@ -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& payload); + OrbisNpMatching2Event req_event, MmCommand cmd, const std::vector& 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, diff --git a/src/core/libraries/np/np_matching2/np_matching2_signaling.cpp b/src/core/libraries/np/np_matching2/np_matching2_signaling.cpp new file mode 100644 index 000000000..306b03ce8 --- /dev/null +++ b/src/core/libraries/np/np_matching2/np_matching2_signaling.cpp @@ -0,0 +1,609 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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(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(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(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(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(pkt.room_id); + const auto member_id = static_cast(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(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::steady_clock::now() - peer.last_check_send); + peer.ping_us = + static_cast(std::min(static_cast(std::max(0, rtt.count())), + std::numeric_limits::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(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(std::chrono::steady_clock::now().time_since_epoch().count() ^ + (static_cast(ctx.ctx_id) << 48) ^ + (static_cast(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(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(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(); + 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(connInfo) = {}; + } else { + *static_cast(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(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(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 diff --git a/src/core/libraries/np/np_matching2/np_matching2_signaling.h b/src/core/libraries/np/np_matching2/np_matching2_signaling.h new file mode 100644 index 000000000..a6a632e65 --- /dev/null +++ b/src/core/libraries/np/np_matching2/np_matching2_signaling.h @@ -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 diff --git a/src/core/libraries/np/np_matching2/np_matching2_types.h b/src/core/libraries/np/np_matching2/np_matching2_types.h index ef43fa919..0d9c0f8ee 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_types.h +++ b/src/core/libraries/np/np_matching2/np_matching2_types.h @@ -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]; diff --git a/src/shadnet/client.cpp b/src/shadnet/client.cpp index 5c3b59a60..5a73af63d 100644 --- a/src/shadnet/client.cpp +++ b/src/shadnet/client.cpp @@ -756,9 +756,13 @@ void ShadNetClient::HandleNotification(u16 cmd_raw, const std::vector& 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& 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& 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)); } diff --git a/src/shadnet/client.h b/src/shadnet/client.h index a8b6c5e31..dbbaf57b2 100644 --- a/src/shadnet/client.h +++ b/src/shadnet/client.h @@ -199,6 +199,8 @@ struct NotifyWebApiPushEvent { struct MatchingBinAttr { u32 attr_id = 0; + u64 update_date = 0; + u32 update_member_id = 0; std::vector 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; diff --git a/src/shadnet/shadnet.proto b/src/shadnet/shadnet.proto index b39f8bdca..8ffcaaa7e 100644 --- a/src/shadnet/shadnet.proto +++ b/src/shadnet/shadnet.proto @@ -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; }