From 0740776d54cfd2e4998819549d87bb4ca43d88da Mon Sep 17 00:00:00 2001 From: BunnyFl <166477956+BunnyFl@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:34:44 +0100 Subject: [PATCH 1/2] fix(libSceConvertKeycode): check key data and fold its modifiers into the state sceConvertKeycodeGetCharacterFromKeyboardData threw for an index outside the key data, accepted any state bits and converted with the caller's state only. Firmware 13.42's module returns INVALID_PARAMETER for a length above 16, an led of 8 or more, a modifier_key of 0x100 or more, a state bit outside 0x1C000080 or an index outside the key data, before the layout checks, and converts key_code[index] with state | modifier_key << 8 | led << 16. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceConvertKeycode/Export.cpp | 13 +++---- core/libs/tests/GuestConvertKeycode.cpp | 36 ++++++++++++++----- docs/dev/debt/libSceConvertKeycode.md | 2 +- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/core/libs/prx/libSceConvertKeycode/Export.cpp b/core/libs/prx/libSceConvertKeycode/Export.cpp index 4464089494..585890186a 100644 --- a/core/libs/prx/libSceConvertKeycode/Export.cpp +++ b/core/libs/prx/libSceConvertKeycode/Export.cpp @@ -226,6 +226,7 @@ constexpr auto VirtualKeys = [] { }(); constexpr int kErrInvalidKeyboardType = static_cast(0x80BC001Du); +constexpr int kErrInvalidParameter = static_cast(0x80BC0030u); constexpr auto CharacterTables = [] { std::array, 111> tables{}; tables[1][4] = 327777; @@ -2453,14 +2454,10 @@ int APS5_VABI sceConvertKeycodeGetVirtualKeycode(std::uint16_t keycode, std::uin int APS5_VABI sceConvertKeycodeGetCharacterFromKeyboardData(const KeyboardData* keyData, std::int32_t index, std::uint32_t state, std::uint32_t keyboardType, char16_t* character) { if (keyData == nullptr || character == nullptr) return kErrInvalidAddress; - if (keyboardType == 0 || keyboardType > 39) return kErrInvalidKeyboardType; - if (!KeyboardTypeAvailable(keyboardType, CompiledSdkVersion(), Ps4ClientOnce())) return kErrInvalidKeyboardType; - if (index < 0 || index >= keyData->length || index >= static_cast(KEYBOARD_MAX_KEYCODES)) { - throw std::runtime_error("sceConvertKeycodeGetCharacterFromKeyboardData: result for an index outside the key data is unknown"); - } - const std::uint16_t keycode = keyData->key_code[index]; - *character = ConvertCharacter(keycode, state, keyboardType); - return 0; + const auto length = static_cast(keyData->length); + if (length > KEYBOARD_MAX_KEYCODES || keyData->led >= 8 || keyData->modifier_key >= 0x100 || (state & 0xE3FFFF7Fu) != 0) return kErrInvalidParameter; + if (index < 0 || static_cast(index) >= length) return kErrInvalidParameter; + return sceConvertKeycodeGetCharacter(keyData->key_code[index], state | keyData->modifier_key << 8 | keyData->led << 16, keyboardType, character); } int APS5_VABI sceConvertKeycodeGetCharacter(std::uint16_t keycode, std::uint32_t state, std::uint32_t type, char16_t* character) { diff --git a/core/libs/tests/GuestConvertKeycode.cpp b/core/libs/tests/GuestConvertKeycode.cpp index e381eb60b3..87290785d0 100644 --- a/core/libs/tests/GuestConvertKeycode.cpp +++ b/core/libs/tests/GuestConvertKeycode.cpp @@ -233,16 +233,26 @@ int main() { char16_t kbdChar = u'?'; Require(sceConvertKeycodeGetCharacterFromKeyboardData(nullptr, 0, 0, 4, &kbdChar) == static_cast(0x80BC0031u)); Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0, 4, nullptr) == static_cast(0x80BC0031u)); + constexpr int invalidParameter = static_cast(0x80BC0030u); for (std::int32_t badIndex : {-1, 1, 16}) { kbdChar = u'?'; - bool rejected = false; - try { - sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, badIndex, 0, 4, &kbdChar); - } catch (const std::runtime_error&) { - rejected = true; - } - Require(rejected && kbdChar == u'?'); + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, badIndex, 0, 4, &kbdChar) == invalidParameter && kbdChar == u'?'); } + for (std::uint32_t badState : {0x1u, 0x200u, 0x20000u}) { + kbdChar = u'?'; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, badState, 4, &kbdChar) == invalidParameter && kbdChar == u'?'); + } + for (std::int32_t badLength : {0, 17, -1}) { + KeyboardData badData = keyData; + badData.length = badLength; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&badData, 0, 0, 4, &kbdChar) == invalidParameter); + } + KeyboardData badLed = keyData; + badLed.led = 8; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&badLed, 0, 0, 4, &kbdChar) == invalidParameter); + KeyboardData badModifier = keyData; + badModifier.modifier_key = 0x100; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&badModifier, 0, 0, 40, &kbdChar) == invalidParameter); for (std::uint32_t layout : {31u, 39u}) { kbdChar = u'?'; Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0, layout, &kbdChar) == 0 && kbdChar == u'a'); @@ -250,6 +260,16 @@ int main() { Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0, 0, &kbdChar) == static_cast(0x80BC001Du)); Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0, 40, &kbdChar) == static_cast(0x80BC001Du)); Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0, 4, &kbdChar) == 0 && kbdChar == u'a'); - Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0x200, 4, &kbdChar) == 0 && kbdChar == u'A'); + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0x80, 4, &kbdChar) == 0 && kbdChar == u'a'); + KeyboardData shifted = keyData; + shifted.modifier_key = 0x02; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&shifted, 0, 0, 4, &kbdChar) == 0 && kbdChar == u'A'); + KeyboardData capsLock = keyData; + capsLock.led = 2; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&capsLock, 0, 0, 4, &kbdChar) == 0 && kbdChar == u'A'); + KeyboardData numLock = keyData; + numLock.led = 1; + numLock.key_code[0] = 89; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&numLock, 0, 0, 4, &kbdChar) == 0 && kbdChar == u'1'); return 0; } diff --git a/docs/dev/debt/libSceConvertKeycode.md b/docs/dev/debt/libSceConvertKeycode.md index ad1c428747..6b2929fc28 100644 --- a/docs/dev/debt/libSceConvertKeycode.md +++ b/docs/dev/debt/libSceConvertKeycode.md @@ -3,5 +3,5 @@ ### Functional - [sceConvertKeycodeGetVirtualKeycode](../../../core/libs/prx/libSceConvertKeycode/Export.cpp), `sceConvertKeycodeGetCharacter` and `sceConvertKeycodeGetCharacterFromKeyboardData`: layouts 31 and up need a PS5 title or a PS4 SDK of 0x05000000, 38 and 39 a PS5 SDK of 0x06000000 or a PS4 SDK of 0x10000000, and 39 a PS5 title with SDK 0x08000000; other titles get `0x80BC001D`. `GetVirtualKeycode` swaps the Y and Z keys of layouts 2, 3 and 12 below PS5 SDK 0x04000000 or PS4 SDK 0x09000000. These rules come from firmware 13.42's module, run natively, not from a console; the SDK version is the one in the process parameters. Three titles disassembled for this entry call `sceConvertKeycodeGetVirtualKeycode` at four sites (PPSA24779 0xa9199a, PPSA29866 0x2d8bdd, PPSA30601 0x15e4624 and 0x15e47da): the keyboard type is the constant 4 at 0xa9199a and 0x15e47da and a run-time value at the other two, and no value read is 2, 3, 12 or in 31 to 39. -- [sceConvertKeycodeGetCharacterFromKeyboardData](../../../core/libs/prx/libSceConvertKeycode/Export.cpp): an index outside `keyData->length` or `KEYBOARD_MAX_KEYCODES` throws; no reference gives the real result or validation order. +- [sceConvertKeycodeGetCharacterFromKeyboardData](../../../core/libs/prx/libSceConvertKeycode/Export.cpp): returns `0x80BC0030` for a `length` above 16 (or negative), an `led` of 8 or more, a `modifier_key` of 0x100 or more, a `state` bit outside 0x1C000080, or an index outside the key data, before the layout checks, and otherwise converts `key_code[index]` with `state | modifier_key << 8 | led << 16`. From firmware 13.42's module, run natively, not from a console. - [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, malformed or oversized ID or title, 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). From 3c314f5fb74ba38729e8dc2f154bfc57b09af5f5 Mon Sep 17 00:00:00 2001 From: BunnyFl <166477956+BunnyFl@users.noreply.github.com> Date: Thu, 29 Oct 2026 13:46:58 +0100 Subject: [PATCH 2/2] test(libSceConvertKeycode): pin the accepted key-data bounds, state bits and check order Adds the rows suggested in review: a full 16-key report with led 7 and modifier_key 0xFF, the allowed state bits on layouts 4, 24 and 30, more rejected state bits, and at SDK 0x03000000 the key-data checks before the layout check. Every value matches firmware 13.42's module. Co-Authored-By: Claude Opus 5.5 --- core/libs/tests/GuestConvertKeycode.cpp | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/core/libs/tests/GuestConvertKeycode.cpp b/core/libs/tests/GuestConvertKeycode.cpp index 2920139aa2..db7f8aba6e 100644 --- a/core/libs/tests/GuestConvertKeycode.cpp +++ b/core/libs/tests/GuestConvertKeycode.cpp @@ -288,5 +288,31 @@ int main() { numLock.led = 1; numLock.key_code[0] = 89; Require(sceConvertKeycodeGetCharacterFromKeyboardData(&numLock, 0, 0, 4, &kbdChar) == 0 && kbdChar == u'1'); + KeyboardData full = keyData; + full.length = 16; + full.key_code[15] = 5; + full.led = 7; + full.modifier_key = 0xFF; + char16_t expected = u'?'; + Require(sceConvertKeycodeGetCharacter(5, 0x7FF00, 4, &expected) == 0); + kbdChar = u'?'; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&full, 15, 0, 4, &kbdChar) == 0 && kbdChar == expected); + for (std::uint32_t goodState : {0x4000000u, 0x8000000u, 0x10000000u, 0x1C000080u}) { + for (std::uint32_t layout : {4u, 24u, 30u}) { + Require(sceConvertKeycodeGetCharacter(4, goodState, layout, &expected) == 0); + kbdChar = u'?'; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, goodState, layout, &kbdChar) == 0 && kbdChar == expected); + } + } + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0x4000000, 24, &kbdChar) == 0 && kbdChar == 0x3141); + for (std::uint32_t badState : {0x40u, 0x100u, 0x20000000u, 0x80000000u}) { + kbdChar = u'?'; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, badState, 4, &kbdChar) == invalidParameter && kbdChar == u'?'); + } + const std::uint32_t oldSdkVersion = 0x03000000u; + std::memcpy(process.data() + 16, &oldSdkVersion, sizeof(oldSdkVersion)); + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0, 38, &kbdChar) == static_cast(0x80BC001Du)); + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&badLed, 0, 0, 38, &kbdChar) == invalidParameter); + std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion)); return 0; }