Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions core/libs/prx/libc/src/FileAndHeap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>(protection);
constexpr int In = 0x01, Out = 0x02, Ate = 0x04, App = 0x08, Trunc = 0x10, Binary = 0x20, Nocreate = 0x40,
Noreplace = 0x80;
constexpr std::pair<int, const char*> 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);
Expand Down
17 changes: 17 additions & 0 deletions core/libs/prx/libc/src/LocaleSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::byte*>(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);
Expand Down
4 changes: 4 additions & 0 deletions core/libs/prx/libc/src/RuntimeSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
6 changes: 6 additions & 0 deletions core/libs/tests/GuestCrt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
84 changes: 84 additions & 0 deletions core/libs/tests/GuestFiopen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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*);
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
52 changes: 52 additions & 0 deletions core/libs/tests/GuestLocale.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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<std::uint64_t, 8> 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<std::int64_t>(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;
Expand Down Expand Up @@ -247,6 +298,7 @@ int main() {
CheckCharacterConversions();
CheckStreamDestruction();
CheckStreamWidth();
CheckStreamWidthV2();
CheckStandardStreams();
CheckWideCaseTranslation();
_Locksyslock_nid_postfix();
Expand Down
3 changes: 3 additions & 0 deletions docs/dev/debt/libc.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading