diff --git a/CMakeLists.txt b/CMakeLists.txt index 323915186..eeb14210d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -634,6 +634,8 @@ set(NP_LIBS src/core/libraries/np/np_error.h src/core/libraries/np/np_matching2/np_matching2.h src/core/libraries/np/np_matching2/np_matching2_internal.cpp 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_types.h src/core/libraries/np/np_trophy.cpp src/core/libraries/np/np_trophy.h diff --git a/src/core/libraries/np/np_handler.cpp b/src/core/libraries/np/np_handler.cpp index 4642f3322..1b210c9d2 100644 --- a/src/core/libraries/np/np_handler.cpp +++ b/src/core/libraries/np/np_handler.cpp @@ -10,6 +10,7 @@ #include "core/emulator_settings.h" #include "core/libraries/np/np_error.h" #include "core/libraries/np/np_manager.h" +#include "core/libraries/np/np_matching2/np_matching2_mm.h" #include "core/libraries/np/np_score/np_score.h" #include "core/libraries/np/np_web_api/np_web_api.h" #include "core/user_settings.h" @@ -167,7 +168,7 @@ bool NpHandler::ConnectUser(s32 user_id, const std::string& host, u16 port, cons }; client->onAsyncReply = [this, user_id](ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType err, const std::vector& body) { - OnScoreReply(user_id, cmd, pkt_id, err, body); + OnAsyncReply(user_id, cmd, pkt_id, err, body); }; client->onLoginResult = [this, user_id](const ShadNet::LoginResult& res) { OnLoginResult(user_id, res); @@ -194,6 +195,8 @@ bool NpHandler::ConnectUser(s32 user_id, const std::string& host, u16 port, cons LOG_INFO(NpHandler, "user_id={} signed in npid='{}' accountId={}", user_id, npid, client->GetUserId()); + NpMatching2::SetMmShadNetClient(client, host, port); + // Build OrbisNpId { OrbisNpId np_id{}; @@ -1603,6 +1606,16 @@ static u32 FillRankArrayFromProto(const shadnet::GetScoreResponse& resp, return found; } +void NpHandler::OnAsyncReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, + ShadNet::ErrorType error, const std::vector& body) { + const auto cmd_val = static_cast(cmd); + if (cmd_val >= 12 && cmd_val <= 23) { + NpMatching2::OnMatchingReply(cmd, pkt_id, error, body); + } else { + OnScoreReply(user_id, cmd, pkt_id, error, body); + } +} + void NpHandler::OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType error, const std::vector& body) { PendingScoreRequest pending; diff --git a/src/core/libraries/np/np_handler.h b/src/core/libraries/np/np_handler.h index 8e47625d8..5572a0a9e 100644 --- a/src/core/libraries/np/np_handler.h +++ b/src/core/libraries/np/np_handler.h @@ -215,8 +215,11 @@ private: void OnWebApiPushEvent(s32 user_id, const ShadNet::NotifyWebApiPushEvent& n); void OnLoginResult(s32 user_id, const ShadNet::LoginResult& res); - // Async reply dispatch for score commands. Called from the per-user - // ShadNetClient on the reader thread. + // General async reply dispatch. Routes a reply to the owning subsystem by + // command. Called from the per-user ShadNetClient on the reader thread. + void OnAsyncReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType error, + const std::vector& body); + void OnScoreReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType error, const std::vector& body); diff --git a/src/core/libraries/np/np_matching2/np_matching2.cpp b/src/core/libraries/np/np_matching2/np_matching2.cpp index 2b417fe64..c07cc4870 100644 --- a/src/core/libraries/np/np_matching2/np_matching2.cpp +++ b/src/core/libraries/np/np_matching2/np_matching2.cpp @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include +#include #include "common/logging/log.h" #include "core/emulator_settings.h" @@ -12,6 +11,7 @@ #include "core/libraries/np/np_manager.h" #include "core/libraries/np/np_matching2/np_matching2.h" #include "core/libraries/np/np_matching2/np_matching2_internal.h" +#include "core/libraries/np/np_matching2/np_matching2_mm.h" #include "core/libraries/np/np_matching2/np_matching2_types.h" #include "core/libraries/np/np_types.h" #include "core/libraries/system/userservice.h" @@ -19,24 +19,18 @@ namespace Libraries::Np::NpMatching2 { static bool g_initialized = false; -static OrbisNpMatching2ContextId contextId = 1; - -static std::mutex g_events_mutex; -static std::deque g_ctx_events; -static std::deque g_lobby_events; -static std::deque g_room_events; -static std::mutex g_responses_mutex; -static std::deque> g_responses; int PS4_SYSV_ABI sceNpMatching2CreateContext(const OrbisNpMatching2CreateContextParameter* param, OrbisNpMatching2ContextId* ctxId) { - LOG_DEBUG(Lib_NpMatching2, "called, npId = {}, serviceLabel = {}, size = {}", - param->npId->handle.data, param->serviceLabel, param->size); + LOG_INFO(Lib_NpMatching2, "called, npId = {}, serviceLabel = {}, size = {}", + param->npId->handle.data, param->serviceLabel, param->size); if (!g_initialized) { + LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } if (!param || param->size != 0x28 || !ctxId) { + LOG_ERROR(Lib_NpMatching2, "param null, invalid size, or ctxId null"); return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } @@ -45,13 +39,15 @@ int PS4_SYSV_ABI sceNpMatching2CreateContext(const OrbisNpMatching2CreateContext int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContextParameterA* param, OrbisNpMatching2ContextId* ctxId) { - LOG_DEBUG(Lib_NpMatching2, "called, userId = {}, serviceLabel = {}, size = {}", param->userId, - param->serviceLabel, param->size); + LOG_INFO(Lib_NpMatching2, "called, userId = {}, serviceLabel = {}, size = {}", param->userId, + param->serviceLabel, param->size); if (!g_initialized) { + LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } if (!param || param->size != 0x10 || !ctxId) { + LOG_ERROR(Lib_NpMatching2, "param null, invalid size, or ctxId null"); return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } @@ -61,219 +57,215 @@ int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContex return ContextManager::Instance().CreateContext(&np_id, param->serviceLabel, ctxId); } -static std::optional defaultRequestOptParam = std::nullopt; +static void StoreRequestCallback(ContextObject* ctx, OrbisNpMatching2RequestOptParam* requestOpt) { + if (requestOpt && requestOpt->callback) { + ctx->default_request_callback = requestOpt->callback; + ctx->default_request_callback_arg = requestOpt->arg; + } +} -auto GetOptParam(OrbisNpMatching2RequestOptParam* requestOpt) { - return requestOpt ? *requestOpt - : (defaultRequestOptParam ? defaultRequestOptParam - : std::optional{}); +int PS4_SYSV_ABI sceNpMatching2CreateJoinRoom(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2CreateJoinRoomRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId) { + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId); + if (!g_initialized) { + 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; + MmCreateJoinRoom(ctxId, reqId, *request); + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpMatching2JoinRoom(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2JoinRoomRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId) { + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId); + if (!g_initialized) { + 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; + MmJoinRoom(ctxId, reqId, *request); + return ORBIS_OK; } int PS4_SYSV_ABI sceNpMatching2CreateJoinRoomA(OrbisNpMatching2ContextId ctxId, OrbisNpMatching2CreateJoinRoomRequestA* request, OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) { - LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); if (!g_initialized) { + 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; } - LOG_DEBUG(Lib_NpMatching2, - "maxSlot = {}, teamId = {}, worldId = {}, lobbyId = {}, groupConfig = {}, " - "joinGroupLabel = {}", - request->maxSlot, request->teamId, request->worldId, request->lobbyId, - request->groupConfig, request->joinGroupLabel); - - static OrbisNpMatching2RequestId id = 10; - *requestId = id++; - - if (auto optParam = GetOptParam(requestOpt); optParam) { - LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout, - optParam->appId); - std::scoped_lock lk{g_responses_mutex}; - auto reqIdCopy = *requestId; - auto requestCopy = *request; - g_responses.emplace_back([=]() { - Libraries::Np::OrbisNpOnlineId onlineId{}; - if (NpManager::sceNpGetOnlineId(1, &onlineId) != ORBIS_OK) { - return; - } - - OrbisNpMatching2RoomMemberDataInternalA me{ - nullptr, - 0, - {0xace104e, Libraries::Np::OrbisNpPlatformType::PS4}, - onlineId, - {0, 0, 0, 0}, - 1, - requestCopy.teamId, - 1, - 0, - nullptr, - nullptr, - 0}; - OrbisNpMatching2RoomDataInternal room{requestCopy.maxSlot, - 0, - static_cast(requestCopy.maxSlot - 1u), - 0, - 15, - 0xac, - requestCopy.worldId, - requestCopy.lobbyId, - 0x10, - 0, - 0, - nullptr, - 0, - 0, - {0, 0, 0, 0}, - nullptr, - 0}; - OrbisNpMatching2CreateJoinRoomResponseA resp{&room, {&me, 1, &me, &me}}; - optParam->callback(ctxId, reqIdCopy, - ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A, 0, &resp, - optParam->arg); - }); + 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; + LOG_WARNING(Lib_NpMatching2, "not implemented"); return ORBIS_OK; } -std::function npMatching2ContextCallback = nullptr; - int PS4_SYSV_ABI sceNpMatching2RegisterContextCallback(OrbisNpMatching2ContextCallback callback, void* userdata) { - LOG_DEBUG(Lib_NpMatching2, "called, userdata = {}", userdata); + LOG_INFO(Lib_NpMatching2, "called, userdata = {}", userdata); if (!g_initialized) { + LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - npMatching2ContextCallback = [callback, userdata](auto arg) { - callback(arg->contextId, arg->event, arg->cause, arg->errorCode, userdata); - }; - + ContextManager::Instance().ApplyContextCallback(callback, userdata); return ORBIS_OK; } -std::function npMatching2LobbyCallback = nullptr; - int PS4_SYSV_ABI sceNpMatching2RegisterLobbyEventCallback( OrbisNpMatching2ContextId ctxId, OrbisNpMatching2LobbyEventCallback callback, void* userdata) { - LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata); if (!g_initialized) { + LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } + ContextObject* ctx = ContextManager::Instance().Get(ctxId); + if (!ctx) { + LOG_ERROR(Lib_NpMatching2, "invalid context id"); + return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; + } - npMatching2LobbyCallback = [callback, userdata](auto arg) { - callback(arg->contextId, arg->lobbyId, arg->event, arg->data, userdata); - }; - + ctx->lobby_event_callback = callback; + ctx->lobby_event_callback_arg = userdata; return ORBIS_OK; } -std::function npMatching2RoomCallback = nullptr; - int PS4_SYSV_ABI sceNpMatching2RegisterRoomEventCallback(OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RoomEventCallback callback, void* userdata) { - LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata); if (!g_initialized) { + LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } + ContextObject* ctx = ContextManager::Instance().Get(ctxId); + if (!ctx) { + LOG_ERROR(Lib_NpMatching2, "invalid context id"); + return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; + } - npMatching2RoomCallback = [callback, userdata](auto arg) { - callback(arg->contextId, arg->roomId, arg->event, arg->data, userdata); - }; - + ctx->room_event_callback = callback; + ctx->room_event_callback_arg = userdata; return ORBIS_OK; } -std::function npMatching2SignalingCallback = nullptr; - int PS4_SYSV_ABI sceNpMatching2RegisterSignalingCallback(OrbisNpMatching2ContextId ctxId, OrbisNpMatching2SignalingCallback callback, void* userdata) { - LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata); if (!g_initialized) { + LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } + ContextObject* ctx = ContextManager::Instance().Get(ctxId); + if (!ctx) { + LOG_ERROR(Lib_NpMatching2, "invalid context id"); + return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; + } - npMatching2SignalingCallback = [callback, userdata](auto arg) { - callback(arg->contextId, arg->roomId, arg->roomMemberId, arg->event, arg->errorCode, - userdata); - }; - + ctx->signaling_callback = callback; + ctx->signaling_callback_arg = userdata; return ORBIS_OK; } int PS4_SYSV_ABI sceNpMatching2ContextStart(OrbisNpMatching2ContextId ctxId, u64 timeout) { - LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, timeout = {}", ctxId, timeout); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, timeout = {}", ctxId, timeout); if (!g_initialized) { + LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - - std::scoped_lock lk{g_events_mutex}; - if (EmulatorSettings.IsConnectedToNetwork() && EmulatorSettings.IsShadNetEnabled()) { - g_ctx_events.emplace_back(ctxId, ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED, - ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ACTION, 0); - } else { + if (!EmulatorSettings.IsConnectedToNetwork() || !EmulatorSettings.IsShadNetEnabled()) { // error confirmed with a real console disconnected from the internet constexpr int ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT = 0x804101e2; - g_ctx_events.emplace_back(ctxId, ORBIS_NP_MATCHING2_CONTEXT_EVENT_START_OVER, - ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ERROR, - ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT); + LOG_ERROR(Lib_NpMatching2, "not connected to network"); + return ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT; } + const s32 rc = ContextManager::Instance().Start(ctxId); + if (rc != ORBIS_OK) { + return rc; + } + + MmContextStart(ctxId); return ORBIS_OK; } -void ProcessEvents() { - { - std::scoped_lock lk{g_events_mutex}; +int PS4_SYSV_ABI sceNpMatching2ContextStop(OrbisNpMatching2ContextId ctxId) { + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId); - if (npMatching2ContextCallback) { - while (!g_ctx_events.empty()) { - npMatching2ContextCallback(&g_ctx_events.front()); - g_ctx_events.pop_front(); - } - } - if (npMatching2LobbyCallback) { - while (!g_lobby_events.empty()) { - npMatching2LobbyCallback(&g_lobby_events.front()); - g_lobby_events.pop_front(); - } - } - if (npMatching2RoomCallback) { - while (!g_room_events.empty()) { - npMatching2RoomCallback(&g_room_events.front()); - g_room_events.pop_front(); - } - } + if (!g_initialized) { + LOG_ERROR(Lib_NpMatching2, "not initialized"); + return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - std::scoped_lock lk{g_responses_mutex}; - while (!g_responses.empty()) { - (g_responses.front())(); - g_responses.pop_front(); + const s32 rc = ContextManager::Instance().Stop(ctxId); + if (rc != ORBIS_OK) { + return rc; } + + MmContextStop(ctxId); + return ORBIS_OK; } int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* param) { - LOG_DEBUG(Lib_NpMatching2, "called"); + LOG_INFO(Lib_NpMatching2, "called"); if (g_initialized) { + LOG_ERROR(Lib_NpMatching2, "already initialized"); return ORBIS_NP_MATCHING2_ERROR_ALREADY_INITIALIZED; } if (!param || (param->size != 0x30 && param->size != 0x28)) { + LOG_ERROR(Lib_NpMatching2, "param null or invalid size"); return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } @@ -287,21 +279,20 @@ int PS4_SYSV_ABI sceNpMatching2Initialize(OrbisNpMatching2InitializeParameter* p g_initialized = true; g_state.initialized.store(true); - Libraries::Np::NpManager::RegisterNpCallback("NpMatching2", ProcessEvents); return ORBIS_OK; } int PS4_SYSV_ABI sceNpMatching2Terminate() { - LOG_DEBUG(Lib_NpMatching2, "called"); + LOG_INFO(Lib_NpMatching2, "called"); if (!g_initialized) { + LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } g_initialized = false; g_state.initialized.store(false); - Libraries::Np::NpManager::DeregisterNpCallback("NpMatching2"); TermEventDispatcher(); ContextManager::Instance().Reset(); @@ -311,32 +302,42 @@ int PS4_SYSV_ABI sceNpMatching2Terminate() { int PS4_SYSV_ABI sceNpMatching2SetDefaultRequestOptParam( OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RequestOptParam* requestOpt) { - LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}", ctxId); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId); if (!g_initialized) { + LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } if (!requestOpt) { + LOG_ERROR(Lib_NpMatching2, "requestOpt 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; + } - defaultRequestOptParam = *requestOpt; + ctx->default_request_callback = requestOpt->callback; + ctx->default_request_callback_arg = requestOpt->arg; return ORBIS_OK; } int PS4_SYSV_ABI sceNpMatching2GetServerId(OrbisNpMatching2ContextId ctxId, OrbisNpMatching2ServerId* serverId) { - LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}", ctxId); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}", ctxId); if (!g_initialized) { + LOG_ERROR(Lib_NpMatching2, "not initialized"); return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } if (!serverId) { + LOG_ERROR(Lib_NpMatching2, "serverId null"); return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - *serverId = 0xac; + *serverId = 1; return ORBIS_OK; } @@ -345,33 +346,28 @@ int PS4_SYSV_ABI sceNpMatching2GetWorldInfoList(OrbisNpMatching2ContextId ctxId, OrbisNpMatching2GetWorldInfoListRequest* request, OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) { - LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, request.serverId = {}, requestOpt = {}", ctxId, - request ? request->serverId : 0xFFFF, fmt::ptr(requestOpt)); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, request.serverId = {}, requestOpt = {}", ctxId, + request ? request->serverId : 0xFFFF, fmt::ptr(requestOpt)); if (!g_initialized) { + 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; } - static OrbisNpMatching2RequestId id = 1; - *requestId = id++; - - if (auto optParam = GetOptParam(requestOpt); optParam) { - LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout, - optParam->appId); - auto reqIdCopy = *requestId; - std::scoped_lock lk{g_responses_mutex}; - g_responses.emplace_back([=]() { - OrbisNpMatching2World w{nullptr, 1, 10, 0, 10, 0, {}}; - OrbisNpMatching2GetWorldInfoListResponse resp{&w, 1}; - optParam->callback(ctxId, reqIdCopy, - ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST, 0, &resp, - optParam->arg); - }); + 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; + MmGetWorldInfoList(ctxId, reqId, *request); return ORBIS_OK; } @@ -379,29 +375,26 @@ int PS4_SYSV_ABI sceNpMatching2LeaveRoom(OrbisNpMatching2ContextId ctxId, OrbisNpMatching2LeaveRoomRequest* request, OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) { - LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); if (!g_initialized) { + 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; } - - static OrbisNpMatching2RequestId id = 500; - *requestId = id++; - - if (auto optParam = GetOptParam(requestOpt); optParam) { - LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout, - optParam->appId); - std::scoped_lock lk{g_responses_mutex}; - auto reqIdCopy = *requestId; - g_responses.emplace_back([=]() { - optParam->callback(ctxId, reqIdCopy, ORBIS_NP_MATCHING2_REQUEST_EVENT_LEAVE_ROOM, 0, - nullptr, optParam->arg); - }); + 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; + MmLeaveRoom(ctxId, reqId, *request); return ORBIS_OK; } @@ -409,31 +402,27 @@ int PS4_SYSV_ABI sceNpMatching2SearchRoom(OrbisNpMatching2ContextId ctxId, OrbisNpMatching2SearchRoomRequest* request, OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) { - LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); if (!g_initialized) { + 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; } - static OrbisNpMatching2RequestId id = 1; - *requestId = id++; - - if (auto optParam = GetOptParam(requestOpt); optParam) { - LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout, - optParam->appId); - std::scoped_lock lk{g_responses_mutex}; - auto reqIdCopy = *requestId; - auto requestCopy = *request; - g_responses.emplace_back([=]() { - OrbisNpMatching2SearchRoomResponseA resp{{0, 0, 0, {}}, nullptr}; - optParam->callback(ctxId, reqIdCopy, ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM_A, 0, - &resp, optParam->arg); - }); + 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; + MmSearchRoom(ctxId, reqId, *request); return ORBIS_OK; } @@ -441,118 +430,104 @@ int PS4_SYSV_ABI sceNpMatching2SetUserInfo(OrbisNpMatching2ContextId ctxId, OrbisNpMatching2SetUserInfoRequest* request, OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) { - LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); if (!g_initialized) { + 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; } - static OrbisNpMatching2RequestId id = 100; - *requestId = id++; - - if (auto optParam = GetOptParam(requestOpt); optParam) { - LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout, - optParam->appId); - std::scoped_lock lk{g_responses_mutex}; - auto reqIdCopy = *requestId; - g_responses.emplace_back([=]() { - optParam->callback(ctxId, reqIdCopy, ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_USER_INFO, 0, - nullptr, optParam->arg); - }); + 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); + *requestId = AllocRequestId(); + LOG_WARNING(Lib_NpMatching2, "not implemented"); return ORBIS_OK; } int PS4_SYSV_ABI sceNpMatching2SendRoomMessage(OrbisNpMatching2ContextId ctxId, void* request, OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) { - LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); if (!g_initialized) { + 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; } - static OrbisNpMatching2RequestId id = 1000; - *requestId = id++; - - if (auto optParam = GetOptParam(requestOpt); optParam) { - LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout, - optParam->appId); - std::scoped_lock lk{g_responses_mutex}; - auto reqIdCopy = *requestId; - g_responses.emplace_back([=]() { - optParam->callback(ctxId, reqIdCopy, ORBIS_NP_MATCHING2_REQUEST_EVENT_SEND_ROOM_MESSAGE, - 0, nullptr, optParam->arg); - }); + 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); + *requestId = AllocRequestId(); + LOG_WARNING(Lib_NpMatching2, "not implemented"); return ORBIS_OK; } int PS4_SYSV_ABI sceNpMatching2SetRoomDataExternal(OrbisNpMatching2ContextId ctxId, void* request, OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) { - LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); if (!g_initialized) { + 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; } - static OrbisNpMatching2RequestId id = 800; - *requestId = id++; - - if (auto optParam = GetOptParam(requestOpt); optParam) { - LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout, - optParam->appId); - std::scoped_lock lk{g_responses_mutex}; - auto reqIdCopy = *requestId; - g_responses.emplace_back([=]() { - optParam->callback(ctxId, reqIdCopy, - ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_EXTERNAL, 0, nullptr, - optParam->arg); - }); + 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); + *requestId = AllocRequestId(); + LOG_WARNING(Lib_NpMatching2, "not implemented"); return ORBIS_OK; } int PS4_SYSV_ABI sceNpMatching2SetRoomDataInternal(OrbisNpMatching2ContextId ctxId, void* request, OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) { - LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); + LOG_INFO(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); if (!g_initialized) { + 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; } - static OrbisNpMatching2RequestId id = 200; - *requestId = id++; - - if (auto optParam = GetOptParam(requestOpt); optParam) { - LOG_DEBUG(Lib_NpMatching2, "optParam.timeout = {}, optParam.appId = {}", optParam->timeout, - optParam->appId); - std::scoped_lock lk{g_responses_mutex}; - auto reqIdCopy = *requestId; - g_responses.emplace_back([=]() { - optParam->callback(ctxId, reqIdCopy, - ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_INTERNAL, 0, nullptr, - optParam->arg); - }); + 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); + *requestId = AllocRequestId(); + LOG_WARNING(Lib_NpMatching2, "not implemented"); return ORBIS_OK; } @@ -567,6 +542,10 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceNpMatching2CreateContextA); LIB_FUNCTION("V6KSpKv9XJE", "libSceNpMatching2", 1, "libSceNpMatching2", sceNpMatching2CreateJoinRoomA); + LIB_FUNCTION("zCWZmXXN600", "libSceNpMatching2", 1, "libSceNpMatching2", + sceNpMatching2CreateJoinRoom); + LIB_FUNCTION("CSIMDsVjs-g", "libSceNpMatching2", 1, "libSceNpMatching2", + sceNpMatching2JoinRoom); LIB_FUNCTION("fQQfP87I7hs", "libSceNpMatching2", 1, "libSceNpMatching2", sceNpMatching2RegisterContextCallback); LIB_FUNCTION("4Nj7u5B5yCA", "libSceNpMatching2", 1, "libSceNpMatching2", @@ -577,6 +556,8 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceNpMatching2RegisterSignalingCallback); LIB_FUNCTION("7vjNQ6Z1op0", "libSceNpMatching2", 1, "libSceNpMatching2", sceNpMatching2ContextStart); + LIB_FUNCTION("-f6M4caNe8k", "libSceNpMatching2", 1, "libSceNpMatching2", + sceNpMatching2ContextStop); LIB_FUNCTION("LhCPctIICxQ", "libSceNpMatching2", 1, "libSceNpMatching2", sceNpMatching2GetServerId); LIB_FUNCTION("rJNPJqDCpiI", "libSceNpMatching2", 1, "libSceNpMatching2", diff --git a/src/core/libraries/np/np_matching2/np_matching2.h b/src/core/libraries/np/np_matching2/np_matching2.h index 4c1b4cb68..9bf6a8189 100644 --- a/src/core/libraries/np/np_matching2/np_matching2.h +++ b/src/core/libraries/np/np_matching2/np_matching2.h @@ -85,6 +85,44 @@ enum OrbisNpMatching2SignalingType : u8 { ORBIS_NP_MATCHING2_SIGNALING_TYPE_STAR = 2, }; +enum OrbisNpMatching2AttrId : OrbisNpMatching2AttributeId { + ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_1_ID = 0x004C, + ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_2_ID = 0x004D, + ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_3_ID = 0x004E, + ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_4_ID = 0x004F, + ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_5_ID = 0x0050, + ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_6_ID = 0x0051, + ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_7_ID = 0x0052, + ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_INT_ATTR_EXTERNAL_8_ID = 0x0053, + ORBIS_NP_MATCHING2_ROOM_SEARCHABLE_BIN_ATTR_EXTERNAL_1_ID = 0x0054, + ORBIS_NP_MATCHING2_ROOM_BIN_ATTR_EXTERNAL_1_ID = 0x0055, + ORBIS_NP_MATCHING2_ROOM_BIN_ATTR_EXTERNAL_2_ID = 0x0056, + ORBIS_NP_MATCHING2_ROOM_BIN_ATTR_INTERNAL_1_ID = 0x0057, + ORBIS_NP_MATCHING2_ROOM_BIN_ATTR_INTERNAL_2_ID = 0x0058, + ORBIS_NP_MATCHING2_ROOMMEMBER_BIN_ATTR_INTERNAL_1_ID = 0x0059, +}; + +enum OrbisNpMatching2RoomFlagAttr : OrbisNpMatching2Flags { + ORBIS_NP_MATCHING2_ROOM_FLAG_ATTR_OWNER_AUTO_GRANT = 0x80000000, + ORBIS_NP_MATCHING2_ROOM_FLAG_ATTR_CLOSED = 0x40000000, + ORBIS_NP_MATCHING2_ROOM_FLAG_ATTR_FULL = 0x20000000, + ORBIS_NP_MATCHING2_ROOM_FLAG_ATTR_HIDDEN = 0x10000000, + ORBIS_NP_MATCHING2_ROOM_FLAG_ATTR_NAT_TYPE_RESTRICTION = 0x04000000, + ORBIS_NP_MATCHING2_ROOM_FLAG_ATTR_PROHIBITIVE_MODE = 0x02000000, +}; + +enum OrbisNpMatching2RoomMemberFlagAttr : OrbisNpMatching2Flags { + ORBIS_NP_MATCHING2_ROOMMEMBER_FLAG_ATTR_OWNER = 0x80000000, +}; + +enum OrbisNpMatching2Role : u8 { + ORBIS_NP_MATCHING2_ROLE_MEMBER = 1, + ORBIS_NP_MATCHING2_ROLE_OWNER = 2, +}; + +constexpr u32 ORBIS_NP_MATCHING2_SESSION_PASSWORD_SIZE = 8; +constexpr u32 ORBIS_NP_MATCHING2_GROUP_LABEL_SIZE = 8; + enum OrbisNpMatching2InitDefault : u64 { ORBIS_NP_MATCHING2_THREAD_STACK_SIZE_DEFAULT = 32768, ORBIS_NP_MATCHING2_POOLSIZE_DEFAULT = 131072, 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 ca90b977f..bc2f7dbd4 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_internal.cpp +++ b/src/core/libraries/np/np_matching2/np_matching2_internal.cpp @@ -1,17 +1,268 @@ // SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include "common/logging/log.h" +#include "common/thread.h" #include "core/libraries/np/np_common.h" #include "core/libraries/np/np_error.h" #include "core/libraries/np/np_matching2/np_matching2_internal.h" +#include "shadnet.pb.h" namespace Libraries::Np::NpMatching2 { NpMatching2State g_state; +void* BuildCreateJoinRoomPayload(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()); + for (int i = 0; i < rd.groups_size(); ++i) { + const auto& g = rd.groups(i); + auto& dst = p.room_groups[i]; + dst.id = static_cast(g.group_id()); + dst.hasPasswd = g.has_passwd(); + dst.hasLabel = g.has_label(); + std::memset(dst.label.data, 0, sizeof(dst.label.data)); + std::memcpy(dst.label.data, g.label().data(), + std::min(g.label().size(), sizeof(dst.label.data))); + dst.slots = g.slot_count(); + dst.groupMembers = g.num_members(); + } + + p.room_bin_attrs.resize(rd.bin_attrs_internal_size()); + for (int i = 0; i < rd.bin_attrs_internal_size(); ++i) { + const auto& a = rd.bin_attrs_internal(i); + p.bin_buffers.emplace_back(a.data().begin(), a.data().end()); + auto& buf = p.bin_buffers.back(); + auto& dst = p.room_bin_attrs[i]; + dst = {}; + 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 = {}; + room.publicSlots = static_cast(rd.public_slots()); + room.privateSlots = static_cast(rd.private_slots()); + room.openPublicSlots = static_cast(rd.open_public_slots()); + room.openPrivateSlots = static_cast(rd.open_private_slots()); + room.maxSlot = static_cast(rd.max_slot()); + room.serverId = static_cast(rd.server_id()); + room.worldId = rd.world_id(); + room.lobbyId = rd.lobby_id(); + room.roomId = rd.room_id(); + room.passwdSlotMask = rd.passwd_slot_mask(); + room.joinedSlotMask = rd.joined_slot_mask(); + room.flags = rd.flags(); + room.roomGroup = p.room_groups.empty() ? nullptr : p.room_groups.data(); + room.roomGroups = p.room_groups.size(); + 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; + } + } + + RoomCache& rc = ctx.room_cache[room.roomId]; + rc = RoomCache{}; + rc.num_slots = room.maxSlot; + rc.mask_password = room.passwdSlotMask; + rc.owner = resp.me_member_id() == resp.owner_member_id(); + for (const auto& g : p.room_groups) { + rc.groups[g.id] = g; + } + for (int i = 0; i < member_count; ++i) { + const auto& m = resp.members(i); + MemberCache& mc = rc.members[static_cast(m.member_id())]; + mc = MemberCache{}; + mc.member_id = static_cast(m.member_id()); + mc.team_id = static_cast(m.team_id()); + mc.nat_type = static_cast(m.nat_type()); + mc.flag_attr = m.flag_attr(); + mc.group_id = static_cast(m.group_id()); + mc.join_date = m.join_date(); + mc.addr = 0; + mc.port = 0; + std::strncpy(mc.np_id.handle.data, m.npid().c_str(), sizeof(mc.np_id.handle.data) - 1); + for (const auto& a : m.bin_attrs_internal()) { + MemberBinCache& b = mc.bins[a.attr_id()]; + b.id = static_cast(a.attr_id()); + b.data.assign(a.data().begin(), a.data().end()); + } + } + + p.request_data = p.create_join_response.get(); + return p.request_data; +} + +void* BuildGetWorldInfoListPayload(ContextObject& ctx, const shadnet::GetWorldInfoListReply& resp) { + CallbackPayload& p = ctx.request_payload; + p.Reset(); + + p.world_list.resize(resp.worlds_size()); + for (int i = 0; i < resp.worlds_size(); ++i) { + const auto& w = resp.worlds(i); + auto& dst = p.world_list[i]; + dst = OrbisNpMatching2World{}; + dst.worldId = static_cast(w.world_id()); + dst.lobbiesNum = w.lobbies_num(); + dst.maxLobbyMembersNum = w.max_lobby_members(); + dst.lobbyMembersNum = w.lobby_members_num(); + dst.roomsNum = w.rooms_num(); + dst.roomMembersNum = w.room_members_num(); + } + for (size_t i = 0; i + 1 < p.world_list.size(); ++i) { + p.world_list[i].next = &p.world_list[i + 1]; + } + + p.world_info_response = std::make_unique(); + p.world_info_response->world = p.world_list.empty() ? nullptr : p.world_list.data(); + p.world_info_response->worldNum = p.world_list.size(); + + p.request_data = p.world_info_response.get(); + return p.request_data; +} + +void* BuildSearchRoomPayload(ContextObject& ctx, const shadnet::SearchRoomReply& resp) { + CallbackPayload& p = ctx.request_payload; + p.Reset(); + + const int room_count = resp.rooms_size(); + p.room_data_external.resize(room_count); + for (int i = 0; i < room_count; ++i) { + const auto& r = resp.rooms(i); + auto& dst = p.room_data_external[i]; + dst = OrbisNpMatching2RoomDataExternal{}; + dst.next = (i + 1 < room_count) ? &p.room_data_external[i + 1] : nullptr; + dst.maxSlot = static_cast(r.max_slot()); + dst.curMembers = static_cast(r.cur_members()); + dst.flags = r.flags(); + dst.serverId = static_cast(r.server_id()); + dst.worldId = static_cast(r.world_id()); + dst.lobbyId = r.lobby_id(); + dst.roomId = r.room_id(); + dst.passwdSlotMask = r.passwd_slot_mask(); + dst.joinedSlotMask = r.joined_slot_mask(); + dst.publicSlots = static_cast(r.public_slots()); + dst.privateSlots = static_cast(r.private_slots()); + dst.openPublicSlots = static_cast(r.open_public_slots()); + dst.openPrivateSlots = static_cast(r.open_private_slots()); + + if (!r.owner_npid().empty()) { + auto& npid = p.ext_owner_npids.emplace_back(); + npid = Libraries::Np::OrbisNpId{}; + std::strncpy(npid.handle.data, r.owner_npid().c_str(), sizeof(npid.handle.data) - 1); + dst.ownerNpId = &npid; + } + + if (r.groups_size() > 0) { + OrbisNpMatching2RoomGroupInfo* first = nullptr; + for (int g = 0; g < r.groups_size(); ++g) { + const auto& src = r.groups(g); + auto& gi = p.ext_room_groups.emplace_back(); + gi = OrbisNpMatching2RoomGroupInfo{}; + gi.id = static_cast(src.group_id()); + gi.hasPasswd = src.has_passwd(); + gi.slots = src.slot_count(); + gi.groupMembers = src.num_members(); + if (!first) { + first = &gi; + } + } + dst.roomGroup = first; + dst.roomGroups = static_cast(r.groups_size()); + } + + if (r.external_search_int_attrs_size() > 0) { + OrbisNpMatching2IntAttr* first = nullptr; + for (int a = 0; a < r.external_search_int_attrs_size(); ++a) { + const auto& src = r.external_search_int_attrs(a); + auto& ia = p.ext_int_attrs.emplace_back(); + ia = OrbisNpMatching2IntAttr{}; + ia.id = static_cast(src.attr_id()); + ia.num = src.attr_value(); + if (!first) { + first = &ia; + } + } + dst.externalSearchIntAttr = first; + dst.externalSearchIntAttrs = static_cast(r.external_search_int_attrs_size()); + } + + auto build_bin = [&](const auto& src_attrs, OrbisNpMatching2BinAttr*& out_ptr, + u64& out_num) { + if (src_attrs.empty()) { + return; + } + OrbisNpMatching2BinAttr* first = nullptr; + for (const auto& src : src_attrs) { + p.bin_buffers.emplace_back(src.data().begin(), src.data().end()); + auto& buf = p.bin_buffers.back(); + auto& ba = p.ext_bin_attrs.emplace_back(); + ba = OrbisNpMatching2BinAttr{}; + ba.id = static_cast(src.attr_id()); + ba.data = buf.empty() ? nullptr : buf.data(); + ba.dataSize = buf.size(); + if (!first) { + first = &ba; + } + } + out_ptr = first; + out_num = static_cast(src_attrs.size()); + }; + build_bin(r.external_search_bin_attrs(), dst.externalSearchBinAttr, + dst.externalSearchBinAttrs); + build_bin(r.external_bin_attrs(), dst.externalBinAttr, dst.externalBinAttrs); + } + + p.search_room_response = std::make_unique(); + auto& out = *p.search_room_response; + out = OrbisNpMatching2SearchRoomResponse{}; + out.range.start = resp.range_start(); + out.range.total = resp.range_total(); + out.range.results = resp.range_result(); + out.roomDataExt = p.room_data_external.empty() ? nullptr : p.room_data_external.data(); + + p.request_data = p.search_room_response.get(); + return p.request_data; +} + ContextManager& ContextManager::Instance() { static ContextManager instance; return instance; @@ -52,18 +303,15 @@ s32 ContextManager::CreateContext(const OrbisNpId* owner_np_id, OrbisNpServiceLa ctx.service_label = service_label; if (owner_np_id) { ctx.owner_np_id = *owner_np_id; - ctx.online_id = - std::string(owner_np_id->handle.data, - strnlen(owner_np_id->handle.data, sizeof(owner_np_id->handle.data))); - } - if (ctx.online_id.empty()) { - ctx.online_id = "shadPS4_player"; + ctx.online_id = owner_np_id->handle; } + ctx.context_callback = m_pending_context_callback; + ctx.context_callback_arg = m_pending_context_callback_arg; m_used[id] = true; *out_ctx_id = id; - LOG_INFO(Lib_NpMatching2, "context created: id={} online_id={} serviceLabel={:#x}", id, - ctx.online_id, service_label); + LOG_DEBUG(Lib_NpMatching2, "context created: id={} online_id={} serviceLabel={:#x}", id, + ctx.online_id.data, service_label); return ORBIS_OK; } @@ -84,7 +332,7 @@ bool ContextManager::Destroy(OrbisNpMatching2ContextId ctx_id) { } m_contexts[ctx_id] = ContextObject{}; m_used[ctx_id] = false; - LOG_INFO(Lib_NpMatching2, "context destroyed: id={}", ctx_id); + LOG_DEBUG(Lib_NpMatching2, "context destroyed: id={}", ctx_id); return true; } @@ -98,7 +346,7 @@ s32 ContextManager::Start(OrbisNpMatching2ContextId ctx_id) { return ORBIS_NP_MATCHING2_ERROR_CONTEXT_ALREADY_STARTED; } ctx->started = true; - LOG_INFO(Lib_NpMatching2, "context started: id={}", ctx_id); + LOG_DEBUG(Lib_NpMatching2, "context started: id={}", ctx_id); return ORBIS_OK; } @@ -112,15 +360,29 @@ s32 ContextManager::Stop(OrbisNpMatching2ContextId ctx_id) { return ORBIS_NP_MATCHING2_ERROR_CONTEXT_NOT_STARTED; } ctx->started = false; - LOG_INFO(Lib_NpMatching2, "context stopped: id={}", ctx_id); + LOG_DEBUG(Lib_NpMatching2, "context stopped: id={}", ctx_id); return ORBIS_OK; } +void ContextManager::ApplyContextCallback(OrbisNpMatching2ContextCallback callback, void* arg) { + std::lock_guard lock(m_mutex); + m_pending_context_callback = callback; + m_pending_context_callback_arg = arg; + for (u32 id = 1; id <= kMaxContexts; ++id) { + if (m_used[id]) { + m_contexts[id].context_callback = callback; + m_contexts[id].context_callback_arg = arg; + } + } +} + void ContextManager::Reset() { std::lock_guard lock(m_mutex); m_contexts = {}; m_used = {}; m_next_id = 1; + m_pending_context_callback = nullptr; + m_pending_context_callback_arg = nullptr; } OrbisNpMatching2RequestId AllocRequestId() { @@ -137,49 +399,99 @@ namespace { void FireEvent(const PendingEvent& ev) { ContextObject* ctx = ContextManager::Instance().Get(ev.ctx_id); if (!ctx) { + LOG_WARNING(Lib_NpMatching2, "FireEvent type={} dropped: ctx={} not found", + static_cast(ev.type), ev.ctx_id); return; } switch (ev.type) { case PendingEvent::CONTEXT_CB: if (ctx->context_callback) { + LOG_DEBUG(Lib_NpMatching2, "callback CONTEXT ctx={} event={:#x} cause={} err={:#x}", + ev.ctx_id, static_cast(ev.ctx_event), + static_cast(ev.ctx_event_cause), ev.error_code); ctx->context_callback(ev.ctx_id, ev.ctx_event, ev.ctx_event_cause, ev.error_code, ctx->context_callback_arg); + } else { + LOG_WARNING(Lib_NpMatching2, "callback CONTEXT ctx={} event={:#x} SKIPPED: no callback", + ev.ctx_id, static_cast(ev.ctx_event)); } break; case PendingEvent::REQUEST_CB: if (ev.request_cb) { + LOG_DEBUG(Lib_NpMatching2, + "callback REQUEST ctx={} reqId={} event={:#x} err={:#x} data={}", ev.ctx_id, + ev.req_id, static_cast(ev.req_event), ev.error_code, + fmt::ptr(ev.request_data)); ev.request_cb(ev.ctx_id, ev.req_id, ev.req_event, ev.error_code, ev.request_data, ev.request_cb_arg); + } else { + LOG_WARNING(Lib_NpMatching2, + "callback REQUEST ctx={} reqId={} event={:#x} SKIPPED: no callback", + ev.ctx_id, ev.req_id, static_cast(ev.req_event)); } break; case PendingEvent::SIGNALING_CB: if (ctx->signaling_callback) { + LOG_DEBUG(Lib_NpMatching2, + "callback SIGNALING ctx={} room={} member={} event={:#x} err={:#x}", + ev.ctx_id, ev.room_id, ev.member_id, static_cast(ev.sig_event), + ev.error_code); ctx->signaling_callback(ev.ctx_id, ev.room_id, ev.member_id, ev.sig_event, ev.error_code, ctx->signaling_callback_arg); + } else { + LOG_WARNING(Lib_NpMatching2, + "callback SIGNALING ctx={} room={} event={:#x} SKIPPED: no callback", + ev.ctx_id, ev.room_id, static_cast(ev.sig_event)); } break; case PendingEvent::ROOM_EVENT_CB: if (ctx->room_event_callback) { + LOG_DEBUG(Lib_NpMatching2, "callback ROOM_EVENT ctx={} room={} event={:#x} data={}", + ev.ctx_id, ev.room_id, static_cast(ev.room_event), + fmt::ptr(ev.room_event_data)); ctx->room_event_callback(ev.ctx_id, ev.room_id, ev.room_event, ev.room_event_data, ctx->room_event_callback_arg); + } else { + LOG_WARNING(Lib_NpMatching2, + "callback ROOM_EVENT ctx={} room={} event={:#x} SKIPPED: no callback", + ev.ctx_id, ev.room_id, static_cast(ev.room_event)); } break; case PendingEvent::LOBBY_EVENT_CB: if (ctx->lobby_event_callback) { + LOG_DEBUG(Lib_NpMatching2, "callback LOBBY_EVENT ctx={} lobby={} event={:#x} data={}", + ev.ctx_id, ev.lobby_id, static_cast(ev.lobby_event), + fmt::ptr(ev.lobby_event_data)); ctx->lobby_event_callback(ev.ctx_id, ev.lobby_id, ev.lobby_event, ev.lobby_event_data, ctx->lobby_event_callback_arg); + } else { + LOG_WARNING(Lib_NpMatching2, + "callback LOBBY_EVENT ctx={} lobby={} event={:#x} SKIPPED: no callback", + ev.ctx_id, ev.lobby_id, static_cast(ev.lobby_event)); } break; case PendingEvent::LOBBY_MESSAGE_CB: if (ctx->lobby_message_callback) { + LOG_DEBUG(Lib_NpMatching2, "callback LOBBY_MESSAGE ctx={} lobby={} src={} event={:#x}", + ev.ctx_id, ev.lobby_id, ev.src_member_id, static_cast(ev.msg_event)); ctx->lobby_message_callback(ev.ctx_id, ev.lobby_id, ev.src_member_id, ev.msg_event, ev.message_data, ctx->lobby_message_callback_arg); + } else { + LOG_WARNING(Lib_NpMatching2, + "callback LOBBY_MESSAGE ctx={} lobby={} SKIPPED: no callback", ev.ctx_id, + ev.lobby_id); } break; case PendingEvent::ROOM_MESSAGE_CB: if (ctx->room_message_callback) { + LOG_DEBUG(Lib_NpMatching2, "callback ROOM_MESSAGE ctx={} room={} src={} event={:#x}", + ev.ctx_id, ev.room_id, ev.src_member_id, static_cast(ev.msg_event)); ctx->room_message_callback(ev.ctx_id, ev.room_id, ev.src_member_id, ev.msg_event, ev.message_data, ctx->room_message_callback_arg); + } else { + LOG_WARNING(Lib_NpMatching2, + "callback ROOM_MESSAGE ctx={} room={} SKIPPED: no callback", ev.ctx_id, + ev.room_id); } break; } @@ -187,7 +499,8 @@ void FireEvent(const PendingEvent& ev) { } // namespace -PS4_SYSV_ABI void* EventDispatcherThreadMain(void* /*arg*/) { +PS4_SYSV_ABI void* EventDispatcherThreadMain(void*) { + Common::SetCurrentThreadName("Matching2:Dispatch"); std::unique_lock lock(g_state.queue_mutex); while (g_state.dispatch_running.load()) { const auto now = std::chrono::steady_clock::now(); @@ -236,7 +549,7 @@ void InitEventDispatcher() { g_state.dispatch_thread = nullptr; return; } - LOG_INFO(Lib_NpMatching2, "event dispatcher thread created"); + LOG_DEBUG(Lib_NpMatching2, "event dispatcher thread created"); } void TermEventDispatcher() { 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 7e73127ab..6f1b4280d 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_internal.h +++ b/src/core/libraries/np/np_matching2/np_matching2_internal.h @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -18,6 +20,12 @@ #include "core/libraries/np/np_matching2/np_matching2_types.h" #include "core/libraries/np/np_types.h" +namespace shadnet { +class CreateJoinRoomResponse; +class GetWorldInfoListReply; +class SearchRoomReply; +} // namespace shadnet + namespace Libraries::Np::NpMatching2 { struct PeerInfo { @@ -26,7 +34,62 @@ struct PeerInfo { OrbisNpMatching2RoomMemberId member_id = 0; s32 conn_id = 0; s32 status = 0; - std::string online_id; + Libraries::Np::OrbisNpOnlineId online_id{}; +}; + +struct MemberBinCache { + OrbisNpMatching2AttributeId id = 0; + u64 update_date = 0; + std::vector data; +}; + +struct MemberCache { + Libraries::Np::OrbisNpId np_id{}; + u64 join_date = 0; + OrbisNpMatching2RoomMemberId member_id = 0; + OrbisNpMatching2TeamId team_id = 0; + OrbisNpMatching2RoomGroupId group_id = 0; + OrbisNpMatching2NatType nat_type = 0; + OrbisNpMatching2Flags flag_attr = 0; + u32 addr = 0; + u16 port = 0; + std::map bins; +}; + +struct RoomCache { + u32 num_slots = 0; + u64 mask_password = 0; + std::map groups; + std::map members; + bool owner = false; +}; + +struct CallbackPayload { + std::unique_ptr room_data; + std::unique_ptr create_join_response; + std::unique_ptr search_room_response; + std::unique_ptr world_info_response; + std::vector world_list; + std::vector member_data; + std::vector room_groups; + std::vector room_bin_attrs; + std::vector member_bin_attrs; + std::vector room_data_external; + std::vector> bin_buffers; + std::deque ext_int_attrs; + std::deque ext_bin_attrs; + std::deque ext_room_groups; + std::deque ext_owner_npids; + void* request_data = nullptr; + + std::unique_ptr room_member_update; + std::unique_ptr room_update; + std::unique_ptr event_member; + void* room_event_data = nullptr; + + void Reset() { + *this = CallbackPayload{}; + } }; struct ContextObject { @@ -36,7 +99,7 @@ struct ContextObject { OrbisNpMatching2ServerId server_id = 1; OrbisNpServiceLabel service_label = 0; Libraries::Np::OrbisNpId owner_np_id{}; - std::string online_id; + Libraries::Np::OrbisNpOnlineId online_id{}; std::string signaling_addr; u16 signaling_port = 0; @@ -53,6 +116,11 @@ struct ContextObject { std::map peers; + std::map room_cache; + + CallbackPayload request_payload; + CallbackPayload room_event_payload; + OrbisNpMatching2ContextCallback context_callback = nullptr; void* context_callback_arg = nullptr; OrbisNpMatching2RequestCallback default_request_callback = nullptr; @@ -87,6 +155,8 @@ public: s32 Start(OrbisNpMatching2ContextId ctx_id); s32 Stop(OrbisNpMatching2ContextId ctx_id); + void ApplyContextCallback(OrbisNpMatching2ContextCallback callback, void* arg); + void Reset(); private: @@ -97,6 +167,9 @@ private: std::array m_contexts{}; std::array m_used{}; OrbisNpMatching2ContextId m_next_id = 1; + + OrbisNpMatching2ContextCallback m_pending_context_callback = nullptr; + void* m_pending_context_callback_arg = nullptr; }; struct PendingEvent { @@ -157,6 +230,10 @@ extern NpMatching2State g_state; OrbisNpMatching2RequestId AllocRequestId(); +void* BuildCreateJoinRoomPayload(ContextObject& ctx, const shadnet::CreateJoinRoomResponse& resp); +void* BuildGetWorldInfoListPayload(ContextObject& ctx, const shadnet::GetWorldInfoListReply& resp); +void* BuildSearchRoomPayload(ContextObject& ctx, const shadnet::SearchRoomReply& resp); + void InitEventDispatcher(); void TermEventDispatcher(); diff --git a/src/core/libraries/np/np_matching2/np_matching2_mm.cpp b/src/core/libraries/np/np_matching2/np_matching2_mm.cpp new file mode 100644 index 000000000..e4802d757 --- /dev/null +++ b/src/core/libraries/np/np_matching2/np_matching2_mm.cpp @@ -0,0 +1,564 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include + +#include "common/logging/log.h" +#include "core/libraries/network/net.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 "shadnet.pb.h" +#include "shadnet/client.h" + +namespace Libraries::Np::NpMatching2 { + +namespace { + +struct PendingRequest { + OrbisNpMatching2ContextId ctx_id = 0; + OrbisNpMatching2RequestId req_id = 0; + OrbisNpMatching2Event req_event{}; +}; + +struct MmClientState { + std::shared_ptr client; + std::mutex mutex; + u32 server_addr = 0; + u16 server_udp_port = 0; + + std::mutex pending_mutex; + std::map pending; + + std::mutex sig_mutex; + std::condition_variable sig_cv; + std::map>> sig_replies; +}; + +MmClientState g_mm; + +u32 IpStringToAddr(std::string_view ip) { + u32 a, b, c, d; + if (std::sscanf(std::string(ip).c_str(), "%u.%u.%u.%u", &a, &b, &c, &d) != 4) { + return 0; + } + return a | (b << 8) | (c << 16) | (d << 24); +} + +template +std::vector MakeProtoPayload(const T& msg) { + std::string bytes; + (void)msg.SerializeToString(&bytes); + const u32 len = static_cast(bytes.size()); + std::vector out; + out.reserve(4 + bytes.size()); + out.push_back(static_cast(len)); + out.push_back(static_cast(len >> 8)); + out.push_back(static_cast(len >> 16)); + out.push_back(static_cast(len >> 24)); + out.insert(out.end(), bytes.begin(), bytes.end()); + return out; +} + +void AppendBinAttr(shadnet::MatchingBinAttr* dst, const OrbisNpMatching2BinAttr& src) { + dst->set_attr_id(src.id); + if (src.data && src.dataSize > 0) { + dst->set_data(src.data, src.dataSize); + } +} + +void AppendIntAttr(shadnet::MatchingIntAttr* dst, const OrbisNpMatching2IntAttr& src) { + dst->set_attr_id(src.id); + dst->set_attr_value(src.num); +} + +std::string ExtractProtoBytes(const std::vector& payload, size_t offset = 0) { + if (payload.size() < offset + 4) { + return {}; + } + const u32 len = static_cast(payload[offset]) | + (static_cast(payload[offset + 1]) << 8) | + (static_cast(payload[offset + 2]) << 16) | + (static_cast(payload[offset + 3]) << 24); + if (payload.size() < offset + 4 + len) { + return {}; + } + return std::string(reinterpret_cast(payload.data() + offset + 4), len); +} + +void DispatchRequestComplete(const PendingRequest& pr, ShadNet::ErrorType error, + const std::vector& body) { + ContextObject* ctx = ContextManager::Instance().Get(pr.ctx_id); + if (!ctx) { + return; + } + + const s32 error_code = (error == ShadNet::ErrorType::NoError) ? 0 : static_cast(error); + + if (pr.req_event == ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED || + pr.req_event == ORBIS_NP_MATCHING2_CONTEXT_EVENT_STOPPED) { + PendingEvent ev{}; + ev.type = PendingEvent::CONTEXT_CB; + ev.ctx_id = pr.ctx_id; + ev.fire_at = std::chrono::steady_clock::now(); + ev.ctx_event = pr.req_event; + ev.ctx_event_cause = ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ACTION; + ev.error_code = error_code; + ScheduleEvent(std::move(ev)); + return; + } + + void* request_data = nullptr; + + if (error_code == 0) { + const std::string proto = ExtractProtoBytes(body); + if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM || + pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM) { + shadnet::CreateRoomReply reply; + if (reply.ParseFromString(proto) && reply.has_details()) { + request_data = BuildCreateJoinRoomPayload(*ctx, reply.details()); + } + } else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST) { + shadnet::GetWorldInfoListReply reply; + if (reply.ParseFromString(proto)) { + request_data = BuildGetWorldInfoListPayload(*ctx, reply); + } + } else if (pr.req_event == ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM) { + shadnet::SearchRoomReply reply; + if (reply.ParseFromString(proto)) { + request_data = BuildSearchRoomPayload(*ctx, reply); + } + } + } + + PendingEvent ev{}; + ev.type = PendingEvent::REQUEST_CB; + ev.ctx_id = pr.ctx_id; + ev.fire_at = std::chrono::steady_clock::now(); + ev.req_id = pr.req_id; + ev.req_event = pr.req_event; + ev.error_code = error_code; + ev.request_cb = ctx->default_request_callback; + ev.request_cb_arg = ctx->default_request_callback_arg; + ev.request_data = request_data; + ScheduleEvent(std::move(ev)); +} + +void BuildMemberUpdate(CallbackPayload& p, const RoomCache& rc, const MemberCache& mc, + OrbisNpMatching2EventCause cause) { + p.event_member = std::make_unique(); + OrbisNpMatching2RoomMemberDataInternal& m = *p.event_member; + m = OrbisNpMatching2RoomMemberDataInternal{}; + m.memberId = mc.member_id; + m.teamId = mc.team_id; + m.natType = mc.nat_type; + m.flagAttr = mc.flag_attr; + m.joinDate = mc.join_date; + m.npId = mc.np_id; + + if (mc.group_id != 0) { + auto grp_it = rc.groups.find(mc.group_id); + if (grp_it != rc.groups.end()) { + p.room_groups.resize(1); + p.room_groups[0] = grp_it->second; + m.roomGroup = p.room_groups.data(); + } + } + + p.member_bin_attrs.resize(mc.bins.size()); + p.bin_buffers.reserve(mc.bins.size()); + size_t i = 0; + for (const auto& [attr_id, bin] : mc.bins) { + p.bin_buffers.emplace_back(bin.data.begin(), bin.data.end()); + auto& buf = p.bin_buffers.back(); + auto& dst = p.member_bin_attrs[i++]; + dst = OrbisNpMatching2RoomMemberBinAttrInternal{}; + dst.binAttr.id = bin.id; + dst.binAttr.data = buf.empty() ? nullptr : buf.data(); + dst.binAttr.dataSize = buf.size(); + } + m.roomMemberBinAttrInternal = p.member_bin_attrs.empty() ? nullptr : p.member_bin_attrs.data(); + m.roomMemberBinAttrInternalNum = p.member_bin_attrs.size(); + + p.room_member_update = std::make_unique(); + OrbisNpMatching2RoomMemberUpdate& u = *p.room_member_update; + u = OrbisNpMatching2RoomMemberUpdate{}; + u.roomMemberDataInternal = p.event_member.get(); + u.eventCause = cause; + p.room_event_data = p.room_member_update.get(); +} + +void HandleRoomEvent(const ShadNet::NotifyRoomEvent& n) { + ContextObject* ctx = + ContextManager::Instance().Get(static_cast(n.ctx_id)); + if (!ctx) { + return; + } + const auto event = static_cast(n.event); + const auto cause = static_cast(n.event_cause); + const auto room_id = static_cast(n.room_id); + const auto member_id = static_cast(n.member_id); + + CallbackPayload& p = ctx->room_event_payload; + p.Reset(); + + switch (event) { + case ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED: { + auto room_it = ctx->room_cache.find(room_id); + if (room_it == ctx->room_cache.end()) { + return; + } + MemberCache& mc = room_it->second.members[member_id]; + mc = MemberCache{}; + mc.member_id = member_id; + mc.team_id = static_cast(n.member_team_id); + mc.nat_type = static_cast(n.member_nat_type); + mc.flag_attr = n.member_flag_attr; + mc.group_id = static_cast(n.member_group_id); + mc.join_date = n.member_join_date; + 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); + for (const auto& a : n.member_bin_attrs) { + MemberBinCache& b = mc.bins[a.attr_id]; + b.id = static_cast(a.attr_id); + b.data = a.data; + } + + PeerInfo pi{}; + pi.member_id = member_id; + pi.addr = mc.addr; + pi.port = mc.port; + std::strncpy(pi.online_id.data, n.member_npid.c_str(), sizeof(pi.online_id.data) - 1); + ctx->peers[member_id] = pi; + + BuildMemberUpdate(p, room_it->second, mc, cause); + break; + } + case ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_LEFT: { + auto room_it = ctx->room_cache.find(room_id); + if (room_it == ctx->room_cache.end()) { + return; + } + auto mem_it = room_it->second.members.find(member_id); + if (mem_it == room_it->second.members.end()) { + return; + } + BuildMemberUpdate(p, room_it->second, mem_it->second, cause); + room_it->second.members.erase(mem_it); + ctx->peers.erase(member_id); + break; + } + case ORBIS_NP_MATCHING2_ROOM_EVENT_KICKEDOUT: { + p.room_update = std::make_unique(); + OrbisNpMatching2RoomUpdate& u = *p.room_update; + u = OrbisNpMatching2RoomUpdate{}; + u.eventCause = cause; + u.errorCode = n.error_code; + p.room_event_data = p.room_update.get(); + break; + } + default: + LOG_WARNING(Lib_NpMatching2, "unhandled room event {:#x}", n.event); + return; + } + + LOG_DEBUG(Lib_NpMatching2, "RoomEvent ctx={} room={} event={:#x} cause={}", n.ctx_id, n.room_id, + n.event, n.event_cause); + + PendingEvent ev{}; + ev.type = PendingEvent::ROOM_EVENT_CB; + ev.ctx_id = ctx->ctx_id; + ev.fire_at = std::chrono::steady_clock::now(); + ev.room_id = room_id; + ev.room_event = event; + ev.room_event_data = p.room_event_data; + ScheduleEvent(std::move(ev)); +} + +} // namespace + +void OnMatchingReply(ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType error, + const std::vector& body) { + if (cmd == ShadNet::CommandType::RequestSignalingInfos) { + std::lock_guard lock(g_mm.sig_mutex); + g_mm.sig_replies[pkt_id] = {error, body}; + g_mm.sig_cv.notify_all(); + return; + } + + PendingRequest pr; + { + std::lock_guard lock(g_mm.pending_mutex); + auto it = g_mm.pending.find(pkt_id); + if (it == g_mm.pending.end()) { + LOG_WARNING(Lib_NpMatching2, "reply pkt_id={} cmd={} has no pending entry (have {})", + pkt_id, static_cast(cmd), g_mm.pending.size()); + return; + } + pr = it->second; + g_mm.pending.erase(it); + } + LOG_DEBUG(Lib_NpMatching2, "reply pkt_id={} cmd={} -> ctx={} reqId={} event={:#x}", pkt_id, + static_cast(cmd), pr.ctx_id, pr.req_id, static_cast(pr.req_event)); + DispatchRequestComplete(pr, error, body); +} + +void SetMmShadNetClient(std::shared_ptr client, + std::string_view server_host, u16 tcp_port) { + { + std::lock_guard lock(g_mm.mutex); + g_mm.client = client; + g_mm.server_addr = client ? client->GetAddrServer() : 0; + if (g_mm.server_addr == 0) { + g_mm.server_addr = IpStringToAddr(server_host); + } + g_mm.server_udp_port = Libraries::Net::sceNetHtons(static_cast(tcp_port + 1)); + } + + if (!client) { + return; + } + + client->onRoomEvent = [](const ShadNet::NotifyRoomEvent& n) { HandleRoomEvent(n); }; +} + +void ClearMmShadNetClient() { + std::shared_ptr old_client; + { + std::lock_guard lock(g_mm.mutex); + old_client = std::move(g_mm.client); + g_mm.server_addr = 0; + g_mm.server_udp_port = 0; + } + if (old_client) { + old_client->onRoomEvent = nullptr; + } + { + std::lock_guard lock(g_mm.pending_mutex); + g_mm.pending.clear(); + } + g_mm.sig_cv.notify_all(); +} + +bool IsMmClientRunning() { + std::lock_guard lock(g_mm.mutex); + return g_mm.client && g_mm.client->IsAuthenticated(); +} + +void MmContextStart(OrbisNpMatching2ContextId ctx_id) { + shadnet::ContextStartRequest req; + req.set_ctx_id(ctx_id); + MmSubmitRequest(ctx_id, 0, ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED, MmCommand::ContextStart, + MakeProtoPayload(req)); +} + +void MmContextStop(OrbisNpMatching2ContextId ctx_id) { + shadnet::ContextStopRequest req; + req.set_ctx_id(ctx_id); + MmSubmitRequest(ctx_id, 0, ORBIS_NP_MATCHING2_CONTEXT_EVENT_STOPPED, MmCommand::ContextStop, + MakeProtoPayload(req)); +} + +s32 MmSubmitRequest(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, + OrbisNpMatching2Event req_event, MmCommand cmd, + const std::vector& payload) { + std::shared_ptr client; + { + std::lock_guard lock(g_mm.mutex); + client = g_mm.client; + } + if (!client || !client->IsAuthenticated()) { + LOG_ERROR(Lib_NpMatching2, "MmSubmitRequest({}): not connected", static_cast(cmd)); + return ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID; + } + + 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}; + } + LOG_DEBUG(Lib_NpMatching2, "submit cmd={} pkt_id={} ctx={} reqId={}", static_cast(cmd), + pkt_id, ctx_id, req_id); + return ORBIS_OK; +} + +s32 MmCreateJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, + const OrbisNpMatching2CreateJoinRoomRequest& 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)); + 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, + MmCommand::CreateRoom, MakeProtoPayload(req)); +} + +s32 MmJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, + const OrbisNpMatching2JoinRoomRequest& 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)); + 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, + MmCommand::JoinRoom, MakeProtoPayload(req)); +} + +s32 MmLeaveRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, + const OrbisNpMatching2LeaveRoomRequest& request) { + shadnet::LeaveRoomRequest req; + req.set_room_id(request.roomId); + req.set_req_id(req_id); + return MmSubmitRequest(ctx_id, req_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_LEAVE_ROOM, + MmCommand::LeaveRoom, MakeProtoPayload(req)); +} + +s32 MmGetWorldInfoList(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, + const OrbisNpMatching2GetWorldInfoListRequest& request) { + shadnet::GetWorldInfoListRequest req; + req.set_server_id(request.serverId); + return MmSubmitRequest(ctx_id, req_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST, + MmCommand::GetWorldInfoList, MakeProtoPayload(req)); +} + +s32 MmSearchRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, + const OrbisNpMatching2SearchRoomRequest& request) { + shadnet::SearchRoomRequest req; + req.set_world_id(request.worldId); + req.set_lobby_id(request.lobbyId); + req.set_range_filter_start(request.rangeFilter.start); + req.set_range_filter_max(request.rangeFilter.max); + req.set_flag_filter(request.flags1); + req.set_flag_attr(request.flags2); + LOG_DEBUG(Lib_NpMatching2, + "search world={} lobby={} range={}+{} flags={:#x}/{:#x} intN={} binN={} attrN={}", + request.worldId, request.lobbyId, request.rangeFilter.start, request.rangeFilter.max, + request.flags1, request.flags2, request.intFilters, request.binFilters, + request.attrs); + for (u64 i = 0; i < request.intFilters; ++i) { + const auto& f = request.intFilter[i]; + LOG_DEBUG(Lib_NpMatching2, " intFilter[{}] op={} id={:#x} num={}", i, f.searchOperator, + f.attr.id, f.attr.num); + auto* dst = req.add_int_filters(); + dst->set_op(f.searchOperator); + dst->set_attr_id(f.attr.id); + dst->set_attr_value(f.attr.num); + } + for (u64 i = 0; i < request.binFilters; ++i) { + const auto& f = request.binFilter[i]; + auto* dst = req.add_bin_filters(); + dst->set_op(f.searchOperator); + dst->set_attr_id(f.attr.id); + if (f.attr.data && f.attr.dataSize > 0) { + dst->set_data(f.attr.data, f.attr.dataSize); + } + } + for (u64 i = 0; i < request.attrs; ++i) { + req.add_attr_ids(request.attr[i]); + } + return MmSubmitRequest(ctx_id, req_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM, + MmCommand::SearchRoom, MakeProtoPayload(req)); +} + +u32 GetMmServerAddr() { + std::lock_guard lock(g_mm.mutex); + return g_mm.server_addr; +} + +u16 GetMmServerUdpPort() { + std::lock_guard lock(g_mm.mutex); + return g_mm.server_udp_port; +} + +bool RequestSignalingInfos(std::string_view target_online_id, u32* out_addr, u16* out_port) { + if (!out_addr || !out_port) { + return false; + } + std::shared_ptr client; + { + std::lock_guard lock(g_mm.mutex); + client = g_mm.client; + } + if (!client || !client->IsAuthenticated()) { + return false; + } + + shadnet::RequestSignalingInfosRequest req; + req.set_target_npid(std::string(target_online_id)); + const u64 pkt_id = + client->SubmitRequest(ShadNet::CommandType::RequestSignalingInfos, MakeProtoPayload(req)); + + std::pair> reply; + { + std::unique_lock lock(g_mm.sig_mutex); + if (!g_mm.sig_cv.wait_for(lock, std::chrono::seconds(5), + [&] { return g_mm.sig_replies.count(pkt_id) > 0; })) { + LOG_WARNING(Lib_NpMatching2, "timed out for '{}'", target_online_id); + return false; + } + reply = std::move(g_mm.sig_replies[pkt_id]); + g_mm.sig_replies.erase(pkt_id); + } + if (reply.first != ShadNet::ErrorType::NoError) { + return false; + } + + shadnet::RequestSignalingInfosReply rep; + const std::string proto = ExtractProtoBytes(reply.second); + if (proto.empty() || !rep.ParseFromString(proto)) { + return false; + } + const std::string addr_str = rep.target_ip(); + const u16 port_host = static_cast(rep.target_port()); + if (addr_str.empty() || port_host == 0) { + return false; + } + + u32 addr_nbo = 0; + if (Libraries::Net::sceNetInetPton(Net::ORBIS_NET_AF_INET, addr_str.c_str(), &addr_nbo) <= 0) { + return false; + } + *out_addr = addr_nbo; + *out_port = Libraries::Net::sceNetHtons(port_host); + LOG_DEBUG(Lib_NpMatching2, "'{}' -> {}:{}", target_online_id, addr_str, port_host); + return true; +} + +} // namespace Libraries::Np::NpMatching2 diff --git a/src/core/libraries/np/np_matching2/np_matching2_mm.h b/src/core/libraries/np/np_matching2/np_matching2_mm.h new file mode 100644 index 000000000..a0ec301dd --- /dev/null +++ b/src/core/libraries/np/np_matching2/np_matching2_mm.h @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "common/types.h" +#include "core/libraries/np/np_matching2/np_matching2.h" +#include "core/libraries/np/np_matching2/np_matching2_types.h" + +namespace ShadNet { +class ShadNetClient; +enum class CommandType : u16; +enum class ErrorType : u8; +} // namespace ShadNet + +namespace Libraries::Np::NpMatching2 { + +enum class MmCommand : u16 { + ContextStart = 12, + CreateRoom = 13, + JoinRoom = 14, + LeaveRoom = 15, + SearchRoom = 16, + RequestSignalingInfos = 17, + ContextStop = 18, + SetRoomDataInternal = 20, + SetRoomDataExternal = 21, + KickoutRoomMember = 22, + GetWorldInfoList = 23, +}; + +void SetMmShadNetClient(std::shared_ptr client, + std::string_view server_host, u16 tcp_port); +void ClearMmShadNetClient(); +bool IsMmClientRunning(); + +void OnMatchingReply(ShadNet::CommandType cmd, u64 pkt_id, ShadNet::ErrorType error, + const std::vector& body); + +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); + +s32 MmCreateJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, + const OrbisNpMatching2CreateJoinRoomRequest& request); +s32 MmJoinRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, + const OrbisNpMatching2JoinRoomRequest& request); +s32 MmLeaveRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, + const OrbisNpMatching2LeaveRoomRequest& request); +s32 MmGetWorldInfoList(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, + const OrbisNpMatching2GetWorldInfoListRequest& request); +s32 MmSearchRoom(OrbisNpMatching2ContextId ctx_id, OrbisNpMatching2RequestId req_id, + const OrbisNpMatching2SearchRoomRequest& request); + +u32 GetMmServerAddr(); +u16 GetMmServerUdpPort(); + +bool RequestSignalingInfos(std::string_view target_online_id, u32* out_addr, u16* out_port); + +} // 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 2d1fa7c73..dd6e2a787 100644 --- a/src/core/libraries/np/np_matching2/np_matching2_types.h +++ b/src/core/libraries/np/np_matching2/np_matching2_types.h @@ -261,6 +261,20 @@ struct OrbisNpMatching2PresenceOptionData { u64 len; }; +struct OrbisNpMatching2RoomMemberUpdateA { + OrbisNpMatching2RoomMemberDataInternalA* roomMemberDataInternal; + OrbisNpMatching2EventCause eventCause; + u8 padding[7]; + OrbisNpMatching2PresenceOptionData optData; +}; + +struct OrbisNpMatching2RoomUpdate { + OrbisNpMatching2EventCause eventCause; + u8 padding[3]; + s32 errorCode; + OrbisNpMatching2PresenceOptionData optData; +}; + struct OrbisNpMatching2JoinRoomRequest { OrbisNpMatching2RoomId roomId; OrbisNpMatching2SessionPassword* roomPasswd; @@ -315,6 +329,20 @@ struct OrbisNpMatching2RangeFilter { u32 max; }; +using OrbisNpMatching2Operator = u8; + +struct OrbisNpMatching2IntSearchFilter { + OrbisNpMatching2Operator searchOperator; + u8 padding[7]; + OrbisNpMatching2IntAttr attr; +}; + +struct OrbisNpMatching2BinSearchFilter { + OrbisNpMatching2Operator searchOperator; + u8 padding[7]; + OrbisNpMatching2BinAttr attr; +}; + struct OrbisNpMatching2RequestOptParam { OrbisNpMatching2RequestCallback callback; void* arg; @@ -404,6 +432,13 @@ struct OrbisNpMatching2RoomMemberDataInternal { }; static_assert(sizeof(OrbisNpMatching2RoomMemberDataInternal) == 0x58); +struct OrbisNpMatching2RoomMemberUpdate { + OrbisNpMatching2RoomMemberDataInternal* roomMemberDataInternal; + OrbisNpMatching2EventCause eventCause; + u8 padding[7]; + OrbisNpMatching2PresenceOptionData optData; +}; + struct OrbisNpMatching2RoomMemberDataInternalList { OrbisNpMatching2RoomMemberDataInternal* members; u64 membersNum; @@ -450,9 +485,9 @@ struct OrbisNpMatching2SearchRoomRequest { OrbisNpMatching2RangeFilter rangeFilter; OrbisNpMatching2Flags flags1; OrbisNpMatching2Flags flags2; - void* intFilter; + OrbisNpMatching2IntSearchFilter* intFilter; u64 intFilters; - void* binFilter; + OrbisNpMatching2BinSearchFilter* binFilter; u64 binFilters; OrbisNpMatching2AttributeId* attr; u64 attrs; diff --git a/src/shadnet/client.cpp b/src/shadnet/client.cpp index 3d996d830..06eaa732e 100644 --- a/src/shadnet/client.cpp +++ b/src/shadnet/client.cpp @@ -7,7 +7,9 @@ #include #include "client.h" +#include "common/elf_info.h" #include "common/logging/log.h" +#include "common/thread.h" #include "shadnet.pb.h" #ifdef _WIN32 @@ -132,6 +134,9 @@ u64 ShadNetClient::GetUserId() const { u32 ShadNetClient::GetAddrLocal() const { return m_addr_local.load(); } +u32 ShadNetClient::GetAddrServer() const { + return m_addr_server.load(); +} u32 ShadNetClient::GetNumFriends() const { std::lock_guard lock(m_mutex_friends); @@ -153,6 +158,7 @@ std::string ShadNetClient::GetBearerToken() const { // Threading void ShadNetClient::ConnectThread() { + Common::SetCurrentThreadName("ShadNet:Connect"); bool connected = false; u32 backoff_ms = SHAD_CONNECT_RETRY_BACKOFF_MS; for (u32 attempt = 1; attempt <= SHAD_CONNECT_MAX_ATTEMPTS && !m_terminate; ++attempt) { @@ -187,6 +193,7 @@ void ShadNetClient::ConnectThread() { req.set_password(m_password); if (!m_token.empty()) req.set_token(m_token); + req.set_title_id(std::string(Common::ElfInfo::Instance().GameSerial())); const u64 id = m_pkt_counter.fetch_add(1); if (!SendAll(BuildPacket(CommandType::Login, id, MakeProtoPayload(req)))) { @@ -198,6 +205,7 @@ void ShadNetClient::ConnectThread() { } void ShadNetClient::ReaderThread() { + Common::SetCurrentThreadName("ShadNet:Reader"); while (!m_terminate) { u8 hdr[SHAD_HEADER_SIZE]; if (!RecvN(hdr, SHAD_HEADER_SIZE)) { @@ -239,6 +247,7 @@ void ShadNetClient::ReaderThread() { } void ShadNetClient::WriterThread() { + Common::SetCurrentThreadName("ShadNet:Writer"); while (!m_terminate) { std::unique_lock lock(m_mutex_send_queue); m_cv_send_queue.wait(lock, [&] { return m_terminate.load() || !m_send_queue.empty(); }); @@ -342,6 +351,11 @@ bool ShadNetClient::DoConnect() { if (::getsockname(m_sock, reinterpret_cast(&local), &alen) == 0) m_addr_local.store(local.sin_addr.s_addr); + struct sockaddr_in peer{}; + socklen_t plen = sizeof(peer); + if (::getpeername(m_sock, reinterpret_cast(&peer), &plen) == 0) + m_addr_server.store(peer.sin_addr.s_addr); + LOG_INFO(ShadNet, "TCP connected to {}:{}", m_host, m_port); // Apply receive timeout for the ServerInfo handshake. @@ -717,6 +731,50 @@ void ShadNetClient::HandleNotification(u16 cmd_raw, const std::vector& paylo onFriendStatus(n); break; } + case NotificationType::RoomEvent: { + shadnet::NotifyRoomEvent pb; + if (!pb.ParseFromString(blob)) { + LOG_WARNING(ShadNet, "RoomEvent parse error"); + break; + } + NotifyRoomEvent n; + n.ctx_id = pb.ctx_id(); + n.room_id = pb.room_id(); + n.event = pb.event(); + n.event_cause = pb.event_cause(); + n.error_code = pb.error_code(); + n.flags = pb.flags(); + if (pb.has_member()) { + const auto& m = pb.member(); + n.member_npid = m.npid(); + n.member_id = m.member_id(); + n.member_team_id = m.team_id(); + n.member_is_owner = m.is_owner(); + n.member_join_date = m.join_date(); + n.member_nat_type = m.nat_type(); + n.member_flag_attr = m.flag_attr(); + n.member_group_id = m.group_id(); + n.member_addr = m.addr(); + n.member_port = m.port(); + for (const auto& a : m.bin_attrs_internal()) { + MatchingBinAttr ba; + ba.attr_id = a.attr_id(); + ba.data.assign(a.data().begin(), a.data().end()); + n.member_bin_attrs.push_back(std::move(ba)); + } + } + for (const auto& a : pb.bin_attrs()) { + MatchingBinAttr ba; + ba.attr_id = a.attr_id(); + ba.data.assign(a.data().begin(), a.data().end()); + n.bin_attrs.push_back(std::move(ba)); + } + LOG_DEBUG(ShadNet, "RoomEvent room_id={} event={:#x} cause={}", n.room_id, n.event, + n.event_cause); + if (onRoomEvent) + onRoomEvent(n); + break; + } case NotificationType::WebApiPushEvent: { // Length-prefixed fields: npServiceName, npServiceLabel(u32 LE), dataType, // data, fromNpid, toNpid. ExtractBlob returns {} past the end, so a short diff --git a/src/shadnet/client.h b/src/shadnet/client.h index 4aa8ee1f1..fc5c8d278 100644 --- a/src/shadnet/client.h +++ b/src/shadnet/client.h @@ -66,16 +66,18 @@ enum class CommandType : u16 { AddBlock = 10, RemoveBlock = 11, // Matchmaking - RegisterHandlers = 12, + ContextStart = 12, CreateRoom = 13, JoinRoom = 14, LeaveRoom = 15, - GetRoomList = 16, + SearchRoom = 16, RequestSignalingInfos = 17, + ContextStop = 18, SetRoomDataInternal = 20, SetRoomDataExternal = 21, KickoutRoomMember = 22, - // 23-29 reserved for future matchmaking commands + GetWorldInfoList = 23, + // 24-29 reserved for future matchmaking commands GetBoardInfos = 30, RecordScore = 31, RecordScoreData = 32, @@ -93,6 +95,7 @@ enum class NotificationType : u16 { FriendNew = 6, FriendLost = 7, FriendStatus = 8, + RoomEvent = 10, WebApiPushEvent = 17, // Generic NP WebApi push event }; @@ -189,6 +192,34 @@ struct NotifyWebApiPushEvent { std::string toNpid; // may be empty }; +struct MatchingBinAttr { + u32 attr_id = 0; + std::vector data; +}; + +struct NotifyRoomEvent { + u32 ctx_id = 0; + u64 room_id = 0; + u32 event = 0; + u32 event_cause = 0; + s32 error_code = 0; + u32 flags = 0; + + std::string member_npid; + u32 member_id = 0; + u32 member_team_id = 0; + bool member_is_owner = false; + u64 member_join_date = 0; + u32 member_nat_type = 0; + u32 member_flag_attr = 0; + u32 member_group_id = 0; + std::string member_addr; + u32 member_port = 0; + std::vector member_bin_attrs; + + std::vector bin_attrs; +}; + // ShadNetClient class ShadNetClient { @@ -212,6 +243,7 @@ public: const std::string& GetAvatarUrl() const; u64 GetUserId() const; u32 GetAddrLocal() const; + u32 GetAddrServer() const; u32 GetNumFriends() const; std::optional GetFriendNpid(u32 index) const; @@ -223,6 +255,7 @@ public: std::function onFriendNew; std::function onFriendLost; std::function onFriendStatus; + std::function onRoomEvent; std::function onWebApiPushEvent; // Async reply callback. // cmd —command this reply is for (matches the request's cmd) @@ -298,6 +331,7 @@ private: mutable std::mutex m_mutex_bearer; std::string m_bearer_token; std::atomic m_addr_local{0}; + std::atomic m_addr_server{0}; mutable std::mutex m_mutex_friends; std::vector m_friends; diff --git a/src/shadnet/shadnet.proto b/src/shadnet/shadnet.proto index 321ab2189..4508614b5 100644 --- a/src/shadnet/shadnet.proto +++ b/src/shadnet/shadnet.proto @@ -13,7 +13,8 @@ option optimize_for = LITE_RUNTIME; message LoginRequest { string npid = 1; string password = 2; - string token = 3; + string token = 3; + string title_id = 4; } message FriendEntry { @@ -202,12 +203,6 @@ message MatchingRoomGroup { uint32 num_members = 6; } -message MatchingCallbackEntry { - bool enabled = 1; - uint64 callback_addr = 2; - uint64 callback_arg = 3; -} - // Room data structures message MatchingRoomDataInternal { @@ -234,6 +229,12 @@ message MatchingRoomMemberData { bool is_owner = 4; MatchingRoomGroup group = 5; repeated MatchingBinAttr bin_attrs_internal = 6; + uint64 join_date = 7; + uint32 nat_type = 8; + uint32 flag_attr = 9; + string addr = 10; + uint32 port = 11; + uint32 group_id = 12; } message MatchingRoomDataExternal { @@ -266,12 +267,12 @@ message CreateJoinRoomResponse { // Command requests -message RegisterHandlersRequest { - string addr = 1; - uint32 port = 2; - uint32 ctx_id = 3; - uint32 service_label = 4; - repeated MatchingCallbackEntry callbacks = 5; +message ContextStartRequest { + uint32 ctx_id = 1; +} + +message ContextStopRequest { + uint32 ctx_id = 1; } message CreateRoomRequest { @@ -364,8 +365,52 @@ message JoinRoomReply { CreateJoinRoomResponse details = 6; } -message GetRoomListReply { +message MatchingIntSearchFilter { + uint32 op = 1; // 1=Eq 2=Ne 3=Lt 4=Le 5=Gt 6=Ge + uint32 attr_id = 2; + uint32 attr_value = 3; +} + +message MatchingBinSearchFilter { + uint32 op = 1; // 1=Eq 2=Ne + uint32 attr_id = 2; + bytes data = 3; +} + +message SearchRoomRequest { + uint32 world_id = 1; + uint64 lobby_id = 2; + uint32 range_filter_start = 3; + uint32 range_filter_max = 4; + uint32 flag_filter = 5; + uint32 flag_attr = 6; + repeated MatchingIntSearchFilter int_filters = 7; + repeated MatchingBinSearchFilter bin_filters = 8; + repeated uint32 attr_ids = 9; +} + +message SearchRoomReply { repeated MatchingRoomDataExternal rooms = 1; + uint32 range_start = 2; + uint32 range_total = 3; + uint32 range_result = 4; +} + +message MatchingWorld { + uint32 world_id = 1; + uint32 lobbies_num = 2; + uint32 max_lobby_members = 3; + uint32 lobby_members_num = 4; + uint32 rooms_num = 5; + uint32 room_members_num = 6; +} + +message GetWorldInfoListRequest { + uint32 server_id = 1; +} + +message GetWorldInfoListReply { + repeated MatchingWorld worlds = 1; } message RequestSignalingInfosReply { @@ -382,74 +427,19 @@ message KickoutRoomMemberReply { uint64 room_id = 1; } // Notification payloads // Every notification carries ctx_id so the client can route it to the owning -// context (the server tracks ctx_id per connection from RegisterHandlersRequest). +// context (the server tracks ctx_id per connection from ContextStartRequest). -// NotificationType::RequestEvent (9) -message NotifyRequestEvent { - uint32 ctx_id = 1; - uint32 server_id = 2; - uint32 world_id = 3; - uint32 lobby_id = 4; - uint32 req_event = 5; - uint32 req_id = 6; - uint32 error_code = 7; - uint64 room_id = 8; - uint32 member_id = 9; - uint32 max_slots = 10; - uint32 flags = 11; - bool is_owner = 12; - bytes response_blob = 13; -} - -// NotificationType::MemberJoined (10) -message NotifyMemberJoined { - uint64 room_id = 1; - uint32 member_id = 2; - string npid = 3; - string addr = 4; - uint32 port = 5; - repeated MatchingBinAttr bin_attrs = 6; - uint32 ctx_id = 7; -} - -// NotificationType::MemberLeft (11) -message NotifyMemberLeft { - uint64 room_id = 1; - uint32 member_id = 2; - string npid = 3; - uint32 ctx_id = 4; -} - -// NotificationType::SignalingHelper (12) -message NotifySignalingHelper { - string npid = 1; - uint32 member_id = 2; - string addr = 3; - uint32 port = 4; - uint32 ctx_id = 5; -} - -// NotificationType::SignalingEvent (13) -message NotifySignalingEvent { - uint32 event_type = 1; - uint64 room_id = 2; - uint32 member_id = 3; - uint32 conn_id = 4; - uint32 ctx_id = 5; -} - -// NotificationType::RoomDataInternalUpdated (15) -message NotifyRoomDataInternalUpdated { - uint64 room_id = 1; - uint32 flags = 2; - repeated MatchingBinAttr bin_attrs = 3; - uint32 ctx_id = 4; -} - -// NotificationType::KickedOut (16) -message NotifyKickedOut { - uint64 room_id = 1; - int32 status_code = 2; - uint32 guard_value = 3; - uint32 ctx_id = 4; +// NotificationType::RoomEvent (10) — every room event maps onto +// OrbisNpMatching2RoomEventCallback(ctxId, roomId, event, data). The Orbis +// event id (0x11xx) is carried in `event`; the data shape depends on it: +// member events fill `member`; room-data-updated fills bin_attrs + flags. +message NotifyRoomEvent { + uint32 ctx_id = 1; + uint64 room_id = 2; + uint32 event = 3; + uint32 event_cause = 4; + int32 error_code = 5; + MatchingRoomMemberData member = 6; + repeated MatchingBinAttr bin_attrs = 7; + uint32 flags = 8; }