diff --git a/.gitmodules b/.gitmodules index 55ae48ea3cc..eb827da428b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -133,3 +133,9 @@ [submodule "externals/openal-soft"] path = externals/openal-soft url = https://github.com/shadexternals/openal-soft.git +[submodule "externals/cppcodec"] + path = externals/cppcodec + url = https://github.com/tplgy/cppcodec.git +[submodule "externals/ixwebsocket"] + path = externals/ixwebsocket + url = https://github.com/machinezone/IXWebSocket diff --git a/CMakeLists.txt b/CMakeLists.txt index cbf373e5707..767c11283a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,6 +265,12 @@ endif() add_subdirectory(externals) include_directories(src) +set(SHAD_NET src/core/shadnet/matching_context.cpp + src/core/shadnet/matching_context.h + src/core/shadnet/matching_json.cpp + src/core/shadnet/matching_json.h +) + set(AJM_LIB src/core/libraries/ajm/ajm.cpp src/core/libraries/ajm/ajm.h src/core/libraries/ajm/ajm_aac.cpp @@ -860,6 +866,7 @@ set(CORE src/core/aerolib/stubs.cpp ${CAMERA_LIBS} ${COMPANION_LIBS} ${DEV_TOOLS} + ${SHAD_NET} src/core/debug_state.cpp src/core/debug_state.h src/core/debugger.cpp @@ -1137,7 +1144,7 @@ create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml) -target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib) +target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib Cppcodec ixwebsocket) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 41a0f71c71d..eb9b713034c 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -317,3 +317,14 @@ endif() add_library(Cpp_Httplib INTERFACE) target_include_directories(Cpp_Httplib INTERFACE cpp-httplib/) +# cppcodec +add_library(Cppcodec INTERFACE) +target_include_directories(Cppcodec INTERFACE cppcodec/) + +# IXWebSocket +if (NOT TARGET ixwebsocket) + if (NOT WIN32) + set(USE_TLS ON CACHE BOOL "" FORCE) + endif() + add_subdirectory(ixwebsocket) +endif() diff --git a/externals/cppcodec b/externals/cppcodec new file mode 160000 index 00000000000..8019b8b580f --- /dev/null +++ b/externals/cppcodec @@ -0,0 +1 @@ +Subproject commit 8019b8b580f8573c33c50372baec7039dfe5a8ce diff --git a/externals/ixwebsocket b/externals/ixwebsocket new file mode 160000 index 00000000000..150e3d83b5f --- /dev/null +++ b/externals/ixwebsocket @@ -0,0 +1 @@ +Subproject commit 150e3d83b5f6a2a47f456b79330a9afe87cd379c diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index fd48faf72aa..adac4db27f1 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -162,6 +162,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { CLS(ImGui) \ CLS(Input) \ CLS(Tty) \ + CLS(ShadNet) \ CLS(Loader) // GetClassName is a macro defined by Windows.h, grrr... diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 2c6edef3bac..bd5be6835b6 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -130,6 +130,7 @@ enum class Class : u8 { Loader, ///< ROM loader Input, ///< Input emulation Tty, ///< Debug output from emu + ShadNet, ///< ShadNet Count ///< Total number of logging classes }; diff --git a/src/core/libraries/np/np_manager.cpp b/src/core/libraries/np/np_manager.cpp index 0ffbb682af6..4e7b83975e9 100644 --- a/src/core/libraries/np/np_manager.cpp +++ b/src/core/libraries/np/np_manager.cpp @@ -19,6 +19,7 @@ namespace Libraries::Np::NpManager { static bool g_signed_in = false; static s32 g_active_requests = 0; static std::mutex g_request_mutex; +OrbisNpTitleId g_np_title_id = {}; static std::map> g_np_callbacks; static std::mutex g_np_callbacks_mutex; @@ -686,6 +687,17 @@ sceNpGetUserIdByAccountId(u64 account_id, Libraries::UserService::OrbisUserServi return ORBIS_OK; } +s32 PS4_SYSV_ABI sceNpSetNpTitleId(OrbisNpTitleId* title_id, OrbisNpTitleSecret* title_secret) { + if (!title_id || !title_secret) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + LOG_DEBUG(Lib_NpManager, "titleId = {}", title_id->id); + + g_np_title_id = *title_id; + + return ORBIS_OK; +} + s32 PS4_SYSV_ABI sceNpHasSignedUp(Libraries::UserService::OrbisUserServiceUserId user_id, bool* has_signed_up) { LOG_DEBUG(Lib_NpManager, "called"); @@ -825,6 +837,7 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("XDncXQIJUSk", "libSceNpManager", 1, "libSceNpManager", sceNpGetOnlineId); LIB_FUNCTION("eQH7nWPcAgc", "libSceNpManager", 1, "libSceNpManager", sceNpGetState); LIB_FUNCTION("VgYczPGB5ss", "libSceNpManager", 1, "libSceNpManager", sceNpGetUserIdByAccountId); + LIB_FUNCTION("Ec63y59l9tw", "libSceNpManager", 1, "libSceNpManager", sceNpSetNpTitleId); LIB_FUNCTION("Oad3rvY-NJQ", "libSceNpManager", 1, "libSceNpManager", sceNpHasSignedUp); LIB_FUNCTION("3Zl8BePTh9Y", "libSceNpManager", 1, "libSceNpManager", sceNpCheckCallback); LIB_FUNCTION("JELHf4xPufo", "libSceNpManager", 1, "libSceNpManager", sceNpCheckCallbackForLib); diff --git a/src/core/libraries/np/np_manager.h b/src/core/libraries/np/np_manager.h index 49250db031b..9dce0812299 100644 --- a/src/core/libraries/np/np_manager.h +++ b/src/core/libraries/np/np_manager.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include "common/types.h" #include "core/libraries/np/np_error.h" @@ -90,6 +91,15 @@ struct OrbisNpCreateAsyncRequestParameter { u8 padding[4]; }; +struct OrbisNpTitleId { + char id[33]; + u8 padding[3]; +}; + +struct OrbisNpTitleSecret { + u8 data[128]; +}; + void RegisterNpCallback(std::string key, std::function cb); void DeregisterNpCallback(std::string key); @@ -98,5 +108,7 @@ s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId use s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId user_id, OrbisNpOnlineId* online_id); +extern OrbisNpTitleId g_np_title_id; + void RegisterLib(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Np::NpManager diff --git a/src/core/libraries/np/np_matching2.cpp b/src/core/libraries/np/np_matching2.cpp index dcd2a9c233e..710f6e91f82 100644 --- a/src/core/libraries/np/np_matching2.cpp +++ b/src/core/libraries/np/np_matching2.cpp @@ -2,47 +2,65 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include +#ifdef _WIN32 +#ifndef NOGDI +#define NOGDI +#endif +#endif + +#include "common/config.h" #include "common/logging/log.h" #include "core/emulator_settings.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" +#include "core/libraries/network/net.h" #include "core/libraries/np/np_manager.h" #include "core/libraries/np/np_matching2.h" +#include "core/libraries/np/np_matching2_requests.h" #include "core/libraries/np/np_types.h" +#include "core/libraries/np/object_manager.h" #include "core/libraries/system/userservice.h" +#include "core/shadnet/matching_context.h" +#include "cppcodec/base64_rfc4648.hpp" + +#include "magic_enum/magic_enum.hpp" namespace Libraries::Np::NpMatching2 { +using base64 = cppcodec::base64_rfc4648; +using MatchingContext = Core::ShadNet::MatchingContext; + static bool g_initialized = false; -static OrbisNpMatching2ContextId contextId = 1; -struct NpMatching2ContextEvent { +struct NpMatching2LobbyEvent { OrbisNpMatching2ContextId contextId; + OrbisNpMatching2LobbyId lobbyId; OrbisNpMatching2Event event; - OrbisNpMatching2EventCause cause; - int errorCode; + void* data; }; -struct NpMatching2LobbyEvent { +struct NpMatching2RoomEvent { OrbisNpMatching2ContextId contextId; - OrbisNpMatching2LobbyId lobbyId; + OrbisNpMatching2RoomId roomId; OrbisNpMatching2Event event; void* data; }; -struct NpMatching2RoomEvent { +struct NpMatching2RoomMessage { OrbisNpMatching2ContextId contextId; OrbisNpMatching2RoomId roomId; + OrbisNpMatching2RoomMemberId roomMemberId; OrbisNpMatching2Event event; void* data; }; 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::deque g_room_messages; static std::mutex g_responses_mutex; static std::deque> g_responses; @@ -56,6 +74,12 @@ struct OrbisNpMatching2CreateContextParameter { static_assert(sizeof(OrbisNpMatching2CreateContextParameter) == 0x28); +using NpMatching2ContextManager = + ObjectManager; + +static NpMatching2ContextManager ctxManager; + int PS4_SYSV_ABI sceNpMatching2CreateContext(const OrbisNpMatching2CreateContextParameter* param, OrbisNpMatching2ContextId* ctxId) { LOG_DEBUG(Lib_NpMatching2, "called, npId = {}, serviceLabel = {}, size = {}", @@ -68,7 +92,12 @@ int PS4_SYSV_ABI sceNpMatching2CreateContext(const OrbisNpMatching2CreateContext return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - *ctxId = contextId++; + auto id = ctxManager.CreateObject(); + if (id < 0) { + return id; + } + + *ctxId = id; return ORBIS_OK; } @@ -93,24 +122,16 @@ int PS4_SYSV_ABI sceNpMatching2CreateContextA(const OrbisNpMatching2CreateContex return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - *ctxId = contextId++; + auto id = ctxManager.CreateObject(); + if (id < 0) { + return id; + } + + *ctxId = id; return ORBIS_OK; } -using OrbisNpMatching2RequestCallback = PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId, - OrbisNpMatching2RequestId, - OrbisNpMatching2Event, int, void*, - void*); - -struct OrbisNpMatching2RequestOptParam { - OrbisNpMatching2RequestCallback callback; - void* arg; - u32 timeout; - u16 appId; - u8 dummy[2]; -}; - static std::optional defaultRequestOptParam = std::nullopt; auto GetOptParam(OrbisNpMatching2RequestOptParam* requestOpt) { @@ -119,83 +140,34 @@ auto GetOptParam(OrbisNpMatching2RequestOptParam* requestOpt) { : std::optional{}); } -struct OrbisNpMatching2CreateJoinRoomRequestA { - u16 maxSlot; - OrbisNpMatching2TeamId teamId; - u8 pad[5]; - OrbisNpMatching2Flags flags; - OrbisNpMatching2WorldId worldId; - OrbisNpMatching2LobbyId lobbyId; - void* roomPasswd; - void* passwdSlotMask; - void* groupConfig; - u64 groupConfigs; - void* joinGroupLabel; - Libraries::Np::OrbisNpAccountId* allowedUser; - u64 allowedUsers; - Libraries::Np::OrbisNpAccountId* blockedUser; - u64 blockedUsers; - void* internalBinAttr; - u64 internalBinAttrs; - void* externalSearchIntAttr; - u64 externalSearchIntAttrs; - void* externalSearchBinAttr; - u64 externalSearchBinAttrs; - void* externalBinAttr; - u64 externalBinAttrs; - void* memberInternalBinAttr; - u64 memberInternalBinAttrs; - void* signalingParam; -}; +int PS4_SYSV_ABI sceNpMatching2CreateJoinRoom(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2CreateJoinRoomRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId) { + LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); -static_assert(sizeof(OrbisNpMatching2CreateJoinRoomRequestA) == 184); + if (!g_initialized) { + return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; + } + if (!request || !requestId) { + return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + } -struct OrbisNpMatching2RoomDataInternal { - u16 publicSlots; - u16 privateSlots; - u16 openPublicSlots; - u16 openPrivateSlots; - u16 maxSlot; - OrbisNpMatching2ServerId serverId; - OrbisNpMatching2WorldId worldId; - OrbisNpMatching2LobbyId lobbyId; - OrbisNpMatching2RoomId roomId; - u64 passwdSlotMask; - u64 joinedSlotMask; - void* roomGroup; - u64 roomGroups; - OrbisNpMatching2Flags flags; - u8 pad[4]; - void* internalBinAttr; - u64 internalBinAttrs; -}; + MatchingContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } -struct OrbisNpMatching2RoomMemberDataInternalA { - OrbisNpMatching2RoomMemberDataInternalA* next; - u64 joinDateTicks; - Libraries::Np::OrbisNpPeerAddressA user; - Libraries::Np::OrbisNpOnlineId onlineId; - u8 pad[4]; - OrbisNpMatching2RoomMemberId memberId; - OrbisNpMatching2TeamId teamId; - OrbisNpMatching2NatType natType; - OrbisNpMatching2Flags flags; - void* roomGroup; - void* roomMemberInternalBinAttr; - u64 roomMemberInternalBinAttrs; -}; + if (auto ret = request->Validate(); ret < 0) { + return ret; + } -struct OrbisNpMatching2RoomMemberDataInternalListA { - OrbisNpMatching2RoomMemberDataInternalA* members; - u64 membersNum; - OrbisNpMatching2RoomMemberDataInternalA* me; - OrbisNpMatching2RoomMemberDataInternalA* owner; -}; + auto id = ctx->CreateJoinRoom(*request, requestOpt); -struct OrbisNpMatching2CreateJoinRoomResponseA { - OrbisNpMatching2RoomDataInternal* roomData; - OrbisNpMatching2RoomMemberDataInternalListA members; -}; + *requestId = id; + + return ORBIS_OK; +} int PS4_SYSV_ABI sceNpMatching2CreateJoinRoomA(OrbisNpMatching2ContextId ctxId, OrbisNpMatching2CreateJoinRoomRequestA* request, @@ -210,73 +182,22 @@ int PS4_SYSV_ABI sceNpMatching2CreateJoinRoomA(OrbisNpMatching2ContextId ctxId, 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); + MatchingContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } - static OrbisNpMatching2RequestId id = 10; - *requestId = id++; + if (auto ret = request->Validate(); ret < 0) { + return ret; + } - 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; - } + auto id = ctx->CreateJoinRoom(*request, requestOpt); + + *requestId = id; - 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); - }); - } return ORBIS_OK; } -using OrbisNpMatching2ContextCallback = PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId contextId, - OrbisNpMatching2Event event, - OrbisNpMatching2EventCause cause, - int errorCode, void* userdata); - -std::function npMatching2ContextCallback = nullptr; - int PS4_SYSV_ABI sceNpMatching2RegisterContextCallback(OrbisNpMatching2ContextCallback callback, void* userdata) { LOG_DEBUG(Lib_NpMatching2, "called, userdata = {}", userdata); @@ -285,9 +206,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterContextCallback(OrbisNpMatching2ContextCa return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - npMatching2ContextCallback = [callback, userdata](auto arg) { - callback(arg->contextId, arg->event, arg->cause, arg->errorCode, userdata); - }; + MatchingContext::SetContextCallback(callback, userdata); return ORBIS_OK; } @@ -316,7 +235,7 @@ int PS4_SYSV_ABI sceNpMatching2RegisterLobbyEventCallback( using OrbisNpMatching2RoomEventCallback = PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId contextId, OrbisNpMatching2RoomId roomId, OrbisNpMatching2Event event, - void* data, void* userdata); + const void* data, void* userdata); std::function npMatching2RoomCallback = nullptr; @@ -329,27 +248,37 @@ int PS4_SYSV_ABI sceNpMatching2RegisterRoomEventCallback(OrbisNpMatching2Context return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - npMatching2RoomCallback = [callback, userdata](auto arg) { - callback(arg->contextId, arg->roomId, arg->event, arg->data, userdata); - }; + MatchingContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } + + ctx->SetRoomCallback(callback, userdata); return ORBIS_OK; } -struct OrbisNpMatching2SignalingEvent { - OrbisNpMatching2ContextId contextId; - OrbisNpMatching2RoomId roomId; - OrbisNpMatching2RoomMemberId roomMemberId; - OrbisNpMatching2Event event; - int errorCode; -}; - -using OrbisNpMatching2SignalingCallback = +using OrbisNpMatching2RoomMessageCallback = PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId contextId, OrbisNpMatching2RoomId roomId, OrbisNpMatching2RoomMemberId roomMemberId, OrbisNpMatching2Event event, - int errorCode, void* userdata); + void* data, void* userdata); + +std::function npMatching2RoomMessageCallback = nullptr; -std::function npMatching2SignalingCallback = nullptr; +int PS4_SYSV_ABI sceNpMatching2RegisterRoomMessageCallback( + OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RoomMessageCallback callback, void* userdata) { + LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, userdata = {}", ctxId, userdata); + + if (!g_initialized) { + return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; + } + + npMatching2RoomMessageCallback = [callback, userdata](auto arg) { + callback(arg->contextId, arg->roomId, arg->roomMemberId, arg->event, arg->data, userdata); + }; + + return ORBIS_OK; +} int PS4_SYSV_ABI sceNpMatching2RegisterSignalingCallback(OrbisNpMatching2ContextId ctxId, OrbisNpMatching2SignalingCallback callback, @@ -360,10 +289,12 @@ int PS4_SYSV_ABI sceNpMatching2RegisterSignalingCallback(OrbisNpMatching2Context return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - npMatching2SignalingCallback = [callback, userdata](auto arg) { - callback(arg->contextId, arg->roomId, arg->roomMemberId, arg->event, arg->errorCode, - userdata); - }; + MatchingContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } + + ctx->SetSignalingCallback(callback, userdata); return ORBIS_OK; } @@ -375,31 +306,44 @@ int PS4_SYSV_ABI sceNpMatching2ContextStart(OrbisNpMatching2ContextId ctxId, u64 return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - std::scoped_lock lk{g_events_mutex}; - if (EmulatorSettings.IsConnectedToNetwork() && EmulatorSettings.IsPSNSignedIn()) { - g_ctx_events.emplace_back(ctxId, ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED, - ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ACTION, 0); - } else { - // 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); + MatchingContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; } - return ORBIS_OK; + return ctx->Start(ctxId, timeout); +} + +int PS4_SYSV_ABI sceNpMatching2ContextStop(OrbisNpMatching2ContextId ctxId) { + LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}", ctxId); + + if (!g_initialized) { + return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; + } + + MatchingContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } + + return ctx->Stop(); +} + +int PS4_SYSV_ABI sceNpMatching2ContextDestroy(OrbisNpMatching2ContextId ctxId) { + LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}", ctxId); + + if (!g_initialized) { + return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; + } + + return ctxManager.DeleteObject(ctxId); } void ProcessEvents() { + // processing will be moved to MatchingContext eventually { std::scoped_lock lk{g_events_mutex}; - 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()); @@ -412,6 +356,13 @@ void ProcessEvents() { g_room_events.pop_front(); } } + if (npMatching2RoomMessageCallback) { + while (!g_room_messages.empty()) { + LOG_INFO(Lib_NpMatching2, "calling room message"); + npMatching2RoomMessageCallback(&g_room_messages.front()); + g_room_messages.pop_front(); + } + } } std::scoped_lock lk{g_responses_mutex}; @@ -463,7 +414,12 @@ int PS4_SYSV_ABI sceNpMatching2SetDefaultRequestOptParam( return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - defaultRequestOptParam = *requestOpt; + MatchingContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } + + ctx->SetDefaultRequestOptParam(*requestOpt); return ORBIS_OK; } @@ -500,7 +456,7 @@ struct OrbisNpMatching2World { }; struct OrbisNpMatching2GetWorldInfoListResponse { - OrbisNpMatching2World* world; + const OrbisNpMatching2World* world; u64 worldNum; }; @@ -518,7 +474,7 @@ int PS4_SYSV_ABI sceNpMatching2GetWorldInfoList(OrbisNpMatching2ContextId ctxId, return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - static OrbisNpMatching2RequestId id = 1; + static OrbisNpMatching2RequestId id = 8866; *requestId = id++; if (auto optParam = GetOptParam(requestOpt); optParam) { @@ -527,26 +483,46 @@ int PS4_SYSV_ABI sceNpMatching2GetWorldInfoList(OrbisNpMatching2ContextId ctxId, auto reqIdCopy = *requestId; std::scoped_lock lk{g_responses_mutex}; g_responses.emplace_back([=]() { - OrbisNpMatching2World w{nullptr, 1, 10, 0, 10, 0, {}}; + OrbisNpMatching2World w{nullptr, 51966, 0, 0, 0, 1, 1, {}}; OrbisNpMatching2GetWorldInfoListResponse resp{&w, 1}; + LOG_DEBUG(Lib_NpMatching2, "foo {}", fmt::ptr(&resp)); optParam->callback(ctxId, reqIdCopy, - ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST, 0, &resp, - optParam->arg); + ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST, 0, + reinterpret_cast(&resp), optParam->arg); }); } return ORBIS_OK; } -struct OrbisNpMatching2PresenceOptionData { - u8 data[16]; - u64 len; -}; +int PS4_SYSV_ABI sceNpMatching2JoinRoom(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2JoinRoomRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, + OrbisNpMatching2RequestId* requestId) { + LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); -struct OrbisNpMatching2LeaveRoomRequest { - OrbisNpMatching2RoomId roomId; - OrbisNpMatching2PresenceOptionData optData; -}; + if (!g_initialized) { + return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; + } + if (!request || !requestId) { + return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + } + + MatchingContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } + + if (auto ret = request->Validate(); ret < 0) { + return ret; + } + + auto id = ctx->JoinRoom(*request, requestOpt); + + *requestId = id; + + return ORBIS_OK; +} int PS4_SYSV_ABI sceNpMatching2LeaveRoom(OrbisNpMatching2ContextId ctxId, OrbisNpMatching2LeaveRoomRequest* request, @@ -561,54 +537,21 @@ int PS4_SYSV_ABI sceNpMatching2LeaveRoom(OrbisNpMatching2ContextId ctxId, 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); - }); + MatchingContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; } - return ORBIS_OK; -} - -struct OrbisNpMatching2RangeFilter { - u32 start; - u32 max; -}; + if (auto ret = request->Validate(); ret < 0) { + return ret; + } -struct OrbisNpMatching2SearchRoomRequest { - int option; - OrbisNpMatching2WorldId worldId; - OrbisNpMatching2LobbyId lobbyId; - OrbisNpMatching2RangeFilter rangeFilter; - OrbisNpMatching2Flags flags1; - OrbisNpMatching2Flags flags2; - void* intFilter; - u64 intFilters; - void* binFilter; - u64 binFilters; - OrbisNpMatching2AttributeId* attr; - u64 attrs; -}; + auto id = ctx->LeaveRoom(*request, requestOpt); -struct OrbisNpMatching2Range { - u32 start; - u32 total; - u32 results; - u8 pad[4]; -}; + *requestId = id; -struct OrbisNpMatching2SearchRoomResponseA { - OrbisNpMatching2Range range; - void* roomDataExt; -}; + return ORBIS_OK; +} int PS4_SYSV_ABI sceNpMatching2SearchRoom(OrbisNpMatching2ContextId ctxId, OrbisNpMatching2SearchRoomRequest* request, @@ -623,29 +566,26 @@ int PS4_SYSV_ABI sceNpMatching2SearchRoom(OrbisNpMatching2ContextId ctxId, return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } - static OrbisNpMatching2RequestId id = 1; - *requestId = id++; + MatchingContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } - 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); - }); + if (auto ret = request->Validate(); ret < 0) { + return ret; } + auto id = ctx->SearchRoom(*request, requestOpt); + + *requestId = id; + return ORBIS_OK; } struct OrbisNpMatching2SetUserInfoRequest { OrbisNpMatching2ServerId serverId; u8 padding[6]; - void* userBinAttr; + OrbisNpMatching2BinAttr* userBinAttr; u64 userBinAttrs; }; @@ -679,7 +619,34 @@ int PS4_SYSV_ABI sceNpMatching2SetUserInfo(OrbisNpMatching2ContextId ctxId, return ORBIS_OK; } -int PS4_SYSV_ABI sceNpMatching2SendRoomMessage(OrbisNpMatching2ContextId ctxId, void* request, +enum class OrbisNpMatching2Cast : u8 { + Broadcast = 1, + Unicast = 2, + Multicast = 3, + Team = 4, +}; + +union OrbisNpMatching2Addressee { + OrbisNpMatching2RoomMemberId unicast; + struct { + OrbisNpMatching2RoomMemberId* members; + u64 len; + } multicast; + OrbisNpMatching2TeamId teamId; +}; + +struct OrbisNpMatching2SendRoomMessageRequest { + OrbisNpMatching2RoomId roomId; + OrbisNpMatching2Cast cast; + u8 pad[3]; + int unk; + OrbisNpMatching2Addressee addressee; + void* message; + u64 messageLen; +}; + +int PS4_SYSV_ABI sceNpMatching2SendRoomMessage(OrbisNpMatching2ContextId ctxId, + OrbisNpMatching2SendRoomMessageRequest* request, OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) { LOG_DEBUG(Lib_NpMatching2, "called, ctxId = {}, requestOpt = {}", ctxId, fmt::ptr(requestOpt)); @@ -691,19 +658,32 @@ int PS4_SYSV_ABI sceNpMatching2SendRoomMessage(OrbisNpMatching2ContextId ctxId, return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; } + auto cast = std::to_underlying(request->cast); + LOG_DEBUG(Lib_NpMatching2, "roomId = {}, cast = {}, addressee = {}", request->roomId, + magic_enum::enum_name(request->cast), + cast == 0 ? "broadcast" + : (cast == 1 ? std::to_string(request->addressee.unicast) + : (cast == 2 ? "multicast" + : std::to_string(request->addressee.teamId)))); + static OrbisNpMatching2RequestId id = 1000; *requestId = id++; + auto reqIdCopy = *requestId; 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); }); } + if (request->cast == OrbisNpMatching2Cast::Broadcast) { + std::scoped_lock lk{g_events_mutex}; + g_room_messages.emplace_back(ctxId, request->roomId, 0x1FE8, + ORBIS_NP_MATCHING2_ROOM_MSG_EVENT_MESSAGE_A, request->message); + } return ORBIS_OK; } @@ -768,6 +748,76 @@ int PS4_SYSV_ABI sceNpMatching2SetRoomDataInternal(OrbisNpMatching2ContextId ctx return ORBIS_OK; } +int PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionStatus( + OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RoomId roomId, + OrbisNpMatching2RoomMemberId roomMemberId, int* connectionStatus, + Libraries::Net::OrbisNetInAddr* addr, u16* port) { + LOG_ERROR(Lib_NpMatching2, "(STUBBED) ctxId = {}, roomId = {}, roomMemberId = {}", ctxId, + roomId, roomMemberId); + + if (connectionStatus) { + *connectionStatus = 1; + if (addr) { + addr->inaddr_addr = inet_addr("127.0.0.1"); + } + if (port) { + *port = 6666; + } + } + + return ORBIS_OK; +} + +union OrbisNpMatching2SignalingConnectionInfo { + u32 ping; + u32 bps; + // +}; + +int PS4_SYSV_ABI sceNpMatching2SignalingGetConnectionInfo( + OrbisNpMatching2ContextId ctxId, OrbisNpMatching2RoomId roomId, + OrbisNpMatching2RoomMemberId roomMemberId, int info, + OrbisNpMatching2SignalingConnectionInfo* connInfo) { + LOG_ERROR(Lib_NpMatching2, "(STUBBED) ctxId = {}, roomId = {}, roomMemberId = {}, info = {}", + ctxId, roomId, roomMemberId, info); + + if (connInfo) { + if (info == 1) { + connInfo->ping = 2500; + } + } + + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceNpMatching2SignalingGetPingInfo( + OrbisNpMatching2ContextId ctxId, OrbisNpMatching2SignalingGetPingInfoRequest* request, + OrbisNpMatching2RequestOptParam* requestOpt, OrbisNpMatching2RequestId* requestId) { + LOG_DEBUG(Lib_NpMatching2, "ctxId = {}, roomId = {}", ctxId, request ? request->roomId : -1); + + if (!g_initialized) { + return ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED; + } + if (!request || !requestId) { + return ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + } + + MatchingContext* ctx = nullptr; + if (auto ret = ctxManager.GetObject(ctxId, &ctx); ret < 0) { + return ret; + } + + if (auto ret = request->Validate(); ret < 0) { + return ret; + } + + auto id = ctx->SignalingGetPingInfo(*request, requestOpt); + + *requestId = id; + + return ORBIS_OK; +} + void RegisterLib(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("10t3e5+JPnU", "libSceNpMatching2", 1, "libSceNpMatching2", sceNpMatching2Initialize); @@ -777,6 +827,8 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceNpMatching2CreateContext); LIB_FUNCTION("ajvzc8e2upo", "libSceNpMatching2", 1, "libSceNpMatching2", sceNpMatching2CreateContextA); + LIB_FUNCTION("zCWZmXXN600", "libSceNpMatching2", 1, "libSceNpMatching2", + sceNpMatching2CreateJoinRoom); LIB_FUNCTION("V6KSpKv9XJE", "libSceNpMatching2", 1, "libSceNpMatching2", sceNpMatching2CreateJoinRoomA); LIB_FUNCTION("fQQfP87I7hs", "libSceNpMatching2", 1, "libSceNpMatching2", @@ -785,14 +837,22 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceNpMatching2RegisterLobbyEventCallback); LIB_FUNCTION("p+2EnxmaAMM", "libSceNpMatching2", 1, "libSceNpMatching2", sceNpMatching2RegisterRoomEventCallback); + LIB_FUNCTION("uBESzz4CQws", "libSceNpMatching2", 1, "libSceNpMatching2", + sceNpMatching2RegisterRoomMessageCallback); LIB_FUNCTION("0UMeWRGnZKA", "libSceNpMatching2", 1, "libSceNpMatching2", sceNpMatching2RegisterSignalingCallback); + LIB_FUNCTION("Nz-ZE7ur32I", "libSceNpMatching2", 1, "libSceNpMatching2", + sceNpMatching2ContextDestroy); 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", sceNpMatching2GetWorldInfoList); + LIB_FUNCTION("CSIMDsVjs-g", "libSceNpMatching2", 1, "libSceNpMatching2", + sceNpMatching2JoinRoom); LIB_FUNCTION("BD6kfx442Do", "libSceNpMatching2", 1, "libSceNpMatching2", sceNpMatching2LeaveRoom); LIB_FUNCTION("+8e7wXLmjds", "libSceNpMatching2", 1, "libSceNpMatching2", @@ -807,6 +867,12 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceNpMatching2SetRoomDataExternal); LIB_FUNCTION("S9D8JSYIrjE", "libSceNpMatching2", 1, "libSceNpMatching2", sceNpMatching2SetRoomDataInternal); + LIB_FUNCTION("tHD5FPFXtu4", "libSceNpMatching2", 1, "libSceNpMatching2", + sceNpMatching2SignalingGetConnectionStatus); + LIB_FUNCTION("twVupeaYYrk", "libSceNpMatching2", 1, "libSceNpMatching2", + sceNpMatching2SignalingGetConnectionInfo); + LIB_FUNCTION("wUmwXZHaX1w", "libSceNpMatching2", 1, "libSceNpMatching2", + sceNpMatching2SignalingGetPingInfo); }; } // namespace Libraries::Np::NpMatching2 \ No newline at end of file diff --git a/src/core/libraries/np/np_matching2.h b/src/core/libraries/np/np_matching2.h index 6f7fca90012..5ff5218c989 100644 --- a/src/core/libraries/np/np_matching2.h +++ b/src/core/libraries/np/np_matching2.h @@ -20,26 +20,57 @@ using OrbisNpMatching2LobbyId = u64; using OrbisNpMatching2NatType = u8; using OrbisNpMatching2RequestId = u16; using OrbisNpMatching2RoomId = u64; +using OrbisNpMatching2RoomGroupId = u8; using OrbisNpMatching2RoomMemberId = u16; using OrbisNpMatching2ServerId = u16; using OrbisNpMatching2TeamId = u8; using OrbisNpMatching2WorldId = u32; +using OrbisNpMatching2ContextCallback = PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId contextId, + OrbisNpMatching2Event event, + OrbisNpMatching2EventCause cause, + int errorCode, void* userdata); +using OrbisNpMatching2RoomCallback = PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId contextId, + OrbisNpMatching2RoomId roomId, + OrbisNpMatching2Event event, + const void* data, void* userdata); + +using OrbisNpMatching2SignalingCallback = + PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId contextId, OrbisNpMatching2RoomId roomId, + OrbisNpMatching2RoomMemberId roomMemberId, OrbisNpMatching2Event event, + int errorCode, void* userdata); + constexpr int ORBIS_NP_MATCHING2_ERROR_NOT_INITIALIZED = 0x80550c01; constexpr int ORBIS_NP_MATCHING2_ERROR_ALREADY_INITIALIZED = 0x80550c02; +constexpr int ORBIS_NP_MATCHING2_ERROR_CONTEXT_MAX = 0x80550c04; +constexpr int ORBIS_NP_MATCHING2_ERROR_CONTEXT_ALREADY_EXISTS = 0x80550c05; +constexpr int ORBIS_NP_MATCHING2_ERROR_CONTEXT_NOT_FOUND = 0x80550c06; +constexpr int ORBIS_NP_MATCHING2_ERROR_CONTEXT_ALREADY_STARTED = 0x80550c07; +constexpr int ORBIS_NP_MATCHING2_ERROR_CONTEXT_NOT_STARTED = 0x80550c08; +constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID = 0x80550c0b; constexpr int ORBIS_NP_MATCHING2_ERROR_INVALID_ARGUMENT = 0x80550c15; +constexpr int ORBIS_NP_MATCHING2_ERROR_TIMEDOUT = 0x80550c36; constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM = 0x0101; constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM_A = 0x7101; constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_GET_WORLD_INFO_LIST = 0x0002; +constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM = 0x106; constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM_A = 0x7106; +constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM = 0x0102; constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_LEAVE_ROOM = 0x0103; constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SEND_ROOM_MESSAGE = 0x0108; constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_EXTERNAL = 0x0004; constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_ROOM_DATA_INTERNAL = 0x1106; constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SET_USER_INFO = 0x0007; +constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_REQUEST_EVENT_SIGNALING_GET_PING_INFO = 0x0E01; constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_CONTEXT_EVENT_START_OVER = 0x6F01; constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED = 0x6F02; +constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_CONTEXT_EVENT_STOPPED = 0x6F03; +constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_SIGNALING_EVENT_ESTABLISHED = 0x5102; +constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED = 0x1101; +constexpr OrbisNpMatching2Event ORBIS_NP_MATCHING2_ROOM_MSG_EVENT_MESSAGE_A = 0x9102; + +constexpr OrbisNpMatching2Flags ORBIS_NP_MATCHING2_ROOM_MEMBER_FLAG_ATTR_OWNER = 0x80000000; constexpr OrbisNpMatching2EventCause ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ERROR = 10; constexpr OrbisNpMatching2EventCause ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ACTION = 11; diff --git a/src/core/libraries/np/np_matching2_requests.h b/src/core/libraries/np/np_matching2_requests.h new file mode 100644 index 00000000000..247719ae7b7 --- /dev/null +++ b/src/core/libraries/np/np_matching2_requests.h @@ -0,0 +1,383 @@ +// 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_manager.h" +#include "core/libraries/np/np_matching2.h" +#include "core/libraries/rtc/rtc.h" + +namespace Libraries::Np::NpMatching2 { + +struct OrbisNpMatching2SignalingParam { + int type; + int flag; + OrbisNpMatching2RoomMemberId mainMember; + u8 pad[4]; +}; + +struct OrbisNpMatching2SessionPassword { + u8 data[8]; +}; + +struct OrbisNpMatching2RoomPassword { + u8 data[8]; +}; + +struct OrbisNpMatching2GroupLabel { + u8 data[8]; +}; + +struct OrbisNpMatching2RoomGroupConfig { + u32 slots; + bool hasLabel; + OrbisNpMatching2GroupLabel label; + bool hasPassword; + u8 pad[2]; +}; + +struct OrbisNpMatching2BinAttr { + OrbisNpMatching2AttributeId id; + u8 pad[6]; + u8* data; + u64 dataSize; +}; + +struct OrbisNpMatching2RoomBinAttrInternal { + Libraries::Rtc::OrbisRtcTick lastUpdate; + OrbisNpMatching2RoomMemberId memberId; + u8 pad[6]; + OrbisNpMatching2BinAttr binAttr; +}; + +struct OrbisNpMatching2RoomMemberBinAttrInternal { + Libraries::Rtc::OrbisRtcTick lastUpdate; + OrbisNpMatching2BinAttr binAttr; +}; + +struct OrbisNpMatching2IntAttr { + OrbisNpMatching2AttributeId id; + u8 pad[2]; + u32 attr; +}; + +template +struct OrbisNpMatching2CreateJoinRoomRequest_ { + u16 maxSlot; + OrbisNpMatching2TeamId teamId; + u8 pad[5]; + OrbisNpMatching2Flags flags; + OrbisNpMatching2WorldId worldId; + OrbisNpMatching2LobbyId lobbyId; + OrbisNpMatching2RoomPassword* roomPasswd; + u64* passwdSlotMask; + OrbisNpMatching2RoomGroupConfig* groupConfig; + u64 groupConfigs; + OrbisNpMatching2GroupLabel* joinGroupLabel; + T* allowedUser; + u64 allowedUsers; + T* blockedUser; + u64 blockedUsers; + OrbisNpMatching2BinAttr* internalBinAttr; + u64 internalBinAttrs; + OrbisNpMatching2IntAttr* externalSearchIntAttr; + u64 externalSearchIntAttrs; + OrbisNpMatching2BinAttr* externalSearchBinAttr; + u64 externalSearchBinAttrs; + OrbisNpMatching2BinAttr* externalBinAttr; + u64 externalBinAttrs; + OrbisNpMatching2BinAttr* memberInternalBinAttr; + u64 memberInternalBinAttrs; + OrbisNpMatching2SignalingParam* signalingParam; + + int Validate() { + return 0; + } +}; + +using OrbisNpMatching2CreateJoinRoomRequest = + OrbisNpMatching2CreateJoinRoomRequest_; +using OrbisNpMatching2CreateJoinRoomRequestA = + OrbisNpMatching2CreateJoinRoomRequest_; + +static_assert(sizeof(OrbisNpMatching2CreateJoinRoomRequestA) == 184); + +struct OrbisNpMatching2RoomGroup { + OrbisNpMatching2RoomGroupId id; + bool hasPasswd; + bool hasLabel; + u8 pad; + OrbisNpMatching2GroupLabel label; + u32 slots; + u32 groupMembers; +}; + +struct OrbisNpMatching2RoomGroupInfo { + OrbisNpMatching2RoomGroupId id; + bool hasPasswd; + u8 pad[2]; + u32 slots; + u32 groupMembers; +}; + +struct OrbisNpMatching2RangeFilter { + u32 start; + u32 max; +}; + +enum class OrbisNpMatching2Operator : u8 { Eq = 1, Ne = 2, Lt = 3, Le = 4, Gt = 5, Ge = 6 }; + +struct OrbisNpMatching2IntFilter { + OrbisNpMatching2Operator op; + u8 pad[7]; + OrbisNpMatching2IntAttr attr; +}; + +struct OrbisNpMatching2BinFilter { + OrbisNpMatching2Operator op; + u8 pad[7]; + OrbisNpMatching2BinAttr attr; +}; + +struct OrbisNpMatching2SearchRoomRequest { + int option; + OrbisNpMatching2WorldId worldId; + OrbisNpMatching2LobbyId lobbyId; + OrbisNpMatching2RangeFilter rangeFilter; + OrbisNpMatching2Flags flagFilter; + OrbisNpMatching2Flags flagAttrs; + OrbisNpMatching2IntFilter* intFilter; + u64 intFilters; + OrbisNpMatching2BinFilter* binFilter; + u64 binFilters; + OrbisNpMatching2AttributeId* attr; + u64 attrs; + + int Validate() { + return 0; + } +}; + +struct OrbisNpMatching2RoomDataInternal { + u16 publicSlots; + u16 privateSlots; + u16 openPublicSlots; + u16 openPrivateSlots; + u16 maxSlot; + OrbisNpMatching2ServerId serverId; + OrbisNpMatching2WorldId worldId; + OrbisNpMatching2LobbyId lobbyId; + OrbisNpMatching2RoomId roomId; + u64 passwdSlotMask; + u64 joinedSlotMask; + const OrbisNpMatching2RoomGroup* roomGroup; + u64 roomGroups; + OrbisNpMatching2Flags flags; + u8 pad[4]; + const OrbisNpMatching2RoomBinAttrInternal* roomBinAttrInternal; + u64 roomBinAttrInternalNum; +}; + +static_assert(offsetof(OrbisNpMatching2RoomDataInternal, roomBinAttrInternal) == 0x48); + +template +struct OrbisNpMatching2RoomMemberDataInternal_ { + OrbisNpMatching2RoomMemberDataInternal_* next; + u64 joinDateTicks; + T user; + Libraries::Np::OrbisNpOnlineId onlineId; + u8 pad[4]; + OrbisNpMatching2RoomMemberId memberId; + OrbisNpMatching2TeamId teamId; + OrbisNpMatching2NatType natType; + OrbisNpMatching2Flags flags; + OrbisNpMatching2RoomGroup* roomGroup; + OrbisNpMatching2RoomMemberBinAttrInternal* roomMemberInternalBinAttr; + u64 roomMemberInternalBinAttrs; +}; + +using OrbisNpMatching2RoomMemberDataInternal = + OrbisNpMatching2RoomMemberDataInternal_; +using OrbisNpMatching2RoomMemberDataInternalA = + OrbisNpMatching2RoomMemberDataInternal_; + +// static_assert(sizeof(OrbisNpMatching2RoomMemberDataInternal) == 0x60); + +template +struct OrbisNpMatching2RoomMemberDataInternalList_ { + OrbisNpMatching2RoomMemberDataInternal_* members; + u64 membersNum; + OrbisNpMatching2RoomMemberDataInternal_* me; + OrbisNpMatching2RoomMemberDataInternal_* owner; +}; + +using OrbisNpMatching2RoomMemberDataInternalList = + OrbisNpMatching2RoomMemberDataInternalList_; +using OrbisNpMatching2RoomMemberDataInternalListA = + OrbisNpMatching2RoomMemberDataInternalList_; + +struct OrbisNpMatching2CreateJoinRoomResponse { + const OrbisNpMatching2RoomDataInternal* roomData; + OrbisNpMatching2RoomMemberDataInternalList members; +}; + +struct OrbisNpMatching2CreateJoinRoomResponseA { + OrbisNpMatching2RoomDataInternal* roomData; + OrbisNpMatching2RoomMemberDataInternalListA members; +}; + +struct OrbisNpMatching2RoomDataExternalA { + OrbisNpMatching2RoomDataExternalA* next; + u16 maxSlot; + u16 curMembers; + OrbisNpMatching2Flags flags; + OrbisNpMatching2ServerId serverId; + u8 pad[2]; + OrbisNpMatching2WorldId worldId; + OrbisNpMatching2LobbyId lobbyId; + OrbisNpMatching2RoomId roomId; + u64 passwdSlotMask; + u64 joinedSlotMask; + u16 publicSlots; + u16 privateSlots; + u16 openPublicSlots; + u16 openPrivateSlots; + Np::OrbisNpPeerAddressA owner; + OrbisNpOnlineId ownerOnlineId; + OrbisNpMatching2RoomGroupInfo* roomGroup; + u64 roomGroups; + OrbisNpMatching2IntAttr* externalSearchIntAttr; + u64 externalSearchIntAttrs; + OrbisNpMatching2BinAttr* externalSearchBinAttr; + u64 externalSearchBinAttrs; + OrbisNpMatching2BinAttr* externalBinAttr; + u64 externalBinAttrs; +}; + +struct OrbisNpMatching2RoomDataExternal { + OrbisNpMatching2RoomDataExternal* next; + u16 maxSlot; + u16 curMembers; + OrbisNpMatching2Flags flags; + OrbisNpMatching2ServerId serverId; + u8 pad[2]; + OrbisNpMatching2WorldId worldId; + OrbisNpMatching2LobbyId lobbyId; + OrbisNpMatching2RoomId roomId; + u64 passwdSlotMask; + u64 joinedSlotMask; + u16 publicSlots; + u16 privateSlots; + u16 openPublicSlots; + u16 openPrivateSlots; + u64 unk; + OrbisNpMatching2RoomGroupInfo* roomGroup; + u64 roomGroups; + OrbisNpMatching2IntAttr* externalSearchIntAttr; + u64 externalSearchIntAttrs; + OrbisNpMatching2BinAttr* externalSearchBinAttr; + u64 externalSearchBinAttrs; + OrbisNpMatching2BinAttr* externalBinAttr; + u64 externalBinAttrs; +}; + +static_assert(sizeof(OrbisNpMatching2RoomDataExternal) == 0x88); + +struct OrbisNpMatching2Range { + u32 start; + u32 total; + u32 results; + u8 pad[4]; +}; + +struct OrbisNpMatching2SearchRoomResponseA { + OrbisNpMatching2Range range; + OrbisNpMatching2RoomDataExternalA* roomDataExt; +}; + +struct OrbisNpMatching2SearchRoomResponse { + OrbisNpMatching2Range range; + OrbisNpMatching2RoomDataExternal* roomDataExt; +}; + +struct OrbisNpMatching2SignalingGetPingInfoRequest { + OrbisNpMatching2RoomId roomId; + u8 pad[16]; + + int Validate() { + return 0; + } +}; + +struct OrbisNpMatching2SignalingGetPingInfoResponse { + OrbisNpMatching2ServerId serverId; + u8 pad[2]; + OrbisNpMatching2WorldId worldId; + OrbisNpMatching2RoomId roomId; + u32 pingUs; + u8 reserved[20]; +}; + +struct OrbisNpMatching2PresenceOptionData { + u8 data[16]; + u64 len; +}; + +struct OrbisNpMatching2JoinRoomRequest { + OrbisNpMatching2RoomId roomId; + OrbisNpMatching2SessionPassword* roomPasswd; + OrbisNpMatching2GroupLabel* joinGroupLabel; + OrbisNpMatching2BinAttr* roomMemberBinInternalAttr; + u64 roomMemberBinInternalAttrNum; + OrbisNpMatching2PresenceOptionData optData; + OrbisNpMatching2TeamId teamId; + u8 pad[3]; + OrbisNpMatching2Flags flags; + OrbisNpOnlineId* blockedUser; + u64 blockedUsers; + + int Validate() { + return 0; + } +}; + +static_assert(sizeof(OrbisNpMatching2JoinRoomRequest) == 0x58); + +struct OrbisNpMatching2LeaveRoomRequest { + OrbisNpMatching2RoomId roomId; + OrbisNpMatching2PresenceOptionData optData; + + int Validate() { + return 0; + } +}; + +struct OrbisNpMatching2LeaveRoomResponse { + OrbisNpMatching2RoomId roomId; +}; + +struct OrbisNpMatching2RoomMemberUpdateInfo { + OrbisNpMatching2RoomMemberDataInternal* roomMemberDataInternal; + OrbisNpMatching2EventCause eventCause; + u8 pad[7]; + OrbisNpMatching2PresenceOptionData optData; +}; + +using OrbisNpMatching2RequestCallback = PS4_SYSV_ABI void (*)(OrbisNpMatching2ContextId, + OrbisNpMatching2RequestId, + OrbisNpMatching2Event, int, + const void*, void*); +using OrbisNpMatching2RequestFn = PS4_SYSV_ABI void(OrbisNpMatching2ContextId, + OrbisNpMatching2RequestId, + OrbisNpMatching2Event, int, const void*, void*); + +struct OrbisNpMatching2RequestOptParam { + OrbisNpMatching2RequestCallback callback; + void* arg; + u32 timeout; + u16 appId; + u8 dummy[2]; +}; + +} // namespace Libraries::Np::NpMatching2 diff --git a/src/core/libraries/np/np_types.h b/src/core/libraries/np/np_types.h index 58c119becd7..dd823d2a7d8 100644 --- a/src/core/libraries/np/np_types.h +++ b/src/core/libraries/np/np_types.h @@ -46,16 +46,21 @@ struct OrbisNpIdToken { }; using OrbisNpServiceLabel = u32; -constexpr s32 ORBIS_NP_INVALID_SERVICE_LABEL = 0xFFFFFFFF; +constexpr OrbisNpServiceLabel ORBIS_NP_INVALID_SERVICE_LABEL = 0xFFFFFFFF; -using OrbisNpAccountId = u64; -enum OrbisNpPlatformType : s32 { - None = 0, +enum class OrbisNpPlatformType : s32 { + NONE = 0, PS3 = 1, - Vita = 2, + VITA = 2, PS4 = 3, }; +struct OrbisNpPeerAddress { + OrbisNpId* npId; + OrbisNpPlatformType platformType; + u8 padding[4]; +}; + struct OrbisNpPeerAddressA { OrbisNpAccountId accountId; OrbisNpPlatformType platform; diff --git a/src/core/shadnet/matching_context.cpp b/src/core/shadnet/matching_context.cpp new file mode 100644 index 00000000000..314ed167c09 --- /dev/null +++ b/src/core/shadnet/matching_context.cpp @@ -0,0 +1,349 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "common/logging/log.h" +#include "core/emulator_settings.h" +#include "core/libraries/network/net_error.h" +#include "core/libraries/np/np_manager.h" +#include "core/shadnet/matching_context.h" +#include "core/shadnet/matching_json.h" + +namespace Core::ShadNet { + +const char* SHADNET_WS_URL = "ws://127.0.0.1:3000/matching/v1/ws"; + +using namespace Libraries::Np::NpMatching2; +using json = nlohmann::json; + +void to_json(json& j, const WsEnvelope& e) { + j = json{{"id", e.id}, {"type", e.type}, {"payload", e.payload}}; +} + +void from_json(const json& j, WsMessage& msg) { + j.at("id").get_to(msg.request_id); + if (j.contains("error_code")) { + j.at("error_code").get_to(msg.error_code); + } + if (j.contains("error")) { + j.at("error").get_to(msg.error); + } + if (j.contains("payload")) { + j.at("payload").get_to(msg.payload); + } +} + +void from_json(const json& j, WsEvent& ev) { + j.at("type").get_to(ev.type); + j.at("ev").get_to(ev.ev); +} + +struct NpMatching2ContextEvent { + OrbisNpMatching2ContextId contextId; + OrbisNpMatching2Event event; + OrbisNpMatching2EventCause cause; + int errorCode; +}; + +std::function npMatching2ContextCallback = nullptr; + +int MatchingContext::Start(OrbisNpMatching2ContextId ctxId, u64 timeout) { + std::unique_lock lk{this->mutex}; + + if (!(EmulatorSettings.IsConnectedToNetwork() && EmulatorSettings.IsPSNSignedIn())) { + NpMatching2ContextEvent ev{.contextId = this->ctxId, + .event = ORBIS_NP_MATCHING2_CONTEXT_EVENT_START_OVER, + .cause = ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ERROR, + .errorCode = ORBIS_NET_ERROR_RESOLVER_ETIMEDOUT}; + npMatching2ContextCallback(&ev); + + return ORBIS_OK; + } + + if (websocket.isOnMessageCallbackRegistered()) { + // make it a proper state machine + return ORBIS_NP_MATCHING2_ERROR_CONTEXT_ALREADY_STARTED; + } + + LOG_DEBUG(ShadNet, "starting context ctxId = {}", this->ctxId); + + this->ctxId = ctxId; + + ix::initNetSystem(); + + websocket.setUrl(SHADNET_WS_URL); + websocket.setExtraHeaders({ + {"X-NP-TITLE-ID", Libraries::Np::NpManager::g_np_title_id.id}, + {"Authorization", std::format("Bearer {}", "BEARER_TOKEN")} // fix with some auth manager + }); + websocket.setPingInterval(10); + + auto timeoutSec = 20; + if (timeout > 10'000'000) { + timeoutSec = timeout / 1'000'000; + } + websocket.setHandshakeTimeout(timeoutSec); + + websocket.setOnMessageCallback([this](const ix::WebSocketMessagePtr& msg) { + switch (msg->type) { + case ix::WebSocketMessageType::Open: { + LOG_DEBUG(ShadNet, "ws connection opened for ctxId = {}", this->ctxId); + connected = true; + NpMatching2ContextEvent ev{.contextId = this->ctxId, + .event = ORBIS_NP_MATCHING2_CONTEXT_EVENT_STARTED, + .cause = ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ACTION, + .errorCode = 0}; + npMatching2ContextCallback(&ev); + break; + } + case ix::WebSocketMessageType::Message: { + if (msg->binary) { + LOG_ERROR(ShadNet, "received binary ws message"); + break; + } + LOG_DEBUG(ShadNet, "text message: {}", msg->str); + try { + this->HandleMessage(msg->str); + } catch (const json::exception& e) { + LOG_ERROR(ShadNet, "json error when handling message: {}", e.what()); + } catch (const std::exception& e) { + LOG_ERROR(ShadNet, "handling message failed: {}", e.what()); + } + break; + } + case ix::WebSocketMessageType::Close: { + LOG_DEBUG(ShadNet, "close message, code = {}, reason = {}", msg->closeInfo.code, + msg->closeInfo.reason); + break; + } + case ix::WebSocketMessageType::Error: { + LOG_DEBUG(ShadNet, "error message, http_status = {}, reason = {}", + msg->errorInfo.http_status, msg->errorInfo.reason); + break; + } + default: { + LOG_DEBUG(ShadNet, "message type {}", magic_enum::enum_name(msg->type)); + break; + } + } + }); + + websocket.start(); + + std::thread([this, timeoutSec]() { + std::this_thread::sleep_for(std::chrono::seconds(timeoutSec)); + if (!this->connected) { + this->websocket.stop(); + this->websocket.setOnMessageCallback(nullptr); + + NpMatching2ContextEvent ev{.contextId = this->ctxId, + .event = ORBIS_NP_MATCHING2_CONTEXT_EVENT_START_OVER, + .cause = ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ACTION, + .errorCode = ORBIS_NP_MATCHING2_ERROR_TIMEDOUT}; + npMatching2ContextCallback(&ev); + } + }).detach(); + + return ORBIS_OK; +} + +int MatchingContext::Stop() { + std::unique_lock lk{this->mutex}; + + if (!websocket.isOnMessageCallbackRegistered()) { + // make it a proper state machine + return ORBIS_NP_MATCHING2_ERROR_CONTEXT_NOT_STARTED; + } + + this->websocket.stop(); + this->websocket.setOnMessageCallback(nullptr); + + NpMatching2ContextEvent ev{.contextId = this->ctxId, + .event = ORBIS_NP_MATCHING2_CONTEXT_EVENT_STOPPED, + .cause = ORBIS_NP_MATCHING2_EVENT_CAUSE_CONTEXT_ACTION, + .errorCode = 0}; + npMatching2ContextCallback(&ev); + + return ORBIS_OK; +} + +int MatchingContext::CreateJoinRoom(const OrbisNpMatching2CreateJoinRoomRequest& req, + const OrbisNpMatching2RequestOptParam* optParam) { + return SendRequest(req, optParam); +} + +int MatchingContext::CreateJoinRoom(const OrbisNpMatching2CreateJoinRoomRequestA& req, + const OrbisNpMatching2RequestOptParam* optParam) { + return SendRequest(req, optParam); +} + +int MatchingContext::JoinRoom(const OrbisNpMatching2JoinRoomRequest& req, + const OrbisNpMatching2RequestOptParam* optParam) { + return SendRequest(req, optParam); +} + +int MatchingContext::LeaveRoom(const OrbisNpMatching2LeaveRoomRequest& req, + const OrbisNpMatching2RequestOptParam* optParam) { + return SendRequest(req, optParam); +} + +int MatchingContext::SearchRoom(const OrbisNpMatching2SearchRoomRequest& req, + const OrbisNpMatching2RequestOptParam* optParam) { + return SendRequest(req, optParam); +} + +int MatchingContext::SignalingGetPingInfo(const OrbisNpMatching2SignalingGetPingInfoRequest& req, + const OrbisNpMatching2RequestOptParam* optParam) { + return SendRequest(req, optParam); +} + +void MatchingContext::SetDefaultRequestOptParam(const OrbisNpMatching2RequestOptParam& optParam) { + std::scoped_lock lk{this->mutex}; + this->optParam = optParam; +} + +// call under lock as it might access internal state +auto MatchingContext::GetRequestCallback( + std::optional requestOptParam) { + OrbisNpMatching2RequestCallback cb = nullptr; + void* arg = nullptr; + if (requestOptParam) { + cb = requestOptParam->callback; + arg = requestOptParam->arg; + } else if (this->optParam) { + cb = this->optParam->callback; + arg = this->optParam->arg; + } + + return [cb, arg](auto ctxId, auto reqId, auto ev, int errorCode, auto data) { + if (cb) { + cb(ctxId, reqId, ev, errorCode, data, arg); + } + }; +} + +template +int MatchingContext::SendRequest(const T& req, const OrbisNpMatching2RequestOptParam* optParam) { + json j = req; + WsEnvelope envelope{this->reqId++, request_tag(req), j.dump()}; + + { + std::scoped_lock lk{this->mutex}; + this->pendingRequests.emplace( + envelope.id, std::make_tuple(request_tag(req), + optParam ? std::make_optional(*optParam) : std::nullopt)); + } + + json e = envelope; + websocket.send(e.dump()); + + return envelope.id; +} + +void MatchingContext::SetContextCallback(OrbisNpMatching2ContextCallback cb, void* userdata) { + npMatching2ContextCallback = [cb, userdata](auto arg) { + cb(arg->contextId, arg->event, arg->cause, arg->errorCode, userdata); + }; +} + +void MatchingContext::SetRoomCallback(OrbisNpMatching2RoomCallback cb, void* userdata) { + this->roomCallback = [cb, userdata](auto ctxId, auto roomId, auto event, const void* data) { + cb(ctxId, roomId, event, data, userdata); + }; +} + +void MatchingContext::SetSignalingCallback(OrbisNpMatching2SignalingCallback cb, void* userdata) { + this->signalingCallback = [cb, userdata](auto ctxId, auto roomId, auto roomMemberId, auto event, + auto errorCode) { + cb(ctxId, roomId, roomMemberId, event, errorCode, userdata); + }; +} + +class Finalizer { + std::function f; + +public: + explicit Finalizer(std::function f) : f(f) {} + ~Finalizer() { + f(); + } +}; + +void MatchingContext::HandleResponse(const WsMessage& response) { + Finalizer f([this, response] { + std::scoped_lock lk{this->mutex}; + this->pendingRequests.erase(response.request_id); + }); + + std::unique_lock lk{this->mutex}; + auto [type, optParam] = this->pendingRequests.at(response.request_id); + auto cb = GetRequestCallback(optParam); + lk.unlock(); + + if (response.error_code) { + LOG_ERROR(ShadNet, "matching request {} failed with {} (code {})", response.request_id, + response.error, response.error_code); + + } else { + LOG_DEBUG(ShadNet, "matching request {} response received", response.request_id); + if (type == request_tag_t()) { + auto resp = response.payload.get(); + auto view = resp.view(); + cb(this->ctxId, response.request_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_CREATE_JOIN_ROOM, + ORBIS_OK, &view); + } else if (type == request_tag_t()) { + auto resp = response.payload.get(); + auto view = resp.view(); + cb(this->ctxId, response.request_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_SEARCH_ROOM, + ORBIS_OK, &view); + } else if (type == request_tag_t()) { + // it's the same response as createjoin + auto resp = response.payload.get(); + auto view = resp.view(); + cb(this->ctxId, response.request_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_JOIN_ROOM, + ORBIS_OK, &view); + } else if (type == request_tag_t()) { + auto resp = response.payload.get(); + cb(this->ctxId, response.request_id, ORBIS_NP_MATCHING2_REQUEST_EVENT_LEAVE_ROOM, + ORBIS_OK, &resp); + } + /// + else { + LOG_ERROR(ShadNet, "unhandled response type: {}", type); + } + } +} + +void MatchingContext::HandleEvent(const WsEvent& event) { + LOG_DEBUG(ShadNet, "handling event {}", event.type); + + if (event.type == "member_joined") { + auto evData = event.ev.get(); + auto view = evData.view(); + this->roomCallback(this->ctxId, evData.roomId, ORBIS_NP_MATCHING2_ROOM_EVENT_MEMBER_JOINED, + &view); + LOG_DEBUG(ShadNet, "room callback called"); + } else if (event.type == "signaling_established") { + auto evData = event.ev.get(); + this->signalingCallback(this->ctxId, evData.roomId, evData.roomMemberId, + ORBIS_NP_MATCHING2_SIGNALING_EVENT_ESTABLISHED, 0); + LOG_DEBUG(ShadNet, "signaling callback called"); + } else { + LOG_ERROR(ShadNet, "unhandled event type: {}", event.type); + } +} + +void MatchingContext::HandleMessage(const std::string& wsMessage) { + auto j = json::parse(wsMessage); + + if (j.contains("id")) { + auto message = j.get(); + HandleResponse(message); + } else { + auto ev = j.get(); + HandleEvent(ev); + } +} + +} // namespace Core::ShadNet diff --git a/src/core/shadnet/matching_context.h b/src/core/shadnet/matching_context.h new file mode 100644 index 00000000000..64b26f00d93 --- /dev/null +++ b/src/core/shadnet/matching_context.h @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "common/singleton.h" +#include "common/types.h" +#include "core/libraries/np/np_matching2.h" +#include "core/libraries/np/np_matching2_requests.h" +#include "nlohmann/json.hpp" + +namespace Core::ShadNet { + +using namespace Libraries::Np::NpMatching2; +using json = nlohmann::json; + +struct Error { + u32 code; + std::string message; +}; + +struct WsEnvelope { + u64 id; + std::string type; + std::string payload; +}; + +struct WsMessage { + u64 request_id; + u32 error_code; + std::string error; + json payload; +}; + +struct WsEvent { + std::string type; + json ev; +}; + +class MatchingContext { + ix::WebSocket websocket; + OrbisNpMatching2ContextId ctxId; + std::mutex mutex; + std::unordered_map>> + pendingRequests; + std::atomic reqId{1}; + std::atomic connected = false; + std::optional optParam; + std::function + roomCallback; + std::function + signalingCallback; + +public: + static void SetContextCallback(OrbisNpMatching2ContextCallback cb, void* userdata); + int Start(OrbisNpMatching2ContextId ctxId, u64 timeout); + int Stop(); + void SetRoomCallback(OrbisNpMatching2RoomCallback cb, void* userdata); + void SetSignalingCallback(OrbisNpMatching2SignalingCallback cb, void* userdata); + + int CreateJoinRoom(const OrbisNpMatching2CreateJoinRoomRequest& req, + const OrbisNpMatching2RequestOptParam* optParam); + int CreateJoinRoom(const OrbisNpMatching2CreateJoinRoomRequestA& req, + const OrbisNpMatching2RequestOptParam* optParam); + int JoinRoom(const OrbisNpMatching2JoinRoomRequest& req, + const OrbisNpMatching2RequestOptParam* optParam); + int LeaveRoom(const OrbisNpMatching2LeaveRoomRequest& req, + const OrbisNpMatching2RequestOptParam* optParam); + int SearchRoom(const OrbisNpMatching2SearchRoomRequest& req, + const OrbisNpMatching2RequestOptParam* optParam); + int SignalingGetPingInfo(const OrbisNpMatching2SignalingGetPingInfoRequest& req, + const OrbisNpMatching2RequestOptParam* optParam); + void SetDefaultRequestOptParam(const OrbisNpMatching2RequestOptParam& optParam); + +private: + auto GetRequestCallback(std::optional requestOptParam); + + void HandleResponse(const WsMessage& response); + void HandleEvent(const WsEvent& event); + void HandleMessage(const std::string& wsResponse); + + template + int SendRequest(const T&, const OrbisNpMatching2RequestOptParam* optParam); +}; + +} // namespace Core::ShadNet \ No newline at end of file diff --git a/src/core/shadnet/matching_json.cpp b/src/core/shadnet/matching_json.cpp new file mode 100644 index 00000000000..196635de04d --- /dev/null +++ b/src/core/shadnet/matching_json.cpp @@ -0,0 +1,558 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "matching_json.h" + +#include "common/config.h" + +#include "cppcodec/base64_rfc4648.hpp" + +namespace nlohmann { +template +struct adl_serializer> { + static void to_json(json& j, const std::span& s) { + if (s.empty()) { + j = nullptr; + } else { + j = json::array(); + for (const auto& v : s) { + j.push_back(v); + } + } + } +}; +} // namespace nlohmann + +namespace Libraries::Rtc { +using json = nlohmann::json; + +void from_json(const json& j, OrbisRtcTick& tick) { + auto t = j.get(); + tick.tick = t; +} + +} // namespace Libraries::Rtc + +namespace Libraries::Np { + +using json = nlohmann::json; + +void to_json(json& j, const OrbisNpOnlineId& npId) { + j = json{std::string{npId.data}}; +} + +void from_json(const json& j, OrbisNpOnlineId& npId) { + auto id = j.get(); + strncpy(npId.data, id.c_str(), sizeof(npId.data)); +} + +NLOHMANN_JSON_SERIALIZE_ENUM(OrbisNpPlatformType, { + {OrbisNpPlatformType::NONE, "NONE"}, + {OrbisNpPlatformType::PS3, "PS3"}, + {OrbisNpPlatformType::VITA, "VITA"}, + {OrbisNpPlatformType::PS4, "PS4"}, + }) + +} // namespace Libraries::Np + +namespace Libraries::Np::NpMatching2 { + +using base64 = cppcodec::base64_rfc4648; +using json = nlohmann::json; + +std::string request_tag(const OrbisNpMatching2SearchRoomRequest&) { + return "search_room"; +} + +std::string request_tag(const OrbisNpMatching2CreateJoinRoomRequest&) { + return "create_join_room"; +} + +std::string request_tag(const OrbisNpMatching2CreateJoinRoomRequestA&) { + return "create_join_room_a"; +} + +std::string request_tag(const OrbisNpMatching2JoinRoomRequest&) { + return "join_room"; +} + +std::string request_tag(const OrbisNpMatching2LeaveRoomRequest&) { + return "leave_room"; +} + +std::string request_tag(const OrbisNpMatching2SignalingGetPingInfoRequest&) { + return "signaling_get_ping_info"; +} + +void to_json(json& j, const OrbisNpMatching2SignalingParam& p) { + j["type"] = p.type; + j["flag"] = p.flag; + j["hubMemberId"] = p.mainMember; +} + +void to_json(json& j, const OrbisNpMatching2BinAttr& attr) { + j["attrId"] = attr.id; + j["data"] = base64::encode(attr.data, attr.dataSize); +} + +void from_json(const json& j, OrbisNpMatching2BinAttrOwned& attr) { + j.at("attrId").get_to(attr.id); + auto data = j.at("data").get(); + attr.data = base64::decode(data); +} + +void to_json(json& j, const OrbisNpMatching2IntAttr& attr) { + j["attrId"] = attr.id; + j["value"] = attr.attr; +} + +void from_json(const json& j, OrbisNpMatching2IntAttr& attr) { + j.at("attrId").get_to(attr.id); + j.at("value").get_to(attr.attr); +} + +void to_json(json& j, const OrbisNpMatching2SessionPassword& pw) { + j = json{{"data", pw.data}}; +} + +void to_json(json& j, const OrbisNpMatching2RoomPassword& pw) { + j = json{{"data", pw.data}}; +} + +void from_json(const json& j, OrbisNpMatching2PresenceOptionData& optData) { + j.at("data").get_to(optData.data); + j.at("len").get_to(optData.len); +} + +void to_json(json& j, const OrbisNpMatching2PresenceOptionData& optData) { + j["data"] = optData.data; + j["len"] = optData.len; +} + +void to_json(json& j, const OrbisNpMatching2GroupLabel& label) { + j = json{{"data", label.data}}; +} + +void from_json(const json& j, OrbisNpMatching2GroupLabel& label) { + j.at("data").get_to(label.data); +} + +void to_json(json& j, const OrbisNpMatching2RoomGroupConfig& config) { + j["slots"] = config.slots; + if (config.hasLabel) { + j["label"] = config.label; + } + j["hasPassword"] = config.hasPassword; +} + +template +void to_json_a(json& j, const OrbisNpMatching2CreateJoinRoomRequest_& req) { + j["maxSlot"] = req.maxSlot; + j["teamId"] = req.teamId; + j["flags"] = req.flags; + j["worldId"] = req.worldId; + j["lobbyId"] = req.lobbyId; + if (req.roomPasswd) { + j["roomPasswd"] = *req.roomPasswd; + } + if (req.passwdSlotMask) { + j["passwdSlotMask"] = *req.passwdSlotMask; + } + if (req.groupConfig && req.groupConfigs > 0) { + j["groupConfig"] = std::span(req.groupConfig, req.groupConfigs); + } + if (req.joinGroupLabel) { + j["joinGroupLabel"] = *req.joinGroupLabel; + } + if (req.allowedUser && req.allowedUsers > 0) { + j["allowedUsers"] = std::span(req.allowedUser, req.allowedUsers); + } + if (req.blockedUser && req.blockedUsers > 0) { + j["blockedUsers"] = std::span(req.blockedUser, req.blockedUsers); + } + if (req.internalBinAttr && req.internalBinAttrs > 0) { + j["roomBinAttrInternal"] = std::span(req.internalBinAttr, req.internalBinAttrs); + } + if (req.externalSearchIntAttr && req.externalSearchIntAttrs > 0) { + j["roomSearchableIntAttrExternal"] = + std::span(req.externalSearchIntAttr, req.externalSearchIntAttrs); + } + if (req.externalSearchBinAttr && req.externalSearchBinAttrs > 0) { + j["roomSearchableBinAttrExternal"] = + std::span(req.externalSearchBinAttr, req.externalSearchBinAttrs); + } + if (req.externalBinAttr && req.externalBinAttrs > 0) { + j["roomBinAttrExternal"] = std::span(req.externalBinAttr, req.externalBinAttrs); + } + if (req.memberInternalBinAttr && req.memberInternalBinAttrs > 0) { + j["roomMemberBinAttrInternal"] = + std::span(req.memberInternalBinAttr, req.memberInternalBinAttrs); + } + if (req.signalingParam) { + j["signaling"] = *req.signalingParam; + } +} + +void to_json(json& j, const OrbisNpMatching2CreateJoinRoomRequest& req) { + to_json_a(j, req); +} + +void to_json(json& j, const OrbisNpMatching2CreateJoinRoomRequestA& req) { + to_json_a(j, req); +} + +void to_json(json& j, const OrbisNpMatching2RangeFilter& f) { + j["startIndex"] = f.start; + j["max"] = f.max; +} + +void to_json(json& j, const OrbisNpMatching2IntFilter& f) { + j["operator"] = f.op; + j["attr"] = f.attr; +} + +void to_json(json& j, const OrbisNpMatching2BinFilter& f) { + j["operator"] = f.op; + j["attr"] = f.attr; +} + +void to_json(json& j, const OrbisNpMatching2JoinRoomRequest& req) { + j["roomId"] = req.roomId; + if (req.roomPasswd) { + j["roomPasswd"] = *req.roomPasswd; + } + if (req.joinGroupLabel) { + j["joinGroupLabel"] = *req.joinGroupLabel; + } + if (req.roomMemberBinInternalAttr && req.roomMemberBinInternalAttrNum > 0) { + j["roomMemberBinAttrInternal"] = + std::span(req.roomMemberBinInternalAttr, req.roomMemberBinInternalAttrNum); + } + j["optData"] = req.optData; + j["teamId"] = req.teamId; + j["flags"] = req.flags; + if (req.blockedUser && req.blockedUsers > 0) { + j["blockedUser"] = std::span(req.blockedUser, req.blockedUsers); + } +} + +void to_json(json& j, const OrbisNpMatching2LeaveRoomRequest& req) { + j["roomId"] = req.roomId; + j["optData"] = req.optData; +} + +void to_json(json& j, const OrbisNpMatching2SearchRoomRequest& req) { + j["option"] = req.option; + j["worldId"] = req.worldId; + j["lobbyId"] = req.lobbyId; + j["rangeFilter"] = req.rangeFilter; + j["flagFilter"] = req.flagFilter; + j["flagAttr"] = req.flagAttrs; + if (req.intFilter && req.intFilters > 0) { + j["intFilter"] = std::span(req.intFilter, req.intFilters); + } + if (req.binFilter && req.binFilters > 0) { + j["binFilter"] = std::span(req.binFilter, req.binFilters); + } + if (req.attr && req.attrs > 0) { + j["attrIds"] = std::span(req.attr, req.attrs); + } +} + +void to_json(json& j, const OrbisNpMatching2SignalingGetPingInfoRequest& req) { + j["roomId"] = req.roomId; +} + +void from_json(const json& j, OrbisNpMatching2RoomGroup& group) { + j.at("id").get_to(group.id); + j.at("hasPasswd").get_to(group.hasPasswd); + j.at("hasLabel").get_to(group.hasLabel); + j.at("label").get_to(group.label); + j.at("slots").get_to(group.slots); + j.at("groupMembers").get_to(group.groupMembers); +} + +void from_json(const json& j, OrbisNpMatching2RoomGroupInfo& group) { + j.at("id").get_to(group.id); + j.at("hasPasswd").get_to(group.hasPasswd); + j.at("slots").get_to(group.slots); + j.at("groupMembers").get_to(group.groupMembers); +} + +void from_json(const json& j, OrbisNpMatching2RoomBinAttrInternalOwned& res) { + j.at("lastUpdate").get_to(res.lastUpdate); + j.at("memberId").get_to(res.memberId); + j.at("binAttr").get_to(res.binAttr); +} + +void from_json(const json& j, OrbisNpMatching2RoomMemberBinAttrInternalOwned& res) { + j.at("lastUpdate").get_to(res.lastUpdate); + j.at("binAttr").get_to(res.binAttr); +} + +void from_json(const json& j, OrbisNpMatching2RoomDataInternalOwned& res) { + j.at("publicSlotNum").get_to(res.publicSlots); + j.at("privateSlotNum").get_to(res.privateSlots); + j.at("openPublicSlotNum").get_to(res.openPublicSlots); + j.at("openPrivateSlotNum").get_to(res.openPrivateSlots); + j.at("maxSlot").get_to(res.maxSlot); + j.at("serverId").get_to(res.serverId); + j.at("worldId").get_to(res.worldId); + j.at("lobbyId").get_to(res.lobbyId); + j.at("roomId").get_to(res.roomId); + j.at("passwordSlotMask").get_to(res.passwdSlotMask); + j.at("joinedSlotMask").get_to(res.joinedSlotMask); + if (j.contains("roomGroup")) { + res.roomGroup = j.at("roomGroup").get>(); + } + j.at("flags").get_to(res.flags); + if (j.contains("roomBinAttrInternal")) { + j.at("roomBinAttrInternal").get_to(res.internalBinAttr); + } +} + +void from_json(const json& j, OrbisNpPeerAddressOwned& addr) { + auto npId = j.at("onlineId").get(); + strncpy(addr.npId.handle.data, npId.c_str(), sizeof(addr.npId.handle.data)); + j.at("platform").get_to(addr.platform); +} + +void from_json(const json& j, OrbisNpMatching2RoomMemberDataInternalOwned& res) { + j.at("joinDate").get_to(res.joinDateTicks); + j.at("user").get_to(res.user); + j.at("onlineId").get_to(res.onlineId); + j.at("memberId").get_to(res.memberId); + j.at("teamId").get_to(res.teamId); + j.at("natType").get_to(res.natType); + j.at("flags").get_to(res.flags); + if (j.contains("roomGroup")) { + res.roomGroup = j.at("roomGroup").get(); + } + if (j.contains("roomMemberBinAttrInternal")) { + j.at("roomMemberBinAttrInternal").get_to(res.roomMemberInternalBinAttr); + } +} + +void from_json(const json& j, OrbisNpMatching2CreateJoinRoomResponseOwned& res) { + j.at("roomDataInternal").get_to(res.roomData); + j.at("members").get_to(res.members); +} + +OrbisNpMatching2RoomDataInternal OrbisNpMatching2RoomDataInternalOwned::view() { + for (auto& attr : this->internalBinAttr) { + OrbisNpMatching2RoomBinAttrInternal a{ + attr.lastUpdate, + attr.memberId, + {}, + {attr.binAttr.id, {}, attr.binAttr.data.data(), attr.binAttr.data.size()}}; + this->internalRoomBinAttrView.push_back(a); + } + return { + .publicSlots = this->publicSlots, + .privateSlots = this->privateSlots, + .openPublicSlots = this->openPublicSlots, + .openPrivateSlots = this->openPrivateSlots, + .maxSlot = this->maxSlot, + .serverId = this->serverId, + .worldId = this->worldId, + .lobbyId = this->lobbyId, + .roomId = this->roomId, + .passwdSlotMask = this->passwdSlotMask, + .joinedSlotMask = this->joinedSlotMask, + .roomGroup = this->roomGroup.data(), + .roomGroups = this->roomGroup.size(), + .flags = this->flags, + .roomBinAttrInternal = this->internalRoomBinAttrView.data(), + .roomBinAttrInternalNum = this->internalRoomBinAttrView.size(), + }; +} + +OrbisNpMatching2CreateJoinRoomResponse OrbisNpMatching2CreateJoinRoomResponseOwned::view() { + OrbisNpMatching2RoomMemberDataInternal* me = nullptr; + OrbisNpMatching2RoomMemberDataInternal* owner = nullptr; + this->roomDataView = roomData.view(); + this->membersView.reserve(this->members.size()); + + for (auto& member : this->members) { + std::vector attrVec; + for (auto& attr : member.roomMemberInternalBinAttr) { + OrbisNpMatching2RoomMemberBinAttrInternal a{ + attr.lastUpdate, + {attr.binAttr.id, {}, attr.binAttr.data.data(), attr.binAttr.data.size()}}; + attrVec.push_back(a); + } + this->membersBinAttrs.push_back(attrVec); + + OrbisNpMatching2RoomMemberDataInternal m = { + .next = nullptr, + .joinDateTicks = member.joinDateTicks, + .user = {&member.user.npId, member.user.platform}, + .onlineId = member.onlineId, + .memberId = member.memberId, + .teamId = member.teamId, + .natType = member.natType, + .flags = member.flags, + .roomGroup = member.roomGroup ? &*member.roomGroup : nullptr, + .roomMemberInternalBinAttr = this->membersBinAttrs.back().data(), + .roomMemberInternalBinAttrs = this->membersBinAttrs.back().size(), + }; + this->membersView.push_back(m); + + if ((member.flags & ORBIS_NP_MATCHING2_ROOM_MEMBER_FLAG_ATTR_OWNER) != 0) { + owner = &this->membersView.back(); + } + if (strncmp(member.onlineId.data, Config::getUserName().c_str(), + sizeof(member.onlineId.data))) { + me = &this->membersView.back(); + } + } + + OrbisNpMatching2RoomMemberDataInternal* next = nullptr; + for (auto& x : this->membersView | std::views::reverse) { + x.next = next; + next = &x; + } + + return {&this->roomDataView, + { + .members = this->membersView.data(), + .membersNum = this->membersView.size(), + .me = me, + .owner = owner, + }}; +} + +void from_json(const json& j, OrbisNpMatching2Range& range) { + j.at("startIndex").get_to(range.start); + j.at("total").get_to(range.total); + j.at("resultCount").get_to(range.results); +} + +void from_json(const json& j, OrbisNpMatching2RoomDataExternalOwned& data) { + j.at("maxSlot").get_to(data.maxSlot); + j.at("currentMemberNum").get_to(data.curMembers); + j.at("flagAttr").get_to(data.flags); + j.at("serverId").get_to(data.serverId); + j.at("worldId").get_to(data.worldId); + j.at("lobbyId").get_to(data.lobbyId); + j.at("roomId").get_to(data.roomId); + if (j.contains("passwdSlotMask")) { + j.at("passwdSlotMask").get_to(data.passwdSlotMask); + } + j.at("joinedSlotMask").get_to(data.joinedSlotMask); + j.at("publicSlotNum").get_to(data.publicSlots); + j.at("privateSlotNum").get_to(data.privateSlots); + j.at("openPublicSlotNum").get_to(data.openPublicSlots); + j.at("openPrivateSlotNum").get_to(data.openPrivateSlots); + j.at("owner").get_to(data.owner); + j.at("ownerOnlineId").get_to(data.ownerOnlineId); + if (j.contains("roomGroup")) { + j.at("roomGroup").get_to(data.roomGroup); + } + j.at("roomSearchableIntAttrExternal").get_to(data.externalSearchIntAttr); + j.at("roomSearchableBinAttrExternal").get_to(data.externalSearchBinAttr); + j.at("roomBinAttrExternal").get_to(data.externalBinAttr); +} + +void from_json(const json& j, OrbisNpMatching2SearchRoomResponseOwned& res) { + j.at("range").get_to(res.range); + j.at("rooms").get_to(res.roomDataExt); +} + +OrbisNpMatching2RoomDataExternal OrbisNpMatching2RoomDataExternalOwned::view() { + for (auto& attr : this->externalSearchBinAttr) { + OrbisNpMatching2BinAttr a{attr.id, {}, attr.data.data(), attr.data.size()}; + this->externalSearchBinAttrView.push_back(a); + } + + for (auto& attr : this->externalBinAttr) { + OrbisNpMatching2BinAttr a{attr.id, {}, attr.data.data(), attr.data.size()}; + this->externalBinAttrView.push_back(a); + } + + return { + .next = nullptr, + .maxSlot = this->maxSlot, + .curMembers = this->curMembers, + .flags = this->flags, + .serverId = this->serverId, + .worldId = this->worldId, + .lobbyId = this->lobbyId, + .roomId = this->roomId, + .passwdSlotMask = this->passwdSlotMask, + .joinedSlotMask = this->joinedSlotMask, + .publicSlots = this->publicSlots, + .privateSlots = this->privateSlots, + .openPublicSlots = this->openPublicSlots, + .openPrivateSlots = this->openPrivateSlots, + .unk = 0xCAFEDEAD, + .roomGroup = this->roomGroup.data(), + .roomGroups = this->roomGroup.size(), + .externalSearchIntAttr = this->externalSearchIntAttr.data(), + .externalSearchIntAttrs = this->externalSearchIntAttr.size(), + .externalSearchBinAttr = this->externalSearchBinAttrView.data(), + .externalSearchBinAttrs = this->externalSearchBinAttrView.size(), + .externalBinAttr = this->externalBinAttrView.data(), + .externalBinAttrs = this->externalBinAttrView.size(), + }; +} + +OrbisNpMatching2SearchRoomResponse OrbisNpMatching2SearchRoomResponseOwned::view() { + for (auto& roomData : this->roomDataExt) { + auto view = roomData.view(); + this->roomDataExtView.push_back(view); + } + + OrbisNpMatching2RoomDataExternal* next = nullptr; + for (auto& x : this->roomDataExtView | std::views::reverse) { + x.next = next; + next = &x; + } + + return {this->range, this->roomDataExtView.data()}; +} + +OrbisNpMatching2RoomMemberUpdateInfo OrbisNpMatching2RoomMemberUpdateInfoOwned::view() { + for (auto& attr : roomMemberDataInternal.roomMemberInternalBinAttr) { + OrbisNpMatching2RoomMemberBinAttrInternal a{ + attr.lastUpdate, + {attr.binAttr.id, {}, attr.binAttr.data.data(), attr.binAttr.data.size()}}; + this->memberBinAttrs.push_back(a); + } + + OrbisNpMatching2RoomMemberDataInternal m = { + .next = nullptr, + .joinDateTicks = roomMemberDataInternal.joinDateTicks, + .user = {&roomMemberDataInternal.user.npId, roomMemberDataInternal.user.platform}, + .onlineId = roomMemberDataInternal.onlineId, + .memberId = roomMemberDataInternal.memberId, + .teamId = roomMemberDataInternal.teamId, + .natType = roomMemberDataInternal.natType, + .flags = roomMemberDataInternal.flags, + .roomGroup = + roomMemberDataInternal.roomGroup ? &*roomMemberDataInternal.roomGroup : nullptr, + .roomMemberInternalBinAttr = this->memberBinAttrs.data(), + .roomMemberInternalBinAttrs = this->memberBinAttrs.size(), + }; + this->roomMemberDataInternalView = m; + + return {&this->roomMemberDataInternalView, this->eventCause, {}, this->optData}; +} + +void from_json(const json& j, OrbisNpMatching2RoomMemberUpdateInfoOwned& res) { + j.at("roomMemberDataInternal").get_to(res.roomMemberDataInternal); + j.at("eventCause").get_to(res.eventCause); + j.at("optData").get_to(res.optData); + j.at("roomId").get_to(res.roomId); +} + +void from_json(const json& j, SignalingEstablishedInfo& res) { + j.at("roomMemberId").get_to(res.roomMemberId); + j.at("roomId").get_to(res.roomId); +} + +void from_json(const json& j, OrbisNpMatching2LeaveRoomResponse& res) { + j.at("roomId").get_to(res.roomId); +} + +} // namespace Libraries::Np::NpMatching2 diff --git a/src/core/shadnet/matching_json.h b/src/core/shadnet/matching_json.h new file mode 100644 index 00000000000..41dfc2010b9 --- /dev/null +++ b/src/core/shadnet/matching_json.h @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/libraries/np/np_matching2_requests.h" +#include "nlohmann/json.hpp" + +namespace Libraries::Np::NpMatching2 { + +using json = nlohmann::json; + +struct OrbisNpPeerAddressOwned { + OrbisNpId npId; + OrbisNpPlatformType platform; +}; + +struct OrbisNpMatching2BinAttrOwned { + OrbisNpMatching2AttributeId id; + std::vector data; +}; + +struct OrbisNpMatching2RoomBinAttrInternalOwned { + Libraries::Rtc::OrbisRtcTick lastUpdate; + OrbisNpMatching2RoomMemberId memberId; + u8 pad[6]; + OrbisNpMatching2BinAttrOwned binAttr; +}; + +struct OrbisNpMatching2RoomMemberBinAttrInternalOwned { + Libraries::Rtc::OrbisRtcTick lastUpdate; + OrbisNpMatching2BinAttrOwned binAttr; +}; + +struct OrbisNpMatching2RoomMemberDataInternalOwned { + u64 joinDateTicks; + OrbisNpPeerAddressOwned user; + Libraries::Np::OrbisNpOnlineId onlineId; + u8 pad[4]; + OrbisNpMatching2RoomMemberId memberId; + OrbisNpMatching2TeamId teamId; + OrbisNpMatching2NatType natType; + OrbisNpMatching2Flags flags; + std::optional roomGroup; + std::vector roomMemberInternalBinAttr; +}; + +struct OrbisNpMatching2RoomDataInternalOwned { + u16 publicSlots; + u16 privateSlots; + u16 openPublicSlots; + u16 openPrivateSlots; + u16 maxSlot; + OrbisNpMatching2ServerId serverId; + OrbisNpMatching2WorldId worldId; + OrbisNpMatching2LobbyId lobbyId; + OrbisNpMatching2RoomId roomId; + u64 passwdSlotMask; + u64 joinedSlotMask; + std::vector roomGroup; + OrbisNpMatching2Flags flags; + u8 pad[4]; + std::vector internalBinAttr; + + std::vector internalRoomBinAttrView; + OrbisNpMatching2RoomDataInternal view(); +}; + +struct OrbisNpMatching2CreateJoinRoomResponseOwned { + OrbisNpMatching2RoomDataInternalOwned roomData; + std::vector members; + + OrbisNpMatching2RoomDataInternal roomDataView; + std::vector membersView; + std::vector> membersBinAttrs; + OrbisNpMatching2CreateJoinRoomResponse view(); +}; + +struct OrbisNpMatching2RoomDataExternalOwned { + u16 maxSlot; + u16 curMembers; + OrbisNpMatching2Flags flags; + OrbisNpMatching2ServerId serverId; + OrbisNpMatching2WorldId worldId; + OrbisNpMatching2LobbyId lobbyId; + OrbisNpMatching2RoomId roomId; + u64 passwdSlotMask; + u64 joinedSlotMask; + u16 publicSlots; + u16 privateSlots; + u16 openPublicSlots; + u16 openPrivateSlots; + OrbisNpPeerAddressOwned owner; + OrbisNpOnlineId ownerOnlineId; + std::vector roomGroup; + std::vector externalSearchIntAttr; + std::vector externalSearchBinAttr; + std::vector externalBinAttr; + + std::vector externalSearchBinAttrView; + std::vector externalBinAttrView; + + OrbisNpMatching2RoomDataExternal view(); +}; + +struct OrbisNpMatching2SearchRoomResponseOwned { + OrbisNpMatching2Range range; + std::vector roomDataExt; + + std::vector roomDataExtView; + OrbisNpMatching2SearchRoomResponse view(); +}; + +struct OrbisNpMatching2RoomMemberUpdateInfoOwned { + OrbisNpMatching2RoomMemberDataInternalOwned roomMemberDataInternal; + OrbisNpMatching2EventCause eventCause; + u8 pad[7]; + OrbisNpMatching2PresenceOptionData optData; + OrbisNpMatching2RoomId roomId; + + std::vector memberBinAttrs; + OrbisNpMatching2RoomMemberDataInternal roomMemberDataInternalView; + + OrbisNpMatching2RoomMemberUpdateInfo view(); +}; + +struct SignalingEstablishedInfo { + OrbisNpMatching2RoomId roomId; + OrbisNpMatching2RoomMemberId roomMemberId; +}; + +std::string request_tag(const OrbisNpMatching2CreateJoinRoomRequest&); +std::string request_tag(const OrbisNpMatching2CreateJoinRoomRequestA&); +std::string request_tag(const OrbisNpMatching2JoinRoomRequest&); +std::string request_tag(const OrbisNpMatching2LeaveRoomRequest&); +std::string request_tag(const OrbisNpMatching2SearchRoomRequest&); +std::string request_tag(const OrbisNpMatching2SignalingGetPingInfoRequest&); + +template +std::string request_tag_t() { + T t; + return request_tag(t); +} + +void to_json(json& j, const OrbisNpMatching2CreateJoinRoomRequest& req); +void to_json(json& j, const OrbisNpMatching2CreateJoinRoomRequestA& req); +void to_json(json& j, const OrbisNpMatching2JoinRoomRequest& req); +void to_json(json& j, const OrbisNpMatching2LeaveRoomRequest& req); +void to_json(json& j, const OrbisNpMatching2SearchRoomRequest& req); +void to_json(json& j, const OrbisNpMatching2SignalingGetPingInfoRequest& req); + +void from_json(const json& j, OrbisNpMatching2CreateJoinRoomResponseOwned& res); +void from_json(const json& j, OrbisNpMatching2LeaveRoomResponse& res); +void from_json(const json& j, OrbisNpMatching2SearchRoomResponseOwned& res); +void from_json(const json& j, OrbisNpMatching2RoomMemberUpdateInfoOwned& res); +void from_json(const json& j, SignalingEstablishedInfo& res); + +} // namespace Libraries::Np::NpMatching2