From 802ec551c2e9837249bbcb57f747a1a0f2965e8b Mon Sep 17 00:00:00 2001 From: hugodearaujo Date: Sun, 11 Oct 2026 20:12:43 -0300 Subject: [PATCH] fix(libSceUserService): answer sceUserServiceGetUserName as the console does On a PS5 (firmware 13.60) a null name returns INVALID_ARGUMENT first, then a user id the console does not know returns 0x80960105 whatever the buffer, and a buffer shorter than 17 bytes is zeroed before BUFFER_TOO_SHORT is returned. Here an unknown user returned INVALID_ARGUMENT and a short buffer was left untouched. Co-Authored-By: Claude Opus 5.5 --- .../libs/prx/libSceUserService/CMakeLists.txt | 11 +++++ core/libs/prx/libSceUserService/Export.cpp | 6 ++- .../prx/libSceUserService/UserService.hpp | 1 + core/libs/tests/GuestUserServiceUserName.cpp | 49 +++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 5 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 core/libs/tests/GuestUserServiceUserName.cpp diff --git a/core/libs/prx/libSceUserService/CMakeLists.txt b/core/libs/prx/libSceUserService/CMakeLists.txt index 444c378c2..ac0ab70e9 100644 --- a/core/libs/prx/libSceUserService/CMakeLists.txt +++ b/core/libs/prx/libSceUserService/CMakeLists.txt @@ -12,3 +12,14 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_user_service_user_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestUserServiceUserName.cpp) + target_include_directories(guest_user_service_user_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_user_service_user_name_tests PRIVATE libSceUserService) + add_test(NAME guest_user_service_user_name COMMAND guest_user_service_user_name_tests) + if(WIN32) + set_tests_properties(guest_user_service_user_name PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceUserService/Export.cpp b/core/libs/prx/libSceUserService/Export.cpp index 2a54f7c9f..e3b110d81 100644 --- a/core/libs/prx/libSceUserService/Export.cpp +++ b/core/libs/prx/libSceUserService/Export.cpp @@ -117,10 +117,14 @@ int APS5_VABI sceUserServiceGetLoginUserIdList(UserServiceLoginUserIdList* user_ } int APS5_VABI sceUserServiceGetUserName(int user_id, char* name, size_t size) { - if (name == nullptr || user_id != USER_SERVICE_INITIAL_USER_ID) { + if (name == nullptr) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } + if (user_id != USER_SERVICE_INITIAL_USER_ID) { + return USER_SERVICE_ERROR_UNKNOWN_USER; + } if (size < USER_SERVICE_MAX_USER_NAME_LENGTH + 1) { + std::memset(name, 0, size); return USER_SERVICE_ERROR_BUFFER_TOO_SHORT; } std::strncpy(name, USER_SERVICE_INITIAL_USER_NAME, size); diff --git a/core/libs/prx/libSceUserService/UserService.hpp b/core/libs/prx/libSceUserService/UserService.hpp index dcc140dad..8c4c9c0e2 100644 --- a/core/libs/prx/libSceUserService/UserService.hpp +++ b/core/libs/prx/libSceUserService/UserService.hpp @@ -10,6 +10,7 @@ constexpr int USER_SERVICE_ERROR_NO_EVENT = -2137653241; constexpr int USER_SERVICE_USER_ID_INVALID = -1; constexpr int USER_SERVICE_INITIAL_USER_ID = 0x10000000; constexpr int USER_SERVICE_ERROR_BUFFER_TOO_SHORT = -2137653238; +constexpr int USER_SERVICE_ERROR_UNKNOWN_USER = static_cast(0x80960105u); constexpr std::size_t USER_SERVICE_MAX_USER_NAME_LENGTH = 16; constexpr const char* USER_SERVICE_INITIAL_USER_NAME = "Player"; diff --git a/core/libs/tests/GuestUserServiceUserName.cpp b/core/libs/tests/GuestUserServiceUserName.cpp new file mode 100644 index 000000000..f75516297 --- /dev/null +++ b/core/libs/tests/GuestUserServiceUserName.cpp @@ -0,0 +1,49 @@ +#include "SceTypes.hpp" +#include "prx/libSceUserService/UserService.hpp" +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceUserServiceGetUserName(int user_id, char* name, std::size_t size); +} + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +bool Filled(const char* buffer, std::size_t from, std::size_t to, char value) { + for (std::size_t i = from; i < to; ++i) { + if (buffer[i] != value) return false; + } + return true; +} + +} + +int main() { + char name[32]; + const std::size_t length = std::strlen(USER_SERVICE_INITIAL_USER_NAME); + + for (std::size_t size = 0; size <= USER_SERVICE_MAX_USER_NAME_LENGTH; ++size) { + std::memset(name, 'A', sizeof(name)); + Require(sceUserServiceGetUserName(USER_SERVICE_INITIAL_USER_ID, name, size) == USER_SERVICE_ERROR_BUFFER_TOO_SHORT); + Require(Filled(name, 0, size, 0) && Filled(name, size, sizeof(name), 'A')); + } + for (std::size_t size : {USER_SERVICE_MAX_USER_NAME_LENGTH + 1, sizeof(name)}) { + std::memset(name, 'A', sizeof(name)); + Require(sceUserServiceGetUserName(USER_SERVICE_INITIAL_USER_ID, name, size) == USER_SERVICE_OK); + Require(std::strcmp(name, USER_SERVICE_INITIAL_USER_NAME) == 0); + Require(Filled(name, length, size, 0) && Filled(name, size, sizeof(name), 'A')); + } + + for (int user : {0, USER_SERVICE_USER_ID_INVALID, 1, 0xff, 0x12345678}) { + std::memset(name, 'A', sizeof(name)); + Require(sceUserServiceGetUserName(user, name, sizeof(name)) == USER_SERVICE_ERROR_UNKNOWN_USER); + Require(sceUserServiceGetUserName(user, name, 1) == USER_SERVICE_ERROR_UNKNOWN_USER); + Require(Filled(name, 0, sizeof(name), 'A')); + Require(sceUserServiceGetUserName(user, nullptr, sizeof(name)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + } + Require(sceUserServiceGetUserName(USER_SERVICE_INITIAL_USER_ID, nullptr, sizeof(name)) == USER_SERVICE_ERROR_INVALID_ARGUMENT); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 0fa78fec4..cc1522eee 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -291,6 +291,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceHttpSetRedirectCallback, sceHttpSetCookieRecvCallback, sceHttpsSetSslVersion, sceHttpsGetSslError](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - signatures follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.cpp) (PS4), which only logs and returns 0; a null `errNum` or `detail` in `sceHttpsGetSslError` is assumed to return `SCE_HTTP_ERROR_INVALID_VALUE` like `sceHttpGetLastErrno` - [sceHttpSetAuthInfoCallback](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - signature and callback type follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.h) (PS4); no PS5 reference has them. The `id` is not checked, as in the other libSceHttp setters - [libSceHttp2 cookie boxes](../../core/libs/prx/libSceHttp2/Export.cpp) (libSceHttp2) - `sceHttp2CreateCookieBox` is taken to take the library context id first, as in the [prosper](https://github.com/mattias800/prosper) reimplementation; further arguments are unknown and ignored. `sceHttp2SetRequestNoContentLength` is assumed to take only the request id. Cookies are never stored, so `sceHttp2CookieFlush` has nothing to discard +- [sceUserServiceGetUserName](../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - `0x80960105`, which a PS5 (firmware 13.60) returns for a user id it does not know, has no known name; it is `USER_SERVICE_ERROR_UNKNOWN_USER` here. On the console a registered user who is not logged in still gets their name; here only the initial user exists - [sceNpSessionSignalingGetMemoryInfo](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature - [sceNpSessionSignalingGetConnectionStatistics](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature - [sceUserServiceGetAgeLevel](../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - the meaning of the value is unknown; 0 is reported, as for the unset game presets. PPSA12544 (Unity) only stores it for its user profile