diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index b85e0eab2..38c54e381 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -4,6 +4,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/VarArgsAbi.hpp" +#include "prx/libc/include/FileStream.hpp" #ifdef _WIN32 #include "prx/libc/include/WindowsFormatting.hpp" @@ -11,6 +12,37 @@ extern "C" { +int APS5_VABI vfprintf_nid_postfix(FileStream* stream, const char* format, VaList* args) { + auto* native = GetNativeStream(stream); +#ifdef _WIN32 + std::string buffer; + const int count = LibcDetail::FormatWindows(nullptr, 0, format, args, &buffer); + const int result = std::fwrite(buffer.data(), 1, static_cast(count), native) == + static_cast(count) ? count : -1; +#else + const int result = std::vfprintf(native, format, *reinterpret_cast(args)); +#endif + stream->SyncStatus(); + return result; +} + +int APS5_VABI fprintf_nid_postfix(FileStream* stream, const char* format, ...) { +#ifdef _WIN32 + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); +#else + std::va_list args; + va_start(args, format); +#endif + const int result = vfprintf_nid_postfix(stream, format, reinterpret_cast(args)); +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif + return result; +} + #ifdef _WIN32 int APS5_VABI printf_nid_postfix(const char* format, ...) { diff --git a/core/libs/tests/GuestStandardStreams.cpp b/core/libs/tests/GuestStandardStreams.cpp index 3ced61b07..add7614aa 100644 --- a/core/libs/tests/GuestStandardStreams.cpp +++ b/core/libs/tests/GuestStandardStreams.cpp @@ -2,11 +2,15 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include +#include extern "C" { extern FileStream* __stdinp_nid_postfix; extern FileStream* __stdoutp_nid_postfix; extern FileStream* __stderrp_nid_postfix; extern int __isthreaded_nid_postfix; +int APS5_VABI fprintf_nid_postfix(FileStream*, const char*, ...); +int APS5_VABI vfprintf_nid_postfix(FileStream*, const char*, void*); int APS5_VABI fgetc_nid_postfix(FileStream*); int APS5_VABI fputc_nid_postfix(int, FileStream*); int APS5_VABI __srget_nid_postfix(FileStream*); @@ -19,6 +23,22 @@ void APS5_VABI clearerr_nid_postfix(FileStream*); int APS5_VABI setvbuf_nid_postfix(FileStream*, char*, int, std::size_t); } static void Require(bool value) { if (!value) std::abort(); } +static int APS5_VABI WriteFormatted(FileStream* stream, const char* format, ...) { +#ifdef _WIN32 + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); +#else + std::va_list args; + va_start(args, format); +#endif + const int result = vfprintf_nid_postfix(stream, format, args); +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif + return result; +} int main() { Require(__isthreaded_nid_postfix == 1); Require(__stdoutp_nid_postfix == &_Stdout_nid_postfix); @@ -46,4 +66,17 @@ int main() { Require(!feof_nid_postfix(&stream) && !(guest.flags & 0x20)); stream.Close(); Require(guest.flags == 0 && guest.descriptor == -1); + + FileStream formatted(std::tmpfile()); + const char expected[] = "guest 4294967297 1.25 1 2 3 4 5 6 7 8\n"; + Require(fprintf_nid_postfix(&formatted, "%s %ld %.2f %d %d %d %d %d %d %d %d\n", + "guest", std::int64_t{4294967297}, 1.25, 1, 2, 3, 4, 5, 6, 7, 8) == sizeof(expected) - 1); + Require(WriteFormatted(&formatted, "%*.*f:%s", 6, 2, 3.5, "end") == 10); + std::rewind(formatted.GetHandle()); + char output[128]{}; + Require(fgets_nid_postfix(output, sizeof(output), &formatted) == output); + Require(std::strcmp(output, expected) == 0); + Require(fgets_nid_postfix(output, sizeof(output), &formatted) == output); + Require(std::strcmp(output, " 3.50:end") == 0); + formatted.Close(); } diff --git a/docs/YAMAGI_QUAKE2.md b/docs/YAMAGI_QUAKE2.md index 8f4d13e40..d2b86cc65 100644 --- a/docs/YAMAGI_QUAKE2.md +++ b/docs/YAMAGI_QUAKE2.md @@ -217,3 +217,22 @@ and closure. All fourteen suites pass and the compatibility libraries rebuild. Latest startup resolves the stream globals and stops at `8nY19bKoiZk` (`fcntl`). The game has not reached its entry point or displayed a frame. + +## Follow-up: descriptor flags and formatted stream output + +Added fcntl F_GETFL/F_SETFL for tracked UDP sockets. Nonblocking state is shared +with FIONBIO ioctl and protected against concurrent mode updates. Unsupported +flags are rejected; ordinary file descriptors and other fcntl commands are not +implemented. The UDP test checks both APIs agree, mode transitions, and unchanged +state after rejected flags. + +Added fprintf and vfprintf using the guest FileStream wrapper and the existing +SysV variadic formatter on Windows. Tests write and read back mixed strings, +64-bit guest long integers, floating-point values, register-overflow arguments, +and dynamic width/precision through an explicit guest va_list. Write failure +returns a negative result and refreshes the guest stream status. + +All fourteen CTest suites pass and patched compatibility libraries rebuild. +The unchanged converted game now resolves fcntl and fprintf, and startup stops +at `smbQukfxYJM` (`getenv`). This remains an import-resolution result: guest +entry-point execution and a rendered frame are still unverified.