diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index 410fa6f309e..dab667190fb 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -427,6 +427,28 @@ FileStream* APS5_VABI _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(const return nullptr; } +FileStream* APS5_VABI _ZNSt8__sce_v27_FiopenEPKcii_nid_postfix(const char* filename, int mode, int protection) { + static_cast(protection); + constexpr int In = 0x01, Out = 0x02, Ate = 0x04, App = 0x08, Trunc = 0x10, Binary = 0x20, Nocreate = 0x40, + Noreplace = 0x80; + constexpr std::pair modes[] = { + {In, "r"}, {Out, "w"}, {Out | Trunc, "w"}, {Out | App, "a"}, + {In | Binary, "rb"}, {Out | Binary, "wb"}, {Out | Trunc | Binary, "wb"}, {Out | App | Binary, "ab"}, + {In | Out, "r+"}, {In | Out | Trunc, "w+"}, {In | Out | App, "a+"}, + {In | Out | Binary, "r+b"}, {In | Out | Trunc | Binary, "w+b"}, {In | Out | App | Binary, "a+b"}, + {Out | Noreplace, "wx"}, {Out | Trunc | Noreplace, "wx"}, {In | Out | Trunc | Noreplace, "w+x"}, + {Out | Binary | Noreplace, "wbx"}, {Out | Trunc | Binary | Noreplace, "wbx"}, + {In | Out | Trunc | Binary | Noreplace, "w+bx"}}; + const int open = (mode & ~(Ate | Nocreate)) | ((mode & App) ? Out : 0) | ((mode & Nocreate) ? In : 0); + const char* openMode = nullptr; + for (const auto& [flags, text] : modes) if (flags == open) openMode = text; + if (!openMode) return nullptr; + auto* stream = fopen_nid_postfix(filename, openMode); + if (!stream || !(mode & Ate) || fseek_nid_postfix(stream, 0, SEEK_END) == 0) return stream; + fclose_nid_postfix(stream); + return nullptr; +} + size_t APS5_VABI fread_nid_postfix(void* buffer, size_t size, size_t count, FileStream* stream) { if (stream && !stream->IsOpen()) { errno = 9; return 0; } auto* handle = GetStreamHandle(stream); diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index a4388c8dfe5..81b7ed2754a 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -334,6 +334,23 @@ StreamManipulator APS5_VABI _ZSt4setwi_nid_postfix(std::int32_t width) { return {&SetStreamWidth, width}; } +void* APS5_VABI SetStreamWidthV2(void* stream, std::int64_t width) { + if (stream == nullptr) throw std::invalid_argument("setw: null stream"); + std::memcpy(static_cast(stream) + 0x28, &width, sizeof(width)); + return stream; +} + +struct StreamManipulatorV2 { + void* (APS5_VABI* function)(void*, std::int64_t); + std::int64_t argument; +}; + +static_assert(sizeof(StreamManipulatorV2) == 0x10); + +StreamManipulatorV2 APS5_VABI _ZNSt8__sce_v24setwEx_nid_postfix(std::int64_t width) { + return {&SetStreamWidthV2, width}; +} + void APS5_VABI _ZNSt8ios_base7_AddstdEPS__nid_postfix(GuestLocale::IosBase* self) { if (self == nullptr) throw std::invalid_argument("ios_base _Addstd: null stream"); std::lock_guard lock(g_standardStreamMutex); diff --git a/core/libs/prx/libc/src/RuntimeSupport.cpp b/core/libs/prx/libc/src/RuntimeSupport.cpp index f7204ea7f18..046456adb43 100644 --- a/core/libs/prx/libc/src/RuntimeSupport.cpp +++ b/core/libs/prx/libc/src/RuntimeSupport.cpp @@ -132,6 +132,10 @@ unsigned int APS5_VABI _ZSt14_Random_devicev_nid_postfix() { return value; } +unsigned int APS5_VABI _ZNSt8__sce_v214_Random_deviceEv_nid_postfix() { + return _ZSt14_Random_devicev_nid_postfix(); +} + [[noreturn]] void APS5_VABI _ZSt14_Throw_C_errori_nid_postfix(int code) { throw std::runtime_error("_Throw_C_error: C11 thread error " + std::to_string(code)); } diff --git a/core/libs/tests/GuestCrt.cpp b/core/libs/tests/GuestCrt.cpp index d2fe45d744d..d672d2fbdf2 100644 --- a/core/libs/tests/GuestCrt.cpp +++ b/core/libs/tests/GuestCrt.cpp @@ -37,6 +37,7 @@ __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(); +unsigned int APS5_VABI _ZNSt8__sce_v214_Random_deviceEv_nid_postfix(); } #define Require(condition) do { if (!(condition)) { std::fprintf(stderr, "guest_crt: line %d: %s\n", __LINE__, #condition); std::abort(); } } while (0) @@ -238,4 +239,9 @@ int main() { bool randomChanged = false; for (int draw = 0; draw < 8; ++draw) randomChanged |= _ZSt14_Random_devicev_nid_postfix() != firstRandom; Require(randomChanged); + + const unsigned int firstVersioned = _ZNSt8__sce_v214_Random_deviceEv_nid_postfix(); + bool versionedChanged = false; + for (int draw = 0; draw < 64; ++draw) versionedChanged |= _ZNSt8__sce_v214_Random_deviceEv_nid_postfix() != firstVersioned; + Require(versionedChanged); } diff --git a/core/libs/tests/GuestFiopen.cpp b/core/libs/tests/GuestFiopen.cpp index 276459ec07b..a6f98bcaaa3 100644 --- a/core/libs/tests/GuestFiopen.cpp +++ b/core/libs/tests/GuestFiopen.cpp @@ -9,6 +9,7 @@ extern "C" { FileStream* APS5_VABI _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(const char*, int, int); +FileStream* APS5_VABI _ZNSt8__sce_v27_FiopenEPKcii_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*); int APS5_VABI fclose_nid_postfix(FileStream*); @@ -28,6 +29,12 @@ constexpr int In = 0x01, Out = 0x02, Ate = 0x04, App = 0x08, Trunc = 0x10, Nocre constexpr int GuestEinval = 22; constexpr int GuestEnoent = 2; +constexpr int V2Binary = 0x20, V2Nocreate = 0x40, V2Noreplace = 0x80; + +static FileStream* OpenV2(const char* name, int mode) { + return _ZNSt8__sce_v27_FiopenEPKcii_nid_postfix(name, mode, 0x1b6); +} + static FileStream* Open(const char* name, int mode) { return _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(name, mode, 0x1b6); } @@ -152,6 +159,83 @@ int main() { Require(stream != nullptr && std::fgetc(stream->GetHandle()) == 'k'); fclose_nid_postfix(stream); + const char* versioned = "guest_fiopen_dir/v2"; + for (int mode : {0, Ate, Trunc, V2Binary, V2Nocreate | Trunc, V2Noreplace, In | Trunc, In | V2Noreplace, + In | V2Binary | V2Noreplace, In | 0x100, In | 0x8000, -1, Out | 0x100}) { + Require(OpenV2(versioned, mode) == nullptr); + Require(!Exists(versioned)); + } + for (int mode : {In, In | Ate, In | V2Binary, In | Out, V2Nocreate, V2Nocreate | Ate, Out | V2Nocreate, + Out | V2Nocreate | V2Binary}) { + Require(OpenV2(versioned, mode) == nullptr); + Require(!Exists(versioned)); + } + Write(OpenV2(versioned, Out | V2Binary), "abc"); + Require(Contents(versioned) == "abc"); + Require(OpenV2(versioned, Out | V2Noreplace) == nullptr); + Require(OpenV2(versioned, In | Out | V2Binary | V2Noreplace | Trunc) == nullptr); + Require(OpenV2(versioned, Out | App | V2Noreplace) == nullptr); + Require(OpenV2(versioned, Out | App | V2Noreplace | Ate) == nullptr); + Require(OpenV2(versioned, In | 0x100) == nullptr); + Require(OpenV2(versioned, In | 0x8000) == nullptr); + Require(OpenV2(versioned, -1) == nullptr); + Require(Contents(versioned) == "abc"); + + Write(OpenV2(versioned, App), "d"); + Require(Contents(versioned) == "abcd"); + Write(OpenV2(versioned, Out | App | V2Binary), "e"); + Require(Contents(versioned) == "abcde"); + Write(OpenV2(versioned, In | App), "f"); + Require(Contents(versioned) == "abcdef"); + + stream = OpenV2(versioned, In | Ate); + Require(stream != nullptr && ftello_nid_postfix(stream) == 6); + fclose_nid_postfix(stream); + stream = OpenV2(versioned, In | V2Binary); + Require(stream != nullptr && ftello_nid_postfix(stream) == 0); + fclose_nid_postfix(stream); + stream = OpenV2(versioned, V2Nocreate | Ate); + Require(stream != nullptr && ftello_nid_postfix(stream) == 6); + fclose_nid_postfix(stream); + + Write(OpenV2(versioned, Out | V2Nocreate), "X"); + Require(Contents(versioned) == "Xbcdef"); + Write(OpenV2(versioned, In | Out), "Y"); + Require(Contents(versioned) == "Ybcdef"); + stream = OpenV2(versioned, Out | V2Nocreate | V2Binary | Ate); + Require(stream != nullptr && ftello_nid_postfix(stream) == 6); + Write(stream, "g"); + Require(Contents(versioned) == "Ybcdefg"); + + Write(OpenV2(versioned, Out | Trunc), "h"); + Require(Contents(versioned) == "h"); + Write(OpenV2(versioned, In | Out | Trunc | V2Binary), "ij"); + Require(Contents(versioned) == "ij"); + Write(OpenV2(versioned, Out), "k"); + Require(Contents(versioned) == "k"); + + const char* created = "guest_fiopen_dir/v2created"; + Write(OpenV2(created, Out | V2Noreplace), "new"); + Require(Contents(created) == "new"); + std::filesystem::remove(dir / "v2created"); + Write(OpenV2(created, In | Out | Trunc | V2Noreplace), "plus"); + Require(Contents(created) == "plus"); + std::filesystem::remove(dir / "v2created"); + Write(OpenV2(created, Out | V2Binary | V2Noreplace), "bin"); + Require(Contents(created) == "bin"); + Require(OpenV2(created, Out | Trunc | V2Binary | V2Noreplace) == nullptr); + Require(Contents(created) == "bin"); + std::filesystem::remove(dir / "v2created"); + Write(OpenV2(created, Out | Trunc | V2Binary | V2Noreplace), "x"); + Require(Contents(created) == "x"); + Require(OpenV2(created, In | Out | Trunc | V2Binary | V2Noreplace) == nullptr); + std::filesystem::remove(dir / "v2created"); + Write(OpenV2(created, Out | App), "app"); + Require(Contents(created) == "app"); + std::filesystem::remove(dir / "v2created"); + Write(OpenV2(created, Out | Trunc | V2Nocreate), "made"); + Require(Contents(created) == "made"); + #ifndef _WIN32 auto* full = std::fopen("/dev/full", "wb"); Require(full != nullptr); diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index 71b71a16690..40f59e031ff 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -24,6 +24,11 @@ struct StreamManipulator { std::int32_t argument; }; StreamManipulator APS5_VABI _ZSt4setwi_nid_postfix(std::int32_t width); +struct StreamManipulatorV2 { + void* (APS5_VABI* function)(void*, std::int64_t); + std::int64_t argument; +}; +StreamManipulatorV2 APS5_VABI _ZNSt8__sce_v24setwEx_nid_postfix(std::int64_t width); void APS5_VABI _ZNSt8ios_base7_AddstdEPS__nid_postfix(GuestLocale::IosBase* self); const short* APS5_VABI _Getpctype_nid_postfix(); const short* APS5_VABI _Getptolower_nid_postfix(); @@ -177,6 +182,52 @@ static void CheckStreamWidth() { RequireException([&] { manipulator.function(nullptr, manipulator.argument); }); } +static void CheckStreamWidthV2() { + struct GuardedStream { + std::uint64_t before = 0x12345678; + std::array stream{}; + std::uint64_t after = 0x87654321; + } object; + object.stream.fill(0x1111111111111111); + const auto width = [&] { return object.stream[5]; }; + const auto untouched = [&] { + for (std::size_t index = 0; index < object.stream.size(); ++index) + if (index != 5 && object.stream[index] != 0x1111111111111111) return false; + return object.before == 0x12345678 && object.after == 0x87654321; + }; + + const auto manipulator = _ZNSt8__sce_v24setwEx_nid_postfix(12); + Require(manipulator.argument == 12 && manipulator.function != nullptr); + Require(manipulator.function(object.stream.data(), manipulator.argument) == object.stream.data()); + Require(width() == 12 && untouched()); + + const std::int64_t wide = static_cast(INT_MAX) + 5; + const auto large = _ZNSt8__sce_v24setwEx_nid_postfix(wide); + Require(large.argument == wide); + large.function(object.stream.data(), large.argument); + Require(width() == 0x80000004 && untouched()); + + const std::int64_t huge = 0x123456789abcdef0; + const auto highest = _ZNSt8__sce_v24setwEx_nid_postfix(huge); + highest.function(object.stream.data(), highest.argument); + Require(width() == 0x123456789abcdef0 && untouched()); + + const auto negative = _ZNSt8__sce_v24setwEx_nid_postfix(-1); + negative.function(object.stream.data(), negative.argument); + Require(width() == ~std::uint64_t{} && untouched()); + + const auto reset = _ZNSt8__sce_v24setwEx_nid_postfix(0); + reset.function(object.stream.data(), reset.argument); + Require(width() == 0 && untouched()); + RequireException([&] { manipulator.function(nullptr, manipulator.argument); }); + + GuestLocale::IosBase legacy{}; + legacy.width = 7; + const auto narrow = _ZSt4setwi_nid_postfix(12); + narrow.function(&legacy, narrow.argument); + Require(legacy.width == 12); +} + static void CheckStandardStreams() { struct GuardedStream { std::uint64_t before = 0x12345678; @@ -247,6 +298,7 @@ int main() { CheckCharacterConversions(); CheckStreamDestruction(); CheckStreamWidth(); + CheckStreamWidthV2(); CheckStandardStreams(); CheckWideCaseTranslation(); _Locksyslock_nid_postfix(); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index ef9b20680c7..70e42994468 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -134,3 +134,6 @@ - [fopen_s](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) - returns the errno of the failed open, as C11 K.3.5.2.1 leaves the nonzero value open and Microsoft's CRT returns errno; the console's Dinkumware `fopen_s` is not verified - [ios_base standard streams](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) `_Addstd` queues the stream in the standard stream table of Dinkumware [ios.cpp](https://github.com/ganboing/crt.vc12/blob/master/src/ios.cpp) (`stdstr`, `stdopens`, `_Addstd`, `_Ios_base_dtor`): it takes the first of slots 1 to 7 that is free or already holds the stream, and slot 8 (`NSTDSTR`) when none is, so slot 8 is shared by every stream past the first seven and registration never fails. It stores the 1-based slot index in the guest `IosBase::standardStream` (Dinkumware's `_Stdstr`, where 0 means not a standard stream) and counts the stream objects registered per slot; registering the same stream again reuses its slot and raises its count. Measured on a PS5 (firmware 13.60) from a homebrew payload calling the system `libSceLibcInternal` export: in a process whose own standard streams already hold slots 1 to 7, twelve distinct streams and a repeated one all get 8, written as a 64-bit value at offset 0x8 with no other byte of the object touched; the assignment of slots 1 to 7 to fresh streams is not observable there and follows VC12. The counts are `int`, not VC12's `char`, and the registration and the destructor lock the table where VC12 locks only `_Addstd`; a slot index past the table throws in the destructor instead of indexing out of bounds. The destructor decrements its slot's count and suppresses the rest of the destruction while the count stays positive, as `_Ios_base_dtor` does, and the slots of destroyed streams keep their pointer and stay reserved for that stream, as `stdstr` does. The streams themselves are destroyed by the title's own code as it registers them through `__cxa_atexit`; when the last reference closes, the unsupported stream storage and callback cleanup is still the throw it was and the locale is released as for a non-standard stream. - [std::_Pad, _Thrd_join, _Thrd_detach, _Thrd_id](../../../core/libs/prx/libc/src/Threads.cpp) (libc) follow Dinkumware V6.00 `thr/xthread` and `thr/threadcall.cpp` ([crt.vc12 `f1d47126`](https://github.com/ganboing/crt.vc12/tree/f1d47126/src/thr)), without console verification. `_Thrd_id` throws because whether the console returns `pthread_t` or a kernel thread ID and at what width is unknown; `_Launch` omits `_Cnd_do_broadcast_at_thread_exit` and calls `pthread_create` registered by libkernel with default attributes. The 32-byte pad layout (`_Go()` vtable at 0x0, `_Cnd_t` at 0x8, `_Mtx_t` at 0x10, `bool _Started` at 0x18) is assumed from VC12, unverified. Failing primitives or thread start throw the guest `std::system_error`; null arguments throw. `_Thrd_join` returns `_Thrd_error` (4) on `pthread_join` failure and truncates the exit code to `int`; `_Thrd_detach` returns `_Thrd_error` (4) on `pthread_detach` failure and throws when none is registered +- [std::__sce_v2::setw](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc, NID `I0vuMo7qix8`) returns the console's two-register manipulator: the function pointer and the 64-bit width. The function stores the width as 8 bytes at offset 0x28 of the stream it is given, which is where the console's versioned `ios_base` keeps its width. This libc exports no versioned `ios_base`, so only streams built by a title's own `libc.prx` carry that layout; the unversioned `ios_base` here keeps a 32-bit width at 0x20. A null stream throws, where the console faults. +- [std::__sce_v2::_Fiopen](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc, NID `lEeJadNvMbo`) follows the console's `libc.prx` (disassembled from a title's copy), whose open-mode bits differ from the unversioned `_Fiopen`: binary is 0x20, no-create 0x40 and no-replace 0x80, and a mode with any other bit set, or a combination outside the console's table of twenty, returns null. No-create reads as `in`, `app` as `out|app`, and the table maps no-replace modes to the host `x` mode flag. The protection argument is ignored, as in the unversioned call. Not run on a console. +- [std::__sce_v2::_Xregex_error](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libc, NID `luv6hxzFAhY`) is not implemented. The console's body throws a `std::__sce_v2::regex_error` of 0x18 bytes whose message comes from a table of fifteen texts indexed by the error code, not the unversioned `std::regex_error` of 0x38 bytes, so it needs the versioned exception types of #8901 and cannot reuse the unversioned thrower.