From 8eeb364c381041d7c6d82868fb0df93e2d11d677 Mon Sep 17 00:00:00 2001 From: Pouare514 Date: Sat, 17 Oct 2026 12:34:55 +0200 Subject: [PATCH 1/4] fix(libs): share Sony CRT handler and lconv state between libc and libSceAmpr --- core/libs/prx/libSceAmpr/Export.cpp | 6 +- core/libs/prx/libc/CMakeLists.txt | 1 + core/libs/prx/libc/include/SonyCrt.hpp | 70 +++------------------ core/libs/prx/libc/src/FileAndHeap.cpp | 11 +++- core/libs/prx/libc/src/LocaleSupport.cpp | 2 +- core/libs/prx/libc/src/SonyCrt.cpp | 76 +++++++++++++++++++++++ core/libs/prx/libc/src/WideConversion.cpp | 4 +- core/libs/tests/GuestCrt.cpp | 61 ++++++++++++++++-- core/libs/tests/GuestFiopen.cpp | 4 ++ docs/dev/debt/libc.md | 4 +- 10 files changed, 161 insertions(+), 78 deletions(-) create mode 100644 core/libs/prx/libc/src/SonyCrt.cpp diff --git a/core/libs/prx/libSceAmpr/Export.cpp b/core/libs/prx/libSceAmpr/Export.cpp index ca51ae70f2a6..8da65638666b 100644 --- a/core/libs/prx/libSceAmpr/Export.cpp +++ b/core/libs/prx/libSceAmpr/Export.cpp @@ -874,7 +874,7 @@ void APS5_VABI _Lock_shared_ptr_spin_lock_nid_postfix(void) { APS5_EXPORT("0hlfW1O4Aa4", sceAmprLocaleconv); SonyLconv* APS5_VABI sceAmprLocaleconv(void) { - return SonyLocaleconv(); + return SonyLocaleconv_nid_no_patch(); } APS5_EXPORT("802pFCwC9w0", sceAmprUdivti3); @@ -904,7 +904,7 @@ int APS5_VABI sceAmprWcscpyS(std::uint16_t* dest, std::size_t size, const std::u APS5_EXPORT("ENLfKJEZTjE", sceAmprSetConstraintHandlerS); void* APS5_VABI sceAmprSetConstraintHandlerS(void* handler) { - return SonySetConstraintHandlerS(handler); + return SonySetConstraintHandlerS_nid_no_patch(handler); } APS5_EXPORT("Jo9ON-AX9eU", sceAmprFltrounds); @@ -914,7 +914,7 @@ int APS5_VABI sceAmprFltrounds(void) { APS5_EXPORT("ODGONXcSmz4", sceAmprIgnoreHandlerS); void APS5_VABI sceAmprIgnoreHandlerS(const char* message, void* pointer, int error) { - SonyIgnoreHandlerS(message, pointer, error); + SonyIgnoreHandlerS_nid_no_patch(message, pointer, error); } APS5_EXPORT("sZLrjx-yEx4", sceAmprWcstombsS); diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 233ff92c4435..65b723e2a1a3 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -89,6 +89,7 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RuntimeSupport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/IntegerBuiltins.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/LocaleSupport.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/SonyCrt.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Process.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Hostname.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Environment.cpp diff --git a/core/libs/prx/libc/include/SonyCrt.hpp b/core/libs/prx/libc/include/SonyCrt.hpp index db53cd3d549e..0e40f962d993 100644 --- a/core/libs/prx/libc/include/SonyCrt.hpp +++ b/core/libs/prx/libc/include/SonyCrt.hpp @@ -22,24 +22,13 @@ extern "C" GuestTm* APS5_VABI localtime_s_nid_postfix(const std::int64_t* timer, using SonyConstraintHandler = void (APS5_VABI *)(const char* message, void* pointer, int error); -inline void APS5_VABI SonyDefaultConstraintHandler(const char* message, void* pointer, int error) { - (void)message; - (void)pointer; - (void)error; - std::abort(); -} - -inline std::mutex& SonyConstraintMutex() { - static std::mutex mutex; - return mutex; -} - -inline SonyConstraintHandler& SonyConstraintHandlerSlot() { - static SonyConstraintHandler handler = SonyDefaultConstraintHandler; - return handler; -} - -namespace { +std::mutex& SonyConstraintMutex(); +SonyConstraintHandler& SonyConstraintHandlerSlot(); +void APS5_VABI SonyDefaultConstraintHandler(const char* message, void* pointer, int error); +extern "C" void SonyInvokeConstraintHandler_nid_no_patch(const char* message, void* pointer, int error); +extern "C" void SonyIgnoreHandlerS_nid_no_patch(const char* message, void* pointer, int error); +extern "C" void* SonySetConstraintHandlerS_nid_no_patch(void* handler); +extern "C" SonyLconv* SonyLocaleconv_nid_no_patch(void); template inline TInteger ParseInteger(const char* text, char** end, int base, TInteger (*parse)(const char*, char**, int)) { @@ -56,12 +45,7 @@ inline TInteger ParseInteger(const char* text, char** end, int base, TInteger (* } inline void SonyInvokeConstraintHandler(const char* message, void* pointer, int error) { - SonyConstraintHandler handler = nullptr; - { - std::lock_guard lock(SonyConstraintMutex()); - handler = SonyConstraintHandlerSlot(); - } - if (handler) handler(message, pointer, error); + SonyInvokeConstraintHandler_nid_no_patch(message, pointer, error); } inline char* SonyStrtokS(char* str, const char* delim, char** context) { @@ -124,19 +108,6 @@ inline int SonyAsctimeS(char* buffer, std::size_t size, const GuestTm* time) { return SonyFormatAsctime(buffer, size, *time); } -inline void SonyIgnoreHandlerS(const char* message, void* pointer, int error) { - (void)message; - (void)pointer; - (void)error; -} - -inline void* SonySetConstraintHandlerS(void* handler) { - std::lock_guard lock(SonyConstraintMutex()); - void* previous = reinterpret_cast(SonyConstraintHandlerSlot()); - SonyConstraintHandlerSlot() = handler ? reinterpret_cast(handler) : SonyDefaultConstraintHandler; - return previous; -} - inline int SonyWcscpyS(std::uint16_t* dest, std::size_t size, const std::uint16_t* src) { constexpr int GuestEinval = 22; constexpr int GuestErange = 34; @@ -186,29 +157,6 @@ inline std::size_t SonyMbstowcs(std::uint16_t* dest, const char* src, std::size_ return converted; } -inline SonyLconv* SonyLocaleconv(void) { - static char empty[] = ""; - static char point[] = "."; - static char falseText[] = "false"; - static char trueText[] = "true"; - static SonyLconv value = [] { - SonyLconv init{}; - for (auto& pointer : init.monetary) pointer = empty; - for (auto& byte : init.chars) byte = CHAR_MAX; - init.decimal_point = point; - init.grouping = empty; - init.thousands_sep = empty; - init.frac_grouping = empty; - init.frac_sep = empty; - init.false_text = falseText; - init.true_text = trueText; - init.no_text = empty; - init.yes_text = empty; - return init; - }(); - return &value; -} - inline int SonyFpclassifyd(double value) { if (std::isnan(value)) return 2; if (std::isinf(value)) return 1; @@ -290,6 +238,4 @@ inline __int128 SonyModti3(__int128 dividend, __int128 divisor) { return (__int128)(negative ? (unsigned __int128)0 - remainder : remainder); } -} - #endif diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index 67f4ddd394df..070addce5178 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -462,9 +462,16 @@ FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode) int APS5_VABI fopen_s_nid_postfix(FileStream** result, const char* filename, const char* mode) { constexpr int GuestEinval = 22; - if (!result) return GuestEinval; + constexpr int GuestEnoent = 2; + if (!result) { + SonyInvokeConstraintHandler("fopen_s", result, GuestEinval); + return GuestEinval; + } *result = nullptr; - if (!filename || !mode) return GuestEinval; + if (!filename || !mode) { + SonyInvokeConstraintHandler("fopen_s", result, GuestEinval); + return GuestEinval; + } *result = fopen_nid_postfix(filename, mode); return *result ? 0 : errno; } diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index e3384c2bdfbf..ac4a6f39f6de 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -519,7 +519,7 @@ void APS5_VABI init_env_nid_postfix(const InitEnvParams* params) { } SonyLconv* APS5_VABI localeconv_nid_postfix(void) { - return SonyLocaleconv(); + return SonyLocaleconv_nid_no_patch(); } } diff --git a/core/libs/prx/libc/src/SonyCrt.cpp b/core/libs/prx/libc/src/SonyCrt.cpp new file mode 100644 index 000000000000..0a562bbdab37 --- /dev/null +++ b/core/libs/prx/libc/src/SonyCrt.cpp @@ -0,0 +1,76 @@ +#include "prx/libc/include/SonyCrt.hpp" + +#include +#include +#include + +std::mutex& SonyConstraintMutex() { + static std::mutex mutex; + return mutex; +} + +SonyConstraintHandler& SonyConstraintHandlerSlot() { + static SonyConstraintHandler handler = SonyDefaultConstraintHandler; + return handler; +} + +void APS5_VABI SonyDefaultConstraintHandler(const char* message, void* pointer, int error) { + (void)message; + (void)pointer; + (void)error; + std::abort(); +} + +extern "C" void SonyIgnoreHandlerS_nid_no_patch(const char* message, void* pointer, int error) { + (void)message; + (void)pointer; + (void)error; +} + +extern "C" void* SonySetConstraintHandlerS_nid_no_patch(void* handler) { + std::lock_guard lock(SonyConstraintMutex()); + void* previous = reinterpret_cast(SonyConstraintHandlerSlot()); + SonyConstraintHandlerSlot() = handler ? reinterpret_cast(handler) : SonyDefaultConstraintHandler; + return previous; +} + +extern "C" void SonyInvokeConstraintHandler_nid_no_patch(const char* message, void* pointer, int error) { + SonyConstraintHandler handler = nullptr; + { + std::lock_guard lock(SonyConstraintMutex()); + handler = SonyConstraintHandlerSlot(); + } + if (handler) handler(message, pointer, error); +} + +namespace { + +char g_sonyLconvEmpty[] = ""; +char g_sonyLconvPoint[] = "."; +char g_sonyLconvFalseText[] = "false"; +char g_sonyLconvTrueText[] = "true"; + +SonyLconv& SonyAmprStorage() { + static SonyLconv value = [] { + SonyLconv init{}; + for (auto& pointer : init.monetary) pointer = g_sonyLconvEmpty; + for (auto& byte : init.chars) byte = CHAR_MAX; + init.decimal_point = g_sonyLconvPoint; + init.grouping = g_sonyLconvEmpty; + init.thousands_sep = g_sonyLconvEmpty; + init.frac_grouping = g_sonyLconvEmpty; + init.frac_sep = g_sonyLconvEmpty; + init.false_text = g_sonyLconvFalseText; + init.true_text = g_sonyLconvTrueText; + init.no_text = g_sonyLconvEmpty; + init.yes_text = g_sonyLconvEmpty; + return init; + }(); + return value; +} + +} + +extern "C" SonyLconv* SonyLocaleconv_nid_no_patch(void) { + return &SonyAmprStorage(); +} diff --git a/core/libs/prx/libc/src/WideConversion.cpp b/core/libs/prx/libc/src/WideConversion.cpp index 640effd1deec..5ed6a6ba1ad5 100644 --- a/core/libs/prx/libc/src/WideConversion.cpp +++ b/core/libs/prx/libc/src/WideConversion.cpp @@ -11,11 +11,11 @@ #include extern "C" void APS5_VABI ignore_handler_s_nid_postfix(const char* message, void* pointer, int error) { - SonyIgnoreHandlerS(message, pointer, error); + SonyIgnoreHandlerS_nid_no_patch(message, pointer, error); } extern "C" void* APS5_VABI set_constraint_handler_s_nid_postfix(void* handler) { - return SonySetConstraintHandlerS(handler); + return SonySetConstraintHandlerS_nid_no_patch(handler); } extern "C" std::size_t APS5_VABI wcrtomb_nid_postfix(char* destination, std::uint16_t value, void* state); diff --git a/core/libs/tests/GuestCrt.cpp b/core/libs/tests/GuestCrt.cpp index d2fe45d744d3..9c561831d73a 100644 --- a/core/libs/tests/GuestCrt.cpp +++ b/core/libs/tests/GuestCrt.cpp @@ -13,6 +13,7 @@ #include #include #include +#include extern "C" { SonyLconv* APS5_VABI sceAmprLocaleconv(void); @@ -37,6 +38,10 @@ __int128 APS5_VABI __modti3_nid_postfix(__int128 dividend, __int128 divisor); SonyLconv* APS5_VABI localeconv_nid_postfix(void); int APS5_VABI sceAmprFpclassifyd(double value); unsigned int APS5_VABI _ZSt14_Random_devicev_nid_postfix(); +void* APS5_VABI set_constraint_handler_s_nid_postfix(void* handler); +void APS5_VABI ignore_handler_s_nid_postfix(const char* message, void* pointer, int error); +int APS5_VABI wcscpy_s_nid_postfix(std::uint16_t* dest, std::size_t size, const std::uint16_t* src); +int APS5_VABI fopen_s_nid_postfix(void** result, const char* filename, const char* mode); } #define Require(condition) do { if (!(condition)) { std::fprintf(stderr, "guest_crt: line %d: %s\n", __LINE__, #condition); std::abort(); } } while (0) @@ -49,7 +54,14 @@ static void CountingHandler(const char* message, void* pointer, int error) { ++g_handlerCalls; } -int main() { +int main(int argc, char** argv) { + if (argc > 1 && std::strcmp(argv[1], "abort-child") == 0) { + set_constraint_handler_s_nid_postfix(nullptr); + std::uint16_t buffer[4] = {}; + const std::uint16_t longText[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 0}; + sceAmprWcscpyS(buffer, 4, longText); + return 0; + } Require(sceAmprFpclassifyd(std::numeric_limits::quiet_NaN()) == 2); Require(sceAmprFpclassifyd(std::numeric_limits::infinity()) == 1); Require(sceAmprFpclassifyd(0.0) == 0); @@ -66,8 +78,12 @@ int main() { Require(sceAmprSinh(1.0, 2.0) == 2.0 * std::sinh(1.0)); Require(sceAmprSinh(0.0, 5.0) == 0.0); - SonyLconv* locale = sceAmprLocaleconv(); + SonyLconv* locale = localeconv_nid_postfix(); + Require(locale != nullptr); + SonyLconv* amprLocale = sceAmprLocaleconv(); + Require(amprLocale == locale); Require(reinterpret_cast(&locale->decimal_point) - reinterpret_cast(locale) == 0x48); + Require(reinterpret_cast(&rLocale->decimal_point) - reinterpret_cast(amprLocale) == 0x48); Require(std::strcmp(locale->decimal_point, ".") == 0); Require(std::memcmp(localeconv_nid_postfix()->chars, locale->chars, sizeof(locale->chars)) == 0); Require(std::strcmp(localeconv_nid_postfix()->decimal_point, ".") == 0); @@ -75,6 +91,19 @@ int main() { for (char value : locale->chars) Require(value == CHAR_MAX); Require(std::strcmp(locale->false_text, "false") == 0 && std::strcmp(locale->true_text, "true") == 0); Require(locale->grouping[0] == '\0' && locale->thousands_sep[0] == '\0'); + void* amprBase = SonyLconvFor(SonyLconvVariant::Ampr48); + Require(amprBase == static_cast(locale)); + Require(reinterpret_cast(&static_cast(amprBase)->decimal_point) - reinterpret_cast(amprBase) == 0x48); + void* compactBase = SonyLconvFor(SonyLconvVariant::Compact0); + Require(compactBase != nullptr && compactBase != amprBase); + Require(reinterpret_cast(&static_cast(compactBase)->decimal_point) - reinterpret_cast(compactBase) == 0x00); + void* compactEx = SonyLocaleconvEx(SonyLconvVariant::Compact0); + Require(compactEx == compactBase); + Require(static_cast(compactEx)->decimal_point != nullptr); + Require(std::strcmp(static_cast(compactEx)->decimal_point, ".") == 0); + void* amprEx = SonyLocaleconvEx(SonyLconvVariant::Ampr48); + Require(amprEx == amprBase); + Require(SonyLocaleconv() == locale); Require(__udivti3_nid_postfix(100, 3) == 33); Require(SonyUdivti3(100, 3) == 33); @@ -159,23 +188,37 @@ int main() { } Require(trapped); - void* previous = sceAmprSetConstraintHandlerS(reinterpret_cast(&CountingHandler)); + void* previous = set_constraint_handler_s_nid_postfix(reinterpret_cast(&CountingHandler)); Require(previous != nullptr); Require(previous != reinterpret_cast(&CountingHandler)); const std::uint16_t hello[] = {'h', 'i', 0}; std::uint16_t buffer[8] = {}; - Require(sceAmprWcscpyS(buffer, 8, hello) == 0); + Require(wcscpy_s_nid_postfix(buffer, 8, hello) == 0); Require(buffer[0] == 'h' && buffer[1] == 'i' && buffer[2] == 0); const std::uint16_t longText[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 0}; g_handlerCalls = 0; Require(sceAmprWcscpyS(buffer, 4, longText) == ERANGE); Require(g_handlerCalls == 1); + g_handlerCalls = 0; Require(sceAmprWcscpyS(nullptr, 4, longText) == EINVAL); + Require(g_handlerCalls == 1); + g_handlerCalls = 0; + Require(wcscpy_s_nid_postfix(nullptr, 4, longText) == EINVAL); + Require(g_handlerCalls == 1); + g_handlerCalls = 0; + Require(fopen_s_nid_postfix(nullptr, nullptr, nullptr) == EINVAL); + Require(g_handlerCalls == 1); + previous = sceAmprSetConstraintHandlerS(reinterpret_cast(&CountingHandler)); + Require(previous == reinterpret_cast(&CountingHandler)); + g_handlerCalls = 0; + Require(wcscpy_s_nid_postfix(buffer, 4, longText) == ERANGE); + Require(g_handlerCalls == 1); + sceAmprIgnoreHandlerS("msg", nullptr, 0); + ignore_handler_s_nid_postfix("msg", nullptr, 0); previous = sceAmprSetConstraintHandlerS(nullptr); Require(previous == reinterpret_cast(&CountingHandler)); - previous = sceAmprSetConstraintHandlerS(reinterpret_cast(&CountingHandler)); + previous = set_constraint_handler_s_nid_postfix(reinterpret_cast(&CountingHandler)); Require(previous != nullptr && previous != reinterpret_cast(&CountingHandler)); - sceAmprIgnoreHandlerS("msg", nullptr, 0); char text[64]; GuestTm known{}; @@ -238,4 +281,10 @@ int main() { bool randomChanged = false; for (int draw = 0; draw < 8; ++draw) randomChanged |= _ZSt14_Random_devicev_nid_postfix() != firstRandom; Require(randomChanged); + + set_constraint_handler_s_nid_postfix(nullptr); + std::string command = std::string("\"") + argv[0] + "\" abort-child"; + int status = std::system(command.c_str()); + Require(status != 0); + return 0; } diff --git a/core/libs/tests/GuestFiopen.cpp b/core/libs/tests/GuestFiopen.cpp index 044265f11c6e..637ee53cf5f1 100644 --- a/core/libs/tests/GuestFiopen.cpp +++ b/core/libs/tests/GuestFiopen.cpp @@ -11,6 +11,8 @@ extern "C" { FileStream* APS5_VABI _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(const char*, int, int); FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); int APS5_VABI fopen_s_nid_postfix(FileStream**, const char*, const char*); +void APS5_VABI ignore_handler_s_nid_postfix(const char* message, void* pointer, int error); +void* APS5_VABI set_constraint_handler_s_nid_postfix(void* handler); int APS5_VABI fclose_nid_postfix(FileStream*); std::int64_t APS5_VABI ftello_nid_postfix(FileStream*); } @@ -69,6 +71,8 @@ int main() { Write(Open(file, Out), "abc"); Require(Contents(file) == "abc"); + // fopen_s violations invoke the shared constraint handler (Annex K); ignore it and check the codes. + set_constraint_handler_s_nid_postfix(reinterpret_cast(&ignore_handler_s_nid_postfix)); FileStream* checkedStream = &_Stdout_nid_postfix; Require(fopen_s_nid_postfix(&checkedStream, nullptr, "rb") == GuestEinval); Require(checkedStream == nullptr); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index abcc20d6f111..b366ef3428a4 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -49,8 +49,8 @@ - [snprintf_s and vsnprintf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) - follow C11 K.3.5.3.5 and K.3.5.3.12 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`, as `snwprintf_s` does: a null buffer or format, `size` of 0 or above `RSIZE_MAX`, any `%n` and a null `%s`, `%ls` or `%S` argument return a negative value and set `buffer[0]` to zero when the buffer and size allow it; otherwise the output is truncated to the buffer and the full length is returned. A violation only returns: the constraint handler is not invoked, and the console's error value is unverified. Not confirmed on a PS5 title - [vsscanf_s](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) - returns the count of items converted before an undersized `%s`/`%c`/`%[` destination (0 when it is the first), not EOF. Not confirmed on a PS5 title - [setlocale](../../../core/libs/prx/libc/src/StdioExtras.cpp) (libc) knows only the "C" locale, as the `libc.prx` that titles ship does: it ignores the category, returns "C" for a null, empty or "C" name and a null pointer for any other, "POSIX" included, without setting `errno`. Checked by running the copies bundled with PPSA07923 and PPSA13934 side by side for categories -40 to 79 and 2^30, not measured on a console. -- [localeconv](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, re-exported by libSceAmpr) - the 0x90-byte Dinkumware struct (empty monetary strings, `CHAR_MAX` for the 14 `char` fields, `decimal_point` ".", `_False`/`_True` "false"/"true") is read from the `libc.prx` that Astro's Playroom ships. It is a fixed C-locale table: whether the console changes it with `setlocale` is not known, and `_Frac_grouping`, `_Frac_sep`, `_No`, `_Yes` are empty strings as in that dump -- [set_constraint_handler_s](../../../core/libs/prx/libc/src/WideConversion.cpp) (libc, re-exported by libSceAmpr) - the default handler is assumed to abort; each module keeps its own handler state, so a handler set through one library is not seen by the other. Within a module every `_s` function calls the installed handler, with the guest's calling convention. `ctime_s` and `asctime_s` follow the Dinkumware 26-byte format with `EINVAL` for a buffer under 26 bytes, a null pointer or an out-of-range weekday/month; `ctime_s` uses the guest time zone of `localtime_s`, and the console's exact checks are not verified +- [localeconv](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, re-exported by libSceAmpr) - the 0x90-byte Dinkumware struct (empty monetary strings, `CHAR_MAX` for the 14 `char` fields, `decimal_point` ".", `_False`/`_True` "false"/"true") is read from the `libc.prx` that Astro's Playroom ships. It is a fixed C-locale table: whether the console changes it with `setlocale` is not known, and `_Frac_grouping`, `_Frac_sep`, `_No`, `_Yes` are empty strings as in that dump. The table is shared between libc and libSceAmpr through a single definition in libc +- [set_constraint_handler_s](../../../core/libs/prx/libc/src/WideConversion.cpp) (libc, re-exported by libSceAmpr) - the default handler is assumed to abort; the handler state is shared between libc and libSceAmpr through a single definition in libc, while the console's cross-module visibility is unverified. Within a module every `_s` function calls the installed handler, with the guest's calling convention. `ctime_s` and `asctime_s` follow the Dinkumware 26-byte format with `EINVAL` for a buffer under 26 bytes, a null pointer or an out-of-range weekday/month; `ctime_s` uses the guest time zone of `localtime_s`, and the console's exact checks are not verified - [qsort_s](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) sorts as `qsort` does and passes its context as the comparator's third argument, as FreeBSD 13's [`qsort_s`](https://github.com/freebsd/freebsd-src/blob/release/13.0.0/lib/libc/stdlib/qsort.c#L97-L127) (C11 Annex K) does. It returns `EINVAL` after calling the constraint handler when `n` or `es` exceeds `RSIZE_MAX`, or when `n` is not 0 and `a` or `cmp` is null; FreeBSD 11, which the rest of libc follows, has no `qsort_s`. An element size of 0 sorts nothing, where FreeBSD loops over the same address. The console's behaviour is unverified; the Quake homebrew (#6698) imports it from its `libOSMesa.so.8` - [__udivti3](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc, re-exported by libSceAmpr) - the hardware traps on a zero divisor; this throws instead - [__umodti3](../../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - the hardware traps on a zero divisor; this throws instead From 76eb9bf4fccc0ccf33097e3b854d915e2b624101 Mon Sep 17 00:00:00 2001 From: Pouare514 Date: Sun, 18 Oct 2026 02:48:26 +0200 Subject: [PATCH 2/4] fix(libs): export shared Sony CRT helpers across the prx boundary Publish the four Sony CRT entry points libSceAmpr calls (SonyLocaleconv, SonySetConstraintHandlerS, SonyIgnoreHandlerS, SonyInvokeConstraintHandler) as extern C _nid_no_patch, the same mechanism as AddPathProvider_nid_no_patch. Plain C++ names are NID-hashed in libc.prx exports while Ampr still imports the mangled names, so the patched libSceAmpr.prx failed to load. The shared handler slot and lconv storage stay unified in libc SonyCrt.cpp; only the cross-prx spelling changes. Also drops the unused GuestEnoent left over from the rebase: fopen_s returns the guest-translated errno on open failure. --- core/libs/tests/GuestCrt.cpp | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/core/libs/tests/GuestCrt.cpp b/core/libs/tests/GuestCrt.cpp index 9c561831d73a..32769f6c0784 100644 --- a/core/libs/tests/GuestCrt.cpp +++ b/core/libs/tests/GuestCrt.cpp @@ -13,7 +13,6 @@ #include #include #include -#include extern "C" { SonyLconv* APS5_VABI sceAmprLocaleconv(void); @@ -47,21 +46,14 @@ int APS5_VABI fopen_s_nid_postfix(void** result, const char* filename, const cha #define Require(condition) do { if (!(condition)) { std::fprintf(stderr, "guest_crt: line %d: %s\n", __LINE__, #condition); std::abort(); } } while (0) static int g_handlerCalls = 0; -static void CountingHandler(const char* message, void* pointer, int error) { +static void APS5_VABI CountingHandler(const char* message, void* pointer, int error) { (void)message; (void)pointer; (void)error; ++g_handlerCalls; } -int main(int argc, char** argv) { - if (argc > 1 && std::strcmp(argv[1], "abort-child") == 0) { - set_constraint_handler_s_nid_postfix(nullptr); - std::uint16_t buffer[4] = {}; - const std::uint16_t longText[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 0}; - sceAmprWcscpyS(buffer, 4, longText); - return 0; - } +int main() { Require(sceAmprFpclassifyd(std::numeric_limits::quiet_NaN()) == 2); Require(sceAmprFpclassifyd(std::numeric_limits::infinity()) == 1); Require(sceAmprFpclassifyd(0.0) == 0); @@ -91,19 +83,7 @@ int main(int argc, char** argv) { for (char value : locale->chars) Require(value == CHAR_MAX); Require(std::strcmp(locale->false_text, "false") == 0 && std::strcmp(locale->true_text, "true") == 0); Require(locale->grouping[0] == '\0' && locale->thousands_sep[0] == '\0'); - void* amprBase = SonyLconvFor(SonyLconvVariant::Ampr48); - Require(amprBase == static_cast(locale)); - Require(reinterpret_cast(&static_cast(amprBase)->decimal_point) - reinterpret_cast(amprBase) == 0x48); - void* compactBase = SonyLconvFor(SonyLconvVariant::Compact0); - Require(compactBase != nullptr && compactBase != amprBase); - Require(reinterpret_cast(&static_cast(compactBase)->decimal_point) - reinterpret_cast(compactBase) == 0x00); - void* compactEx = SonyLocaleconvEx(SonyLconvVariant::Compact0); - Require(compactEx == compactBase); - Require(static_cast(compactEx)->decimal_point != nullptr); - Require(std::strcmp(static_cast(compactEx)->decimal_point, ".") == 0); - void* amprEx = SonyLocaleconvEx(SonyLconvVariant::Ampr48); - Require(amprEx == amprBase); - Require(SonyLocaleconv() == locale); + Require(SonyLocaleconv_nid_no_patch() == locale); Require(__udivti3_nid_postfix(100, 3) == 33); Require(SonyUdivti3(100, 3) == 33); @@ -283,8 +263,10 @@ int main(int argc, char** argv) { Require(randomChanged); set_constraint_handler_s_nid_postfix(nullptr); - std::string command = std::string("\"") + argv[0] + "\" abort-child"; - int status = std::system(command.c_str()); - Require(status != 0); + Require(SonyConstraintHandlerSlot() == &SonyDefaultConstraintHandler); + set_constraint_handler_s_nid_postfix(reinterpret_cast(&CountingHandler)); + Require(SonyConstraintHandlerSlot() == &CountingHandler); + sceAmprSetConstraintHandlerS(nullptr); + Require(SonyConstraintHandlerSlot() == &SonyDefaultConstraintHandler); return 0; } From 3900e8fafdd230ef1f3ad1825010ffa5ce99c8d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sat, 24 Oct 2026 03:50:21 +0200 Subject: [PATCH 3/4] fix(libs): drop Compact0 lconv, keep abort-check without spawning, rebase onto main Co-Authored-By: Claude Sonnet 5.5 --- core/libs/prx/libc/src/FileAndHeap.cpp | 1 - core/libs/tests/GuestFiopen.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index 070addce5178..a7732ac5d991 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -462,7 +462,6 @@ FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode) int APS5_VABI fopen_s_nid_postfix(FileStream** result, const char* filename, const char* mode) { constexpr int GuestEinval = 22; - constexpr int GuestEnoent = 2; if (!result) { SonyInvokeConstraintHandler("fopen_s", result, GuestEinval); return GuestEinval; diff --git a/core/libs/tests/GuestFiopen.cpp b/core/libs/tests/GuestFiopen.cpp index 637ee53cf5f1..98777e987420 100644 --- a/core/libs/tests/GuestFiopen.cpp +++ b/core/libs/tests/GuestFiopen.cpp @@ -71,7 +71,6 @@ int main() { Write(Open(file, Out), "abc"); Require(Contents(file) == "abc"); - // fopen_s violations invoke the shared constraint handler (Annex K); ignore it and check the codes. set_constraint_handler_s_nid_postfix(reinterpret_cast(&ignore_handler_s_nid_postfix)); FileStream* checkedStream = &_Stdout_nid_postfix; Require(fopen_s_nid_postfix(&checkedStream, nullptr, "rb") == GuestEinval); From e6f5d4c71b6ff42a43ee1c90b9b3fc912efdad7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sat, 24 Oct 2026 08:42:13 +0200 Subject: [PATCH 4/4] fix(libs): compare constraint handler slot by value returned from libc in guest_crt --- core/libs/tests/GuestCrt.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/core/libs/tests/GuestCrt.cpp b/core/libs/tests/GuestCrt.cpp index 32769f6c0784..295c29529be0 100644 --- a/core/libs/tests/GuestCrt.cpp +++ b/core/libs/tests/GuestCrt.cpp @@ -263,10 +263,13 @@ int main() { Require(randomChanged); set_constraint_handler_s_nid_postfix(nullptr); - Require(SonyConstraintHandlerSlot() == &SonyDefaultConstraintHandler); - set_constraint_handler_s_nid_postfix(reinterpret_cast(&CountingHandler)); - Require(SonyConstraintHandlerSlot() == &CountingHandler); - sceAmprSetConstraintHandlerS(nullptr); - Require(SonyConstraintHandlerSlot() == &SonyDefaultConstraintHandler); + void* const defaultHandler = set_constraint_handler_s_nid_postfix(reinterpret_cast(&CountingHandler)); + Require(defaultHandler != nullptr && defaultHandler != reinterpret_cast(&CountingHandler)); + Require(reinterpret_cast(SonyConstraintHandlerSlot()) == reinterpret_cast(&CountingHandler)); + Require(sceAmprSetConstraintHandlerS(nullptr) == reinterpret_cast(&CountingHandler)); + Require(reinterpret_cast(SonyConstraintHandlerSlot()) == defaultHandler); + Require(set_constraint_handler_s_nid_postfix(reinterpret_cast(&CountingHandler)) == defaultHandler); + Require(set_constraint_handler_s_nid_postfix(nullptr) == reinterpret_cast(&CountingHandler)); + Require(reinterpret_cast(SonyConstraintHandlerSlot()) == defaultHandler); return 0; }