diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index 38c54e381..207069071 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -152,6 +152,14 @@ int APS5_VABI vprintf_nid_postfix(const char* str, VaList* c) { #endif } +int APS5_VABI vsprintf_nid_postfix(char* str, const char* format, VaList* args) { +#ifdef _WIN32 + return LibcDetail::FormatWindows(str, SIZE_MAX, format, args); +#else + return std::vsprintf(str, format, *reinterpret_cast(args)); +#endif +} + int APS5_VABI vsnprintf_nid_postfix(char* str, size_t size, const char* format, VaList* c) { #ifdef _WIN32 return LibcDetail::FormatWindows(str, size, format, c); diff --git a/core/libs/tests/GuestStandardStreams.cpp b/core/libs/tests/GuestStandardStreams.cpp index 62ce707ca..4ffb0fcaf 100644 --- a/core/libs/tests/GuestStandardStreams.cpp +++ b/core/libs/tests/GuestStandardStreams.cpp @@ -20,6 +20,7 @@ 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 vsprintf_nid_postfix(char*, 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*); @@ -48,7 +49,35 @@ static int APS5_VABI WriteFormatted(FileStream* stream, const char* format, ...) #endif return result; } +static int APS5_VABI FormatString(char* buffer, 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 = vsprintf_nid_postfix(buffer, format, args); +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif + return result; +} int main() { + char stringOutput[256]; + std::memset(stringOutput, '!', sizeof(stringOutput)); + std::int64_t count = -1; + const char expectedString[] = "guest:4294967297: 3.50:1,2,3,4,5,6,7,8:%"; + const int written = FormatString(stringOutput, "%s:%ld:%*.*f:%d,%d,%d,%d,%d,%d,%d,%d:%%%ln", + "guest", std::int64_t{4294967297}, 6, 2, 3.5, 1, 2, 3, 4, 5, 6, 7, 8, &count); + Require(written == sizeof(expectedString) - 1 && count == written); + Require(std::strcmp(stringOutput, expectedString) == 0 && stringOutput[written + 1] == '!'); + Require(FormatString(stringOutput, "%.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.2Lf", + 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 1.25L) == 25); + Require(std::strcmp(stringOutput, "1 2 3 4 5 6 7 8 9 10 1.25") == 0); + Require(FormatString(stringOutput, "") == 0 && stringOutput[0] == '\0'); Require(__isthreaded_nid_postfix == 1); Require(__stdoutp_nid_postfix == &_Stdout_nid_postfix); Require(__stderrp_nid_postfix == &_Stderr_nid_postfix); diff --git a/docs/YAMAGI_QUAKE2.md b/docs/YAMAGI_QUAKE2.md index 6cbfe79b1..8e133d90d 100644 --- a/docs/YAMAGI_QUAKE2.md +++ b/docs/YAMAGI_QUAKE2.md @@ -541,3 +541,19 @@ empty and singleton arrays, and unchanged input bytes. All twenty-nine tests have passed (the new test was rerun after correcting its padding-byte snapshot). The unchanged game resolves bsearch and stops at `jbz9I9vkqkk` (`vsprintf`), still before guest entry with exit `0xc0000139`. + +## Follow-up: unbounded varargs formatting + +Added vsprintf through the existing guest varargs formatter on Windows and the +native SysV va_list path on Linux. The caller remains responsible for buffer +capacity, as with the C interface. Windows tests cover mixed integer/string/ +floating arguments, guest 64-bit long, width and precision arguments, register +overflow onto the stack, long double, percent escapes, count writes, empty +output, termination, and the byte immediately after the output. The formatter's +existing unsupported conversions still fail explicitly; this does not establish +complete printf compatibility or Linux runtime validation. + +All twenty-nine CTest cases pass. The unchanged Quake II executable now resolves +vsprintf and stops at `Jc6E7N+dHz0` (`system`), before guest entry, with exit +`0xc0000139`. No game frame has been rendered. Upstream was fetched again and +had no new commits to merge.