diff --git a/core/libs/prx/libSceConvertKeycode/CMakeLists.txt b/core/libs/prx/libSceConvertKeycode/CMakeLists.txt index d04045b5c6..34d023d5ac 100644 --- a/core/libs/prx/libSceConvertKeycode/CMakeLists.txt +++ b/core/libs/prx/libSceConvertKeycode/CMakeLists.txt @@ -6,7 +6,7 @@ configure_windows_unwind(libSceConvertKeycode) if(BUILD_TESTING) add_test_executable(guest_convert_keycode_tests ${LIBS_INCLUDE_DIR}/tests/GuestConvertKeycode.cpp) target_include_directories(guest_convert_keycode_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_convert_keycode_tests PRIVATE libSceConvertKeycode libSceUserService libc) + target_link_libraries(guest_convert_keycode_tests PRIVATE libSceConvertKeycode libSceUserService libSceSystemService libc) configure_windows_unwind(guest_convert_keycode_tests) add_test(NAME guest_convert_keycode COMMAND guest_convert_keycode_tests) if(WIN32) diff --git a/core/libs/prx/libSceConvertKeycode/Export.cpp b/core/libs/prx/libSceConvertKeycode/Export.cpp index 24e61537bb..11d1fb7f32 100644 --- a/core/libs/prx/libSceConvertKeycode/Export.cpp +++ b/core/libs/prx/libSceConvertKeycode/Export.cpp @@ -7,6 +7,8 @@ #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" #include "prx/libSceConvertKeycode/ImeKeyboardType.hpp" +#include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" +#include extern "C" { int APS5_VABI sceUserServiceGetKeyboardType(int32_t userId, int32_t* type); @@ -2402,13 +2404,20 @@ char16_t ConvertCharacter(std::uint16_t keycode, std::uint32_t state, std::uint3 } return character; } +bool Ps4Client() { + try { + const auto titleId = GetAppTitleId_nid_postfix(); + return ImeKeyboardType::IsPs4TitleId(std::string_view(titleId.value, strnlen(titleId.value, sizeof(titleId.value)))); + } catch (const std::runtime_error&) { + return false; + } +} } // namespace extern "C" { int APS5_VABI sceConvertKeycodeGetImeKeyboardType(int32_t userId, uint32_t* type) { - // The PS4/PS5 client rule: AnyPS5 only runs PS5 processes (sceKernelIsPs4Process is unimplemented and reports 0). - constexpr bool ps4Client = false; + const bool ps4Client = Ps4Client(); const auto* process = static_cast(ApplicationProcessParameters_nid_no_patch()); std::uint32_t sdkVersion; std::memcpy(&sdkVersion, process + 16, sizeof(sdkVersion)); diff --git a/core/libs/prx/libSceConvertKeycode/ImeKeyboardType.hpp b/core/libs/prx/libSceConvertKeycode/ImeKeyboardType.hpp index fcf9ccbe16..4ace3c252c 100644 --- a/core/libs/prx/libSceConvertKeycode/ImeKeyboardType.hpp +++ b/core/libs/prx/libSceConvertKeycode/ImeKeyboardType.hpp @@ -2,6 +2,7 @@ #define CORE_LIBS_PRX_LIBSCECONVERTKEYCODE_IMEKEYBOARDTYPE_HPP #include +#include // sceConvertKeycodeGetImeKeyboardType: the UserService keyboard type (plus the Japanese and Traditional Chinese // input variants) to IME keyboard type mapping and the SDK compatibility rules, as specified by the repository @@ -46,6 +47,10 @@ constexpr std::uint32_t ApplySdkCompatibility(std::uint32_t imeType, std::uint32 return imeType; } +constexpr bool IsPs4TitleId(std::string_view titleId) { + return titleId.substr(0, 4) == "CUSA"; +} + template int Resolve(std::int32_t userId, std::uint32_t* type, std::uint32_t sdkVersion, bool ps4Client, GetKeyboardType getKeyboardType, GetJapaneseInputType getJapaneseInputType, GetTraditionalChineseInputType getTraditionalChineseInputType) { if (type == nullptr) return kErrInvalidAddress; diff --git a/core/libs/prx/libSceUserService/CMakeLists.txt b/core/libs/prx/libSceUserService/CMakeLists.txt index 6d606a67e8..c78fcbc8d7 100644 --- a/core/libs/prx/libSceUserService/CMakeLists.txt +++ b/core/libs/prx/libSceUserService/CMakeLists.txt @@ -1,11 +1,12 @@ add_library(libSceUserService SHARED EXCLUDE_FROM_ALL Export.cpp) target_include_directories(libSceUserService PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceUserService PRIVATE libSceSystemService) configure_windows_unwind(libSceUserService) if(BUILD_TESTING) add_test_executable(guest_user_service_tests ${LIBS_INCLUDE_DIR}/tests/GuestUserService.cpp) target_include_directories(guest_user_service_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_user_service_tests PRIVATE libSceUserService) + target_link_libraries(guest_user_service_tests PRIVATE libSceUserService libSceSystemService) configure_windows_unwind(guest_user_service_tests) add_test(NAME guest_user_service COMMAND guest_user_service_tests) if(WIN32) @@ -17,7 +18,7 @@ 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) + target_link_libraries(guest_user_service_user_name_tests PRIVATE libSceUserService libSceSystemService) 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 @@ -28,7 +29,7 @@ endif() if(BUILD_TESTING) add_test_executable(guest_user_service_lifecycle_tests ${LIBS_INCLUDE_DIR}/tests/GuestUserServiceLifecycle.cpp) target_include_directories(guest_user_service_lifecycle_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_user_service_lifecycle_tests PRIVATE libSceUserService) + target_link_libraries(guest_user_service_lifecycle_tests PRIVATE libSceUserService libSceSystemService) add_test(NAME guest_user_service_lifecycle COMMAND guest_user_service_lifecycle_tests) if(WIN32) set_tests_properties(guest_user_service_lifecycle PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libSceUserService/Export.cpp b/core/libs/prx/libSceUserService/Export.cpp index 6bd5434acd..513d813d46 100644 --- a/core/libs/prx/libSceUserService/Export.cpp +++ b/core/libs/prx/libSceUserService/Export.cpp @@ -21,17 +21,36 @@ static int ReportAccessibilitySetting(int user_id, int32_t* setting, int32_t val } -// Per-user keyboard settings. Nothing in AnyPS5 stores a keyboard language or input variant yet, so every known user -// reports the same default: 20 (US English) for the keyboard type, 0 for the input variants (see +extern "C" int APS5_VABI sceSystemServiceParamGetInt(int param_id, int* value); + +// Per-user keyboard settings. Nothing in AnyPS5 stores a keyboard type or input variant, so every known user reports +// the keyboard type of the configured system language and 0 for the input variants (see // docs/dev/debt/libSceUserService.md). -static int ReportKeyboardSetting(int user_id, int32_t* type, int32_t value) { +static int32_t KeyboardTypeForSystemLanguage() { + constexpr int param_id_lang = 1; + constexpr int32_t fallback = 20; + constexpr int32_t keyboard_types[] = {37, 20, 26, 21, 17, 28, 29, 33, 34, 38, 39, 40, 23, 35, 16, + 30, 31, 32, 19, 36, 22, 41, 25, 44, 48, 43, 47, 42, 46, 45}; + int language = -1; + if (sceSystemServiceParamGetInt(param_id_lang, &language) != 0 || language < 0 || + language >= static_cast(sizeof(keyboard_types) / sizeof(keyboard_types[0]))) { + return fallback; + } + return keyboard_types[language]; +} + +static int32_t NoInputVariant() { + return 0; +} + +static int ReportKeyboardSetting(int user_id, int32_t* type, int32_t (*value)()) { if (type == nullptr) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } if (user_id != USER_SERVICE_INITIAL_USER_ID) { return USER_SERVICE_ERROR_UNKNOWN_USER; } - *type = value; + *type = value(); return USER_SERVICE_OK; } @@ -234,15 +253,15 @@ int APS5_VABI sceUserServiceGetUserColor(int user_id, int* color) { } int APS5_VABI sceUserServiceGetKeyboardType(int user_id, int32_t* type) { - return ReportKeyboardSetting(user_id, type, 20); + return ReportKeyboardSetting(user_id, type, KeyboardTypeForSystemLanguage); } int APS5_VABI sceUserServiceGetJapaneseInputType(int user_id, int32_t* type) { - return ReportKeyboardSetting(user_id, type, 0); + return ReportKeyboardSetting(user_id, type, NoInputVariant); } int APS5_VABI sceUserServiceGetTraditionalChineseInputType(int user_id, int32_t* type) { - return ReportKeyboardSetting(user_id, type, 0); + return ReportKeyboardSetting(user_id, type, NoInputVariant); } int APS5_VABI sceUserServiceGetNpAccountId(int user_id, uint64_t* account_id) { diff --git a/core/libs/tests/GuestConvertKeycode.cpp b/core/libs/tests/GuestConvertKeycode.cpp index 60721d3937..3bb2493a66 100644 --- a/core/libs/tests/GuestConvertKeycode.cpp +++ b/core/libs/tests/GuestConvertKeycode.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include extern "C" { @@ -19,6 +20,14 @@ int APS5_VABI sceConvertKeycodeGetCharacter(std::uint16_t, std::uint32_t, std::u static void Require(bool value) { if (!value) std::abort(); } +static void SetLanguage(const char* value) { +#ifdef _WIN32 + Require(_putenv_s("ANYPS5_LANGUAGE", value != nullptr ? value : "") == 0); +#else + Require((value != nullptr ? ::setenv("ANYPS5_LANGUAGE", value, 1) : ::unsetenv("ANYPS5_LANGUAGE")) == 0); +#endif +} + namespace { struct FakeUser { @@ -148,7 +157,26 @@ int main() { Require(sceConvertKeycodeGetImeKeyboardType(-1, &type) == invalidUserId); Require(sceConvertKeycodeGetImeKeyboardType(0, &type) == invalidUserId); Require(type == 0xFFFFFFFFu); + SetLanguage(nullptr); Require(sceConvertKeycodeGetImeKeyboardType(0x10000000, &type) == 0 && type == 4); + constexpr std::uint32_t imeTypeByLanguage[] = {22, 4, 9, 6, 2, 13, 14, 18, 19, 24, 26, 25, 8, 20, 1, 15, 16, 17, 5, 21, 7, 30, 11, 32, 37, 33, 36, 31, 35, 34}; + for (int language = 0; language < 30; ++language) { + SetLanguage(std::to_string(language).c_str()); + type = 0xFFFFFFFFu; + Require(sceConvertKeycodeGetImeKeyboardType(0x10000000, &type) == 0 && type == imeTypeByLanguage[language]); + } + SetLanguage("30"); + Require(sceConvertKeycodeGetImeKeyboardType(0x10000000, &type) == 0 && type == 4); + SetLanguage(nullptr); + + Require(ImeKeyboardType::IsPs4TitleId("CUSA00000")); + Require(ImeKeyboardType::IsPs4TitleId("CUSA")); + Require(!ImeKeyboardType::IsPs4TitleId("PPSA00000")); + Require(!ImeKeyboardType::IsPs4TitleId("")); + Require(!ImeKeyboardType::IsPs4TitleId("CUS")); + Require(!ImeKeyboardType::IsPs4TitleId("cusa00000")); + Require(!ImeKeyboardType::IsPs4TitleId("XCUSA0000")); + Require(!ImeKeyboardType::IsPs4TitleId("HOMEBREW1")); RequireImeKeyboardTypeMapping(); Require(sceConvertKeycodeGetCharacter(4, 0, 0, nullptr) == invalidAddress); diff --git a/core/libs/tests/GuestUserService.cpp b/core/libs/tests/GuestUserService.cpp index b40b52e694..b913ced298 100644 --- a/core/libs/tests/GuestUserService.cpp +++ b/core/libs/tests/GuestUserService.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include extern "C" { @@ -30,6 +31,14 @@ namespace { void Require(bool value) { if (!value) std::abort(); } +void SetLanguage(const char* value) { +#ifdef _WIN32 + Require(_putenv_s("ANYPS5_LANGUAGE", value != nullptr ? value : "") == 0); +#else + Require((value != nullptr ? ::setenv("ANYPS5_LANGUAGE", value, 1) : ::unsetenv("ANYPS5_LANGUAGE")) == 0); +#endif +} + } int main() { @@ -62,6 +71,7 @@ int main() { Require(sceUserServiceGetUserColor(123, &color) == USER_SERVICE_ERROR_INVALID_ARGUMENT); Require(sceUserServiceGetUserColor(USER_SERVICE_INITIAL_USER_ID, &color) == USER_SERVICE_OK); + SetLanguage(nullptr); using KeyboardSetting = decltype(&sceUserServiceGetKeyboardType); const std::pair keyboardSettings[] = { {sceUserServiceGetKeyboardType, 20}, {sceUserServiceGetJapaneseInputType, 0}, {sceUserServiceGetTraditionalChineseInputType, 0}}; @@ -72,6 +82,21 @@ int main() { Require(setting(USER_SERVICE_USER_ID_INVALID, &value) == USER_SERVICE_ERROR_UNKNOWN_USER && value == 0x5A5A); Require(setting(USER_SERVICE_INITIAL_USER_ID, &value) == USER_SERVICE_OK && value == expected); } + constexpr std::int32_t keyboardTypeByLanguage[] = {37, 20, 26, 21, 17, 28, 29, 33, 34, 38, 39, 40, 23, 35, 16, 30, 31, 32, 19, 36, 22, 41, 25, 44, 48, 43, 47, 42, 46, 45}; + for (int language = 0; language < 30; ++language) { + SetLanguage(std::to_string(language).c_str()); + std::int32_t value = 0x5A5A; + Require(sceUserServiceGetKeyboardType(USER_SERVICE_INITIAL_USER_ID, &value) == USER_SERVICE_OK && value == keyboardTypeByLanguage[language]); + Require(sceUserServiceGetKeyboardType(123, &value) == USER_SERVICE_ERROR_UNKNOWN_USER && value == keyboardTypeByLanguage[language]); + for (const auto variant : {sceUserServiceGetJapaneseInputType, sceUserServiceGetTraditionalChineseInputType}) { + value = 0x5A5A; + Require(variant(USER_SERVICE_INITIAL_USER_ID, &value) == USER_SERVICE_OK && value == 0); + } + } + SetLanguage("30"); + std::int32_t fallback = 0x5A5A; + Require(sceUserServiceGetKeyboardType(USER_SERVICE_INITIAL_USER_ID, &fallback) == USER_SERVICE_OK && fallback == 20); + SetLanguage(nullptr); Require(color == 0); std::uint64_t account_id = 0xdeadbeefu; diff --git a/docs/dev/debt/libSceConvertKeycode.md b/docs/dev/debt/libSceConvertKeycode.md index 01812a5b99..9255ca607c 100644 --- a/docs/dev/debt/libSceConvertKeycode.md +++ b/docs/dev/debt/libSceConvertKeycode.md @@ -3,4 +3,4 @@ ### Functional - [sceConvertKeycodeGetVirtualKeycode](../../../core/libs/prx/libSceConvertKeycode/Export.cpp) and `sceConvertKeycodeGetCharacter`: layouts 31–39 throw until the guest platform and compiled SDK version are exposed. `GetVirtualKeycode` also throws for version-dependent Y/Z mappings in layouts 2, 3 and 12. Mapping tables and modifier behavior are recovered; no SDK version is guessed. -- [sceConvertKeycodeGetImeKeyboardType](../../../core/libs/prx/libSceConvertKeycode/Export.cpp): implemented exactly as the repository owner (boykopovar) specified the UserService keyboard type (plus the Japanese variant for type 37 and the Traditional Chinese variant for type 39) to IME keyboard type table, the SDK compatibility rules (38 becomes 4 for PS5 SDK below 0x06000000 or PS4 SDK below 0x10000000; 39 becomes 29 for PS5 SDK below 0x08000000 and for every PS4 client), the error codes and the unmatched-type result (success, IME type 0). The PS4/PS5 client rule assumes a PS5 process because `sceKernelIsPs4Process` is unimplemented and AnyPS5 runs PS5 processes. UserService has no stored keyboard setting yet, so every user reports the US-English default keyboard type 20 and the function returns IME type 4 until one is stored (see libSceUserService.md). +- [sceConvertKeycodeGetImeKeyboardType](../../../core/libs/prx/libSceConvertKeycode/Export.cpp): implemented exactly as the repository owner (boykopovar) specified the UserService keyboard type (plus the Japanese variant for type 37 and the Traditional Chinese variant for type 39) to IME keyboard type table, the SDK compatibility rules (38 becomes 4 for PS5 SDK below 0x06000000 or PS4 SDK below 0x10000000; 39 becomes 29 for PS5 SDK below 0x08000000 and for every PS4 client), the error codes and the unmatched-type result (success, IME type 0). The PS4/PS5 client rule reads the title ID prefix from `param.sfo` or `param.json` (`CUSA` is PS4); a missing or custom ID, as in homebrew, is treated as PS5. Reliable detection inspects the original `eboot.bin`, which only the relinker reads, so it is not done. UserService reports the keyboard type of the configured system language, so the function returns the owner's IME type for that language (see libSceUserService.md). diff --git a/docs/dev/debt/libSceUserService.md b/docs/dev/debt/libSceUserService.md index 65ca188348..bab39a1f05 100644 --- a/docs/dev/debt/libSceUserService.md +++ b/docs/dev/debt/libSceUserService.md @@ -8,4 +8,4 @@ - [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 - [sceUserServiceGetAgeLevel](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - on a PS5 (firmware 13.60) the value is the age set in the user's parental restriction (0, 7, 12 and 15 were measured) and `0xFFFFFFFF` without one; a user who is not logged in returns `0x80960009` with or without an output. Parental restrictions are not modelled, so the initial user is always reported as unrestricted. PPSA12544 (Unity) only stores the value for its user profile - [sceUserServiceGetAccessibilityChatTranscription, PressAndHoldDelay, TriggerEffect, Vibration, ZoomEnabled, ZoomFollowFocus](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - report 0, 0, 1, 1, 0 and 1, what a PS5 (firmware 13.60) reports for a user whose accessibility settings are all at their defaults; the console reports the user's own settings (a changed vibration intensity read 3), which are not modelled. What each number means is unknown. A user who is not logged in returns `0x80960009` with or without an output -- [sceUserServiceGetKeyboardType, sceUserServiceGetJapaneseInputType, sceUserServiceGetTraditionalChineseInputType](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - AnyPS5 stores no per-user keyboard setting, so the initial user reports the US-English default 20 for the keyboard type (the owner's table maps it to IME keyboard type 4) and 0 for the two input variants, and any other user id returns `USER_SERVICE_ERROR_UNKNOWN_USER`. The repository owner allows initialising the keyboard type from the system language through an explicit conversion to UserService keyboard-type ids; that table is not recovered yet, so 20 stays the fixed default until it exists and no other value is guessed. `sceConvertKeycodeGetImeKeyboardType` consumes these values. +- [sceUserServiceGetKeyboardType, sceUserServiceGetJapaneseInputType, sceUserServiceGetTraditionalChineseInputType](../../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - AnyPS5 stores no per-user keyboard setting, so the initial user reports the keyboard type of the configured system language (`ANYPS5_LANGUAGE`, English US by default) and 0 for the two input variants, and any other user id returns `USER_SERVICE_ERROR_UNKNOWN_USER`. The language-to-keyboard-type table is the repository owner's and covers languages 0 to 29; a language outside it, such as Ukrainian (30), reports 20 (US English). The input variant is 0 because the owner's table needs one only for Japanese and Traditional Chinese, and gives 0 for both. `sceConvertKeycodeGetImeKeyboardType` consumes these values.