diff --git a/src/common/elf_info.h b/src/common/elf_info.h index 7a00fa486..0525679a9 100644 --- a/src/common/elf_info.h +++ b/src/common/elf_info.h @@ -94,6 +94,7 @@ public: static constexpr u32 FW_700 = 0x7000000; static constexpr u32 FW_750 = 0x7500000; static constexpr u32 FW_800 = 0x8000000; + static constexpr u32 FW_900 = 0x9000000; static constexpr u32 FW_1000 = 0x10000000; static constexpr u32 FW_1150 = 0x11500000; diff --git a/src/core/libraries/np/np_handler.cpp b/src/core/libraries/np/np_handler.cpp index 0f70a5e09..70abe28cf 100644 --- a/src/core/libraries/np/np_handler.cpp +++ b/src/core/libraries/np/np_handler.cpp @@ -27,7 +27,8 @@ bool NpHandler::IsPsnSignedIn(s32 user_id) const { } s32 NpHandler::GetUserIdByOnlineId(const OrbisNpOnlineId& online_id) const { - return 1000; // return dummy user + UserManager& user_manager = UserManagement; + return user_manager.GetDefaultUser().user_id; // dummy return default user id for now //TODO } } // namespace Libraries::Np \ No newline at end of file diff --git a/src/core/libraries/np/np_manager.cpp b/src/core/libraries/np/np_manager.cpp index 9a3c5e228..c9ee76f86 100644 --- a/src/core/libraries/np/np_manager.cpp +++ b/src/core/libraries/np/np_manager.cpp @@ -2,23 +2,29 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include #include #include +#include "common/elf_info.h" #include "common/logging/log.h" #include "core/emulator_settings.h" #include "core/libraries/error_codes.h" +#include "core/libraries/kernel/process.h" #include "core/libraries/libs.h" #include "core/libraries/np/np_error.h" #include "core/libraries/np/np_manager.h" #include "core/tls.h" +#include "core/user_manager.h" +#include "np_handler.h" namespace Libraries::Np::NpManager { static bool g_shadnet_enabled = false; +static s32 g_firmware_version = -1; static s32 g_active_requests = 0; static std::mutex g_request_mutex; @@ -43,17 +49,76 @@ struct NpRequest { s32 result; }; +static void FillCountryCodeFromProfile(Libraries::UserService::OrbisUserServiceUserId user_id, + OrbisNpCountryCode* out) { + std::memset(out, 0, sizeof(OrbisNpCountryCode)); + const User* u = UserManagement.GetUserByID(user_id); + const std::string cfg = u ? u->np_country : std::string(); + const bool ok = cfg.size() == 2 && std::isalpha(static_cast(cfg[0])) && + std::isalpha(static_cast(cfg[1])); + const char* src = ok ? cfg.c_str() : "us"; + std::memcpy(out->country_code, src, 2); +} + +static void FillLanguageCodeFromProfile(Libraries::UserService::OrbisUserServiceUserId user_id, + OrbisNpLanguageCode* out) { + std::memset(out, 0, sizeof(OrbisNpLanguageCode)); + const User* u = UserManagement.GetUserByID(user_id); + const std::string cfg = u ? u->np_language : std::string(); + const bool ok = cfg.size() == 2 && std::isalpha(static_cast(cfg[0])) && + std::isalpha(static_cast(cfg[1])); + const char* src = ok ? cfg.c_str() : "en"; + std::memcpy(out->code, src, 2); +} + +static s8 GetAgeFromProfile(Libraries::UserService::OrbisUserServiceUserId user_id) { + const User* u = UserManagement.GetUserByID(user_id); + const int v = u ? static_cast(u->np_age) : 0; + if (v <= 0 || v > 127) { + return 13; + } + return static_cast(v); +} + +static void FillDateOfBirthFromProfile(Libraries::UserService::OrbisUserServiceUserId user_id, + OrbisNpDate* out) { + const User* u = UserManagement.GetUserByID(user_id); + const std::string s = u ? u->np_date_of_birth : std::string(); + + int y = 0, m = 0, d = 0; + bool ok = s.size() == 10 && s[4] == '-' && s[7] == '-'; + if (ok) { + auto is_digit_at = [&](size_t i) { return std::isdigit(static_cast(s[i])); }; + for (size_t i : {0u, 1u, 2u, 3u, 5u, 6u, 8u, 9u}) { + if (!is_digit_at(i)) { + ok = false; + break; + } + } + } + if (ok) { + y = (s[0] - '0') * 1000 + (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0'); + m = (s[5] - '0') * 10 + (s[6] - '0'); + d = (s[8] - '0') * 10 + (s[9] - '0'); + ok = y >= 1900 && y <= 2100 && m >= 1 && m <= 12 && d >= 1 && d <= 31; + } + + out->year = ok ? static_cast(y) : 2000; + out->month = ok ? static_cast(m) : 1; + out->day = ok ? static_cast(d) : 1; +} + static std::vector g_requests; -s32 CreateNpRequest(bool async) { +static s32 CreateNpRequest(bool async) { + std::scoped_lock lk{g_request_mutex}; + if (g_active_requests == ORBIS_NP_MANAGER_REQUEST_LIMIT) { return ORBIS_NP_ERROR_REQUEST_MAX; } - std::scoped_lock lk{g_request_mutex}; - s32 req_index = 0; - while (req_index < g_requests.size()) { + while (req_index < static_cast(g_requests.size())) { // Find first nonexistant request if (g_requests[req_index].state == NpRequestState::None) { // There is no request at this index, set the index to ready then break. @@ -64,7 +129,7 @@ s32 CreateNpRequest(bool async) { req_index++; } - if (req_index == g_requests.size()) { + if (req_index == static_cast(g_requests.size())) { // There are no requests to replace. NpRequest new_request{NpRequestState::Ready, async, 0}; g_requests.emplace_back(new_request); @@ -75,6 +140,45 @@ s32 CreateNpRequest(bool async) { return req_index + ORBIS_NP_MANAGER_REQUEST_ID_OFFSET + 1; } +// Validate a request ID and return the NpRequest*, or nullptr + error code. +// Writes the error code to *out_err when returning nullptr. +static NpRequest* GetRequest(s32 req_id, s32* out_err) { + if (req_id == 0) { + *out_err = ORBIS_NP_ERROR_INVALID_ARGUMENT; + return nullptr; + } + s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; + if (g_active_requests == 0 || req_index < 0 || + req_index >= static_cast(g_requests.size()) || + g_requests[req_index].state == NpRequestState::None) { + *out_err = ORBIS_NP_ERROR_REQUEST_NOT_FOUND; + return nullptr; + } + auto& req = g_requests[req_index]; + if (req.state == NpRequestState::Complete) { + req.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; + *out_err = ORBIS_NP_ERROR_INVALID_ARGUMENT; + return nullptr; + } + if (req.state == NpRequestState::Aborted) { + req.result = ORBIS_NP_ERROR_ABORTED; + *out_err = ORBIS_NP_ERROR_ABORTED; + return nullptr; + } + return &req; +} + +// Complete a request and return OK (or SIGNED_OUT for async when disabled). +static s32 CompleteRequest(NpRequest& req, s32 result) { + req.state = NpRequestState::Complete; + req.result = result; + if (result != ORBIS_OK && req.async) { + // Async requests always return OK immediately; result is read via PollAsync/WaitAsync. + return ORBIS_OK; + } + return result; +} + s32 PS4_SYSV_ABI sceNpCreateRequest() { LOG_DEBUG(Lib_NpManager, "called"); return CreateNpRequest(false); @@ -97,166 +201,83 @@ s32 PS4_SYSV_ABI sceNpCheckNpAvailability(s32 req_id, OrbisNpOnlineId* online_id if (online_id == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; + const s32 user_id = Libraries::Np::NpHandler::GetInstance().GetUserIdByOnlineId(*online_id); + if (user_id == -1) { + return ORBIS_NP_ERROR_USER_NOT_FOUND; } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; - } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; - } - - LOG_ERROR(Lib_NpManager, "(STUBBED) called, req_id = {:#x}, is_async = {}", req_id, - request.async); - - request.result = ORBIS_OK; - return ORBIS_OK; + return sceNpCheckNpAvailabilityA(req_id, user_id); } s32 PS4_SYSV_ABI sceNpCheckNpAvailabilityA(s32 req_id, Libraries::UserService::OrbisUserServiceUserId user_id) { - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; - } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; + if (user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + LOG_ERROR(Lib_NpManager, "invalid user_id {}", user_id); return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; + std::scoped_lock lk{g_request_mutex}; + s32 err; + NpRequest* req = GetRequest(req_id, &err); + if (!req) + return err; + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { + return CompleteRequest(*req, ORBIS_NP_ERROR_SIGNED_OUT); } - - LOG_ERROR(Lib_NpManager, "(STUBBED) called, req_id = {:#x}, is_async = {}", req_id, - request.async); - - request.result = ORBIS_OK; - return ORBIS_OK; + LOG_DEBUG(Lib_NpManager, "req_id = {:#x}, user_id = {}", req_id, user_id); + return CompleteRequest(*req, ORBIS_OK); } s32 PS4_SYSV_ABI sceNpCheckNpReachability(s32 req_id, Libraries::UserService::OrbisUserServiceUserId user_id) { - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; - } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; + if (user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; + std::scoped_lock lk{g_request_mutex}; + s32 err; + NpRequest* req = GetRequest(req_id, &err); + if (!req) + return err; + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { + return CompleteRequest(*req, ORBIS_NP_ERROR_SIGNED_OUT); } - - LOG_ERROR(Lib_NpManager, "(STUBBED) called, req_id = {:#x}, is_async = {}", req_id, - request.async); - - request.result = ORBIS_OK; - return ORBIS_OK; + LOG_DEBUG(Lib_NpManager, "req_id = {:#x}, user_id = {}", req_id, user_id); + return CompleteRequest(*req, ORBIS_OK); } s32 PS4_SYSV_ABI sceNpCheckPlus(s32 req_id, const OrbisNpCheckPlusParameter* param, OrbisNpCheckPlusResult* result) { - if (req_id == 0 || param == nullptr || result == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (param->size != sizeof(OrbisNpCheckPlusParameter)) { return ORBIS_NP_ERROR_INVALID_SIZE; } - + if (param->user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } if (param->features < 1 || param->features > 3) { // TODO: If compiled SDK version is greater or equal to fw 3.50, - // error if param->features != 1 instead. + // // error if param->features != 1 instead. return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; - } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; - } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; + // The reserved field must be zero-initialized by the caller. + for (u8 b : param->reserved) { + if (b != 0) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - return ORBIS_NP_ERROR_SIGNED_OUT; } - - LOG_ERROR(Lib_NpManager, - "(STUBBED) called, req_id = {:#x}, is_async = {}, param.features = {:#x}", req_id, - request.async, param->features); - - // For now, set authorized to true to signal PS+ access. + std::scoped_lock lk{g_request_mutex}; + s32 err; + NpRequest* req = GetRequest(req_id, &err); + if (!req) + return err; + if (!g_shadnet_enabled || + !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(param->user_id)) { + return CompleteRequest(*req, ORBIS_NP_ERROR_SIGNED_OUT); + } + LOG_DEBUG(Lib_NpManager, "req_id = {:#x}, features = {:#x}", req_id, param->features); + // Grant PS+ — shadNet has no subscription gating. result->authorized = true; - - request.result = ORBIS_OK; - return ORBIS_OK; + return CompleteRequest(*req, ORBIS_OK); } s32 PS4_SYSV_ABI sceNpGetAccountLanguage(s32 req_id, OrbisNpOnlineId* online_id, @@ -264,84 +285,31 @@ s32 PS4_SYSV_ABI sceNpGetAccountLanguage(s32 req_id, OrbisNpOnlineId* online_id, if (online_id == nullptr || language == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; + const s32 user_id = Libraries::Np::NpHandler::GetInstance().GetUserIdByOnlineId(*online_id); + if (user_id == -1) { + return ORBIS_NP_ERROR_USER_NOT_FOUND; } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; - } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; - } - - LOG_ERROR(Lib_NpManager, "(STUBBED) called, req_id = {:#x}, is_async = {}", req_id, - request.async); - - std::memset(language, 0, sizeof(OrbisNpLanguageCode)); - - request.result = ORBIS_OK; - return ORBIS_OK; + return sceNpGetAccountLanguageA(req_id, user_id, language); } s32 PS4_SYSV_ABI sceNpGetAccountLanguageA(s32 req_id, Libraries::UserService::OrbisUserServiceUserId user_id, OrbisNpLanguageCode* language) { - if (language == nullptr) { + if (language == nullptr || + user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; + s32 err; + NpRequest* req = GetRequest(req_id, &err); + if (!req) + return err; + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { + return CompleteRequest(*req, ORBIS_NP_ERROR_SIGNED_OUT); } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; - } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; - } - - LOG_ERROR(Lib_NpManager, "(STUBBED) called, req_id = {:#x}, user_id = {}, is_async = {}", - req_id, user_id, request.async); - - std::memset(language, 0, sizeof(OrbisNpLanguageCode)); - - request.result = ORBIS_OK; - return ORBIS_OK; + LOG_DEBUG(Lib_NpManager, "req_id = {:#x}, user_id = {}", req_id, user_id); + FillLanguageCodeFromProfile(user_id, language); + return CompleteRequest(*req, ORBIS_OK); } s32 PS4_SYSV_ABI sceNpGetParentalControlInfo(s32 req_id, OrbisNpOnlineId* online_id, s8* age, @@ -349,88 +317,32 @@ s32 PS4_SYSV_ABI sceNpGetParentalControlInfo(s32 req_id, OrbisNpOnlineId* online if (online_id == nullptr || age == nullptr || info == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; + const s32 user_id = Libraries::Np::NpHandler::GetInstance().GetUserIdByOnlineId(*online_id); + if (user_id == -1) { + return ORBIS_NP_ERROR_USER_NOT_FOUND; } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; - } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; - } - - LOG_ERROR(Lib_NpManager, "(STUBBED) called, req_id = {:#x}, is_async = {}", req_id, - request.async); - - // TODO: Add to config? - *age = 13; - std::memset(info, 0, sizeof(OrbisNpParentalControlInfo)); - - request.result = ORBIS_OK; - return ORBIS_OK; + return sceNpGetParentalControlInfoA(req_id, user_id, age, info); } s32 PS4_SYSV_ABI sceNpGetParentalControlInfoA(s32 req_id, Libraries::UserService::OrbisUserServiceUserId user_id, s8* age, OrbisNpParentalControlInfo* info) { - if (age == nullptr || info == nullptr) { + if (age == nullptr || info == nullptr || + user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - std::scoped_lock lk{g_request_mutex}; - - s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || - g_requests[req_index].state == NpRequestState::None) { - return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; + s32 err; + NpRequest* req = GetRequest(req_id, &err); + if (!req) + return err; + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { + return CompleteRequest(*req, ORBIS_NP_ERROR_SIGNED_OUT); } - - auto& request = g_requests[req_index]; - if (request.state == NpRequestState::Complete) { - request.result = ORBIS_NP_ERROR_INVALID_ARGUMENT; - return ORBIS_NP_ERROR_INVALID_ARGUMENT; - } else if (request.state == NpRequestState::Aborted) { - request.result = ORBIS_NP_ERROR_ABORTED; - return ORBIS_NP_ERROR_ABORTED; - } - - request.state = NpRequestState::Complete; - if (!g_shadnet_enabled) { - request.result = ORBIS_NP_ERROR_SIGNED_OUT; - // If the request is processed in some form, and it's an async request, then it returns OK. - if (request.async) { - return ORBIS_OK; - } - return ORBIS_NP_ERROR_SIGNED_OUT; - } - - LOG_ERROR(Lib_NpManager, "(STUBBED) called, req_id = {:#x}, user_id = {}, is_async = {}", - req_id, user_id, request.async); - - // TODO: Add to config? - *age = 13; + LOG_DEBUG(Lib_NpManager, "req_id = {:#x}, user_id = {}", req_id, user_id); + *age = GetAgeFromProfile(user_id); std::memset(info, 0, sizeof(OrbisNpParentalControlInfo)); - - request.result = ORBIS_OK; - return ORBIS_OK; + return CompleteRequest(*req, ORBIS_OK); } s32 PS4_SYSV_ABI sceNpAbortRequest(s32 req_id) { @@ -439,7 +351,8 @@ s32 PS4_SYSV_ABI sceNpAbortRequest(s32 req_id) { std::scoped_lock lk{g_request_mutex}; s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || + if (g_active_requests == 0 || req_index < 0 || + req_index >= static_cast(g_requests.size()) || g_requests[req_index].state == NpRequestState::None) { return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; } @@ -461,7 +374,8 @@ s32 PS4_SYSV_ABI sceNpWaitAsync(s32 req_id, s32* result) { std::scoped_lock lk{g_request_mutex}; s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || + if (g_active_requests == 0 || req_index < 0 || + req_index >= static_cast(g_requests.size()) || g_requests[req_index].state == NpRequestState::None) { return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; } @@ -486,7 +400,8 @@ s32 PS4_SYSV_ABI sceNpPollAsync(s32 req_id, s32* result) { std::scoped_lock lk{g_request_mutex}; s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || + if (g_active_requests == 0 || req_index < 0 || + req_index >= static_cast(g_requests.size()) || g_requests[req_index].state == NpRequestState::None) { return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; } @@ -509,7 +424,8 @@ s32 PS4_SYSV_ABI sceNpDeleteRequest(s32 req_id) { std::scoped_lock lk{g_request_mutex}; s32 req_index = req_id - ORBIS_NP_MANAGER_REQUEST_ID_OFFSET - 1; - if (g_active_requests == 0 || g_requests.size() <= req_index || + if (g_active_requests == 0 || req_index < 0 || + req_index >= static_cast(g_requests.size()) || g_requests[req_index].state == NpRequestState::None) { return ORBIS_NP_ERROR_REQUEST_NOT_FOUND; } @@ -524,26 +440,30 @@ s32 PS4_SYSV_ABI sceNpGetAccountCountry(OrbisNpOnlineId* online_id, if (online_id == nullptr || country_code == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (!g_shadnet_enabled) { + const s32 user_id = Libraries::Np::NpHandler::GetInstance().GetUserIdByOnlineId(*online_id); + if (user_id == -1) { + return ORBIS_NP_ERROR_USER_NOT_FOUND; + } + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { return ORBIS_NP_ERROR_SIGNED_OUT; } - std::memset(country_code, 0, sizeof(OrbisNpCountryCode)); - // TODO: get NP country code from config - std::memcpy(country_code->country_code, "us", 2); + FillCountryCodeFromProfile(user_id, country_code); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNpGetAccountCountryA(Libraries::UserService::OrbisUserServiceUserId user_id, OrbisNpCountryCode* country_code) { - if (country_code == nullptr) { + if (country_code == nullptr || + user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (!g_shadnet_enabled) { + if (UserManagement.GetUserByID(user_id) == nullptr) { + return ORBIS_NP_ERROR_USER_NOT_FOUND; + } + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { return ORBIS_NP_ERROR_SIGNED_OUT; } - std::memset(country_code, 0, sizeof(OrbisNpCountryCode)); - // TODO: get NP country code from config - std::memcpy(country_code->country_code, "us", 2); + FillCountryCodeFromProfile(user_id, country_code); return ORBIS_OK; } @@ -552,30 +472,30 @@ s32 PS4_SYSV_ABI sceNpGetAccountDateOfBirth(OrbisNpOnlineId* online_id, if (online_id == nullptr || date_of_birth == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (!g_shadnet_enabled) { + const s32 user_id = Libraries::Np::NpHandler::GetInstance().GetUserIdByOnlineId(*online_id); + if (user_id == -1) { + return ORBIS_NP_ERROR_USER_NOT_FOUND; + } + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { return ORBIS_NP_ERROR_SIGNED_OUT; } - - // TODO: maybe add to config? - date_of_birth->day = 1; - date_of_birth->month = 1; - date_of_birth->year = 2000; + FillDateOfBirthFromProfile(user_id, date_of_birth); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNpGetAccountDateOfBirthA(Libraries::UserService::OrbisUserServiceUserId user_id, OrbisNpDate* date_of_birth) { - if (date_of_birth == nullptr) { + if (date_of_birth == nullptr || + user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (!g_shadnet_enabled) { + if (UserManagement.GetUserByID(user_id) == nullptr) { + return ORBIS_NP_ERROR_USER_NOT_FOUND; + } + if (!g_shadnet_enabled || !Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) { return ORBIS_NP_ERROR_SIGNED_OUT; } - - // TODO: maybe add to config? - date_of_birth->day = 1; - date_of_birth->month = 1; - date_of_birth->year = 2000; + FillDateOfBirthFromProfile(user_id, date_of_birth); return ORBIS_OK; } @@ -584,9 +504,11 @@ s32 PS4_SYSV_ABI sceNpGetGamePresenceStatus(OrbisNpOnlineId* online_id, if (online_id == nullptr || game_status == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - - *game_status = - g_shadnet_enabled ? OrbisNpGamePresenseStatus::Online : OrbisNpGamePresenseStatus::Offline; + const s32 user_id = Libraries::Np::NpHandler::GetInstance().GetUserIdByOnlineId(*online_id); + *game_status = (g_shadnet_enabled && user_id != -1 && + Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) + ? OrbisNpGamePresenseStatus::Online + : OrbisNpGamePresenseStatus::Offline; return ORBIS_OK; } @@ -595,9 +517,10 @@ s32 PS4_SYSV_ABI sceNpGetGamePresenceStatusA(Libraries::UserService::OrbisUserSe if (game_status == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - *game_status = - g_shadnet_enabled ? OrbisNpGamePresenseStatus::Online : OrbisNpGamePresenseStatus::Offline; + (g_shadnet_enabled && Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) + ? OrbisNpGamePresenseStatus::Online + : OrbisNpGamePresenseStatus::Offline; return ORBIS_OK; } @@ -677,8 +600,14 @@ s32 PS4_SYSV_ABI sceNpGetNpReachabilityState(Libraries::UserService::OrbisUserSe return ORBIS_NP_ERROR_USER_NOT_FOUND; } - *state = g_shadnet_enabled ? OrbisNpReachabilityState::Reachable - : OrbisNpReachabilityState::Unavailable; + if (user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + if (g_firmware_version < 0 || g_firmware_version >= Common::ElfInfo::FW_400) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + } + *state = (g_shadnet_enabled && Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id)) + ? OrbisNpReachabilityState::Reachable + : OrbisNpReachabilityState::Unavailable; return ORBIS_OK; } @@ -690,8 +619,21 @@ s32 PS4_SYSV_ABI sceNpGetState(Libraries::UserService::OrbisUserServiceUserId us if (UserManagement.GetUserByID(user_id) == nullptr) { return ORBIS_NP_ERROR_USER_NOT_FOUND; } - *state = g_shadnet_enabled ? OrbisNpState::SignedIn : OrbisNpState::SignedOut; - LOG_DEBUG(Lib_NpManager, "Signed {}", g_shadnet_enabled ? "in" : "out"); + if (user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + if (g_firmware_version < 0 || g_firmware_version >= Common::ElfInfo::FW_900) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + } + if (!g_shadnet_enabled) { + *state = OrbisNpState::SignedOut; + LOG_DEBUG(Lib_NpManager, "shadNet disabled,SignedOut"); + return ORBIS_OK; + } + *state = Libraries::Np::NpHandler::GetInstance().IsPsnSignedIn(user_id) + ? OrbisNpState::SignedIn + : OrbisNpState::SignedOut; + LOG_DEBUG(Lib_NpManager, "user_id={} state={}", user_id, + *state == OrbisNpState::SignedIn ? "SignedIn" : "SignedOut"); return ORBIS_OK; } @@ -703,7 +645,7 @@ sceNpGetUserIdByAccountId(u64 account_id, Libraries::UserService::OrbisUserServi if (!g_shadnet_enabled) { return ORBIS_NP_ERROR_SIGNED_OUT; } - *user_id = 1; + *user_id = 1000; LOG_DEBUG(Lib_NpManager, "userid({}) = {}", account_id, *user_id); return ORBIS_OK; } @@ -714,10 +656,19 @@ s32 PS4_SYSV_ABI sceNpHasSignedUp(Libraries::UserService::OrbisUserServiceUserId if (has_signed_up == nullptr) { return ORBIS_NP_ERROR_INVALID_ARGUMENT; } - if (UserManagement.GetUserByID(user_id) == nullptr) { + *has_signed_up = false; + const User* u = UserManagement.GetUserByID(user_id); + if (u == nullptr) { return ORBIS_NP_ERROR_USER_NOT_FOUND; } - *has_signed_up = g_shadnet_enabled ? true : false; + if (user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + if (g_firmware_version < 0 || g_firmware_version >= Common::ElfInfo::FW_900) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + } + // A user has signed up if they have a shadNet npid configured. + // This is independent of shadnet_enabled and current connection state. + *has_signed_up = !u->shadnet_npid.empty(); return ORBIS_OK; } @@ -1014,6 +965,8 @@ void DeregisterNpCallback(std::string key) { } void RegisterLib(Core::Loader::SymbolsResolver* sym) { + ASSERT_MSG(Libraries::Kernel::sceKernelGetCompiledSdkVersion(&g_firmware_version) == ORBIS_OK, + "Failed to get compiled SDK version."); g_shadnet_enabled = EmulatorSettings.IsShadNetEnabled(); LIB_FUNCTION("GpLQDNKICac", "libSceNpManager", 1, "libSceNpManager", sceNpCreateRequest); diff --git a/src/core/libraries/np/np_manager.h b/src/core/libraries/np/np_manager.h index 60c8eb892..1cfe86972 100644 --- a/src/core/libraries/np/np_manager.h +++ b/src/core/libraries/np/np_manager.h @@ -113,6 +113,13 @@ s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId use OrbisNpId* np_id); s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId user_id, OrbisNpOnlineId* online_id); - +s32 PS4_SYSV_ABI sceNpCheckNpAvailabilityA(s32 req_id, + Libraries::UserService::OrbisUserServiceUserId user_id); +s32 PS4_SYSV_ABI sceNpGetAccountLanguageA(s32 req_id, + Libraries::UserService::OrbisUserServiceUserId user_id, + OrbisNpLanguageCode* language); +s32 PS4_SYSV_ABI +sceNpGetParentalControlInfoA(s32 req_id, Libraries::UserService::OrbisUserServiceUserId user_id, + s8* age, OrbisNpParentalControlInfo* info); void RegisterLib(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Np::NpManager diff --git a/src/core/user_manager.h b/src/core/user_manager.h index e4045f308..10489b4c2 100644 --- a/src/core/user_manager.h +++ b/src/core/user_manager.h @@ -20,6 +20,11 @@ struct User { std::string shadnet_token = ""; // 2FA/validation token (future use) std::string shadnet_email = ""; // email address (furute use) bool shadnet_enabled = false; // enable shadnet for user + // these are used to populate NP profile fields + std::string np_country = "us"; // ISO 3166-1 alpha-2, e.g. "us", "jp", "gb" + std::string np_language = "en"; // ISO 639-1, e.g. "en", "ja", "fr" + u8 np_age = 30; // 0..127 + std::string np_date_of_birth = "1994-01-01"; // ISO 8601 date "YYYY-MM-DD" }; struct Users { @@ -28,7 +33,8 @@ struct Users { }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(User, user_id, user_color, user_name, player_index, shadnet_npid, shadnet_password, shadnet_token, - shadnet_email, shadnet_enabled) + shadnet_email, shadnet_enabled, np_country, + np_language, np_age, np_date_of_birth) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Users, user, commit_hash) using LoggedInUsers = std::array;