From 24e372834e42705dc09005e984b497ecd5ccdfb5 Mon Sep 17 00:00:00 2001 From: B-Howell <116104752+B-Howell@users.noreply.github.com> Date: Wed, 4 Nov 2026 04:28:15 -0500 Subject: [PATCH] feat(libSceLibcInternal): implement the codecvt members The nine codecvt exports threw. The do_ members now run libc's C-locale conversion table, which libc returns from LibcCodecvtMethods_nid_no_patch without adding any libc export, and in, out, unshift and length call the facet's vtable slot as [locale.codecvt.members] requires. libc's do_unshift now returns noconv, which [locale.codecvt.virtuals] fixes for this specialization. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceLibcInternal/CMakeLists.txt | 13 ++ core/libs/prx/libSceLibcInternal/Codecvt.cpp | 66 +++++++ core/libs/prx/libSceLibcInternal/Err.cpp | 54 ------ .../prx/libSceLibcInternal/include/Api.hpp | 10 ++ core/libs/prx/libc/include/GuestLocale.hpp | 2 + core/libs/prx/libc/src/LocaleSupport.cpp | 6 +- core/libs/tests/GuestLibcInternalCodecvt.cpp | 170 ++++++++++++++++++ core/libs/tests/GuestLocale.cpp | 2 +- docs/dev/debt/libSceLibcInternal.md | 10 +- 9 files changed, 268 insertions(+), 65 deletions(-) create mode 100644 core/libs/prx/libSceLibcInternal/Codecvt.cpp create mode 100644 core/libs/tests/GuestLibcInternalCodecvt.cpp diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt index 774ed8f0634..3590d656775 100644 --- a/core/libs/prx/libSceLibcInternal/CMakeLists.txt +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -45,6 +45,19 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() +target_sources(libSceLibcInternal PRIVATE Codecvt.cpp) +if(BUILD_TESTING) + add_test_executable(guest_libc_internal_codecvt_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcInternalCodecvt.cpp) + target_include_directories(guest_libc_internal_codecvt_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libc_internal_codecvt_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_libc_internal_codecvt_tests) + add_test(NAME guest_libc_internal_codecvt COMMAND guest_libc_internal_codecvt_tests) + set_tests_properties(guest_libc_internal_codecvt PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libc_internal_codecvt PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() if(BUILD_TESTING) add_test_executable(guest_init_once_tests ${LIBS_INCLUDE_DIR}/tests/GuestInitOnce.cpp) target_include_directories(guest_init_once_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libSceLibcInternal/Codecvt.cpp b/core/libs/prx/libSceLibcInternal/Codecvt.cpp new file mode 100644 index 00000000000..89ee5bf0222 --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/Codecvt.cpp @@ -0,0 +1,66 @@ +#include "prx/libSceLibcInternal/include/Api.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestLocale.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include + +namespace { + +const GuestLocale::CodecvtVtable& Conversion(const GuestLocale::Facet* self, const char* function) { + if (self == nullptr || self->vtable == nullptr) throw std::invalid_argument(std::string(function) + ": null facet"); + return *reinterpret_cast(self->vtable); +} + +} + +extern "C" { + +bool APS5_VABI sceLibcInternalCodecvtDoAlwaysNoconv_nid_no_patch(const GuestLocale::Facet* self) { + return LibcCodecvtMethods_nid_no_patch()->alwaysNoconv(self); +} +APS5_EXPORT("7tIwDZyyKHo", sceLibcInternalCodecvtDoAlwaysNoconv_nid_no_patch); + +int APS5_VABI sceLibcInternalCodecvtDoIn_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, const char* first1, const char* last1, const char** next1, char* first3, char* last3, char** next3) { + return LibcCodecvtMethods_nid_no_patch()->in(self, state, first1, last1, next1, first3, last3, next3); +} +APS5_EXPORT("P+q1OLiErP0", sceLibcInternalCodecvtDoIn_nid_no_patch); + +int APS5_VABI sceLibcInternalCodecvtDoOut_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, const char* first1, const char* last1, const char** next1, char* first3, char* last3, char** next3) { + return LibcCodecvtMethods_nid_no_patch()->out(self, state, first1, last1, next1, first3, last3, next3); +} +APS5_EXPORT("Uc+-Sx0UZ3U", sceLibcInternalCodecvtDoOut_nid_no_patch); + +int APS5_VABI sceLibcInternalCodecvtDoUnshift_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, char* first3, char* last3, char** next3) { + return LibcCodecvtMethods_nid_no_patch()->unshift(self, state, first3, last3, next3); +} +APS5_EXPORT("iCWgjeqMHvg", sceLibcInternalCodecvtDoUnshift_nid_no_patch); + +int APS5_VABI sceLibcInternalCodecvtDoLength_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, const char* first1, const char* last1, std::size_t count) { + return static_cast(LibcCodecvtMethods_nid_no_patch()->length(self, state, first1, last1, count)); +} +APS5_EXPORT("Xk7IZcfHDD8", sceLibcInternalCodecvtDoLength_nid_no_patch); + +int APS5_VABI sceLibcInternalCodecvtIn_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, const char* first1, const char* last1, const char** next1, char* first3, char* last3, char** next3) { + return Conversion(self, "codecvt::in").in(self, state, first1, last1, next1, first3, last3, next3); +} +APS5_EXPORT("TNexGlwiVEQ", sceLibcInternalCodecvtIn_nid_no_patch); + +int APS5_VABI sceLibcInternalCodecvtOut_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, const char* first1, const char* last1, const char** next1, char* first3, char* last3, char** next3) { + return Conversion(self, "codecvt::out").out(self, state, first1, last1, next1, first3, last3, next3); +} +APS5_EXPORT("14xKj+SV72o", sceLibcInternalCodecvtOut_nid_no_patch); + +int APS5_VABI sceLibcInternalCodecvtUnshift_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, char* first3, char* last3, char** next3) { + return Conversion(self, "codecvt::unshift").unshift(self, state, first3, last3, next3); +} +APS5_EXPORT("N7z+Dnk1cS0", sceLibcInternalCodecvtUnshift_nid_no_patch); + +int APS5_VABI sceLibcInternalCodecvtLength_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, const char* first1, const char* last1, std::size_t count) { + return static_cast(Conversion(self, "codecvt::length").length(self, state, first1, last1, count)); +} +APS5_EXPORT("ikBt0lqkxPc", sceLibcInternalCodecvtLength_nid_no_patch); + +} diff --git a/core/libs/prx/libSceLibcInternal/Err.cpp b/core/libs/prx/libSceLibcInternal/Err.cpp index d89aa356086..0ba8b8725f0 100644 --- a/core/libs/prx/libSceLibcInternal/Err.cpp +++ b/core/libs/prx/libSceLibcInternal/Err.cpp @@ -315,60 +315,6 @@ int APS5_VABI sceLibcInternalPending3_026_nid_no_patch() { } APS5_EXPORT("lynApfiP6Lw", sceLibcInternalPending3_026_nid_no_patch); -int APS5_VABI sceLibcInternalPending3_027_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -APS5_EXPORT("iCWgjeqMHvg", sceLibcInternalPending3_027_nid_no_patch); - -int APS5_VABI sceLibcInternalPending3_028_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -APS5_EXPORT("7tIwDZyyKHo", sceLibcInternalPending3_028_nid_no_patch); - -int APS5_VABI sceLibcInternalPending3_029_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -APS5_EXPORT("TNexGlwiVEQ", sceLibcInternalPending3_029_nid_no_patch); - -int APS5_VABI sceLibcInternalPending3_030_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -APS5_EXPORT("14xKj+SV72o", sceLibcInternalPending3_030_nid_no_patch); - -int APS5_VABI sceLibcInternalPending3_031_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -APS5_EXPORT("P+q1OLiErP0", sceLibcInternalPending3_031_nid_no_patch); - -int APS5_VABI sceLibcInternalPending3_032_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -APS5_EXPORT("Uc+-Sx0UZ3U", sceLibcInternalPending3_032_nid_no_patch); - -int APS5_VABI sceLibcInternalPending3_033_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -APS5_EXPORT("ikBt0lqkxPc", sceLibcInternalPending3_033_nid_no_patch); - -int APS5_VABI sceLibcInternalPending3_034_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -APS5_EXPORT("N7z+Dnk1cS0", sceLibcInternalPending3_034_nid_no_patch); - -int APS5_VABI sceLibcInternalPending3_035_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -APS5_EXPORT("Xk7IZcfHDD8", sceLibcInternalPending3_035_nid_no_patch); - int APS5_VABI sceLibcInternalPending3_036_nid_no_patch() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibcInternal/include/Api.hpp b/core/libs/prx/libSceLibcInternal/include/Api.hpp index 06d185f0424..2d124700189 100644 --- a/core/libs/prx/libSceLibcInternal/include/Api.hpp +++ b/core/libs/prx/libSceLibcInternal/include/Api.hpp @@ -4,6 +4,7 @@ #include "prx/libSceLibcInternal/include/Spawn.hpp" #include "prx/libkernel/System/include/UserContext.hpp" #include "prx/libc/include/FileStream.hpp" +#include "prx/libc/include/GuestLocale.hpp" #include "prx/libc/include/LegacySignals.hpp" #include "SceTypes.hpp" #include @@ -44,6 +45,15 @@ void APS5_VABI sceLibcHeapGetTraceInfo_nid_postfix(Info* info); int APS5_VABI _sceLibcInternalThreadAtexit_nid_postfix(ThreadDestructorFunction destructor, void* object, void* dsoSymbol); void APS5_VABI _sceLibcInternalThreadDtors_nid_postfix(); int APS5_VABI _sceLibcInternalForceTlsDestructor_nid_postfix(KernelModule handle); +bool APS5_VABI sceLibcInternalCodecvtDoAlwaysNoconv_nid_no_patch(const GuestLocale::Facet* self); +int APS5_VABI sceLibcInternalCodecvtDoIn_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, const char* first1, const char* last1, const char** next1, char* first3, char* last3, char** next3); +int APS5_VABI sceLibcInternalCodecvtDoOut_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, const char* first1, const char* last1, const char** next1, char* first3, char* last3, char** next3); +int APS5_VABI sceLibcInternalCodecvtDoUnshift_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, char* first3, char* last3, char** next3); +int APS5_VABI sceLibcInternalCodecvtDoLength_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, const char* first1, const char* last1, std::size_t count); +int APS5_VABI sceLibcInternalCodecvtIn_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, const char* first1, const char* last1, const char** next1, char* first3, char* last3, char** next3); +int APS5_VABI sceLibcInternalCodecvtOut_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, const char* first1, const char* last1, const char** next1, char* first3, char* last3, char** next3); +int APS5_VABI sceLibcInternalCodecvtUnshift_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, char* first3, char* last3, char** next3); +int APS5_VABI sceLibcInternalCodecvtLength_nid_no_patch(GuestLocale::Facet* self, std::uint64_t* state, const char* first1, const char* last1, std::size_t count); unsigned int APS5_VABI alarm_nid_postfix(unsigned int seconds); int APS5_VABI fstatvfs_nid_postfix(int descriptor, LibcStatvfs* output); int APS5_VABI utime_nid_postfix(const char* path, const LibcUtimbuf* times); diff --git a/core/libs/prx/libc/include/GuestLocale.hpp b/core/libs/prx/libc/include/GuestLocale.hpp index 49918225170..be2116fbf5d 100644 --- a/core/libs/prx/libc/include/GuestLocale.hpp +++ b/core/libs/prx/libc/include/GuestLocale.hpp @@ -264,6 +264,8 @@ static_assert(offsetof(SceV2Locinfo, classicName) == 0x10); } +extern "C" const GuestLocale::CodecvtVtable* LibcCodecvtMethods_nid_no_patch(); + inline constexpr std::size_t kNumPutCharVtableSlots = 14; static_assert(kNumPutCharVtableSlots * sizeof(std::uintptr_t) == 112); extern "C" std::uintptr_t _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[kNumPutCharVtableSlots]; diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index 347fa354170..28bf7f23088 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -295,7 +295,7 @@ int APS5_VABI CodecvtNoConversion(GuestLocale::Facet*, std::uint64_t*, const cha int APS5_VABI CodecvtUnshift(GuestLocale::Facet*, std::uint64_t*, char* first3, char*, char** next3) { *next3 = first3; - return 0; + return 3; } std::size_t APS5_VABI CodecvtLength(GuestLocale::Facet*, std::uint64_t*, const char* first1, const char* last1, std::size_t count) { @@ -914,6 +914,10 @@ std::size_t APS5_VABI _ZNSt7codecvtIcc9_MbstatetE7_GetcatEPPKNSt6locale5facetEPK return 2; } +const GuestLocale::CodecvtVtable* LibcCodecvtMethods_nid_no_patch() { + return &g_codecvtVtable; +} + std::size_t APS5_VABI _ZNSt8numpunctIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const*) { if (facet != nullptr && *facet == nullptr) { auto* punct = static_cast(_Znwm_nid_postfix(sizeof(GuestLocale::NumpunctFacet))); diff --git a/core/libs/tests/GuestLibcInternalCodecvt.cpp b/core/libs/tests/GuestLibcInternalCodecvt.cpp new file mode 100644 index 00000000000..09ed6d0901f --- /dev/null +++ b/core/libs/tests/GuestLibcInternalCodecvt.cpp @@ -0,0 +1,170 @@ +#include "prx/libSceLibcInternal/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/GuestLocale.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "libc internal codecvt: %s\n", message); + std::abort(); +} + +void* APS5_VABI HeapAllocate(std::size_t bytes) { return std::malloc(bytes); } +void APS5_VABI HeapFree(void* pointer) { std::free(pointer); } +void* APS5_VABI HeapCalloc(std::size_t count, std::size_t bytes) { return std::calloc(count, bytes); } +void* APS5_VABI HeapReallocate(void* pointer, std::size_t bytes) { return std::realloc(pointer, bytes); } +void* APS5_VABI HeapAlign(std::size_t, std::size_t bytes) { return std::malloc(bytes); } +void* APS5_VABI HeapRealign(void* pointer, std::size_t bytes, std::size_t) { return std::realloc(pointer, bytes); } +int APS5_VABI HeapPosixAlign(void** pointer, std::size_t, std::size_t bytes) { + *pointer = std::malloc(bytes); + return *pointer == nullptr ? 12 : 0; +} + +constexpr int kOk = 0; +constexpr int kPartial = 1; +constexpr int kError = 2; +constexpr int kNoconv = 3; + +void APS5_VABI FacetNop(GuestLocale::Facet*) {} +GuestLocale::Facet* APS5_VABI FacetRelease(GuestLocale::Facet*) { return nullptr; } +bool APS5_VABI DerivedAlwaysNoconv(const GuestLocale::Facet*) { return false; } +int APS5_VABI DerivedConstant(const GuestLocale::Facet*) { return 4; } +int APS5_VABI DerivedIn(GuestLocale::Facet*, std::uint64_t* state, const char*, const char* last1, const char** next1, char* first3, char*, char** next3) { + *state = 0x11; + *next1 = last1; + *next3 = first3 + 1; + return kOk; +} +int APS5_VABI DerivedOut(GuestLocale::Facet*, std::uint64_t* state, const char* first1, const char*, const char** next1, char*, char* last3, char** next3) { + *state = 0x22; + *next1 = first1 + 1; + *next3 = last3; + return kPartial; +} +int APS5_VABI DerivedUnshift(GuestLocale::Facet*, std::uint64_t* state, char*, char* last3, char** next3) { + *state = 0x33; + *next3 = last3; + return kError; +} +std::size_t APS5_VABI DerivedLength(GuestLocale::Facet*, std::uint64_t* state, const char*, const char*, std::size_t count) { + *state = 0x44; + return 0xffffffff00000000ull | (count + 1); +} +const GuestLocale::CodecvtVtable g_derivedVtable{{FacetNop, FacetNop, FacetNop, FacetRelease}, DerivedAlwaysNoconv, DerivedConstant, DerivedConstant, DerivedIn, DerivedOut, DerivedUnshift, DerivedLength}; + +void CheckClassic(GuestLocale::Facet* classic) { + const char source[] = "abcde"; + const char* const last = source + 5; + std::uint64_t state = 0x5a; + std::array target{'w', 'x', 'y', 'z'}; + const char* next1 = nullptr; + char* next3 = nullptr; + + Require(sceLibcInternalCodecvtDoAlwaysNoconv_nid_no_patch(classic), "do_always_noconv of codecvt is true"); + + Require(sceLibcInternalCodecvtDoIn_nid_no_patch(classic, &state, source, last, &next1, target.data(), target.data() + target.size(), &next3) == kNoconv, "do_in returns noconv"); + Require(next1 == source && next3 == target.data(), "do_in leaves from_next at from and to_next at to"); + Require(sceLibcInternalCodecvtIn_nid_no_patch(classic, &state, source, last, &next1, target.data(), target.data() + target.size(), &next3) == kNoconv, "in returns noconv"); + Require(next1 == source && next3 == target.data(), "in leaves from_next at from and to_next at to"); + + next1 = nullptr; + next3 = nullptr; + Require(sceLibcInternalCodecvtDoOut_nid_no_patch(classic, &state, source, last, &next1, target.data(), target.data() + target.size(), &next3) == kNoconv, "do_out returns noconv"); + Require(next1 == source && next3 == target.data(), "do_out leaves from_next at from and to_next at to"); + Require(sceLibcInternalCodecvtOut_nid_no_patch(classic, &state, source, last, &next1, target.data(), target.data() + target.size(), &next3) == kNoconv, "out returns noconv"); + Require(next1 == source && next3 == target.data(), "out leaves from_next at from and to_next at to"); + + next3 = nullptr; + Require(sceLibcInternalCodecvtDoUnshift_nid_no_patch(classic, &state, target.data(), target.data() + target.size(), &next3) == kNoconv, "do_unshift of codecvt returns noconv"); + Require(next3 == target.data(), "do_unshift stores no characters"); + next3 = nullptr; + Require(sceLibcInternalCodecvtUnshift_nid_no_patch(classic, &state, target.data(), target.data() + target.size(), &next3) == kNoconv, "unshift returns noconv"); + Require(next3 == target.data(), "unshift stores no characters"); + + Require(sceLibcInternalCodecvtDoLength_nid_no_patch(classic, &state, source, last, 3) == 3, "do_length is max when max is smaller"); + Require(sceLibcInternalCodecvtDoLength_nid_no_patch(classic, &state, source, last, 9) == 5, "do_length is from_end - from when that is smaller"); + Require(sceLibcInternalCodecvtLength_nid_no_patch(classic, &state, source, last, 2) == 2, "length is max when max is smaller"); + Require(sceLibcInternalCodecvtLength_nid_no_patch(classic, &state, source, last, 7) == 5, "length is from_end - from when that is smaller"); + Require(sceLibcInternalCodecvtLength_nid_no_patch(classic, &state, source, source, 7) == 0, "length of an empty range is 0"); + + Require(state == 0x5a, "the conversions leave the state alone"); + Require(std::memcmp(target.data(), "wxyz", target.size()) == 0, "the conversions write no characters"); +} + +void CheckDerived() { + GuestLocale::Facet derived{&g_derivedVtable.facet, 1, 0}; + const char source[] = "abcde"; + const char* const last = source + 5; + std::array target{}; + std::uint64_t state = 0; + const char* next1 = nullptr; + char* next3 = nullptr; + + Require(sceLibcInternalCodecvtIn_nid_no_patch(&derived, &state, source, last, &next1, target.data(), target.data() + target.size(), &next3) == kOk, "in returns the overriding do_in's result"); + Require(state == 0x11 && next1 == last && next3 == target.data() + 1, "in passes its arguments to the overriding do_in"); + Require(sceLibcInternalCodecvtOut_nid_no_patch(&derived, &state, source, last, &next1, target.data(), target.data() + target.size(), &next3) == kPartial, "out returns the overriding do_out's result"); + Require(state == 0x22 && next1 == source + 1 && next3 == target.data() + target.size(), "out passes its arguments to the overriding do_out"); + Require(sceLibcInternalCodecvtUnshift_nid_no_patch(&derived, &state, target.data(), target.data() + target.size(), &next3) == kError, "unshift returns the overriding do_unshift's result"); + Require(state == 0x33 && next3 == target.data() + target.size(), "unshift passes its arguments to the overriding do_unshift"); + Require(sceLibcInternalCodecvtLength_nid_no_patch(&derived, &state, source, last, 6) == 7, "length returns the int the overriding do_length returned"); + Require(state == 0x44, "length passes its state to the overriding do_length"); + + state = 0x5a; + next1 = nullptr; + next3 = nullptr; + Require(sceLibcInternalCodecvtDoAlwaysNoconv_nid_no_patch(&derived), "do_always_noconv ignores the overriding vtable"); + Require(sceLibcInternalCodecvtDoIn_nid_no_patch(&derived, &state, source, last, &next1, target.data(), target.data() + target.size(), &next3) == kNoconv && next1 == source && next3 == target.data(), "do_in ignores the overriding vtable"); + Require(sceLibcInternalCodecvtDoOut_nid_no_patch(&derived, &state, source, last, &next1, target.data(), target.data() + target.size(), &next3) == kNoconv && next1 == source && next3 == target.data(), "do_out ignores the overriding vtable"); + Require(sceLibcInternalCodecvtDoUnshift_nid_no_patch(&derived, &state, target.data(), target.data() + target.size(), &next3) == kNoconv && next3 == target.data(), "do_unshift ignores the overriding vtable"); + Require(sceLibcInternalCodecvtDoLength_nid_no_patch(&derived, &state, source, last, 6) == 5, "do_length ignores the overriding vtable"); + Require(state == 0x5a, "the base do_ members leave the state alone"); +} + +void CheckNullFacet() { + std::uint64_t state = 0; + const char source[] = "ab"; + std::array target{}; + const char* next1 = nullptr; + char* next3 = nullptr; + GuestLocale::Facet empty{nullptr, 1, 0}; + for (GuestLocale::Facet* facet : {static_cast(nullptr), &empty}) { + int rejected = 0; + try { sceLibcInternalCodecvtIn_nid_no_patch(facet, &state, source, source + 2, &next1, target.data(), target.data() + 2, &next3); } catch (const std::invalid_argument&) { ++rejected; } + try { sceLibcInternalCodecvtOut_nid_no_patch(facet, &state, source, source + 2, &next1, target.data(), target.data() + 2, &next3); } catch (const std::invalid_argument&) { ++rejected; } + try { sceLibcInternalCodecvtUnshift_nid_no_patch(facet, &state, target.data(), target.data() + 2, &next3); } catch (const std::invalid_argument&) { ++rejected; } + try { sceLibcInternalCodecvtLength_nid_no_patch(facet, &state, source, source + 2, 2); } catch (const std::invalid_argument&) { ++rejected; } + Require(rejected == 4, "the public members reject a null facet or a facet without a vtable"); + } +} + +} + +int main() { + std::array heapApi{}; + heapApi[0] = reinterpret_cast(&HeapAllocate); + heapApi[1] = reinterpret_cast(&HeapFree); + heapApi[2] = reinterpret_cast(&HeapCalloc); + heapApi[3] = reinterpret_cast(&HeapReallocate); + heapApi[4] = reinterpret_cast(&HeapAlign); + heapApi[5] = reinterpret_cast(&HeapRealign); + heapApi[6] = reinterpret_cast(&HeapPosixAlign); + ApplicationHeapRegister_nid_no_patch(heapApi.data()); + + GuestLocale::Facet* classic = nullptr; + Require(_ZNSt7codecvtIcc9_MbstatetE7_GetcatEPPKNSt6locale5facetEPKS2__nid_postfix(&classic, nullptr) == 2 && classic != nullptr, "_Getcat builds the C-locale facet"); + CheckClassic(classic); + CheckDerived(); + CheckNullFacet(); + classic->vtable->deleteObject(classic); + return 0; +} diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index 7ed7e0d3bf4..ee81c76e595 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -474,7 +474,7 @@ static void CheckFacetGetcat() { Require(nextFrom == "ab" && nextTo == target); Require(codecvtVtable->in(codecvt, nullptr, "ab", "ab" + 2, &nextFrom, target, target + 4, &nextTo) == 3); nextTo = reinterpret_cast(0x10); - Require(codecvtVtable->unshift(codecvt, nullptr, nullptr, nullptr, &nextTo) == 0 && nextTo == nullptr); + Require(codecvtVtable->unshift(codecvt, nullptr, nullptr, nullptr, &nextTo) == 3 && nextTo == nullptr); Require(codecvtVtable->length(codecvt, nullptr, "ab", "ab" + 2, 5) == 2); Require(codecvtVtable->length(codecvt, nullptr, "ab", "ab" + 2, 1) == 1); GuestLocale::Facet* punct = nullptr; diff --git a/docs/dev/debt/libSceLibcInternal.md b/docs/dev/debt/libSceLibcInternal.md index 0dea6b26139..1b49394aea7 100644 --- a/docs/dev/debt/libSceLibcInternal.md +++ b/docs/dev/debt/libSceLibcInternal.md @@ -344,6 +344,7 @@ The named entries below use provider-specific NID aliases to avoid interposing h - [inet_aton, inet_addr, inet_ntoa and inet_ntoa_r](../../../core/libs/prx/libSceLibcInternal/Inet.cpp) (libSceLibcInternal) follow the FreeBSD code in the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x8ADE0 to 0x8B3D0) and matched it on a PS5 (firmware 13.60) for 20 `inet_aton` inputs; a null text, which the console dereferences, throws. `inet_ntoa_r` formats through libkernel's `__inet_ntop`, so a null buffer with room for the text throws and a null buffer that is too small sets `ENOSPC`, as `__inet_ntop` does for a null destination; the console's `inet_ntoa_r` was not measured with a null buffer - [__inet_ntoa_r](../../../core/libs/prx/libSceLibcInternal/Inet.cpp) (libSceLibcInternal) was not measured on the console: it runs `inet_ntoa_r`, as FreeBSD 11.4 names the function `__inet_ntoa_r` ([arpa/inet.h](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/include/arpa/inet.h#L129)) and makes `inet_ntoa_r` a weak alias of it ([inet_ntoa.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_ntoa.c#L62-L76)) - [inet_netof, inet_lnaof, inet_makeaddr and inet_network](../../../core/libs/prx/libSceLibcInternal/Inet.cpp) (libSceLibcInternal, NIDs `UXEIr+tUsM4`, `4y7OKpbXUDs`, `5YaIoLorY8A`, `-Qdf3rmtqRA`, and aliases `3R15U5X00nY`, `bD39ou3sn7w`, `9cf7QE7cmto`, `xDCABmswhcM`) follow FreeBSD 11.4 [inet_netof.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_netof.c), [inet_lnaof.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_lnaof.c), [inet_makeaddr.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_makeaddr.c) and [inet_network.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/inet/inet_network.c) (`8c639945a4`). `inet_network` components are bounded to 0xff with a 32-bit accumulator. A null text throws std::invalid_argument. Console unverified. +- [codecvt do_always_noconv, do_in, do_out, do_unshift, do_length, in, out, unshift and length](../../../core/libs/prx/libSceLibcInternal/Codecvt.cpp) (libSceLibcInternal, NIDs `7tIwDZyyKHo`, `P+q1OLiErP0`, `Uc+-Sx0UZ3U`, `iCWgjeqMHvg`, `Xk7IZcfHDD8`, `TNexGlwiVEQ`, `14xKj+SV72o`, `N7z+Dnk1cS0`, `ikBt0lqkxPc`): the console's `libSceLibcInternal.sprx` was not read and no title was seen calling them, so whether it reads the state or checks a null facet is unverified. The `do_` members call the slots of the C-locale `libc` codecvt table that `_Getcat` installs (`LibcCodecvtMethods_nid_no_patch`): the degenerate conversion [locale.codecvt.virtuals] fixes for this specialization (`noconv`, nothing stored, `do_length` the lesser of count and range). The public members call the facet's own vtable slot ([locale.codecvt.members]), and `length` keeps the low 32 bits because `do_length` returns `int` ### Additional PS5 3.20 imports (xdCloudy #9511) @@ -407,15 +408,6 @@ The named entries below use provider-specific NID aliases to avoid interposing h - [_ZNKSt7codecvtIDsc9_MbstatetE5do_inERS0_PKcS4_RS4_PDsS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `+uPwCGfmJHs` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. - [_ZNKSt7codecvtIDsc9_MbstatetE6do_outERS0_PKDsS4_RS4_PcS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `0FKwlv9iH1c` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. - [_ZNKSt7codecvtIDsc9_MbstatetE9do_lengthERS0_PKcS4_m](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `lynApfiP6Lw` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. -- [_ZNKSt7codecvtIcc9_MbstatetE10do_unshiftERS0_PcS3_RS3_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `iCWgjeqMHvg` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. -- [_ZNKSt7codecvtIcc9_MbstatetE16do_always_noconvEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `7tIwDZyyKHo` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. -- [_ZNKSt7codecvtIcc9_MbstatetE2inERS0_PKcS4_RS4_PcS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `TNexGlwiVEQ` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. -- [_ZNKSt7codecvtIcc9_MbstatetE3outERS0_PKcS4_RS4_PcS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `14xKj+SV72o` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. -- [_ZNKSt7codecvtIcc9_MbstatetE5do_inERS0_PKcS4_RS4_PcS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `P+q1OLiErP0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. -- [_ZNKSt7codecvtIcc9_MbstatetE6do_outERS0_PKcS4_RS4_PcS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `Uc+-Sx0UZ3U` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. -- [_ZNKSt7codecvtIcc9_MbstatetE6lengthERS0_PKcS4_m](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `ikBt0lqkxPc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. -- [_ZNKSt7codecvtIcc9_MbstatetE7unshiftERS0_PcS3_RS3_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `N7z+Dnk1cS0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. -- [_ZNKSt7codecvtIcc9_MbstatetE9do_lengthERS0_PKcS4_m](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `Xk7IZcfHDD8` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. - [_ZNKSt7codecvtIwc9_MbstatetE10do_unshiftERS0_PcS3_RS3_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `oDtGxrzLXMU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. - [_ZNKSt7codecvtIwc9_MbstatetE11do_encodingEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `4fPIrH+z+E4` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. - [_ZNKSt7codecvtIwc9_MbstatetE13do_max_lengthEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `5BQIjX7Y5YU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only.