Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions core/libs/prx/libSceNpMatching2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
add_library(libSceNpMatching2 SHARED EXCLUDE_FROM_ALL Export.cpp)
target_include_directories(libSceNpMatching2 PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceNpMatching2)

if(BUILD_TESTING)
add_test_executable(guest_np_matching2_offline_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpMatching2Offline.cpp)
target_include_directories(guest_np_matching2_offline_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_np_matching2_offline_tests PRIVATE libSceNpMatching2)
configure_windows_unwind(guest_np_matching2_offline_tests)
add_test(NAME guest_np_matching2_offline COMMAND guest_np_matching2_offline_tests)
set_tests_properties(guest_np_matching2_offline PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_np_matching2_offline PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNpMatching2>")
endif()
endif()
103 changes: 77 additions & 26 deletions core/libs/prx/libSceNpMatching2/Export.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
#include <cstddef>
#include <cstdint>
#include <mutex>
#include <unordered_set>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"

static constexpr int SCE_NP_MATCHING2_ERROR_ALREADY_INITIALIZED = static_cast<int>(0x80550C02);
static constexpr int SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED = static_cast<int>(0x80550C03);
static constexpr int SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT = static_cast<int>(0x80550C0A);
static constexpr int SCE_NP_MATCHING2_ERROR_CONTEXT_MAX = static_cast<int>(0x80550C04);
static constexpr int SCE_NP_MATCHING2_ERROR_CONTEXT_NOT_STARTED = static_cast<int>(0x80550C08);
static constexpr int SCE_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID = static_cast<int>(0x80550C0B);
static constexpr int SCE_NET_ERROR_RESOLVER_ETIMEDOUT = static_cast<int>(0x804101E2);
static constexpr int MAX_CONTEXTS = 10;
static constexpr std::uint64_t CREATE_CONTEXT_PARAMETER_SIZE = 0x28;

namespace {

Expand All @@ -22,35 +29,67 @@ struct NpMatching2InitializeParameter {
static_assert(offsetof(NpMatching2InitializeParameter, size) == 0x20);
static_assert(sizeof(NpMatching2InitializeParameter) == 0x30);

struct NpMatching2CreateContextParameter {
const void* npId;
const void* npCommunicationId;
const void* npPassphrase;
std::uint32_t serviceLabel;
std::uint64_t size;
};
static_assert(sizeof(NpMatching2CreateContextParameter) == CREATE_CONTEXT_PARAMETER_SIZE);

std::mutex matching2Mutex;
bool matching2Initialized = false;
std::unordered_set<std::uint16_t> contexts;
int nextContextId = 1;

int CheckContext(std::uint16_t ctxId) {
if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED;
return contexts.contains(ctxId) ? 0 : SCE_NP_MATCHING2_ERROR_INVALID_CONTEXT_ID;
}

}

extern "C" {

int APS5_VABI sceNpMatching2ContextStart() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpMatching2ContextStart(std::uint16_t ctxId, std::uint64_t timeout) {
(void)ctxId;
(void)timeout;
std::lock_guard lock(matching2Mutex);
if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED;
return SCE_NET_ERROR_RESOLVER_ETIMEDOUT;
}

int APS5_VABI sceNpMatching2ContextStop() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpMatching2ContextStop(std::uint16_t ctxId) {
std::lock_guard lock(matching2Mutex);
if (const int error = CheckContext(ctxId)) return error;
return SCE_NP_MATCHING2_ERROR_CONTEXT_NOT_STARTED;
}

int APS5_VABI sceNpMatching2CreateContext() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpMatching2CreateContext(const NpMatching2CreateContextParameter* param, std::uint16_t* ctxId) {
std::lock_guard lock(matching2Mutex);
if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED;
if (!param || param->size != CREATE_CONTEXT_PARAMETER_SIZE || !ctxId) return SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
for (int tried = 0; tried < MAX_CONTEXTS; ++tried) {
const auto candidate = static_cast<std::uint16_t>((nextContextId - 1 + tried) % MAX_CONTEXTS + 1);
if (contexts.contains(candidate)) continue;
contexts.insert(candidate);
nextContextId = candidate % MAX_CONTEXTS + 1;
*ctxId = candidate;
return 0;
}
return SCE_NP_MATCHING2_ERROR_CONTEXT_MAX;
}

int APS5_VABI sceNpMatching2CreateJoinRoom() {
NotImplemented_nid_no_patch(__func__);
return 0;
}

int APS5_VABI sceNpMatching2DestroyContext() {
NotImplemented_nid_no_patch(__func__);
int APS5_VABI sceNpMatching2DestroyContext(std::uint16_t ctxId) {
std::lock_guard lock(matching2Mutex);
if (const int error = CheckContext(ctxId)) return error;
contexts.erase(ctxId);
return 0;
}

Expand Down Expand Up @@ -107,24 +146,32 @@ int APS5_VABI sceNpMatching2LeaveRoom() {
return 0;
}

int APS5_VABI sceNpMatching2RegisterContextCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpMatching2RegisterContextCallback(void* callback, void* userdata) {
(void)callback;
(void)userdata;
std::lock_guard lock(matching2Mutex);
return matching2Initialized ? 0 : SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED;
}

int APS5_VABI sceNpMatching2RegisterRoomEventCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpMatching2RegisterRoomEventCallback(std::uint16_t ctxId, void* callback, void* userdata) {
(void)callback;
(void)userdata;
std::lock_guard lock(matching2Mutex);
return CheckContext(ctxId);
}

int APS5_VABI sceNpMatching2RegisterRoomMessageCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpMatching2RegisterRoomMessageCallback(std::uint16_t ctxId, void* callback, void* userdata) {
(void)callback;
(void)userdata;
std::lock_guard lock(matching2Mutex);
return CheckContext(ctxId);
}

int APS5_VABI sceNpMatching2RegisterSignalingCallback() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpMatching2RegisterSignalingCallback(std::uint16_t ctxId, void* callback, void* userdata) {
(void)callback;
(void)userdata;
std::lock_guard lock(matching2Mutex);
return CheckContext(ctxId);
}

int APS5_VABI sceNpMatching2SearchRoom() {
Expand All @@ -142,9 +189,11 @@ int APS5_VABI sceNpMatching2SendRoomMessage() {
return 0;
}

int APS5_VABI sceNpMatching2SetDefaultRequestOptParam() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceNpMatching2SetDefaultRequestOptParam(std::uint16_t ctxId, const void* requestOpt) {
std::lock_guard lock(matching2Mutex);
if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED;
if (!requestOpt) return SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT;
return CheckContext(ctxId);
}

int APS5_VABI sceNpMatching2SetRoomDataExternal() {
Expand Down Expand Up @@ -176,6 +225,8 @@ int APS5_VABI sceNpMatching2Terminate(void) {
std::lock_guard lock(matching2Mutex);
if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED;
matching2Initialized = false;
contexts.clear();
nextContextId = 1;
return 0;
}

Expand Down
119 changes: 119 additions & 0 deletions core/libs/tests/GuestNpMatching2Offline.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#include "SceTypes.hpp"
#include <cstdint>
#include <cstdio>

struct InitializeParameter {
std::uint64_t poolSize;
std::uint64_t reserved;
std::int32_t priority;
std::uint8_t padding[4];
std::uint64_t stackSize;
std::uint64_t size;
std::uint64_t sslPoolSize;
};

struct CreateContextParameter {
const void* npId;
const void* npCommunicationId;
const void* npPassphrase;
std::uint32_t serviceLabel;
std::uint64_t size;
};

extern "C" {
int APS5_VABI sceNpMatching2Initialize(const InitializeParameter* param);
int APS5_VABI sceNpMatching2Terminate();
int APS5_VABI sceNpMatching2CreateContext(const CreateContextParameter* param, std::uint16_t* ctxId);
int APS5_VABI sceNpMatching2DestroyContext(std::uint16_t ctxId);
int APS5_VABI sceNpMatching2ContextStart(std::uint16_t ctxId, std::uint64_t timeout);
int APS5_VABI sceNpMatching2ContextStop(std::uint16_t ctxId);
int APS5_VABI sceNpMatching2RegisterContextCallback(void* callback, void* userdata);
int APS5_VABI sceNpMatching2RegisterRoomEventCallback(std::uint16_t ctxId, void* callback, void* userdata);
int APS5_VABI sceNpMatching2RegisterRoomMessageCallback(std::uint16_t ctxId, void* callback, void* userdata);
int APS5_VABI sceNpMatching2RegisterSignalingCallback(std::uint16_t ctxId, void* callback, void* userdata);
int APS5_VABI sceNpMatching2SetDefaultRequestOptParam(std::uint16_t ctxId, const void* requestOpt);
}

namespace {

constexpr int ResolverTimedOut = static_cast<int>(0x804101E2u);
constexpr int NotInitialized = static_cast<int>(0x80550C03u);
constexpr int ContextMax = static_cast<int>(0x80550C04u);
constexpr int ContextNotStarted = static_cast<int>(0x80550C08u);
constexpr int InvalidArgument = static_cast<int>(0x80550C0Au);
constexpr int InvalidContextId = static_cast<int>(0x80550C0Bu);

int failures = 0;

void Require(bool value, const char* name, const char* what) {
if (value) return;
std::fprintf(stderr, "%s: %s\n", name, what);
++failures;
}

}

int main() {
InitializeParameter init{};
CreateContextParameter create{};
create.size = 0x28;
unsigned char requestOpt[24] = {};
std::uint16_t context = 77;

Require(sceNpMatching2CreateContext(nullptr, nullptr) == NotInitialized, "sceNpMatching2CreateContext", "a call before initialization did not return NOT_INITIALIZED first");
Require(sceNpMatching2DestroyContext(1) == NotInitialized, "sceNpMatching2DestroyContext", "a call before initialization did not return NOT_INITIALIZED");
Require(sceNpMatching2ContextStart(1, 0) == NotInitialized, "sceNpMatching2ContextStart", "a call before initialization did not return NOT_INITIALIZED");
Require(sceNpMatching2ContextStop(1) == NotInitialized, "sceNpMatching2ContextStop", "a call before initialization did not return NOT_INITIALIZED");
Require(sceNpMatching2RegisterContextCallback(nullptr, nullptr) == NotInitialized, "sceNpMatching2RegisterContextCallback", "a call before initialization did not return NOT_INITIALIZED");
Require(sceNpMatching2RegisterRoomEventCallback(1, nullptr, nullptr) == NotInitialized, "sceNpMatching2RegisterRoomEventCallback", "a call before initialization did not return NOT_INITIALIZED");
Require(sceNpMatching2RegisterRoomMessageCallback(1, nullptr, nullptr) == NotInitialized, "sceNpMatching2RegisterRoomMessageCallback", "a call before initialization did not return NOT_INITIALIZED");
Require(sceNpMatching2RegisterSignalingCallback(1, nullptr, nullptr) == NotInitialized, "sceNpMatching2RegisterSignalingCallback", "a call before initialization did not return NOT_INITIALIZED");
Require(sceNpMatching2SetDefaultRequestOptParam(1, nullptr) == NotInitialized, "sceNpMatching2SetDefaultRequestOptParam", "a call before initialization did not return NOT_INITIALIZED first");

init.size = 0x30;
Require(sceNpMatching2Initialize(&init) == 0, "sceNpMatching2Initialize", "initialization failed");

Require(sceNpMatching2CreateContext(nullptr, &context) == InvalidArgument, "sceNpMatching2CreateContext", "a null parameter was taken");
create.size = 0x30;
Require(sceNpMatching2CreateContext(&create, &context) == InvalidArgument, "sceNpMatching2CreateContext", "parameter size 0x30 was taken");
create.size = 0x28;
Require(sceNpMatching2CreateContext(&create, nullptr) == InvalidArgument, "sceNpMatching2CreateContext", "a null output was taken");
Require(context == 77, "sceNpMatching2CreateContext", "a refused call wrote the context id");
Require(sceNpMatching2CreateContext(&create, &context) == 0 && context == 1, "sceNpMatching2CreateContext", "the first context is not id 1");
Require(sceNpMatching2DestroyContext(1) == 0, "sceNpMatching2DestroyContext", "destroying context 1 failed");
std::uint16_t created = 0;
for (std::uint16_t expected = 2; expected <= 10; ++expected) {
Require(sceNpMatching2CreateContext(&create, &created) == 0 && created == expected, "sceNpMatching2CreateContext", "a context id was reused before the ids up to 10 were handed out");
}
Require(sceNpMatching2CreateContext(&create, &created) == 0 && created == 1, "sceNpMatching2CreateContext", "after id 10 the allocation did not wrap to the free id 1");
created = 77;
Require(sceNpMatching2CreateContext(&create, &created) == ContextMax && created == 77, "sceNpMatching2CreateContext", "an eleventh context did not return CONTEXT_MAX");
Require(sceNpMatching2DestroyContext(11) == InvalidContextId, "sceNpMatching2DestroyContext", "an unknown context was destroyed");

Require(sceNpMatching2ContextStart(1, 0) == ResolverTimedOut, "sceNpMatching2ContextStart", "starting a context did not return the offline resolver timeout");
Require(sceNpMatching2ContextStart(99, 0) == ResolverTimedOut, "sceNpMatching2ContextStart", "the offline answer did not come before the context check");
Require(sceNpMatching2ContextStop(1) == ContextNotStarted, "sceNpMatching2ContextStop", "a context that never started did not return CONTEXT_NOT_STARTED");
Require(sceNpMatching2ContextStop(99) == InvalidContextId, "sceNpMatching2ContextStop", "an unknown context was taken");

Require(sceNpMatching2RegisterContextCallback(nullptr, nullptr) == 0, "sceNpMatching2RegisterContextCallback", "registering the context callback failed");
Require(sceNpMatching2RegisterRoomEventCallback(99, nullptr, nullptr) == InvalidContextId, "sceNpMatching2RegisterRoomEventCallback", "an unknown context was taken");
Require(sceNpMatching2RegisterRoomEventCallback(1, nullptr, nullptr) == 0, "sceNpMatching2RegisterRoomEventCallback", "registering failed");
Require(sceNpMatching2RegisterRoomMessageCallback(99, nullptr, nullptr) == InvalidContextId, "sceNpMatching2RegisterRoomMessageCallback", "an unknown context was taken");
Require(sceNpMatching2RegisterRoomMessageCallback(1, nullptr, nullptr) == 0, "sceNpMatching2RegisterRoomMessageCallback", "registering failed");
Require(sceNpMatching2RegisterSignalingCallback(99, nullptr, nullptr) == InvalidContextId, "sceNpMatching2RegisterSignalingCallback", "an unknown context was taken");
Require(sceNpMatching2RegisterSignalingCallback(1, nullptr, nullptr) == 0, "sceNpMatching2RegisterSignalingCallback", "registering failed");
Require(sceNpMatching2SetDefaultRequestOptParam(99, nullptr) == InvalidArgument, "sceNpMatching2SetDefaultRequestOptParam", "a null option was not refused before the context");
Require(sceNpMatching2SetDefaultRequestOptParam(99, requestOpt) == InvalidContextId, "sceNpMatching2SetDefaultRequestOptParam", "an unknown context was taken");
Require(sceNpMatching2SetDefaultRequestOptParam(1, requestOpt) == 0, "sceNpMatching2SetDefaultRequestOptParam", "setting the default option failed");

Require(sceNpMatching2Terminate() == 0, "sceNpMatching2Terminate", "termination failed");
Require(sceNpMatching2DestroyContext(1) == NotInitialized, "sceNpMatching2DestroyContext", "a context was destroyed after termination");
Require(sceNpMatching2Initialize(&init) == 0, "sceNpMatching2Initialize", "initializing again failed");
Require(sceNpMatching2RegisterRoomEventCallback(5, nullptr, nullptr) == InvalidContextId, "sceNpMatching2Terminate", "a context created before termination is still found");
Require(sceNpMatching2CreateContext(&create, &created) == 0 && created == 1, "sceNpMatching2Terminate", "termination did not restart the context ids at 1");
Require(sceNpMatching2Terminate() == 0, "sceNpMatching2Terminate", "the second termination failed");

if (failures != 0) return 1;
std::puts("NpMatching2 offline tests passed");
return 0;
}
1 change: 1 addition & 0 deletions docs/dev/debt/libSceNpMatching2.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
### Functional

- [sceNpMatching2Initialize, sceNpMatching2Terminate](../../../core/libs/prx/libSceNpMatching2/Export.cpp) (libSceNpMatching2) - only the library state is kept: initialization checks the parameter's `size` field (0x28, or 0x30 with an SSL pool) as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/81be949167d0060b08f26dc3e055a0b640d129c8/src/core/libraries/np/np_matching2/np_matching2.cpp) does and starts no thread; a second initialization returns `ALREADY_INITIALIZED` and a termination without one `NOT_INITIALIZED`. The pool, thread and SSL settings are not used: no matching server is reached. Not measured on a console
- [sceNpMatching2CreateContext and the context calls](../../../core/libs/prx/libSceNpMatching2/Export.cpp) (libSceNpMatching2) - offline only, not measured on a PS5: the checks follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_matching2/np_matching2.cpp#L26-L349) and its [context manager](https://github.com/shadps4-emu/shadPS4/blob/91e6afa69b0a64b0afaa0281b80f8fbb0cd88136/src/core/libraries/np/np_matching2/np_matching2_internal.cpp#L1348-L1457) (a `0x28`-byte parameter, ten ids handed out round-robin). `sceNpMatching2ContextStart` returns `0x804101E2` (`SCE_NET_ERROR_RESOLVER_ETIMEDOUT`), which shadPS4 says was confirmed on a console disconnected from the internet, so no context starts and `sceNpMatching2ContextStop` answers `CONTEXT_NOT_STARTED`. Callbacks and the default request option are checked but never used. The room, user, world, server-id and signaling-info calls still throw
Loading