diff --git a/core/libs/prx/libc/include/FileStream.hpp b/core/libs/prx/libc/include/FileStream.hpp index 5df643c16..00c9ebcb9 100644 --- a/core/libs/prx/libc/include/FileStream.hpp +++ b/core/libs/prx/libc/include/FileStream.hpp @@ -65,6 +65,20 @@ public: } GuestFilePrefix& GuestState() { return _guest; } + bool Reopen(const char* filename, const char* mode) { + auto* previous = GetHandle(); + _guest = {}; + _handle = std::freopen(filename, mode, previous); + if (!_handle) return false; + _guest.flags = 0x10; +#ifdef _WIN32 + const int descriptor = _fileno(_handle); +#else + const int descriptor = ::fileno(_handle); +#endif + _guest.descriptor = descriptor >= 0 && descriptor <= 32767 ? static_cast(descriptor) : -1; + return true; + } void SyncStatus() { _guest.readRemaining = 0; _guest.writeRemaining = 0; diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index 2bc2d6cb7..79b7d70fb 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -16,6 +16,25 @@ extern "C" { [[noreturn]] void APS5_VABI _ZSt11_Xbad_allocv_nid_postfix(); +FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode, FileStream* stream) { + if (!stream || !mode) { errno = 22; return nullptr; } + if (!filename) { errno = 45; return nullptr; } // Mode-only reopening is not supported. + const char* supported[] = {"r", "w", "a", "rb", "wb", "ab", "r+", "w+", "a+", + "rb+", "wb+", "ab+", "r+b", "w+b", "a+b"}; + bool valid = false; + for (const auto* candidate : supported) if (std::strcmp(mode, candidate) == 0) valid = true; + if (!valid) { errno = 22; return nullptr; } + try { + const auto path = *filename ? ResolvePath_nid_no_patch(filename).string() : std::string{}; + if (stream->Reopen(path.c_str(), mode)) return stream; + const int error = errno; + if (stream->IsDynamic()) delete stream; + errno = error; + return nullptr; + } catch (const std::bad_alloc&) { errno = 12; return nullptr; } + catch (const std::filesystem::filesystem_error&) { errno = 5; return nullptr; } +} + FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode) { if (!filename || !mode) throw std::runtime_error(std::string(__func__) + ": " + FOPEN_MSG_NULL_ARG); const std::filesystem::path fpath = ResolvePath_nid_no_patch(filename); diff --git a/core/libs/tests/GuestStandardStreams.cpp b/core/libs/tests/GuestStandardStreams.cpp index f4d85607e..62ce707ca 100644 --- a/core/libs/tests/GuestStandardStreams.cpp +++ b/core/libs/tests/GuestStandardStreams.cpp @@ -4,7 +4,11 @@ #include #include #include +#include +#include +#include extern "C" { +FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*); int APS5_VABI fseeko_nid_postfix(FileStream*, std::int64_t, int); std::int64_t APS5_VABI ftello_nid_postfix(FileStream*); int APS5_VABI fseek_nid_postfix(FileStream*, std::int64_t, int); @@ -102,4 +106,24 @@ int main() { Require(fseeko_nid_postfix(&positioned, 0, SEEK_SET) == 0); Require(!feof_nid_postfix(&positioned)); positioned.Close(); + + const auto filename = "anyps5-reopen-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + FileStream redirected(std::tmpfile()); + Require(fgetc_nid_postfix(&redirected) == EOF && feof_nid_postfix(&redirected)); + Require(freopen_nid_postfix(filename.c_str(), "w+b", &redirected) == &redirected); + Require(!feof_nid_postfix(&redirected)); + Require(fputc_nid_postfix('R', &redirected) == 'R'); + Require(fseeko_nid_postfix(&redirected, 0, SEEK_SET) == 0); + Require(fgetc_nid_postfix(&redirected) == 'R'); + Require(freopen_nid_postfix(filename.c_str(), "ab", &redirected) == &redirected); + Require(fputc_nid_postfix('S', &redirected) == 'S'); + Require(freopen_nid_postfix(nullptr, "r", &redirected) == nullptr && *__error_nid_postfix() == 45); + redirected.Close(); + { std::ifstream input(filename, std::ios::binary); std::string contents; std::getline(input, contents); + Require(contents == "RS"); } + Require(std::filesystem::remove(filename)); + FileStream failed(std::tmpfile()); + Require(freopen_nid_postfix(filename.c_str(), "rb", &failed) == nullptr); + Require(*__error_nid_postfix() == 2); + Require(failed.GuestState().flags == 0 && failed.GuestState().descriptor == -1); }