From e251de849d7b9d3bf51ff49053ed9ebd6a4ad7c7 Mon Sep 17 00:00:00 2001 From: pushkarreddyy <212133856+pushkarreddyy@users.noreply.github.com> Date: Sun, 11 Oct 2026 02:14:37 +0530 Subject: [PATCH 1/3] feat(libc): implement vsnprintf_s and vsscanf_s (cherry picked from commit 4ca866ea3b989da6947ad110f4d6c7f4efbe27c8) --- core/libs/prx/libc/Export.cpp | 13 ------------- core/libs/prx/libc/src/Formatting.cpp | 23 +++++++++++++++++++++++ core/libs/tests/Formatting.cpp | 26 ++++++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/core/libs/prx/libc/Export.cpp b/core/libs/prx/libc/Export.cpp index fb67f24bb..c3820cd78 100644 --- a/core/libs/prx/libc/Export.cpp +++ b/core/libs/prx/libc/Export.cpp @@ -25,19 +25,6 @@ int APS5_VABI _ZSt14_Atomic_assertPKcS0__nid_postfix() { return 0; } -// Live Cyberpunk 2077 import used in an fopen/fseek/ftell-like file-size -// idiom as (handle, 0, 2); returning 0 reports success. -int APS5_VABI vsnprintf_s_nid_postfix() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -// Live Cyberpunk 2077 import used as (handle, 0, 0) in the same file-size -// idiom; returning 0 reports success. -int APS5_VABI vsscanf_s_nid_postfix() { - NotImplemented_nid_no_patch(__func__); - return 0; -} APS5_EXPORT("Ye20uNnlglA", libcCyberUnknown02); diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index 37a59d7d2..b59bbb2c8 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -408,6 +408,15 @@ int APS5_VABI vsnprintf_nid_postfix(char* str, size_t size, const char* format, #ifdef _WIN32 +int APS5_VABI vsscanf_s_nid_postfix(const char* buffer, const char* format, VaList* args) { + LibcDetail::FormatArguments arguments(args); + return ScanGuest(buffer, format, true, [&] { return arguments.Next(); }, [&] { return arguments.Next(); }); +} + +int APS5_VABI vsnprintf_s_nid_postfix(char* buffer, size_t size, const char* format, VaList* args) { + return LibcDetail::FormatWindows(buffer, size, format, args); +} + int APS5_VABI snprintf_s_nid_postfix(char* buffer, size_t size, const char* format, ...) { __builtin_sysv_va_list args; __builtin_sysv_va_start(args, format); @@ -426,6 +435,20 @@ int APS5_VABI printf_s_nid_postfix(const char* format, ...) { #else +int APS5_VABI vsscanf_s_nid_postfix(const char* buffer, const char* format, VaList* args) { + LibcDetail::FormatArguments arguments(args); + return ScanGuest(buffer, format, true, [&] { return arguments.Next(); }, [&] { return arguments.Next(); }); +} + +int APS5_VABI vsnprintf_s_nid_postfix(char* buffer, size_t size, const char* format, VaList* args) { + if (HasGuestWideFormat(format)) return LibcDetail::FormatWindows(buffer, size, format, args); + std::va_list copy; + va_copy(copy, *reinterpret_cast(args)); + const int result = std::vsnprintf(buffer, size, format, copy); + va_end(copy); + return result; +} + int APS5_VABI vsprintf_s_nid_postfix(char* buffer, size_t size, const char* format, VaList* args) { if (HasGuestWideFormat(format)) return LibcDetail::FormatWindows(buffer, size, format, args); std::va_list copy; diff --git a/core/libs/tests/Formatting.cpp b/core/libs/tests/Formatting.cpp index 796def8e3..51e0cdf99 100644 --- a/core/libs/tests/Formatting.cpp +++ b/core/libs/tests/Formatting.cpp @@ -11,6 +11,8 @@ int APS5_VABI printf_nid_postfix(const char*, ...); int APS5_VABI libc_printf_nid_postfix(const char*, ...); int APS5_VABI sscanf_nid_postfix(const char*, const char*, ...); int APS5_VABI vsnprintf_nid_postfix(char*, size_t, const char*, VaList*); +int APS5_VABI vsnprintf_s_nid_postfix(char*, size_t, const char*, VaList*); +int APS5_VABI vsscanf_s_nid_postfix(const char*, const char*, VaList*); int APS5_VABI vprintf_nid_postfix(const char*, VaList*); } @@ -30,6 +32,26 @@ static int APS5_VABI FormatList(char* buffer, size_t size, const char* format, . return result; } +static int APS5_VABI FormatListS(char* buffer, size_t size, const char* format, ...) { + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); + VaList list; + std::memcpy(&list, args, sizeof(list)); + const int result = vsnprintf_s_nid_postfix(buffer, size, format, &list); + __builtin_sysv_va_end(args); + return result; +} + +static int APS5_VABI ScanListS(const char* buffer, const char* format, ...) { + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); + VaList list; + std::memcpy(&list, args, sizeof(list)); + const int result = vsscanf_s_nid_postfix(buffer, format, &list); + __builtin_sysv_va_end(args); + return result; +} + static int APS5_VABI PrintList(const char* format, ...) { __builtin_sysv_va_list args; __builtin_sysv_va_start(args, format); @@ -137,6 +159,10 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { Require(libc_printf_nid_postfix("libc_printf: %s\n", "OK") == 16); Require(libc_printf_nid_postfix("%d %d %d %d %d %d %d\n", 1, 22, 333, 4444, 55555, 666666, 7777777) == 35); Require(PrintList("vprintf: %d\n", 42) == 12); + Require(FormatListS(buffer, sizeof(buffer), "%d-%s", 42, "x") == 4 && std::strcmp(buffer, "42-x") == 0); + int scanNum = 0; + char scanWord[8] = {}; + Require(ScanListS("123 test", "%d %s", &scanNum, scanWord, 8u) == 2 && scanNum == 123 && std::strcmp(scanWord, "test") == 0); std::puts("Formatting checks passed: 10000 iterations"); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index bece55309..b7b8c1fdb 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -253,6 +253,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceKernelAddTimerEvent](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) - `usec` is taken as the period in microseconds; re-adding an existing timer keeps its pending expiration count and restarts the period from the time of the call, where FreeBSD 12+ `filt_timertouch` clears pending expirations on `EV_ADD`; `usec == 0` fires on every wait. None of this is confirmed on a PS5 title - [sceRtcFormatRFC2822, sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - only shadPS4 (PS4) has bodies. A null tick returns `INVALID_POINTER` like the existing `sceRtcFormatRFC3339`, where shadPS4 formats the current time instead; offsets outside ±1439 minutes return `INVALID_VALUE` like `sceRtcFormatRFC3339`; the local-time variants use the host's offset at the given instant (shadPS4 uses `sceKernelGettimezone`, the current offset) - [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf` +- [vsnprintf_s and vsscanf_s](../../core/libs/prx/libc/src/Formatting.cpp) (libc) - runtime constraint violations in C11 bounds-checking interfaces return negative/EOF values without invoking a constraint handler - [libSceUlt ulthreads](../../core/libs/prx/libSceUlt/Export.cpp) - `_sceUltUlthreadRuntimeOptParamInitialize`, `_sceUltUlthreadRuntimeCreate` and `_sceUltUlthreadCreate` are assumed to take the same arguments as the existing exports without the underscore, including the trailing build version. `sceUltUlthreadTryJoin` is assumed to return `ULT_ERROR_BUSY` while the ulthread runs, and `sceUltUlthreadRuntimeDestroy` to return `ULT_ERROR_BUSY` while an ulthread created on the runtime is not joined. Neither error has been confirmed on PS5 hardware - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown. - [AC1FtjqMCL0](../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel) - unknown name, signature From 4d537755e98995f2c12501fe9ba22ee10f4baa12 Mon Sep 17 00:00:00 2001 From: MarcZX100 Date: Sat, 10 Oct 2026 21:04:48 +0000 Subject: [PATCH 2/3] test(libc): cover vsscanf_s capacity limits --- core/libs/tests/Formatting.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/libs/tests/Formatting.cpp b/core/libs/tests/Formatting.cpp index 51e0cdf99..0b2846884 100644 --- a/core/libs/tests/Formatting.cpp +++ b/core/libs/tests/Formatting.cpp @@ -163,6 +163,8 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { int scanNum = 0; char scanWord[8] = {}; Require(ScanListS("123 test", "%d %s", &scanNum, scanWord, 8u) == 2 && scanNum == 123 && std::strcmp(scanWord, "test") == 0); + char tooSmall[4] = {'x', 'x', 'x', 'x'}; + Require(ScanListS("abcdef", "%s", tooSmall, 4u) == 0 && tooSmall[0] == '\0'); std::puts("Formatting checks passed: 10000 iterations"); } From db2743e3779b3cc7a22e4957f12f30d2429b2ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sat, 10 Oct 2026 23:52:18 +0200 Subject: [PATCH 3/3] docs(technical-debt): describe the vsnprintf_s/vsscanf_s constraint behaviour as implemented --- docs/dev/TechnicalDebt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index b7b8c1fdb..c29b187b4 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -253,7 +253,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceKernelAddTimerEvent](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel) - `usec` is taken as the period in microseconds; re-adding an existing timer keeps its pending expiration count and restarts the period from the time of the call, where FreeBSD 12+ `filt_timertouch` clears pending expirations on `EV_ADD`; `usec == 0` fires on every wait. None of this is confirmed on a PS5 title - [sceRtcFormatRFC2822, sceRtcFormatRFC2822LocalTime, sceRtcFormatRFC3339LocalTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - only shadPS4 (PS4) has bodies. A null tick returns `INVALID_POINTER` like the existing `sceRtcFormatRFC3339`, where shadPS4 formats the current time instead; offsets outside ±1439 minutes return `INVALID_VALUE` like `sceRtcFormatRFC3339`; the local-time variants use the host's offset at the given instant (shadPS4 uses `sceKernelGettimezone`, the current offset) - [snwprintf_s](../../core/libs/prx/libc/src/FormattingWide.cpp) (libc) - follows C11 K.3.9.1.3 with `RSIZE_MAX` assumed to be `SIZE_MAX >> 1`. A runtime-constraint violation only returns a negative value: `set_constraint_handler_s` is not exported and the console's default handler is unknown. An invalid multibyte `%s` argument is copied byte by byte instead of being reported as an encoding error, as in `vswprintf` -- [vsnprintf_s and vsscanf_s](../../core/libs/prx/libc/src/Formatting.cpp) (libc) - runtime constraint violations in C11 bounds-checking interfaces return negative/EOF values without invoking a constraint handler +- [vsnprintf_s and vsscanf_s](../../core/libs/prx/libc/src/Formatting.cpp) (libc) - `vsnprintf_s` forwards to `vsnprintf` semantics: the C11 K.3.7.1.5 runtime constraints (null buffer, `size == 0`, `%n`, null `%s`) are not checked, so `size == 0` returns the would-be length instead of a negative value, and no constraint handler is invoked. `vsscanf_s` returns the count of items converted before an undersized `%s`/`%c`/`%[` destination (0 when it is the first), not EOF. Neither is confirmed on a PS5 title - [libSceUlt ulthreads](../../core/libs/prx/libSceUlt/Export.cpp) - `_sceUltUlthreadRuntimeOptParamInitialize`, `_sceUltUlthreadRuntimeCreate` and `_sceUltUlthreadCreate` are assumed to take the same arguments as the existing exports without the underscore, including the trailing build version. `sceUltUlthreadTryJoin` is assumed to return `ULT_ERROR_BUSY` while the ulthread runs, and `sceUltUlthreadRuntimeDestroy` to return `ULT_ERROR_BUSY` while an ulthread created on the runtime is not joined. Neither error has been confirmed on PS5 hardware - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) - the added `sceUltQueuePop`, `sceUltQueueTryPush`, `sceUltQueueDestroy` and `sceUltQueueDataResourcePoolDestroy` signatures are inferred from the existing push/pop and object APIs. Queue destruction is assumed to reject blocked callers with `ULT_ERROR_BUSY` and otherwise discard queued data; pool exhaustion returns `ULT_ERROR_AGAIN`. These rules, alignment checks and shared slot limits have not been confirmed on PS5 hardware or a title. Non-null queue and data-pool option parameters throw because their layouts are unknown. - [AC1FtjqMCL0](../../core/libs/prx/libkernel/Unimplemented.cpp) (libkernel) - unknown name, signature