diff --git a/core/libs/prx/libc/include/WindowsFormatting.hpp b/core/libs/prx/libc/include/WindowsFormatting.hpp index 87ef0e6bd..f3deb658d 100644 --- a/core/libs/prx/libc/include/WindowsFormatting.hpp +++ b/core/libs/prx/libc/include/WindowsFormatting.hpp @@ -4,6 +4,7 @@ #include "SceTypes.hpp" #include #include +#include #include #include #include @@ -154,6 +155,15 @@ inline void AppendUtf8(std::string& utf8, char32_t code) { } } +inline std::string NanSpec(const std::string& spec) { + std::string result = "%"; + std::size_t index = 1; + for (; index < spec.size() && std::strchr("-+ #0", spec[index]); ++index) + if (spec[index] == '-' && result.size() == 1) result += '-'; + for (; index < spec.size() && spec[index] >= '0' && spec[index] <= '9'; ++index) result += spec[index]; + return result + 's'; +} + inline std::string FormatPointer(const std::string& spec, const void* pointer) { std::size_t index = 1; bool left = false; @@ -327,13 +337,18 @@ inline int FormatWindows(char* buffer, size_t size, const char* format, const vo spec = spec.substr(0, spec.find('.')) + ".1"; output.Value(spec + "ll" + conversion, value); } else if (std::strchr("aAeEfFgG", conversion)) { + const char* nan = conversion >= 'a' ? "nan" : "NAN"; if (length == "L") { static_assert(sizeof(long double) == 16); static_assert(std::numeric_limits::digits == 64); - output.Value(spec + "L" + conversion, args.Next()); + const long double value = args.Next(); + if (std::isnan(value)) output.Value(NanSpec(spec), nan); + else output.Value(spec + "L" + conversion, value); } else { if (!length.empty() && length != "l") throw std::invalid_argument("Invalid floating length"); - output.Value(spec + conversion, args.Next()); + const double value = args.Next(); + if (std::isnan(value)) output.Value(NanSpec(spec), nan); + else output.Value(spec + conversion, value); } } else if (conversion == 'c' && (length.empty() || length == "h")) { output.Value(spec + conversion, args.Next()); diff --git a/core/libs/tests/Formatting.cpp b/core/libs/tests/Formatting.cpp index 1b2fdf7b3..8b2260536 100644 --- a/core/libs/tests/Formatting.cpp +++ b/core/libs/tests/Formatting.cpp @@ -3,6 +3,7 @@ #include "prx/libc/include/GuestHeap.hpp" #include #include +#include #include #include #include @@ -111,6 +112,10 @@ static bool CheckWidePrecision() { Require(snprintf_nid_postfix(output, sizeof(output), "[%ls]", static_cast(nullptr)) == 8 && std::strcmp(output, "[(null)]") == 0); Require(snprintf_nid_postfix(output, sizeof(output), "a%lcb%C", 0, 0) == 4 && std::memcmp(output, "a\0b\0", 5) == 0); Require(FormatList(output, sizeof(output), "%%ls:%2$d:%1$d", 3, 7) == 7 && std::strcmp(output, "%ls:7:3") == 0); + Require(FormatList(output, sizeof(output), "%ls[%f][%+f][% F][%5.1e]", u"", -NAN, -NAN, NAN, -NAN) == 22 + && std::strcmp(output, "[nan][nan][NAN][ nan]") == 0); + Require(FormatList(output, sizeof(output), "%ls[%-6g][%06a][%LG][%+f]", u"", NAN, -NAN, -static_cast(NAN), -INFINITY) == 27 + && std::strcmp(output, "[nan ][ nan][NAN][-inf]") == 0); for (std::size_t capacity = 0; capacity <= 6; ++capacity) { std::memset(output, '!', sizeof(output)); int count = -1; diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index beed675fd..6bdfa82c3 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -47,7 +47,7 @@ - [getifaddrs](../../../core/libs/prx/libc/src/Interfaces.cpp) (libc) lists the host's IPv4 and IPv6 addresses in FreeBSD's `ifaddrs` and sockaddr layouts. FreeBSD's per-interface AF_LINK entries (MAC, index, MTU) and their `if_data` are not produced, so interfaces without an address are omitted. Names are the host's (Linux `lo`, `eth0`; Windows NDIS names such as `ethernet_32769`, or `if` when one does not fit 15 bytes), not the console's. A Linux point-to-point address without a peer loses `IFF_POINTOPOINT`, and an IPv4 broadcast address the host omits is computed from the netmask. On Windows the flags are only `IFF_UP`, `IFF_RUNNING`, `IFF_LOOPBACK`, `IFF_BROADCAST` and `IFF_MULTICAST`, derived from the adapter: PPP and tunnel adapters get neither `IFF_BROADCAST` nor `IFF_POINTOPOINT` because their peer address is not reported, and an address whose prefix length the host reports as unspecified gets a host mask. - [gethostname](../../../core/libs/prx/libc/src/Hostname.cpp) (libc) reports the fixed guest name `AnyPS5` without querying the host machine name. Buffer copying, null queries and `ENAMETOOLONG` follow [FreeBSD gethostname](https://github.com/freebsd/freebsd-src/blob/main/lib/libc/gen/gethostname.c); the console's configurable hostname and its integration with `sysctl` and name resolution are unverified and not implemented. - [wcsrtombs_s](../../../core/libs/prx/libc/src/Multibyte.cpp) (libc) follows the console's `libc.prx`, but a runtime-constraint violation only returns its error: the default handler there writes the message and a newline to `stderr` before returning, which is not done here. Conversion uses the C locale of `wcrtomb` (`MB_CUR_MAX` 1), so the path where a multibyte character does not fit the remaining space is not reached -- [The narrow printf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) converts 16-bit guest wide arguments to UTF-8 on both platforms, independently of the host locale. It does not use the guest's single-byte `wcrtomb`; the console's treatment of invalid UTF-16 and locale-specific encoding remains unverified. Linux retains host formatting for formats without wide arguments or `%m`, so a narrow `%p` there still follows the host (glibc prints a null pointer as `(nil)`, where FreeBSD prints `0x0`); argument positions above 99 (FreeBSD's `NL_ARGMAX`) throw when a format requires the guest formatter +- [The narrow printf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc) converts 16-bit guest wide arguments to UTF-8 on both platforms, independently of the host locale. It does not use the guest's single-byte `wcrtomb`; the console's treatment of invalid UTF-16 and locale-specific encoding remains unverified. Linux retains host formatting for formats without wide arguments or `%m`, so a narrow `%p` and a NaN there still follow the host (glibc prints a null pointer as `(nil)`, where FreeBSD prints `0x0`, and a NaN with its sign, `-nan`, and with the `+` and space flags, where FreeBSD prints `nan`); argument positions above 99 (FreeBSD's `NL_ARGMAX`) throw when a format requires the guest formatter - [collate::_Getcat](../../../core/libs/prx/libc/src/LocaleSupport.cpp) (libc) builds the facet for the C locale only and throws for any other locale name. The console's `libc.prx` stores pointers to its collation state tables at offsets 0x10 and 0x18 of the facet; ours leaves them null, as only the facet's own functions read them. `do_transform` throws for input with embedded null characters, which the console drops depending on the output buffer size. Its result capacity (`length | 15`) and the over-aligned storage of results of 4080 characters or more follow the console's `libc.prx` code and are not checked on hardware. - [The scanf family](../../../core/libs/prx/libc/src/Formatting.cpp) (libc), the `_s` forms included, on Linux throws for the 16-bit wide conversions (`%ls`, `%lc`, `%l[`, `%S`, `%C`): glibc would store a 32-bit `wchar_t` per character into the guest's 16-bit buffer. Windows hands `%ls`, `%lc` and `%l[` to the host, whose `wchar_t` is 16 bits - [The scanf family](../../../core/libs/prx/libc/include/WindowsScanning.hpp) (libc) on Windows returns 0, as C17 and FreeBSD do, when input fails after only suppressed conversions have completed (`sscanf("12", "%*d%d", &value)`). Linux hands the format to glibc, which returns `EOF` there. On Windows the host stops a `%x`, `%X`, `%i` or `%p` after the `x` of a `0x` that no hex digit follows (or that the width cuts off), and a float conversion (`%e`, `%f`, `%g`, `%a`) after the `e` of an exponent with no digits (`1e`), where C17 and FreeBSD stop before the `x` or the `e`