diff --git a/core/libs/prx/libc/Export.cpp b/core/libs/prx/libc/Export.cpp index ec1701d745..625b27ff93 100644 --- a/core/libs/prx/libc/Export.cpp +++ b/core/libs/prx/libc/Export.cpp @@ -81,11 +81,6 @@ int APS5_VABI fscanf_nid_postfix() { return 0; } -int APS5_VABI sscanf_s_nid_postfix() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI vswprintf_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index 207069071c..10d37e32ea 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -1,7 +1,11 @@ #include #include #include +#include #include +#include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/VarArgsAbi.hpp" #include "prx/libc/include/FileStream.hpp" @@ -143,6 +147,117 @@ int APS5_VABI sscanf_nid_postfix(VA_ARGS) { ); } +#ifdef _WIN32 + +int APS5_VABI sscanf_s_nid_postfix(const char* buffer, const char* format, ...) { + (void)buffer; + (void)format; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +#else + +int APS5_VABI sscanf_s_nid_postfix(const char* buffer, const char* format, ...) { + if (buffer == nullptr || format == nullptr) return EOF; + std::va_list args; + va_start(args, format); + const char* input = buffer; + int assigned = 0; + bool converted = false; + const char* cursor = format; + const auto finish = [&](int result) { + va_end(args); + return result; + }; + while (*cursor != '\0') { + if (std::isspace(static_cast(*cursor))) { + while (std::isspace(static_cast(*cursor))) ++cursor; + while (std::isspace(static_cast(*input))) ++input; + continue; + } + if (*cursor != '%' || cursor[1] == '%') { + const char expected = *cursor == '%' ? '%' : *cursor; + cursor += *cursor == '%' ? 2 : 1; + if (expected == '%') while (std::isspace(static_cast(*input))) ++input; + if (*input != expected) return finish(!converted && *input == '\0' ? EOF : assigned); + ++input; + continue; + } + ++cursor; + const bool suppress = *cursor == '*'; + if (suppress) ++cursor; + unsigned long width = 0; + while (std::isdigit(static_cast(*cursor))) width = width * 10 + static_cast(*cursor++ - '0'); + const char* lengthStart = cursor; + while (std::strchr("hljztL", *cursor) != nullptr && *cursor != '\0') ++cursor; + const std::string length(lengthStart, cursor); + const char conversion = *cursor; + if (conversion == '\0') return finish(assigned); + std::string specifier(1, conversion); + if (conversion == '[') { + const char* setStart = cursor++; + if (*cursor == '^') ++cursor; + if (*cursor == ']') ++cursor; + while (*cursor != '\0' && *cursor != ']') ++cursor; + if (*cursor != ']') return finish(assigned); + specifier.assign(setStart, cursor + 1); + } + ++cursor; + if (conversion == 'n') { + if (!suppress) *va_arg(args, int*) = static_cast(input - buffer); + continue; + } + if (conversion != 'c' && conversion != '[') { + const char* probe = input; + while (std::isspace(static_cast(*probe))) ++probe; + if (*probe == '\0') return finish(converted ? assigned : EOF); + } + const bool sized = conversion == 's' || conversion == 'c' || conversion == '['; + std::string directive = "%"; + void* target = nullptr; + if (suppress) directive += '*'; + else target = va_arg(args, void*); + if (sized && !suppress) { + const auto capacity = static_cast(va_arg(args, unsigned int)); + const unsigned long limit = conversion == 'c' ? capacity : (capacity == 0 ? 0 : capacity - 1); + const unsigned long wanted = conversion == 'c' ? (width == 0 ? 1 : width) : width; + if (limit == 0 || (conversion == 'c' && wanted > limit)) { + if (capacity != 0) static_cast(target)[0] = '\0'; + return finish(assigned); + } + width = wanted == 0 ? limit : std::min(wanted, limit); + } + if (width != 0) directive += std::to_string(width); + directive += length; + directive += specifier; + directive += "%n"; + int consumed = -1; + const int matched = suppress ? std::sscanf(input, directive.c_str(), &consumed) : std::sscanf(input, directive.c_str(), target, &consumed); + if (consumed < 0 || (!suppress && matched != 1)) return finish(!converted && *input == '\0' ? EOF : assigned); + if (sized && !suppress && conversion != 'c') { + const char next = input[consumed]; + bool overflow = false; + if (conversion == 's') overflow = next != '\0' && !std::isspace(static_cast(next)); + else if (next != '\0') { + char probe[2]; + const std::string test = "%1" + specifier; + overflow = std::sscanf(input + consumed, test.c_str(), probe) == 1; + } + if (overflow) { + static_cast(target)[0] = '\0'; + return finish(assigned); + } + } + input += consumed; + converted = true; + if (!suppress) ++assigned; + } + return finish(assigned); +} + +#endif + int APS5_VABI vprintf_nid_postfix(const char* str, VaList* c) { #ifdef _WIN32 return LibcDetail::PrintWindows(str, c); diff --git a/core/libs/tests/GuestStrings.cpp b/core/libs/tests/GuestStrings.cpp index cc8eefab69..fe71ab71d9 100644 --- a/core/libs/tests/GuestStrings.cpp +++ b/core/libs/tests/GuestStrings.cpp @@ -15,6 +15,7 @@ std::size_t APS5_VABI strlcat_nid_postfix(char*, const char*, std::size_t); char* APS5_VABI strtok_r_nid_postfix(char*, const char*, char**); char* APS5_VABI strtok_nid_postfix(char*, const char*); char* APS5_VABI strcasestr_nid_postfix(const char*, const char*); +int APS5_VABI sscanf_s_nid_postfix(const char*, const char*, ...); } static void Require(bool condition) { @@ -24,7 +25,24 @@ static void Require(bool condition) { } } +static void CheckSscanfS() { +#ifndef _WIN32 + int number = 0; + char word[4] = "zz"; + char letter = 0; + char value[8] = {}; + Require(sscanf_s_nid_postfix(" 12 abc x", "%d %s %c", &number, word, 4u, &letter, 1u) == 3 && number == 12 && std::strcmp(word, "abc") == 0 && letter == 'x'); + Require(sscanf_s_nid_postfix("12 abcd", "%d %s", &number, word, 4u) == 1 && word[0] == '\0'); + Require(sscanf_s_nid_postfix("key=val", "%3[a-z]=%3s", word, 4u, value, 8u) == 2 && std::strcmp(word, "key") == 0 && std::strcmp(value, "val") == 0); + int position = 0; + Require(sscanf_s_nid_postfix("7 %", "%d %%%n", &number, &position) == 1 && position == 3); + Require(sscanf_s_nid_postfix(" ", "%d", &number) == EOF); + Require(sscanf_s_nid_postfix("x", "%d", &number) == 0); +#endif +} + int main() { + CheckSscanfS(); Require(std::strcmp(basename_nid_postfix(nullptr), ".") == 0); Require(std::strcmp(basename_nid_postfix(""), ".") == 0); Require(std::strcmp(basename_nid_postfix("////"), "/") == 0);