diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 7742a908e803..73ea0063fd58 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -216,6 +216,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_np_auth_tests tests/GuestNpAuth.cpp) + target_include_directories(guest_np_auth_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_np_auth_tests PRIVATE libSceNpAuth libc) + configure_windows_unwind(guest_np_auth_tests) + add_test(NAME guest_np_auth COMMAND guest_np_auth_tests) + if(WIN32) + set_tests_properties(guest_np_auth PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_rudp_status_tests tests/GuestRudpStatus.cpp) target_include_directories(guest_rudp_status_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(guest_rudp_status_tests PRIVATE libSceRudp libc) diff --git a/core/libs/prx/libSceNpAuth/Export.cpp b/core/libs/prx/libSceNpAuth/Export.cpp index c17210d9f1f5..0cc41fcd2315 100644 --- a/core/libs/prx/libSceNpAuth/Export.cpp +++ b/core/libs/prx/libSceNpAuth/Export.cpp @@ -1,34 +1,62 @@ -#include -#include #include +#include +#include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" -static constexpr int SCE_NP_ERROR_INVALID_ARGUMENT = static_cast(0x80550003); +static constexpr int ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT = static_cast(0x80550301); static constexpr int SCE_NP_ERROR_SIGNED_OUT = static_cast(0x80550006); +static constexpr int ORBIS_NP_AUTH_ERROR_ABORTED = static_cast(0x80550304); +static constexpr int ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND = static_cast(0x80550306); +static constexpr int ORBIS_NP_AUTH_ERROR_INVALID_ID = static_cast(0x80550307); static constexpr int NP_POLL_ASYNC_FINISHED = 0; -static std::atomic g_nextRequest{1}; +namespace { + +struct RequestInfo { + bool isAsync = false; + bool processed = false; + bool aborted = false; + int result = SCE_NP_ERROR_SIGNED_OUT; +}; + +std::mutex g_requestMutex; +std::unordered_map g_requests; +std::atomic g_nextRequest{1}; + +int CreateRequest(bool isAsync) { + const int reqId = g_nextRequest.fetch_add(1, std::memory_order_relaxed); + std::lock_guard lock(g_requestMutex); + g_requests[reqId] = {isAsync, false, false, SCE_NP_ERROR_SIGNED_OUT}; + return reqId; +} + +} extern "C" { int APS5_VABI sceNpAuthAbortRequest(int req_id) { - (void)req_id; + std::lock_guard lock(g_requestMutex); + auto it = g_requests.find(req_id); + if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; + if (!it->second.processed) it->second.aborted = true; return 0; } int APS5_VABI sceNpAuthCreateAsyncRequest(const void* param) { (void)param; - return g_nextRequest.fetch_add(1, std::memory_order_relaxed); + return CreateRequest(true); } int APS5_VABI sceNpAuthCreateRequest(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return CreateRequest(false); } int APS5_VABI sceNpAuthDeleteRequest(int req_id) { - (void)req_id; + std::lock_guard lock(g_requestMutex); + g_requests.erase(req_id); return 0; } @@ -38,31 +66,59 @@ int APS5_VABI sceNpAuthGetAuthorizedAppCode() { } int APS5_VABI sceNpAuthGetAuthorizationCodeV3(int req_id, const void* param, void* auth_code, int* issuer_id) { - (void)req_id; (void)issuer_id; - if (!param || !auth_code) return SCE_NP_ERROR_INVALID_ARGUMENT; + if (!param || !auth_code) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_requestMutex); + auto it = g_requests.find(req_id); + if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; + if (it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; + if (it->second.aborted) { + it->second.result = ORBIS_NP_AUTH_ERROR_ABORTED; + return ORBIS_NP_AUTH_ERROR_ABORTED; + } + it->second.processed = true; + if (it->second.isAsync) { + it->second.result = SCE_NP_ERROR_SIGNED_OUT; + return 0; + } return SCE_NP_ERROR_SIGNED_OUT; } int APS5_VABI sceNpAuthGetIdTokenV3(int req_id, const void* param, void* id_token) { - (void)req_id; - (void)param; - (void)id_token; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!param || !id_token) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_requestMutex); + auto it = g_requests.find(req_id); + if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; + if (it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; + if (it->second.aborted) { + it->second.result = ORBIS_NP_AUTH_ERROR_ABORTED; + return ORBIS_NP_AUTH_ERROR_ABORTED; + } + it->second.processed = true; + if (it->second.isAsync) { + it->second.result = SCE_NP_ERROR_SIGNED_OUT; + return 0; + } + return SCE_NP_ERROR_SIGNED_OUT; } int APS5_VABI sceNpAuthPollAsync(int req_id, int* result) { - (void)req_id; - if (result) *result = SCE_NP_ERROR_SIGNED_OUT; + std::lock_guard lock(g_requestMutex); + auto it = g_requests.find(req_id); + if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; + if (!it->second.isAsync || (!it->second.processed && !it->second.aborted)) return ORBIS_NP_AUTH_ERROR_INVALID_ID; + if (result) *result = it->second.result; return NP_POLL_ASYNC_FINISHED; } int APS5_VABI sceNpAuthWaitAsync(int req_id, int* result) { - (void)req_id; - (void)result; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!result) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(g_requestMutex); + auto it = g_requests.find(req_id); + if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND; + if (!it->second.isAsync || (!it->second.processed && !it->second.aborted)) return ORBIS_NP_AUTH_ERROR_INVALID_ID; + *result = it->second.result; + return NP_POLL_ASYNC_FINISHED; } } diff --git a/core/libs/tests/GuestNpAuth.cpp b/core/libs/tests/GuestNpAuth.cpp new file mode 100644 index 000000000000..dea50561d3c1 --- /dev/null +++ b/core/libs/tests/GuestNpAuth.cpp @@ -0,0 +1,104 @@ +#include "SceTypes.hpp" + +#include +#include + +extern "C" { +int APS5_VABI sceNpAuthAbortRequest(int req_id); +int APS5_VABI sceNpAuthCreateAsyncRequest(const void* param); +int APS5_VABI sceNpAuthCreateRequest(void); +int APS5_VABI sceNpAuthDeleteRequest(int req_id); +int APS5_VABI sceNpAuthGetAuthorizationCodeV3(int req_id, const void* param, void* auth_code, int* issuer_id); +int APS5_VABI sceNpAuthGetIdTokenV3(int req_id, const void* param, void* id_token); +int APS5_VABI sceNpAuthPollAsync(int req_id, int* result); +int APS5_VABI sceNpAuthWaitAsync(int req_id, int* result); +} + +static void Require(bool condition) { + if (!condition) { + std::abort(); + } +} + +int main() { + constexpr int invalidArgument = static_cast(0x80550301); + constexpr int signedOut = static_cast(0x80550006); + constexpr int aborted = static_cast(0x80550304); + constexpr int requestNotFound = static_cast(0x80550306); + constexpr int invalidId = static_cast(0x80550307); + + const int syncReq = sceNpAuthCreateRequest(); + const int syncReq2 = sceNpAuthCreateRequest(); + Require(syncReq > 0); + Require(syncReq2 > syncReq); + + const int asyncReq = sceNpAuthCreateAsyncRequest(nullptr); + const int asyncReq2 = sceNpAuthCreateAsyncRequest(nullptr); + Require(asyncReq > syncReq2); + Require(asyncReq2 > asyncReq); + + alignas(8) char param[16] = {}; + alignas(8) char token[16] = {}; + alignas(8) char authCode[16] = {}; + int issuerId = 0; + + int pollUnprocessed = 0; + Require(sceNpAuthPollAsync(asyncReq, &pollUnprocessed) == invalidId); + int waitUnprocessed = 0; + Require(sceNpAuthWaitAsync(asyncReq, &waitUnprocessed) == invalidId); + + Require(sceNpAuthGetIdTokenV3(syncReq, nullptr, token) == invalidArgument); + Require(sceNpAuthGetIdTokenV3(syncReq, param, nullptr) == invalidArgument); + Require(sceNpAuthGetIdTokenV3(syncReq, nullptr, nullptr) == invalidArgument); + Require(sceNpAuthGetIdTokenV3(syncReq, param, token) == signedOut); + Require(sceNpAuthGetIdTokenV3(syncReq, param, token) == invalidArgument); + Require(sceNpAuthGetAuthorizationCodeV3(syncReq, nullptr, authCode, &issuerId) == invalidArgument); + Require(sceNpAuthGetAuthorizationCodeV3(syncReq, param, nullptr, &issuerId) == invalidArgument); + Require(sceNpAuthGetAuthorizationCodeV3(syncReq2, param, authCode, &issuerId) == signedOut); + Require(sceNpAuthGetAuthorizationCodeV3(syncReq2, param, authCode, &issuerId) == invalidArgument); + + Require(sceNpAuthGetIdTokenV3(asyncReq, param, token) == 0); + Require(sceNpAuthGetIdTokenV3(asyncReq, param, token) == invalidArgument); + int asyncPoll = 0; + Require(sceNpAuthPollAsync(asyncReq, &asyncPoll) == 0); + Require(asyncPoll == signedOut); + int asyncWait = 0; + Require(sceNpAuthWaitAsync(asyncReq, &asyncWait) == 0); + Require(asyncWait == signedOut); + + Require(sceNpAuthGetAuthorizationCodeV3(asyncReq2, param, authCode, &issuerId) == 0); + Require(sceNpAuthGetAuthorizationCodeV3(asyncReq2, param, authCode, &issuerId) == invalidArgument); + int asyncWait2 = 0; + Require(sceNpAuthWaitAsync(asyncReq2, &asyncWait2) == 0); + Require(asyncWait2 == signedOut); + + constexpr int unknownReq = 999999; + Require(sceNpAuthGetIdTokenV3(unknownReq, param, token) == requestNotFound); + Require(sceNpAuthGetAuthorizationCodeV3(unknownReq, param, authCode, &issuerId) == requestNotFound); + int pollResult = 0; + Require(sceNpAuthPollAsync(unknownReq, &pollResult) == requestNotFound); + int waitResult = 0; + Require(sceNpAuthWaitAsync(unknownReq, &waitResult) == requestNotFound); + + Require(sceNpAuthWaitAsync(syncReq, nullptr) == invalidArgument); + Require(sceNpAuthWaitAsync(syncReq, &waitResult) == invalidId); + Require(sceNpAuthPollAsync(syncReq, &pollResult) == invalidId); + + Require(sceNpAuthAbortRequest(asyncReq) == 0); + Require(sceNpAuthGetIdTokenV3(asyncReq, param, token) == invalidArgument); + Require(sceNpAuthAbortRequest(unknownReq) == requestNotFound); + + const int abortedReq = sceNpAuthCreateAsyncRequest(nullptr); + Require(sceNpAuthAbortRequest(abortedReq) == 0); + Require(sceNpAuthGetIdTokenV3(abortedReq, param, token) == aborted); + Require(sceNpAuthGetAuthorizationCodeV3(abortedReq, param, authCode, &issuerId) == aborted); + int abortedWait = 0; + Require(sceNpAuthWaitAsync(abortedReq, &abortedWait) == 0); + Require(abortedWait == aborted); + + Require(sceNpAuthDeleteRequest(syncReq) == 0); + Require(sceNpAuthGetIdTokenV3(syncReq, param, token) == requestNotFound); + Require(sceNpAuthWaitAsync(syncReq, &waitResult) == requestNotFound); + + return 0; +} diff --git a/docs/dev/debt/libSceNpAuth.md b/docs/dev/debt/libSceNpAuth.md index d7ae48d68b70..a7d0e43b7bc3 100644 --- a/docs/dev/debt/libSceNpAuth.md +++ b/docs/dev/debt/libSceNpAuth.md @@ -3,3 +3,4 @@ ### Unknown function info - [sceNpAuthGetAuthorizedAppCode](../../../core/libs/prx/libSceNpAuth/Export.cpp) (libSceNpAuth) - declared without parameters as a throwing stub; Minecraft (PPSA17221) imports it and fails to load without it. The request parameter and code output are not known, and there is no PSN sign-in for it to succeed against. +- [sceNpAuthAbortRequest, sceNpAuthGetAuthorizationCodeV3, sceNpAuthGetIdTokenV3, sceNpAuthPollAsync, sceNpAuthWaitAsync](../../../core/libs/prx/libSceNpAuth/Export.cpp) (libSceNpAuth) - request tracking, `ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND` (0x80550306) on unknown request IDs, `ORBIS_NP_AUTH_ERROR_INVALID_ID` (0x80550307) on non-async or unprocessed requests in Poll/Wait, `ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT` (0x80550301) on null arguments and repeated calls on completed requests, and `ORBIS_NP_AUTH_ERROR_ABORTED` (0x80550304) after an abort follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/np/np_auth.cpp) (PS4); async requests return OK and defer `SCE_NP_ERROR_SIGNED_OUT` to `sceNpAuthPollAsync`/`sceNpAuthWaitAsync` while sync requests fail immediately; PlayStation Network is not contacted. The parameter `size` fields are not checked (`ORBIS_NP_AUTH_ERROR_INVALID_SIZE`, structure layouts unknown), the request table has no limit (`ORBIS_NP_AUTH_ERROR_REQUEST_MAX`), and console behavior on PS5 is unverified