mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
feat(libSceNpMatching2): model contexts offline
The context calls, the callback registrations and SetDefaultRequestOptParam threw. They now follow shadPS4's checks and context ids, ContextStart returns SCE_NET_ERROR_RESOLVER_ETIMEDOUT, the answer shadPS4 records for a console without internet access, and Terminate releases the contexts.
This commit is contained in:
1 parent
26469c711a
commit
4bb91851bb
4 files changed
+213
-26
No files matched your search
@@ -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()
|
||||
@@ -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 {
|
||||
|
||||
@@ -22,26 +29,56 @@ 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() {
|
||||
@@ -49,8 +86,10 @@ int APS5_VABI sceNpMatching2CreateJoinRoom() {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
@@ -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() {
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in new issue
Block a user