From 66165443c0e6de753f247d34c0a6e6b46baf7745 Mon Sep 17 00:00:00 2001 From: xXPBSXx <20614131+xXPBSXx@users.noreply.github.com> Date: Sun, 11 Oct 2026 16:20:47 +0200 Subject: [PATCH 1/2] feat(libSceCesCs): add sceCesRefersUcsProfileCp1251 and sceCesMbcsUcsContextInit PPSA15246 (Teardown) imports both from the module-named libSceCesCs-module and stops at the loader without them. Cp1251 joins Cp1252 in sceCesSbcToUtf8 and sceCesUtf8ToSbc; the context initializer succeeds without storing anything because its layout is unknown (listed in TechnicalDebt.md). Co-Authored-By: Claude Sonnet 5.5 --- .../prx/libSceCesCs-module/CMakeLists.txt | 3 ++ core/libs/prx/libSceCesCs/Export.cpp | 53 ++++++++++++++++--- core/libs/tests/GuestCes.cpp | 26 +++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 core/libs/prx/libSceCesCs-module/CMakeLists.txt diff --git a/core/libs/prx/libSceCesCs-module/CMakeLists.txt b/core/libs/prx/libSceCesCs-module/CMakeLists.txt new file mode 100644 index 000000000..4f216c2f6 --- /dev/null +++ b/core/libs/prx/libSceCesCs-module/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(libSceCesCs-module SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/../libSceCesCs/Export.cpp) +target_include_directories(libSceCesCs-module PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceCesCs-module) diff --git a/core/libs/prx/libSceCesCs/Export.cpp b/core/libs/prx/libSceCesCs/Export.cpp index a2649662d..489bc94b3 100644 --- a/core/libs/prx/libSceCesCs/Export.cpp +++ b/core/libs/prx/libSceCesCs/Export.cpp @@ -13,18 +13,45 @@ constexpr int CES_ERROR_UNASSIGNED_CODE = static_cast(0x805C0020); constexpr int CES_ERROR_INVALID_DST_BUFFER = static_cast(0x805C0030); constexpr int CES_ERROR_DST_BUFFER_END = static_cast(0x805C0031); -constexpr uint8_t CP1252_PROFILE = 0; +constexpr uint8_t PROFILES[2] = {0, 1}; +constexpr const uint8_t* CP1252_PROFILE = &PROFILES[0]; +constexpr const uint8_t* CP1251_PROFILE = &PROFILES[1]; constexpr uint16_t CP1252_HIGH[32] = { 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178, }; -uint32_t cp1252ToUnicode(uint8_t sbc) { +constexpr uint16_t CP1251_HIGH[128] = { + 0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, 0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F, + 0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x0098, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, + 0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7, 0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407, + 0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, 0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, +}; + +uint32_t sbcToUnicode(const uint8_t* profile, uint8_t sbc) { + if (profile == CP1251_PROFILE) return sbc >= 0x80 ? CP1251_HIGH[sbc - 0x80] : sbc; return sbc >= 0x80 && sbc < 0xA0 ? CP1252_HIGH[sbc - 0x80] : sbc; } -bool unicodeToCp1252(uint32_t code, uint8_t& sbc) { +bool unicodeToSbc(const uint8_t* profile, uint32_t code, uint8_t& sbc) { + if (profile == CP1251_PROFILE) { + if (code < 0x80) { + sbc = static_cast(code); + return true; + } + for (uint32_t i = 0; i < 128; ++i) { + if (CP1251_HIGH[i] == code) { + sbc = static_cast(0x80 + i); + return true; + } + } + return false; + } if (code < 0x80 || (code >= 0xA0 && code <= 0xFF)) { sbc = static_cast(code); return true; @@ -81,15 +108,25 @@ uint32_t encodeUtf8(uint32_t code, uint8_t* utf8) { extern "C" { const uint8_t* APS5_VABI sceCesRefersUcsProfileCp1252(void) { - return &CP1252_PROFILE; + return CP1252_PROFILE; +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp1251(void) { + return CP1251_PROFILE; +} + +int APS5_VABI sceCesMbcsUcsContextInit(void* context, const void* profile) { + (void)profile; + if (!context) return CES_ERROR_INVALID_PARAMETER; + return 0; } int APS5_VABI sceCesSbcToUtf8(const uint8_t* profile, uint8_t sbc, uint8_t* utf8, uint32_t utf8max, uint32_t* utf8_len) { - if (profile != &CP1252_PROFILE) return CES_ERROR_INVALID_PARAMETER; + if (profile != CP1252_PROFILE && profile != CP1251_PROFILE) return CES_ERROR_INVALID_PARAMETER; if (!utf8) return CES_ERROR_INVALID_DST_BUFFER; uint8_t encoded[3]; - const uint32_t length = encodeUtf8(cp1252ToUnicode(sbc), encoded); + const uint32_t length = encodeUtf8(sbcToUnicode(profile, sbc), encoded); if (utf8max < length) return CES_ERROR_DST_BUFFER_END; for (uint32_t i = 0; i < length; ++i) utf8[i] = encoded[i]; if (utf8_len) *utf8_len = length; @@ -97,7 +134,7 @@ int APS5_VABI sceCesSbcToUtf8(const uint8_t* profile, uint8_t sbc, uint8_t* utf8 } int APS5_VABI sceCesUtf8ToSbc(const uint8_t* utf8, uint32_t utf8max, uint32_t* utf8_len, const uint8_t* profile, uint8_t* sbc) { - if (profile != &CP1252_PROFILE) return CES_ERROR_INVALID_PARAMETER; + if (profile != CP1252_PROFILE && profile != CP1251_PROFILE) return CES_ERROR_INVALID_PARAMETER; if (!utf8) return CES_ERROR_INVALID_SRC_BUFFER; if (utf8max == 0) return CES_ERROR_SRC_BUFFER_END; if (!sbc) return CES_ERROR_INVALID_DST_BUFFER; @@ -105,7 +142,7 @@ int APS5_VABI sceCesUtf8ToSbc(const uint8_t* utf8, uint32_t utf8max, uint32_t* u uint32_t length = 0; uint32_t code = 0; if (const int result = decodeUtf8(utf8, utf8max, length, code); result != 0) return result; - if (!unicodeToCp1252(code, *sbc)) return CES_ERROR_UNASSIGNED_CODE; + if (!unicodeToSbc(profile, code, *sbc)) return CES_ERROR_UNASSIGNED_CODE; if (utf8_len) *utf8_len = length; return 0; } diff --git a/core/libs/tests/GuestCes.cpp b/core/libs/tests/GuestCes.cpp index 985713bcb..55bd86742 100644 --- a/core/libs/tests/GuestCes.cpp +++ b/core/libs/tests/GuestCes.cpp @@ -5,6 +5,8 @@ extern "C" { const std::uint8_t* APS5_VABI sceCesRefersUcsProfileCp1252(void); +const std::uint8_t* APS5_VABI sceCesRefersUcsProfileCp1251(void); +int APS5_VABI sceCesMbcsUcsContextInit(void*, const void*); int APS5_VABI sceCesSbcToUtf8(const std::uint8_t*, std::uint8_t, std::uint8_t*, std::uint32_t, std::uint32_t*); int APS5_VABI sceCesUtf8ToSbc(const std::uint8_t*, std::uint32_t, std::uint32_t*, const std::uint8_t*, std::uint8_t*); } @@ -80,4 +82,28 @@ int main() { Require(Utf8ToSbc(profile, "\xED\xA0\x80", 3) == invalidEncode); Require(Utf8ToSbc(profile, "\xE3\x81\x82", 3) == unassignedCode); Require(Utf8ToSbc(profile, "\xF0\x9F\x98\x80", 4) == unassignedCode); + + const std::uint8_t* cyrillic = sceCesRefersUcsProfileCp1251(); + Require(cyrillic != nullptr && cyrillic == sceCesRefersUcsProfileCp1251() && cyrillic != profile); + RequireSbcToUtf8(cyrillic, 'A', "A"); + RequireSbcToUtf8(cyrillic, 0x88, "\xE2\x82\xAC"); + RequireSbcToUtf8(cyrillic, 0xC0, "\xD0\x90"); + RequireSbcToUtf8(cyrillic, 0xFF, "\xD1\x8F"); + RequireSbcToUtf8(cyrillic, 0xA8, "\xD0\x81"); + RequireSbcToUtf8(cyrillic, 0xB9, "\xE2\x84\x96"); + RequireUtf8ToSbc(cyrillic, "\xD0\x9F", 0xCF); + RequireUtf8ToSbc(cyrillic, "\xD1\x91", 0xB8); + for (int sbc = 1; sbc < 256; ++sbc) { + std::uint8_t cp[4] = {}; + std::uint32_t cpLength = 0; + std::uint8_t back = 0; + Require(sceCesSbcToUtf8(cyrillic, static_cast(sbc), cp, sizeof(cp), &cpLength) == 0); + Require(sceCesUtf8ToSbc(cp, cpLength, &cpLength, cyrillic, &back) == 0 && back == sbc); + } + Require(Utf8ToSbc(cyrillic, "\xC3\xA9", 2) == unassignedCode); + Require(Utf8ToSbc(profile, "\xD0\x90", 2) == unassignedCode); + + char context[256] = {}; + Require(sceCesMbcsUcsContextInit(context, cyrillic) == 0); + Require(sceCesMbcsUcsContextInit(nullptr, cyrillic) == invalidParameter); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 0c72b62a7..1490f39cd 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -44,6 +44,7 @@ Throughout the project, every function at every stage either **does exactly what - [libSceHttp](../../core/libs/prx/libSceHttp/Export.cpp) - no request reaches the network, so `sceHttpSetResponseHeaderMaxSize` has no response header to limit, `sceHttpSetRecvBlockSize` (`(int id, u32 blockSize)` as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.h)) no response body to read in blocks and `sceHttpRedirectCacheFlush` no redirect to forget; `sceHttpsUnloadCert` returns success like `sceHttpsLoadCert`, which keeps no certificate; `sceHttpSetRedirectCallback` and `sceHttpSetCookieRecvCallback` accept callbacks that are never called, `sceHttpsSetSslVersion` has no TLS connection to configure and `sceHttpsGetSslError` reports error 0 with detail 0; `sceHttpSetAuthInfoCallback` accepts a callback that is never called, as no server asks for authentication - [sceFontGlyphDefineAttribute](../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) checks the glyph and ignores the attribute and its value, whose meaning is unknown. PPSA01325 sets attribute 0x11 to 0 on a glyph from `sceFontGenerateCharGlyph` that it deletes unused, and draws the character with `sceFontRenderCharGlyphImageHorizontal` - [sceAudioInInit](../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - returns 0 without library-wide setup, as `sceAudioOutInit` does; each `sceAudioInOpen` opens its own SDL capture device +- [sceCesMbcsUcsContextInit](../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - returns success without writing to the context, and returns the invalid-parameter error for a null context. The signature `int sceCesMbcsUcsContextInit(union* context, const union* mbcsUcsPrf)` follows the [Orbital IDA type database](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json); the context layout and its size are unknown, so nothing is stored. PPSA15246 calls it and imports no other function that takes the context; no conversion through an initialized context exists. `sceCesRefersUcsProfileCp1251` returns a profile accepted by `sceCesSbcToUtf8` and `sceCesUtf8ToSbc` only, with the Windows-1251 table of Microsoft's [bestfit1251.txt](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WindowsBestFit/bestfit1251.txt), where byte 0x98 (unassigned) maps to U+0098 like the unassigned Cp1252 bytes; the console behavior for that byte is not verified ### Unknown function info From 471e12e4843fe98406c3e65a4f41e06e011ded84 Mon Sep 17 00:00:00 2001 From: xXPBSXx <20614131+xXPBSXx@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:35:09 +0200 Subject: [PATCH 2/2] fix(libSceCesCs): drop the unused module target and move the TechnicalDebt line Co-Authored-By: Claude Sonnet 5.5 --- core/libs/prx/libSceCesCs-module/CMakeLists.txt | 3 --- docs/dev/TechnicalDebt.md | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 core/libs/prx/libSceCesCs-module/CMakeLists.txt diff --git a/core/libs/prx/libSceCesCs-module/CMakeLists.txt b/core/libs/prx/libSceCesCs-module/CMakeLists.txt deleted file mode 100644 index 4f216c2f6..000000000 --- a/core/libs/prx/libSceCesCs-module/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_library(libSceCesCs-module SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/../libSceCesCs/Export.cpp) -target_include_directories(libSceCesCs-module PRIVATE ${LIBS_INCLUDE_DIR}) -configure_windows_unwind(libSceCesCs-module) diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 1490f39cd..87112c296 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -39,12 +39,12 @@ Throughout the project, every function at every stage either **does exactly what - [libSceHttp2](../../core/libs/prx/libSceHttp2/Export.cpp) - no request reaches the network, so `sceHttp2SetMinSslVersion` has no TLS connection to restrict and returns success for any `id`, like `sceHttp2SslEnableOption` (prosper returns the invalid-id error for an unknown one) - [libSceNpTrophy2](../../core/libs/prx/libSceNpTrophy2/src/GameInfo.cpp): `sceNpTrophy2RegisterUnlockCallback` accepts a callback that is never called, as no trophy is ever unlocked, `sceNpTrophy2UnregisterUnlockCallback` only returns success, and `sceNpTrophy2ShowTrophyList` returns success without showing a list (there is no system trophy UI; seen on PPSA15552). - [sceAvPlayerSetTrickSpeed](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) with a negative speed runs the clock backwards but delivers no frames; when a forward speed is set again, playback resumes from the rewound time. +- [sceCesMbcsUcsContextInit](../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - returns success without writing to the context, and returns the invalid-parameter error for a null context. The signature `int sceCesMbcsUcsContextInit(union* context, const union* mbcsUcsPrf)` follows the [Orbital IDA type database](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json); the context layout and its size are unknown, so nothing is stored. PPSA15246 calls it and imports no other function that takes the context; no conversion through an initialized context exists. `sceCesRefersUcsProfileCp1251` returns a profile accepted by `sceCesSbcToUtf8` and `sceCesUtf8ToSbc` only, with the Windows-1251 table of Microsoft's [bestfit1251.txt](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WindowsBestFit/bestfit1251.txt), where byte 0x98 (unassigned) maps to U+0098 like the unassigned Cp1252 bytes; the console behavior for that byte is not verified - [sceRudpActivate](../../core/libs/prx/libSceRudp/Export.cpp) (libSceRudp) - there is no reliable-UDP stack on the host to activate, so it returns success without doing anything, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/rudp/rudp.cpp) does for its own `sceRudpActivate`. PPSA01325 calls it with no arguments during start-up; what the console activates, and whether it reports an error before `sceRudpInit`, is unverified - [ulobjmgr](../../core/libs/prx/ulobjmgr/Export.cpp) registers no object: `_sceUlobjmgrRegisterObject` always hands out id 0 and `_sceUlobjmgrUnregisterObject` releases nothing, as shadPS4 does - [libSceHttp](../../core/libs/prx/libSceHttp/Export.cpp) - no request reaches the network, so `sceHttpSetResponseHeaderMaxSize` has no response header to limit, `sceHttpSetRecvBlockSize` (`(int id, u32 blockSize)` as in [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/network/http.h)) no response body to read in blocks and `sceHttpRedirectCacheFlush` no redirect to forget; `sceHttpsUnloadCert` returns success like `sceHttpsLoadCert`, which keeps no certificate; `sceHttpSetRedirectCallback` and `sceHttpSetCookieRecvCallback` accept callbacks that are never called, `sceHttpsSetSslVersion` has no TLS connection to configure and `sceHttpsGetSslError` reports error 0 with detail 0; `sceHttpSetAuthInfoCallback` accepts a callback that is never called, as no server asks for authentication - [sceFontGlyphDefineAttribute](../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) checks the glyph and ignores the attribute and its value, whose meaning is unknown. PPSA01325 sets attribute 0x11 to 0 on a glyph from `sceFontGenerateCharGlyph` that it deletes unused, and draws the character with `sceFontRenderCharGlyphImageHorizontal` - [sceAudioInInit](../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - returns 0 without library-wide setup, as `sceAudioOutInit` does; each `sceAudioInOpen` opens its own SDL capture device -- [sceCesMbcsUcsContextInit](../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - returns success without writing to the context, and returns the invalid-parameter error for a null context. The signature `int sceCesMbcsUcsContextInit(union* context, const union* mbcsUcsPrf)` follows the [Orbital IDA type database](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json); the context layout and its size are unknown, so nothing is stored. PPSA15246 calls it and imports no other function that takes the context; no conversion through an initialized context exists. `sceCesRefersUcsProfileCp1251` returns a profile accepted by `sceCesSbcToUtf8` and `sceCesUtf8ToSbc` only, with the Windows-1251 table of Microsoft's [bestfit1251.txt](https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WindowsBestFit/bestfit1251.txt), where byte 0x98 (unassigned) maps to U+0098 like the unassigned Cp1252 bytes; the console behavior for that byte is not verified ### Unknown function info