mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
Merge pull request #6121 from fdfawef324/claude/mc-np-state-callbacks
feat(libSceNpManager): register NP state callbacks and report signed-out from sceNpCheckCallback
This commit is contained in:
3 files changed
+203
-7
No files matched your search
@@ -2,6 +2,8 @@
|
||||
#include <cstddef>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libSceUserService/UserService.hpp"
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <unordered_set>
|
||||
@@ -13,6 +15,7 @@ static constexpr int SCE_NP_ERROR_USER_NOT_FOUND = static_cast<int>(0x80550007);
|
||||
static constexpr int SCE_NP_ERROR_CALLBACK_ALREADY_REGISTERED = static_cast<int>(0x80550008);
|
||||
static constexpr int SCE_NP_ERROR_CALLBACK_NOT_REGISTERED = static_cast<int>(0x80550009);
|
||||
static constexpr int SCE_NP_ERROR_REQUEST_NOT_FOUND = static_cast<int>(0x80550014);
|
||||
static constexpr int SCE_NP_ERROR_CALLBACK_MAX = static_cast<int>(0x8055001D);
|
||||
static constexpr uint32_t NP_STATE_SIGNED_OUT = 1;
|
||||
static constexpr int NP_POLL_ASYNC_FINISHED = 0;
|
||||
static constexpr uint32_t NP_REACHABILITY_STATE_UNAVAILABLE = 0;
|
||||
@@ -32,6 +35,27 @@ int AddRequest() {
|
||||
liveRequests.insert(reqId);
|
||||
return reqId;
|
||||
}
|
||||
|
||||
using NpStateCallbackA = void (APS5_VABI *)(int userId, uint32_t state, void* userdata);
|
||||
using NpStateCallback = void (APS5_VABI *)(int userId, uint32_t state, NpId* npId, void* userdata);
|
||||
|
||||
constexpr int NP_STATE_CALLBACK_MAX = 8;
|
||||
|
||||
struct StateCallbackA {
|
||||
NpStateCallbackA callback = nullptr;
|
||||
void* userdata = nullptr;
|
||||
bool pending = false;
|
||||
};
|
||||
|
||||
struct StateCallback {
|
||||
NpStateCallback callback = nullptr;
|
||||
void* userdata = nullptr;
|
||||
bool pending = false;
|
||||
};
|
||||
|
||||
std::mutex stateMutex;
|
||||
std::array<StateCallbackA, NP_STATE_CALLBACK_MAX> stateCallbacksA;
|
||||
StateCallback stateCallback;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
@@ -42,6 +66,19 @@ int APS5_VABI sceNpAbortRequest(int req_id) {
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpCheckCallback(void) {
|
||||
std::array<StateCallbackA, NP_STATE_CALLBACK_MAX> callbacksA;
|
||||
StateCallback legacy;
|
||||
{
|
||||
std::lock_guard lock(stateMutex);
|
||||
callbacksA = stateCallbacksA;
|
||||
legacy = stateCallback;
|
||||
for (auto& entry : stateCallbacksA) entry.pending = false;
|
||||
stateCallback.pending = false;
|
||||
}
|
||||
if (legacy.pending) legacy.callback(USER_SERVICE_INITIAL_USER_ID, NP_STATE_SIGNED_OUT, nullptr, legacy.userdata);
|
||||
for (const auto& entry : callbacksA) {
|
||||
if (entry.pending) entry.callback(USER_SERVICE_INITIAL_USER_ID, NP_STATE_SIGNED_OUT, entry.userdata);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -172,9 +209,11 @@ int APS5_VABI sceNpRegisterPremiumEventCallback(void* callback, void* userdata)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpRegisterStateCallback(void* callback, void* userdata) {
|
||||
(void)userdata;
|
||||
int APS5_VABI sceNpRegisterStateCallback(NpStateCallback callback, void* userdata) {
|
||||
if (!callback) return SCE_NP_ERROR_INVALID_ARGUMENT;
|
||||
std::lock_guard lock(stateMutex);
|
||||
if (stateCallback.callback) return SCE_NP_ERROR_CALLBACK_ALREADY_REGISTERED;
|
||||
stateCallback = {callback, userdata, true};
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -208,6 +247,9 @@ int APS5_VABI sceNpSetTimeout(int reqId, int32_t resolveRetry, uint32_t resolveT
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpUnregisterStateCallback(void) {
|
||||
std::lock_guard lock(stateMutex);
|
||||
if (!stateCallback.callback) return SCE_NP_ERROR_CALLBACK_NOT_REGISTERED;
|
||||
stateCallback = {};
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -223,14 +265,26 @@ int APS5_VABI sceNpNotifyPremiumFeature(const void* param) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpRegisterStateCallbackA(void* callback, void* userdata) {
|
||||
(void)userdata;
|
||||
int APS5_VABI sceNpRegisterStateCallbackA(NpStateCallbackA callback, void* userdata) {
|
||||
if (!callback) return SCE_NP_ERROR_INVALID_ARGUMENT;
|
||||
return 1;
|
||||
std::lock_guard lock(stateMutex);
|
||||
for (const auto& entry : stateCallbacksA) {
|
||||
if (entry.callback == callback) return SCE_NP_ERROR_CALLBACK_ALREADY_REGISTERED;
|
||||
}
|
||||
for (int index = 0; index < NP_STATE_CALLBACK_MAX; ++index) {
|
||||
if (stateCallbacksA[index].callback) continue;
|
||||
stateCallbacksA[index] = {callback, userdata, true};
|
||||
return index + 1;
|
||||
}
|
||||
return SCE_NP_ERROR_CALLBACK_MAX;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNpUnregisterStateCallbackA(int callback_id) {
|
||||
(void)callback_id;
|
||||
int APS5_VABI sceNpUnregisterStateCallbackA(int callbackId) {
|
||||
if (callbackId < 1 || callbackId > NP_STATE_CALLBACK_MAX) return SCE_NP_ERROR_INVALID_ARGUMENT;
|
||||
std::lock_guard lock(stateMutex);
|
||||
auto& entry = stateCallbacksA[callbackId - 1];
|
||||
if (!entry.callback) return SCE_NP_ERROR_CALLBACK_NOT_REGISTERED;
|
||||
entry = {};
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,22 @@ int APS5_VABI sceNpSetTimeout(int reqId, std::int32_t resolveRetry, std::uint32_
|
||||
std::uint32_t connTimeout, std::uint32_t sendTimeout, std::uint32_t recvTimeout);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI sceNpCheckCallback(void);
|
||||
int APS5_VABI sceNpRegisterStateCallback(void* callback, void* userdata);
|
||||
int APS5_VABI sceNpUnregisterStateCallback(void);
|
||||
int APS5_VABI sceNpRegisterStateCallbackA(void* callback, void* userdata);
|
||||
int APS5_VABI sceNpUnregisterStateCallbackA(int callbackId);
|
||||
int APS5_VABI sceNpRegisterNpReachabilityStateCallback(void* callback, void* userdata);
|
||||
int APS5_VABI sceNpUnregisterNpReachabilityStateCallback(void);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int InvalidArgument = static_cast<int>(0x80550003u);
|
||||
constexpr int SignedOut = static_cast<int>(0x80550006u);
|
||||
constexpr int AlreadyRegistered = static_cast<int>(0x80550008u);
|
||||
constexpr int NotRegistered = static_cast<int>(0x80550009u);
|
||||
constexpr int RequestNotFound = static_cast<int>(0x80550014u);
|
||||
|
||||
void Require(bool condition, const char* message) {
|
||||
@@ -35,6 +47,133 @@ void Require(bool condition, const char* message) {
|
||||
}
|
||||
}
|
||||
|
||||
struct StateCall {
|
||||
int calls;
|
||||
int userId;
|
||||
std::uint32_t state;
|
||||
NpId* npId;
|
||||
void* userdata;
|
||||
};
|
||||
|
||||
constexpr int InitialUserId = 0x10000000;
|
||||
constexpr std::uint32_t StateSignedOut = 1;
|
||||
StateCall stateCallsA[9];
|
||||
StateCall legacyStateCall;
|
||||
|
||||
template <int Index>
|
||||
void APS5_VABI StateCallbackA(int userId, std::uint32_t state, void* userdata) {
|
||||
stateCallsA[Index] = {stateCallsA[Index].calls + 1, userId, state, nullptr, userdata};
|
||||
}
|
||||
|
||||
void APS5_VABI LegacyStateCallback(int userId, std::uint32_t state, NpId* npId, void* userdata) {
|
||||
legacyStateCall = {legacyStateCall.calls + 1, userId, state, npId, userdata};
|
||||
}
|
||||
|
||||
bool SignedOutOnce(const StateCall& call, void* userdata) {
|
||||
return call.calls == 1 && call.userId == InitialUserId && call.state == StateSignedOut &&
|
||||
call.npId == nullptr && call.userdata == userdata;
|
||||
}
|
||||
|
||||
void APS5_VABI AbortingReachabilityCallback(int, std::uint32_t, void*) {
|
||||
std::fprintf(stderr, "NpManager: the reachability callback was called\n");
|
||||
std::abort();
|
||||
}
|
||||
|
||||
struct SelfUnregister {
|
||||
int id;
|
||||
int calls;
|
||||
int result;
|
||||
};
|
||||
|
||||
void APS5_VABI SelfUnregisteringCallback(int, std::uint32_t, void* userdata) {
|
||||
auto* context = static_cast<SelfUnregister*>(userdata);
|
||||
++context->calls;
|
||||
context->result = sceNpUnregisterStateCallbackA(context->id);
|
||||
}
|
||||
|
||||
void CheckStateCallbacksA() {
|
||||
constexpr int CallbackMax = static_cast<int>(0x8055001Du);
|
||||
void* const callbacks[] = {
|
||||
reinterpret_cast<void*>(&StateCallbackA<0>), reinterpret_cast<void*>(&StateCallbackA<1>),
|
||||
reinterpret_cast<void*>(&StateCallbackA<2>), reinterpret_cast<void*>(&StateCallbackA<3>),
|
||||
reinterpret_cast<void*>(&StateCallbackA<4>), reinterpret_cast<void*>(&StateCallbackA<5>),
|
||||
reinterpret_cast<void*>(&StateCallbackA<6>), reinterpret_cast<void*>(&StateCallbackA<7>),
|
||||
reinterpret_cast<void*>(&StateCallbackA<8>),
|
||||
};
|
||||
int userdata[9]{};
|
||||
Require(sceNpRegisterStateCallbackA(nullptr, &userdata[0]) == InvalidArgument, "null state callback");
|
||||
int ids[8]{};
|
||||
ids[0] = sceNpRegisterStateCallbackA(callbacks[0], &userdata[0]);
|
||||
Require(ids[0] >= 1 && ids[0] <= 8, "state callback id outside 1..8");
|
||||
Require(sceNpRegisterStateCallbackA(callbacks[0], &userdata[1]) == AlreadyRegistered, "duplicate state callback");
|
||||
Require(stateCallsA[0].calls == 0, "registering calls the state callback");
|
||||
Require(sceNpRegisterNpReachabilityStateCallback(reinterpret_cast<void*>(&AbortingReachabilityCallback), nullptr) == 0,
|
||||
"registering the reachability callback");
|
||||
Require(sceNpCheckCallback() == 0, "sceNpCheckCallback failed");
|
||||
Require(SignedOutOnce(stateCallsA[0], &userdata[0]), "sceNpCheckCallback must deliver SIGNED_OUT once");
|
||||
Require(sceNpUnregisterNpReachabilityStateCallback() == 0, "unregistering the reachability callback");
|
||||
Require(sceNpCheckCallback() == 0, "second sceNpCheckCallback failed");
|
||||
Require(stateCallsA[0].calls == 1, "the second sceNpCheckCallback delivered the state again");
|
||||
for (int index = 1; index < 8; ++index) {
|
||||
ids[index] = sceNpRegisterStateCallbackA(callbacks[index], &userdata[index]);
|
||||
Require(ids[index] >= 1 && ids[index] <= 8, "state callback id outside 1..8");
|
||||
for (int other = 0; other < index; ++other) {
|
||||
Require(ids[index] != ids[other], "two state callbacks got the same id");
|
||||
}
|
||||
}
|
||||
Require(sceNpRegisterStateCallbackA(callbacks[8], &userdata[8]) == CallbackMax, "ninth state callback");
|
||||
Require(sceNpCheckCallback() == 0, "sceNpCheckCallback failed with eight callbacks");
|
||||
for (int index = 0; index < 8; ++index) {
|
||||
Require(SignedOutOnce(stateCallsA[index], &userdata[index]), "each callback gets SIGNED_OUT once");
|
||||
}
|
||||
Require(stateCallsA[8].calls == 0, "a rejected callback was called");
|
||||
Require(sceNpUnregisterStateCallbackA(0) == InvalidArgument, "callback id 0");
|
||||
Require(sceNpUnregisterStateCallbackA(9) == InvalidArgument, "callback id 9");
|
||||
Require(sceNpUnregisterStateCallbackA(ids[3]) == 0, "unregistering one state callback");
|
||||
for (int index = 0; index < 8; ++index) {
|
||||
if (index == 3) continue;
|
||||
Require(sceNpRegisterStateCallbackA(callbacks[index], &userdata[index]) == AlreadyRegistered,
|
||||
"unregistering one id removed another callback");
|
||||
}
|
||||
Require(sceNpRegisterStateCallbackA(callbacks[3], &userdata[3]) == ids[3], "registering again takes the freed slot");
|
||||
Require(sceNpCheckCallback() == 0, "sceNpCheckCallback failed after registering again");
|
||||
for (int index = 0; index < 8; ++index) {
|
||||
Require(stateCallsA[index].calls == (index == 3 ? 2 : 1), "only the registration made again gets SIGNED_OUT");
|
||||
}
|
||||
for (int index = 0; index < 8; ++index) {
|
||||
Require(sceNpUnregisterStateCallbackA(ids[index]) == 0, "unregistering a state callback");
|
||||
}
|
||||
Require(sceNpUnregisterStateCallbackA(ids[0]) == NotRegistered, "unregistering a state callback twice");
|
||||
const int removed = sceNpRegisterStateCallbackA(callbacks[8], &userdata[8]);
|
||||
Require(removed >= 1 && removed <= 8, "a freed slot is reused");
|
||||
Require(sceNpUnregisterStateCallbackA(removed) == 0, "unregistering before sceNpCheckCallback");
|
||||
Require(sceNpCheckCallback() == 0, "sceNpCheckCallback failed with no callbacks");
|
||||
Require(stateCallsA[8].calls == 0, "an unregistered callback was called");
|
||||
SelfUnregister self{};
|
||||
self.id = sceNpRegisterStateCallbackA(reinterpret_cast<void*>(&SelfUnregisteringCallback), &self);
|
||||
Require(self.id >= 1 && self.id <= 8, "state callback id outside 1..8");
|
||||
Require(sceNpCheckCallback() == 0, "sceNpCheckCallback failed with a callback that unregisters itself");
|
||||
Require(self.calls == 1 && self.result == 0, "a state callback must be able to unregister itself");
|
||||
Require(sceNpUnregisterStateCallbackA(self.id) == NotRegistered, "the callback did not unregister itself");
|
||||
}
|
||||
|
||||
void CheckLegacyStateCallback() {
|
||||
int userdata = 0;
|
||||
NpId sentinel{};
|
||||
auto* callback = reinterpret_cast<void*>(&LegacyStateCallback);
|
||||
Require(sceNpUnregisterStateCallback() == NotRegistered, "unregistering with no legacy callback");
|
||||
Require(sceNpRegisterStateCallback(nullptr, &userdata) == InvalidArgument, "null legacy callback");
|
||||
Require(sceNpRegisterStateCallback(callback, &userdata) == 0, "registering the legacy callback");
|
||||
Require(sceNpRegisterStateCallback(callback, nullptr) == AlreadyRegistered, "duplicate legacy callback");
|
||||
legacyStateCall.npId = &sentinel;
|
||||
Require(sceNpCheckCallback() == 0, "sceNpCheckCallback failed with the legacy callback");
|
||||
Require(SignedOutOnce(legacyStateCall, &userdata), "the legacy callback must get SIGNED_OUT with no NpId");
|
||||
Require(sceNpCheckCallback() == 0, "second sceNpCheckCallback failed with the legacy callback");
|
||||
Require(legacyStateCall.calls == 1, "the legacy callback got the state again");
|
||||
Require(sceNpUnregisterStateCallback() == 0, "unregistering the legacy callback");
|
||||
Require(sceNpUnregisterStateCallback() == NotRegistered, "unregistering the legacy callback twice");
|
||||
}
|
||||
|
||||
void CheckRequestTimeouts() {
|
||||
Require(sceNpSetTimeout(0x7fffffff, 2, 0, 0, 0, 0) == RequestNotFound, "never created request");
|
||||
Require(sceNpSetTimeout(0x7fffffff, 0, 0, 0, 0, 0) == InvalidArgument, "arguments checked before the request");
|
||||
@@ -64,6 +203,8 @@ void CheckRequestTimeouts() {
|
||||
}
|
||||
|
||||
int main() {
|
||||
CheckStateCallbacksA();
|
||||
CheckLegacyStateCallback();
|
||||
CheckRequestTimeouts();
|
||||
NpId npId{};
|
||||
std::memset(&npId, 0x5a, sizeof(npId));
|
||||
|
||||
@@ -12,3 +12,4 @@
|
||||
|
||||
- [NP reachability callbacks](../../../core/libs/prx/libSceNpManager/Export.cpp) retain one callback and its user data; duplicate registration and unregistering an absent callback return the errors used by [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/core/libraries/np/np_manager.cpp). No callback is delivered because the existing NP model remains signed out with reachability unavailable.
|
||||
- [sceNpCheckNpAvailability](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - the `(req_id, online_id)` signature and the errors follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0fe263a4760dfbfa973366890061749b4af0de97/src/core/libraries/np/np_manager.cpp) with no user signed in to PSN: a null online id is `SCE_NP_ERROR_INVALID_ARGUMENT`, and any other online id matches no user, `SCE_NP_ERROR_USER_NOT_FOUND`; the request id is not checked, as in the other NpManager requests
|
||||
- [NP state callbacks](../../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - the registration rules are [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L857-L901)'s: `sceNpRegisterStateCallbackA` keeps up to 8 callbacks and returns the slot plus 1, a null callback returns `INVALID_ARGUMENT`, a callback already registered `CALLBACK_ALREADY_REGISTERED` and a ninth one `CALLBACK_MAX` (0x8055001D, [np_error.h](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_error.h#L38)); `sceNpUnregisterStateCallbackA` returns `INVALID_ARGUMENT` for an id outside 1..8 and `CALLBACK_NOT_REGISTERED` for an empty slot; the legacy [sceNpRegisterStateCallback](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L981-L1005) keeps one callback with the same errors. Each registration gets `SIGNED_OUT` for the initial user (0x10000000) once, from the next `sceNpCheckCallback` on the calling thread. For the A form this follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/network.cpp#L3959-L3974), whose [sceNpRegisterStateCallbackA](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/network.cpp#L4021-L4034) marks the registration pending. KytyPS5 never calls the legacy callback ([sceNpRegisterStateCallback](https://github.com/KytyPS5/KytyPS5/blob/9e41d8320a2774b303fbad344c2347e8d0292e83/src/libs/network.cpp#L4009-L4019) stores nothing); it is called as `(user, SIGNED_OUT, nullptr, userdata)`, as in shadPS4, which passes an NpId only for `SIGNED_IN` ([L823](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L823), [L938-L940](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L938-L940)). shadPS4 instead queues `SIGNED_OUT` from the user service login event and delivers it from `sceNpCheckCallback` ([DispatchPendingNpStateCallbacks](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L903-L962)); the reachability callback is not given an initial `UNAVAILABLE`, unlike that function ([L922-L961](https://github.com/shadps4-emu/shadPS4/blob/15abf68c9b52c4b04d094f2837719fdb871021f3/src/core/libraries/np/np_manager.cpp#L922-L961)). A callback being delivered when another thread unregisters it is still called once. When and how often the console calls the state callbacks, and the errors, are not measured
|
||||
Reference in new issue
Block a user