From ea3f7b5ae074881615358c0f5f6a8b48dcd22090 Mon Sep 17 00:00:00 2001 From: boykopovar Date: Fri, 11 Sep 2026 07:49:14 +0300 Subject: [PATCH] fix(windows): va_list sysv abi parse for fomatting functions --- .../prx/libc/include/WindowsFormatting.hpp | 209 ++++++++++++++++++ core/libs/prx/libc/src/Formatting.cpp | 50 +++++ core/libs/tests/Formatting.cpp | 87 ++++++++ 3 files changed, 346 insertions(+) create mode 100644 core/libs/prx/libc/include/WindowsFormatting.hpp create mode 100644 core/libs/tests/Formatting.cpp diff --git a/core/libs/prx/libc/include/WindowsFormatting.hpp b/core/libs/prx/libc/include/WindowsFormatting.hpp new file mode 100644 index 000000000..4d8655a93 --- /dev/null +++ b/core/libs/prx/libc/include/WindowsFormatting.hpp @@ -0,0 +1,209 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_WINDOWSFORMATTING_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_WINDOWSFORMATTING_HPP + +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace LibcDetail { + +class FormatArguments { + VaList args; + +public: + explicit FormatArguments(const void* source) { + std::memcpy(&args, source, sizeof(args)); + } + + template T Next() { + const void* address; + if constexpr (std::is_same_v) { + if (args.fp_offset < 176) { + address = static_cast(args.reg_save_area) + args.fp_offset; + args.fp_offset += 16; + } else { + address = args.overflow_arg_area; + args.overflow_arg_area = static_cast(args.overflow_arg_area) + 8; + } + } else if constexpr (std::is_same_v) { + const auto aligned = (reinterpret_cast(args.overflow_arg_area) + 15) & ~std::uintptr_t(15); + address = reinterpret_cast(aligned); + args.overflow_arg_area = reinterpret_cast(aligned + 16); + } else { + static_assert(sizeof(T) <= 8); + if (args.gp_offset < 48) { + address = static_cast(args.reg_save_area) + args.gp_offset; + args.gp_offset += 8; + } else { + address = args.overflow_arg_area; + args.overflow_arg_area = static_cast(args.overflow_arg_area) + 8; + } + } + T value; + std::memcpy(&value, address, sizeof(value)); + return value; + } + +}; + +class FormatOutput { + char* destination; + size_t capacity; + size_t count = 0; + std::string* complete; + +public: + FormatOutput(char* buffer, size_t size, std::string* text) : destination(buffer), capacity(size), complete(text) { + if (size && !buffer) throw std::invalid_argument("Null formatting buffer"); + if (capacity) destination[0] = 0; + } + + void Append(const char* text, size_t size) { + if (size > static_cast(INT_MAX) - count) + throw std::overflow_error("Formatted output exceeds INT_MAX"); + if (capacity && count < capacity - 1) { + const size_t available = capacity - 1 - count; + const size_t copied = size < available ? size : available; + std::memcpy(destination + count, text, copied); + destination[count + copied] = 0; + } + if (complete) complete->append(text, size); + count += size; + } + + template void Value(const std::string& format, T value) { + const int size = std::snprintf(nullptr, 0, format.c_str(), value); + if (size < 0) throw std::runtime_error("Formatting conversion failed"); + if (static_cast(size) > static_cast(INT_MAX) - count) + throw std::overflow_error("Formatted output exceeds INT_MAX"); + if (complete) { + std::vector text(static_cast(size) + 1); + const int written = std::snprintf(text.data(), text.size(), format.c_str(), value); + if (written != size) throw std::runtime_error("Inconsistent formatting conversion"); + complete->append(text.data(), static_cast(size)); + } + if (capacity && count < capacity - 1) { + const size_t remaining = capacity - count; + const size_t required = static_cast(size) + 1; + const int written = std::snprintf(destination + count, remaining < required ? remaining : required, format.c_str(), value); + if (written != size) throw std::runtime_error("Inconsistent formatting conversion"); + } + count += static_cast(size); + } + + int Count() const { return static_cast(count); } +}; + +inline int FormatWindows(char* buffer, size_t size, const char* format, const void* source, std::string* complete = nullptr) { + if (!format || !source) throw std::invalid_argument("Null formatting argument"); + FormatArguments args(source); + FormatOutput output(buffer, size, complete); + while (*format) { + const char* literal = format; + while (*format && *format != '%') ++format; + output.Append(literal, static_cast(format - literal)); + if (!*format) break; + ++format; + if (*format == '%') { + output.Append(format++, 1); + continue; + } + std::string spec = "%"; + while (*format && std::strchr("-+ #0", *format)) spec += *format++; + if (*format == '*') { + ++format; + const int width = args.Next(); + if (width < 0) spec += '-'; + spec += std::to_string(width < 0 ? -static_cast(width) : width); + } else { + while (*format >= '0' && *format <= '9') spec += *format++; + } + if (*format == '.') { + ++format; + if (*format == '*') { + ++format; + const int precision = args.Next(); + if (precision >= 0) spec += "." + std::to_string(precision); + } else { + spec += '.'; + while (*format >= '0' && *format <= '9') spec += *format++; + } + } + std::string length; + if (*format && std::strchr("hljztL", *format)) { + length += *format++; + if ((length == "h" && *format == 'h') || (length == "l" && *format == 'l')) + length += *format++; + } + const char conversion = *format; + if (!conversion) throw std::invalid_argument("Incomplete format conversion"); + ++format; + const bool integerLength = length.empty() || length == "h" || length == "hh" || + length == "l" || length == "ll" || length == "j" || length == "z" || length == "t"; + if (conversion == 'd' || conversion == 'i') { + if (!integerLength) throw std::invalid_argument("Invalid integer length"); + long long value; + if (length.empty() || length == "h" || length == "hh") { + value = args.Next(); + if (length == "h") value = static_cast(value); + if (length == "hh") value = static_cast(value); + } else value = args.Next(); + output.Value(spec + "ll" + conversion, value); + } else if (std::strchr("ouxX", conversion)) { + if (!integerLength) throw std::invalid_argument("Invalid integer length"); + unsigned long long value; + if (length.empty() || length == "h" || length == "hh") { + value = args.Next(); + if (length == "h") value = static_cast(value); + if (length == "hh") value = static_cast(value); + } else value = args.Next(); + output.Value(spec + "ll" + conversion, value); + } else if (std::strchr("aAeEfFgG", conversion)) { + if (length == "L") { + static_assert(sizeof(long double) == 16); + static_assert(std::numeric_limits::digits == 64); + output.Value(spec + "L" + conversion, args.Next()); + } else { + if (!length.empty() && length != "l") throw std::invalid_argument("Invalid floating length"); + output.Value(spec + conversion, args.Next()); + } + } else if (conversion == 'c' && length.empty()) { + output.Value(spec + conversion, args.Next()); + } else if (conversion == 's' && length.empty()) { + const char* value = args.Next(); + if (!value) throw std::invalid_argument("Null formatted string"); + output.Value(spec + conversion, value); + } else if (conversion == 'p' && length.empty()) { + output.Value(spec + conversion, args.Next()); + } else if (conversion == 'n' && integerLength && spec == "%") { + void* pointer = args.Next(); + if (!pointer) throw std::invalid_argument("Null format count pointer"); + const int count = output.Count(); + if (length == "hh") *static_cast(pointer) = static_cast(count); + else if (length == "h") *static_cast(pointer) = static_cast(count); + else if (length.empty()) *static_cast(pointer) = count; + else *static_cast(pointer) = count; + } else { + throw std::invalid_argument("Unsupported format conversion"); + } + } + return output.Count(); +} + +inline int PrintWindows(const char* format, const void* args) { + std::string buffer; + const int size = FormatWindows(nullptr, 0, format, args, &buffer); + if (std::fwrite(buffer.data(), 1, static_cast(size), stdout) != static_cast(size)) + throw std::runtime_error("Formatted output write failed"); + return size; +} + +} + +#endif diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index 7df166a5e..9aa4167d8 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -5,8 +5,48 @@ #include "SceTypes.hpp" #include "prx/libc/include/VarArgsAbi.hpp" +#ifdef _WIN32 +#include "prx/libc/include/WindowsFormatting.hpp" +#endif + extern "C" { +#ifdef _WIN32 + +int APS5_VABI printf_nid_postfix(const char* format, ...) { + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); + const int result = LibcDetail::PrintWindows(format, args); + __builtin_sysv_va_end(args); + return result; +} + +int APS5_VABI libc_printf_nid_postfix(const char* format, ...) { + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); + const int result = LibcDetail::PrintWindows(format, args); + __builtin_sysv_va_end(args); + return result; +} + +int APS5_VABI snprintf_nid_postfix(char* buffer, size_t size, const char* format, ...) { + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); + const int result = LibcDetail::FormatWindows(buffer, size, format, args); + __builtin_sysv_va_end(args); + return result; +} + +int APS5_VABI sprintf_nid_postfix(char* buffer, const char* format, ...) { + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); + const int result = LibcDetail::FormatWindows(buffer, SIZE_MAX, format, args); + __builtin_sysv_va_end(args); + return result; +} + +#else + int APS5_VABI printf_nid_postfix(const char* format, ...) { std::va_list args; va_start(args, format); @@ -55,6 +95,8 @@ int APS5_VABI sprintf_nid_postfix(VA_ARGS) { ); } +#endif + int APS5_VABI sscanf_nid_postfix(VA_ARGS) { LibcDetail::RegSaveArea regs; LibcDetail::FillRegSaveArea(regs, rdx, rcx, r8, r9, 0, 0, @@ -70,13 +112,21 @@ int APS5_VABI sscanf_nid_postfix(VA_ARGS) { } int APS5_VABI vprintf_nid_postfix(const char* str, VaList* c) { +#ifdef _WIN32 + return LibcDetail::PrintWindows(str, c); +#else std::va_list* va = reinterpret_cast(c); return std::vprintf(str, *va); +#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); +#else std::va_list* va = reinterpret_cast(c); return std::vsnprintf(str, size, format, *va); +#endif } int APS5_VABI puts_nid_postfix(const char* s) { diff --git a/core/libs/tests/Formatting.cpp b/core/libs/tests/Formatting.cpp new file mode 100644 index 000000000..58dd3dfe3 --- /dev/null +++ b/core/libs/tests/Formatting.cpp @@ -0,0 +1,87 @@ +#include "SceTypes.hpp" +#include +#include +#include + +extern "C" { +int APS5_VABI snprintf_nid_postfix(char*, size_t, const char*, ...); +int APS5_VABI sprintf_nid_postfix(char*, const char*, ...); +int APS5_VABI printf_nid_postfix(const char*, ...); +int APS5_VABI libc_printf_nid_postfix(const char*, ...); +int APS5_VABI vsnprintf_nid_postfix(char*, size_t, const char*, VaList*); +int APS5_VABI vprintf_nid_postfix(const char*, VaList*); +} + +static void Require(bool condition) { + if (!condition) throw std::runtime_error("Formatting check failed"); +} + +static int APS5_VABI FormatList(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 VaList original = list; + const int result = vsnprintf_nid_postfix(buffer, size, format, &list); + Require(std::memcmp(&list, &original, sizeof(list)) == 0); + __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); + VaList list; + std::memcpy(&list, args, sizeof(list)); + const int result = vprintf_nid_postfix(format, &list); + __builtin_sysv_va_end(args); + return result; +} + +__attribute__((noinline)) static void APS5_VABI RunChecks() { + char buffer[1024]; + Require(FormatList(buffer, sizeof(buffer), "Mount requested: %d", 0) == 18); + Require(std::strcmp(buffer, "Mount requested: 0") == 0); + for (int index = 0; index < 10000; ++index) { + snprintf_nid_postfix(buffer, sizeof(buffer), "%d %d %d %d %d %d %d %d", 1, 2, 3, 4, 5, 6, 7, 8); + Require(std::strcmp(buffer, "1 2 3 4 5 6 7 8") == 0); + FormatList(buffer, sizeof(buffer), "%.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %d", 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11); + Require(std::strcmp(buffer, "1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11") == 0); + snprintf_nid_postfix(buffer, sizeof(buffer), "%ld %lu %lld %zu %td %jd", -4294967297LL, 4294967297ULL, -5LL, 7ULL, -8LL, 9LL); + Require(std::strcmp(buffer, "-4294967297 4294967297 -5 7 -8 9") == 0); + snprintf_nid_postfix(buffer, sizeof(buffer), "%s:%*.*f:%d", "test", -8, 2, 1.25, 7); + Require(std::strcmp(buffer, "test:1.25 :7") == 0); + FormatList(buffer, sizeof(buffer), "%d %d %d %d %d %d %d %.3Lf %.1f", 1, 2, 3, 4, 5, 6, 7, 1.125L, 2.5); + Require(std::strcmp(buffer, "1 2 3 4 5 6 7 1.125 2.5") == 0); + } + snprintf_nid_postfix(buffer, sizeof(buffer), "%.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f %.1f", 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.); + Require(std::strcmp(buffer, "1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0") == 0); + snprintf_nid_postfix(buffer, sizeof(buffer), "%.3Lf %d %.1f", 1.125L, 7, 2.5); + Require(std::strcmp(buffer, "1.125 7 2.5") == 0); + char expected[1024]; + std::snprintf(expected, sizeof(expected), "%#08x %.3e %a %g %p", 42u, 1.25, 1.25, 1.25, static_cast(buffer)); + snprintf_nid_postfix(buffer, sizeof(buffer), "%#08x %.3e %a %g %p", 42u, 1.25, 1.25, 1.25, static_cast(buffer)); + Require(std::strcmp(buffer, expected) == 0); + snprintf_nid_postfix(buffer, sizeof(buffer), "%.*s", -1, "unlimited"); + Require(std::strcmp(buffer, "unlimited") == 0); + buffer[5] = '!'; + Require(snprintf_nid_postfix(buffer, 5, "%s", "abcdef") == 6); + Require(std::strcmp(buffer, "abcd") == 0 && buffer[5] == '!'); + Require(snprintf_nid_postfix(nullptr, 0, "%s:%d", "abcdef", 123) == 10); + buffer[0] = 'x'; + Require(snprintf_nid_postfix(buffer, 1, "%d", 123) == 3 && buffer[0] == 0); + Require(sprintf_nid_postfix(buffer, "%hhd %hhu %hd %hu %%", 255, 257, 65535, 65537) == 11); + Require(std::strcmp(buffer, "-1 1 -1 1 %") == 0); + long long count = -1; + int smallCount = -1; + Require(snprintf_nid_postfix(buffer, 3, "abcd%lnEF%n", &count, &smallCount) == 6); + Require(count == 4 && smallCount == 6 && std::strcmp(buffer, "ab") == 0); + Require(snprintf_nid_postfix(buffer, sizeof(buffer), "a%cb", 0) == 3); + Require(buffer[0] == 'a' && buffer[1] == 0 && buffer[2] == 'b' && buffer[3] == 0); + Require(printf_nid_postfix("printf: %d %.1f\n", 7, 2.5) == 14); + Require(libc_printf_nid_postfix("libc_printf: %s\n", "OK") == 16); + Require(PrintList("vprintf: %d\n", 42) == 12); + std::puts("Formatting checks passed: 10000 iterations"); +} + +int main() { RunChecks(); }