#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 struct TypeRecord { const void* vtable; const char* name; const TypeRecord* base; }; using GuestWhat = const char* (APS5_VABI *)(const LibcException::ExceptionObject*); using Thrower = void (APS5_VABI *)(const void*); extern "C" { extern const TypeRecord _ZTISt9exception_nid_postfix; extern GuestLocale::Implementation* _ZNSt8__sce_v26locale7_Locimp8_ClocptrE_nid_postfix; extern GuestLocale::Implementation* _ZNSt8__sce_v221_sceLibcClassicLocaleE_nid_postfix; void APS5_VABI SceV2Catch(Thrower thrower, const void* argument); const void* SceV2CatchType = nullptr; const void* SceV2OtherType = nullptr; int SceV2Selector = 0; } void* APS5_VABI TestHeapAllocate(std::size_t bytes) { return std::malloc(bytes); } void APS5_VABI TestHeapFree(void* pointer) { std::free(pointer); } void* APS5_VABI TestHeapCalloc(std::size_t count, std::size_t bytes) { return std::calloc(count, bytes); } void* APS5_VABI TestHeapReallocate(void* pointer, std::size_t bytes) { return std::realloc(pointer, bytes); } void* APS5_VABI TestHeapAlign(std::size_t, std::size_t bytes) { return std::malloc(bytes); } void* APS5_VABI TestHeapRealign(void* pointer, std::size_t bytes, std::size_t) { return std::realloc(pointer, bytes); } int APS5_VABI TestHeapPosixAlign(void** pointer, std::size_t, std::size_t bytes) { *pointer = std::malloc(bytes); return *pointer != nullptr ? 0 : -1; } static unsigned failures = 0; static std::string inspected; static void Require(bool value, const char* what) { if (value) return; std::fprintf(stderr, "FAIL: %s\n", what); ++failures; } extern "C" void APS5_VABI SceV2Inspect(void* object) { const auto* exception = static_cast(object); const auto* vtable = static_cast(exception->vtable); inspected = static_cast(vtable[-1])->name; inspected += '|'; inspected += reinterpret_cast(const_cast(vtable[2]))(exception); } enum class LocinfoCall { Check, Construct, Addcats }; struct LocinfoArguments { LocinfoCall call; GuestLocale::SceV2Locinfo* self; int category; const char* name; }; extern "C" void APS5_VABI SceV2CallLocinfo(const void* argument) { const auto* arguments = static_cast(argument); if (arguments->call == LocinfoCall::Check) _ZNSt8__sce_v215_sceLibcLocinfoEPKc_nid_postfix(arguments->name); if (arguments->call == LocinfoCall::Construct) _ZNSt8__sce_v28_Locinfo13_Locinfo_ctorEPS0_iPKc_nid_postfix(arguments->self, arguments->category, arguments->name); if (arguments->call == LocinfoCall::Addcats) _ZNSt8__sce_v28_Locinfo16_Locinfo_AddcatsEPS0_iPKc_nid_postfix(arguments->self, arguments->category, arguments->name); } static bool ThrowsBadLocaleName(const LocinfoArguments& arguments) { const TypeRecord runtimeError {_ZTISt9exception_nid_postfix.vtable, "NSt8__sce_v213runtime_errorE", nullptr}; const TypeRecord logicError {_ZTISt9exception_nid_postfix.vtable, "NSt8__sce_v211logic_errorE", nullptr}; SceV2CatchType = &runtimeError; SceV2OtherType = &logicError; SceV2Selector = 0; inspected.clear(); SceV2Catch(reinterpret_cast(SceV2CallLocinfo), &arguments); return SceV2Selector == 2 && inspected == "NSt8__sce_v213runtime_errorE|bad locale name"; } static bool IsEmptyLocimp(const GuestLocale::Implementation* locimp, bool transparent) { return locimp != nullptr && locimp->base.vtable != nullptr && locimp->base.references == 1 && locimp->facets == nullptr && locimp->facetCount == 0 && locimp->category == 0 && locimp->transparent == transparent && locimp->name != nullptr && std::strcmp(locimp->name, "*") == 0; } static void CheckClassic() { auto* classic = _ZNSt8__sce_v221_sceLibcClassicLocaleE_nid_postfix; Require(_ZNSt8__sce_v26locale7_Locimp8_ClocptrE_nid_postfix == classic, "_Clocptr points at the classic _Locimp"); auto* facade = _ZNSt8__sce_v26locale7classicEv_nid_postfix(); Require(facade != nullptr && facade->implementation == classic, "classic() holds the classic _Locimp"); Require(_ZNSt8__sce_v26locale7classicEv_nid_postfix() == facade, "classic() returns the same object"); Require(_ZNSt8__sce_v26locale16_GetgloballocaleEv_nid_postfix() == classic, "the global locale starts as the classic one"); Require(_ZNSt8__sce_v26locale5_InitEb_nid_postfix(false) == classic, "_Init returns the global locale"); } static void CheckEmpty() { GuestLocaleFacade first {}; GuestLocaleFacade second {}; Require(_ZNSt8__sce_v26locale5emptyEv_nid_postfix(&first) == &first, "empty() returns its result"); Require(IsEmptyLocimp(first.implementation, true), "empty() builds a transparent _Locimp named *"); _ZNSt8__sce_v26locale5emptyEv_nid_postfix(&second); Require(second.implementation != first.implementation, "empty() builds a new _Locimp each time"); Require(IsEmptyLocimp(_ZNSt8__sce_v26locale7_Locimp11_New_LocimpEb_nid_postfix(false), false), "_New_Locimp(false) builds an opaque _Locimp"); Require(IsEmptyLocimp(_ZNSt8__sce_v26locale7_Locimp11_New_LocimpEb_nid_postfix(true), true), "_New_Locimp(true) builds a transparent _Locimp"); } static void CheckGlobal() { auto* classic = _ZNSt8__sce_v221_sceLibcClassicLocaleE_nid_postfix; const auto classicReferences = classic->base.references; GuestLocaleFacade target {}; _ZNSt8__sce_v26locale5emptyEv_nid_postfix(&target); auto* locimp = target.implementation; GuestLocaleFacade previous {}; Require(_ZNSt8__sce_v26locale6globalERKS0__nid_postfix(&previous, &target) == &previous, "global() returns its result"); Require(previous.implementation == classic, "global() returns the previous global locale"); Require(classic->base.references == classicReferences, "global() releases the reference the old global held"); Require(_ZNSt8__sce_v26locale16_GetgloballocaleEv_nid_postfix() == locimp, "global() installs the new locale"); Require(locimp->base.references == 2, "global() retains the new global locale"); Require(_ZNSt8__sce_v26locale5_InitEb_nid_postfix(false) == locimp, "_Init returns the new global locale"); Require(locimp->base.references == 2, "_Init(false) takes no reference"); GuestLocaleFacade same {}; _ZNSt8__sce_v26locale6globalERKS0__nid_postfix(&same, &target); Require(same.implementation == locimp, "global() with the current locale returns it"); Require(locimp->base.references == 3, "global() with the current locale only takes the result's reference"); GuestLocaleFacade back {classic}; GuestLocaleFacade old {}; _ZNSt8__sce_v26locale6globalERKS0__nid_postfix(&old, &back); Require(old.implementation == locimp, "global() returns the replaced locale"); Require(locimp->base.references == 3, "the replaced locale keeps the result's reference only"); Require(classic->base.references == classicReferences + 1, "the classic locale is retained again as the global"); Require(_ZNSt8__sce_v26locale16_GetgloballocaleEv_nid_postfix() == classic, "global() restores the classic locale"); auto* other = _ZNSt8__sce_v26locale7_Locimp11_New_LocimpEb_nid_postfix(false); _ZNSt8__sce_v26locale16_SetgloballocaleEPv_nid_postfix(other); Require(_ZNSt8__sce_v26locale16_GetgloballocaleEv_nid_postfix() == other, "_Setgloballocale installs the locale"); Require(other->base.references == 1, "_Setgloballocale takes no reference"); _ZNSt8__sce_v26locale16_SetgloballocaleEPv_nid_postfix(classic); bool threw = false; try { _ZNSt8__sce_v26locale16_SetgloballocaleEPv_nid_postfix(nullptr); } catch (const std::invalid_argument&) { threw = true; } Require(threw, "_Setgloballocale(null) throws"); Require(_ZNSt8__sce_v26locale16_GetgloballocaleEv_nid_postfix() == classic, "a rejected _Setgloballocale keeps the global locale"); } static void CheckLocinfo() { const char untouched[] = "untouched"; const char* const accepted[] {"", "C"}; const char* const rejected[] {nullptr, "POSIX", "c", "C.UTF-8", "CX", "en_US.UTF-8"}; const int categories[] {0, 1, 0x3f, -1}; for (const char* name : accepted) { LocinfoArguments check {LocinfoCall::Check, nullptr, 0, name}; SceV2CallLocinfo(&check); for (int category : categories) { GuestLocale::SceV2Locinfo locinfo {0x5a5a5a5a5a5a5a5aull, nullptr, nullptr}; _ZNSt8__sce_v28_Locinfo13_Locinfo_ctorEPS0_iPKc_nid_postfix(&locinfo, category, name); Require(locinfo.name != nullptr && std::strcmp(locinfo.name, "C") == 0, "_Locinfo(int) stores the current name"); Require(locinfo.classicName != nullptr && std::strcmp(locinfo.classicName, "C") == 0, "_Locinfo(int) stores C at offset 0x10"); Require(locinfo.reserved == 0x5a5a5a5a5a5a5a5aull, "_Locinfo(int) preserves the caller word"); GuestLocale::SceV2Locinfo added {0x5a5a5a5a5a5a5a5aull, untouched, untouched}; Require(_ZNSt8__sce_v28_Locinfo16_Locinfo_AddcatsEPS0_iPKc_nid_postfix(&added, category, name) == &added, "_Addcats returns its object"); Require(added.classicName != nullptr && std::strcmp(added.classicName, "C") == 0, "_Addcats stores C at offset 0x10"); Require(added.name == untouched && added.reserved == 0x5a5a5a5a5a5a5a5aull, "_Addcats leaves the other fields"); } } for (const char* name : rejected) { Require(ThrowsBadLocaleName({LocinfoCall::Check, nullptr, 0, name}), "_sceLibcLocinfo rejects the name"); GuestLocale::SceV2Locinfo locinfo {0x5a5a5a5a5a5a5a5aull, nullptr, untouched}; Require(ThrowsBadLocaleName({LocinfoCall::Construct, &locinfo, 0x3f, name}), "_Locinfo(int) rejects the name"); Require(locinfo.name != nullptr && std::strcmp(locinfo.name, "C") == 0, "_Locinfo(int) stores the current name before rejecting"); Require(locinfo.classicName == untouched, "_Locinfo(int) leaves offset 0x10 when rejecting"); GuestLocale::SceV2Locinfo added {0x5a5a5a5a5a5a5a5aull, untouched, untouched}; Require(ThrowsBadLocaleName({LocinfoCall::Addcats, &added, 0x3f, name}), "_Addcats rejects the name"); Require(added.name == untouched && added.classicName == untouched, "_Addcats changes nothing when rejecting"); } } int main() { std::array heapApi{}; heapApi[0] = reinterpret_cast(&TestHeapAllocate); heapApi[1] = reinterpret_cast(&TestHeapFree); heapApi[2] = reinterpret_cast(&TestHeapCalloc); heapApi[3] = reinterpret_cast(&TestHeapReallocate); heapApi[4] = reinterpret_cast(&TestHeapAlign); heapApi[5] = reinterpret_cast(&TestHeapRealign); heapApi[6] = reinterpret_cast(&TestHeapPosixAlign); ApplicationHeapRegister_nid_no_patch(heapApi.data()); CheckClassic(); CheckEmpty(); CheckGlobal(); CheckLocinfo(); if (failures != 0) return 1; std::puts("std::__sce_v2 locale basics passed"); return 0; }