mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 08:33:12 -05:00
sceNpRegisterStateCallback and sceNpRegisterStateCallbackA stored nothing, the unregister calls always succeeded and sceNpCheckCallback did nothing, so a title that waits for its NP state callback never learned the user's state. #1277 reports that Minecraft Preview (PPSA19639) needs the callback (stop 2). sceNpRegisterStateCallbackA now keeps up to 8 callbacks and returns the slot plus 1. A null callback returns INVALID_ARGUMENT, a callback that is already registered CALLBACK_ALREADY_REGISTERED and a ninth one CALLBACK_MAX (0x8055001D). sceNpUnregisterStateCallbackA returns INVALID_ARGUMENT for an id outside 1..8 and CALLBACK_NOT_REGISTERED for an empty slot. The legacy sceNpRegisterStateCallback keeps one callback with the same errors. The rules follow shadPS4 src/core/libraries/np/np_manager.cpp @15abf68c9b52 (RegisterStateCallbackA and UnregisterStateCallbackAById L857-L901, the legacy pair L981-L1005, ORBIS_NP_STATE_CALLBACK_MAX L36) and np_error.h L38. Each registration is marked pending. sceNpCheckCallback takes the pending registrations under the lock and calls them after releasing it, on the calling thread, with SIGNED_OUT for the initial user 0x10000000. For the A form this follows KytyPS5 src/libs/network.cpp @9e41d8320a27 (NpRegisterStateCallbackA L4021-L4034 marks the registration pending, NpCheckCallback L3959-L3974 delivers it once). KytyPS5 never calls the legacy callback (NpRegisterStateCallback and NpUnregisterStateCallback, L4009-L4019, do nothing). The legacy callback is called as (user, SIGNED_OUT, nullptr, userdata), as in shadPS4, which passes an NpId only for SIGNED_IN (L823, L938-L940). shadPS4 queues SIGNED_OUT from the user service login event instead (DispatchPendingNpStateCallbacks L903-L962), and the same event calls the reachability callback once with UNAVAILABLE (L922-L931, L959-L961). The reachability callback is still not called here. These differences and the unmeasured console behaviour are recorded in docs/dev/debt/libSceNpManager.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
307 lines
9.6 KiB
C++
307 lines
9.6 KiB
C++
#include <cstdint>
|
|
#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>
|
|
|
|
// PSN is not emulated: the user is reported as signed out and online queries fail.
|
|
static constexpr int SCE_NP_ERROR_INVALID_ARGUMENT = static_cast<int>(0x80550003);
|
|
static constexpr int SCE_NP_ERROR_SIGNED_OUT = static_cast<int>(0x80550006);
|
|
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;
|
|
|
|
static std::atomic<int> g_nextRequest{1};
|
|
|
|
namespace {
|
|
std::mutex reachabilityMutex;
|
|
void* reachabilityCallback = nullptr;
|
|
void* reachabilityUserdata = nullptr;
|
|
std::mutex requestMutex;
|
|
std::unordered_set<int> liveRequests;
|
|
|
|
int AddRequest() {
|
|
const int reqId = g_nextRequest.fetch_add(1, std::memory_order_relaxed);
|
|
std::lock_guard lock(requestMutex);
|
|
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" {
|
|
|
|
int APS5_VABI sceNpAbortRequest(int req_id) {
|
|
(void)req_id;
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int APS5_VABI sceNpCheckNpAvailability(int req_id, const NpOnlineId* online_id) {
|
|
(void)req_id;
|
|
if (!online_id) return SCE_NP_ERROR_INVALID_ARGUMENT;
|
|
return SCE_NP_ERROR_USER_NOT_FOUND;
|
|
}
|
|
|
|
int APS5_VABI sceNpCheckNpReachability(int req_id, int user_id) {
|
|
(void)req_id;
|
|
(void)user_id;
|
|
return SCE_NP_ERROR_SIGNED_OUT;
|
|
}
|
|
|
|
int APS5_VABI sceNpCheckPremium(int req_id, const NpCheckPremiumParameter* param, NpCheckPremiumResult* result) {
|
|
(void)req_id;
|
|
(void)param;
|
|
(void)result;
|
|
return SCE_NP_ERROR_SIGNED_OUT;
|
|
}
|
|
|
|
int APS5_VABI sceNpCreateAsyncRequest(const NpCreateAsyncRequestParameter* param) {
|
|
(void)param;
|
|
return AddRequest();
|
|
}
|
|
|
|
int APS5_VABI sceNpCreateRequest(void) {
|
|
return AddRequest();
|
|
}
|
|
|
|
int APS5_VABI sceNpDeleteRequest(int req_id) {
|
|
std::lock_guard lock(requestMutex);
|
|
liveRequests.erase(req_id);
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceNpGetAccountAge(int req_id, int user_id, uint8_t* age) {
|
|
(void)req_id;
|
|
(void)user_id;
|
|
if (!age) return SCE_NP_ERROR_INVALID_ARGUMENT;
|
|
return SCE_NP_ERROR_SIGNED_OUT;
|
|
}
|
|
|
|
int APS5_VABI sceNpGetAccountCountryA(int user_id, void* country_code) {
|
|
(void)user_id;
|
|
(void)country_code;
|
|
return SCE_NP_ERROR_SIGNED_OUT;
|
|
}
|
|
|
|
int APS5_VABI sceNpGetAccountIdA(int user_id, uint64_t* account_id) {
|
|
(void)user_id;
|
|
(void)account_id;
|
|
return SCE_NP_ERROR_SIGNED_OUT;
|
|
}
|
|
|
|
int APS5_VABI sceNpGetNpId(int user_id, NpId* np_id) {
|
|
(void)user_id;
|
|
if (!np_id) return SCE_NP_ERROR_INVALID_ARGUMENT;
|
|
return SCE_NP_ERROR_SIGNED_OUT;
|
|
}
|
|
|
|
int APS5_VABI sceNpGetNpReachabilityState(int user_id, uint32_t* state) {
|
|
(void)user_id;
|
|
if (!state) return SCE_NP_ERROR_INVALID_ARGUMENT;
|
|
*state = NP_REACHABILITY_STATE_UNAVAILABLE;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceNpGetOnlineId(int user_id, NpOnlineId* online_id) {
|
|
(void)user_id;
|
|
(void)online_id;
|
|
return SCE_NP_ERROR_SIGNED_OUT;
|
|
}
|
|
|
|
int APS5_VABI sceNpGetState(int user_id, uint32_t* state) {
|
|
(void)user_id;
|
|
if (!state) return SCE_NP_ERROR_INVALID_ARGUMENT;
|
|
*state = NP_STATE_SIGNED_OUT;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceNpHasSignedUp(int user_id, bool* has_signed_up) {
|
|
(void)user_id;
|
|
if (!has_signed_up) return SCE_NP_ERROR_INVALID_ARGUMENT;
|
|
*has_signed_up = false;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceNpPollAsync(int req_id, int* result) {
|
|
(void)req_id;
|
|
if (result) *result = SCE_NP_ERROR_SIGNED_OUT;
|
|
return NP_POLL_ASYNC_FINISHED;
|
|
}
|
|
|
|
void APS5_VABI sceNpRegisterGamePresenceCallback(void* callback, void* userdata) {
|
|
(void)callback;
|
|
(void)userdata;
|
|
NotImplemented_nid_no_patch(__func__);
|
|
}
|
|
|
|
int APS5_VABI sceNpRegisterNpReachabilityStateCallback(void* callback, void* userdata) {
|
|
if (!callback) return SCE_NP_ERROR_INVALID_ARGUMENT;
|
|
std::lock_guard lock(reachabilityMutex);
|
|
if (reachabilityCallback) return SCE_NP_ERROR_CALLBACK_ALREADY_REGISTERED;
|
|
reachabilityCallback = callback;
|
|
reachabilityUserdata = userdata;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceNpUnregisterNpReachabilityStateCallback(void) {
|
|
std::lock_guard lock(reachabilityMutex);
|
|
if (!reachabilityCallback) return SCE_NP_ERROR_CALLBACK_NOT_REGISTERED;
|
|
reachabilityCallback = nullptr;
|
|
reachabilityUserdata = nullptr;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceNpRegisterPlusEventCallback(void* callback, void* userdata) {
|
|
(void)userdata;
|
|
if (!callback) return SCE_NP_ERROR_INVALID_ARGUMENT;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceNpRegisterPremiumEventCallback(void* callback, void* userdata) {
|
|
(void)userdata;
|
|
if (!callback) return SCE_NP_ERROR_INVALID_ARGUMENT;
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int APS5_VABI sceNpSetContentRestriction(const NpContentRestriction* restriction) {
|
|
(void)restriction;
|
|
NotImplemented_nid_no_patch(__func__);
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceNpSetNpTitleId(const NpTitleId* title_id, const NpTitleSecret* title_secret) {
|
|
(void)title_id;
|
|
(void)title_secret;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceNpSetTimeout(int reqId, int32_t resolveRetry, uint32_t resolveTimeout, uint32_t connTimeout,
|
|
uint32_t sendTimeout, uint32_t recvTimeout) {
|
|
constexpr uint32_t minResolveTimeout = 1000000;
|
|
constexpr uint32_t minTransferTimeout = 10000000;
|
|
auto belowMinimum = [](uint32_t timeout, uint32_t minimum) { return timeout != 0 && timeout < minimum; };
|
|
if (reqId <= 0 || resolveRetry < 0) return SCE_NP_ERROR_INVALID_ARGUMENT;
|
|
if (resolveRetry == 0 && resolveTimeout == 0 && connTimeout == 0 && sendTimeout == 0 && recvTimeout == 0) {
|
|
return SCE_NP_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
if (belowMinimum(resolveTimeout, minResolveTimeout) || belowMinimum(connTimeout, minTransferTimeout) ||
|
|
belowMinimum(sendTimeout, minTransferTimeout) || belowMinimum(recvTimeout, minTransferTimeout)) {
|
|
return SCE_NP_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
std::lock_guard lock(requestMutex);
|
|
return liveRequests.count(reqId) ? 0 : SCE_NP_ERROR_REQUEST_NOT_FOUND;
|
|
}
|
|
|
|
int APS5_VABI sceNpUnregisterStateCallback(void) {
|
|
std::lock_guard lock(stateMutex);
|
|
if (!stateCallback.callback) return SCE_NP_ERROR_CALLBACK_NOT_REGISTERED;
|
|
stateCallback = {};
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceNpGetAccountLanguage2(int req_id, int user_id, void* language) {
|
|
(void)req_id;
|
|
(void)user_id;
|
|
(void)language;
|
|
return SCE_NP_ERROR_SIGNED_OUT;
|
|
}
|
|
|
|
int APS5_VABI sceNpNotifyPremiumFeature(const void* param) {
|
|
(void)param;
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceNpRegisterStateCallbackA(NpStateCallbackA callback, void* userdata) {
|
|
if (!callback) return SCE_NP_ERROR_INVALID_ARGUMENT;
|
|
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 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;
|
|
}
|
|
|
|
int APS5_VABI sceNpUnregisterPremiumEventCallback(void) {
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceNpGetUserIdByAccountId() {
|
|
NotImplemented_nid_no_patch(__func__);
|
|
return 0;
|
|
}
|
|
|
|
int APS5_VABI sceNpGetSandboxInfo(void* sandbox_info) {
|
|
(void)sandbox_info;
|
|
NotImplemented_nid_no_patch(__func__);
|
|
return 0;
|
|
}
|
|
|
|
}
|