feat(libc): expose guest vsprintf formatting

This commit is contained in:
SamG-Coder 2026-09-25 07:26:06 +10:00
parent 6cdf4a5be1
commit f37fffdacd
3 changed files with 53 additions and 0 deletions

View File

@ -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<std::va_list*>(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);

View File

@ -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);

View File

@ -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.