From 792fb27631ccf27dc33c6e49b7b75cd2cbcf4e71 Mon Sep 17 00:00:00 2001 From: Federico Marras Date: Mon, 19 Oct 2026 13:28:55 +0200 Subject: [PATCH] feat(libc): list each interface's AF_LINK entry in getifaddrs on Linux getifaddrs listed only the host's IPv4 and IPv6 addresses, so the per-interface AF_LINK entries FreeBSD's getifaddrs puts first were missing, and with them the interface index, hardware address, MTU and counters and every interface without an address. On Linux each interface now starts with an AF_LINK entry, followed by its addresses, as FreeBSD 11.4's getifaddrs orders them. Its address is a sockaddr_dl with the host index, type, name and hardware address (none for a loopback), sized as FreeBSD's if_attach sizes it. Its ifa_data is a 152-byte FreeBSD if_data filled as if_data_copy fills it: type, address and header length, link state, MTU, speed and the host's packet, byte, error, drop and multicast counters from sysfs, or the host list's 32-bit counters when sysfs cannot be read. Fields with no host equivalent are 0 and listed in the debt entry. Windows is unchanged. --- core/libs/prx/libc/src/Interfaces.cpp | 140 +++++++++++++++++++++++++- core/libs/tests/GuestInterfaces.cpp | 132 +++++++++++++++++++++++- docs/dev/debt/libc.md | 2 +- 3 files changed, 265 insertions(+), 9 deletions(-) diff --git a/core/libs/prx/libc/src/Interfaces.cpp b/core/libs/prx/libc/src/Interfaces.cpp index f61e17fbd4..8e39cfa29b 100644 --- a/core/libs/prx/libc/src/Interfaces.cpp +++ b/core/libs/prx/libc/src/Interfaces.cpp @@ -5,9 +5,17 @@ #include #else #include +#include #include +#include #include +#include +#include +#include +#include +#include #include +#include #endif #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -15,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +61,7 @@ struct Entry { std::vector address; std::vector netmask; std::vector destination; + std::vector data; }; std::vector GuestIpv4(const std::uint8_t* bytes) { @@ -150,6 +160,100 @@ int HostEntries(std::vector& entries) { return 0; } #else +constexpr std::uint8_t guestLinkFamily = 18; +constexpr std::size_t guestLinkNameArea = 8 + 16; +constexpr std::size_t guestLinkMinimum = 54; +constexpr std::uint8_t guestTypeOther = 0x1; +constexpr std::uint8_t guestTypeEthernet = 0x6; +constexpr std::uint8_t guestTypePpp = 0x17; +constexpr std::uint8_t guestTypeLoopback = 0x18; +constexpr std::uint8_t guestLinkStateDown = 1; +constexpr std::uint8_t guestLinkStateUp = 2; +constexpr std::size_t guestInterfaceDataSize = 152; +constexpr unsigned int hostLowerUp = 0x10000; + +struct HostInterface { + std::string name; + bool linked = false; + Entry link; + std::vector addresses; +}; + +template +void Store(std::vector& bytes, std::size_t offset, TValue value) { + std::memcpy(bytes.data() + offset, &value, sizeof(value)); +} + +std::vector GuestLinkAddress(const std::string& name, std::uint16_t index, std::uint8_t type, const std::uint8_t* hardware, std::size_t hardwareLength) { + std::size_t size = std::max(guestLinkMinimum, guestLinkNameArea + hardwareLength); + size = Aligned(size); + if (size > 0xFF) throw std::runtime_error("getifaddrs: link-level address of " + name + " does not fit sockaddr_dl"); + std::vector guest(size, 0); + guest[0] = static_cast(size); + guest[1] = guestLinkFamily; + Store(guest, 2, index); + guest[4] = type; + guest[5] = static_cast(name.size()); + guest[6] = static_cast(hardwareLength); + std::memcpy(guest.data() + 8, name.data(), name.size()); + if (hardwareLength != 0) std::memcpy(guest.data() + 8 + name.size(), hardware, hardwareLength); + return guest; +} + +std::optional ReadSysfsCounter(const std::string& name, const char* file) { + std::ifstream input("/sys/class/net/" + name + "/" + file); + long long value = 0; + if (!(input >> value)) return std::nullopt; + return static_cast(value); +} + +std::uint32_t HostMtu(const std::string& name) { + const int probe = ::socket(AF_INET, SOCK_DGRAM, 0); + if (probe < 0) throw std::runtime_error(std::string("getifaddrs: no socket to read the MTU of ") + name + ": " + std::strerror(errno)); + ifreq request{}; + std::memcpy(request.ifr_name, name.c_str(), name.size() + 1); + const int result = ::ioctl(probe, SIOCGIFMTU, &request); + const int error = errno; + ::close(probe); + if (result != 0) throw std::runtime_error(std::string("getifaddrs: SIOCGIFMTU failed on ") + name + ": " + std::strerror(error)); + return static_cast(request.ifr_mtu); +} + +std::vector GuestInterfaceData(const std::string& name, unsigned int hostFlags, const void* hostStats, std::uint8_t type, std::uint8_t hardwareLength) { + std::vector data(guestInterfaceDataSize, 0); + data[0] = type; + data[2] = hardwareLength; + data[3] = type == guestTypeEthernet ? 14 : 0; + if (type == guestTypeLoopback) data[4] = (hostFlags & IFF_UP) ? guestLinkStateUp : guestLinkStateDown; + else data[4] = (hostFlags & hostLowerUp) ? guestLinkStateUp : guestLinkStateDown; + Store(data, 6, static_cast(guestInterfaceDataSize)); + Store(data, 8, HostMtu(name)); + if (const auto speed = ReadSysfsCounter(name, "speed"); speed && static_cast(*speed) > 0) + Store(data, 16, *speed * 1000000u); + constexpr std::pair counters[] = { + {"statistics/rx_packets", 24}, {"statistics/rx_errors", 32}, {"statistics/tx_packets", 40}, {"statistics/tx_errors", 48}, + {"statistics/collisions", 56}, {"statistics/rx_bytes", 64}, {"statistics/tx_bytes", 72}, {"statistics/multicast", 80}, + {"statistics/rx_dropped", 96}, {"statistics/tx_dropped", 104}, + }; + bool complete = true; + for (const auto& [file, offset] : counters) { + const auto value = ReadSysfsCounter(name, file); + if (!value) { complete = false; break; } + Store(data, offset, *value); + } + if (!complete) { + if (!hostStats) throw std::runtime_error("getifaddrs: no counters for " + name + " in sysfs or the host list"); + rtnl_link_stats stats{}; + std::memcpy(&stats, hostStats, sizeof(stats)); + const std::pair fallback[] = { + {stats.rx_packets, 24}, {stats.rx_errors, 32}, {stats.tx_packets, 40}, {stats.tx_errors, 48}, {stats.collisions, 56}, + {stats.rx_bytes, 64}, {stats.tx_bytes, 72}, {stats.multicast, 80}, {stats.rx_dropped, 96}, {stats.tx_dropped, 104}, + }; + for (const auto& [value, offset] : fallback) Store(data, offset, static_cast(value)); + } + return data; +} + std::uint32_t InterfaceFlags(unsigned int host) { constexpr std::pair flags[] = { {IFF_UP, guestUp}, {IFF_BROADCAST, guestBroadcast}, {IFF_LOOPBACK, guestLoopback}, @@ -174,9 +278,31 @@ int HostEntries(std::vector& entries) { default: throw std::runtime_error(std::string("getifaddrs: host enumeration failed: ") + std::strerror(hostError)); } } + std::vector interfaces; + const auto interfaceFor = [&](const std::string& name) -> HostInterface& { + const auto found = std::find_if(interfaces.begin(), interfaces.end(), [&](const HostInterface& candidate) { return candidate.name == name; }); + if (found != interfaces.end()) return *found; + interfaces.push_back({name, false, {}, {}}); + return interfaces.back(); + }; try { for (auto* item = host; item; item = item->ifa_next) { - if (!item->ifa_addr || (item->ifa_addr->sa_family != AF_INET && item->ifa_addr->sa_family != AF_INET6)) continue; + if (!item->ifa_addr) continue; + if (item->ifa_addr->sa_family == AF_PACKET) { + const auto& link = *reinterpret_cast(item->ifa_addr); + HostInterface& owner = interfaceFor(CheckedName(item->ifa_name)); + if (owner.linked) throw std::runtime_error("getifaddrs: two link-level entries for " + owner.name); + if (link.sll_ifindex <= 0 || link.sll_ifindex > 0xFFFF) throw std::runtime_error("getifaddrs: interface index of " + owner.name + " does not fit sockaddr_dl"); + const std::uint8_t type = link.sll_hatype == ARPHRD_ETHER ? guestTypeEthernet : link.sll_hatype == ARPHRD_LOOPBACK ? guestTypeLoopback + : link.sll_hatype == ARPHRD_PPP ? guestTypePpp : guestTypeOther; + const std::uint8_t hardwareLength = type == guestTypeLoopback ? 0 : std::min(link.sll_halen, sizeof(link.sll_addr)); + owner.link = Entry{owner.name, InterfaceFlags(item->ifa_flags), + GuestLinkAddress(owner.name, static_cast(link.sll_ifindex), type, link.sll_addr, hardwareLength), {}, {}, + GuestInterfaceData(owner.name, item->ifa_flags, item->ifa_data, type, hardwareLength)}; + owner.linked = true; + continue; + } + if (item->ifa_addr->sa_family != AF_INET && item->ifa_addr->sa_family != AF_INET6) continue; if (!item->ifa_netmask) throw std::runtime_error(std::string("getifaddrs: host address without a netmask on ") + item->ifa_name); Entry entry{CheckedName(item->ifa_name), InterfaceFlags(item->ifa_flags), GuestAddress(item->ifa_addr), GuestAddress(item->ifa_netmask), {}}; const int family = item->ifa_addr->sa_family; @@ -187,13 +313,17 @@ int HostEntries(std::vector& entries) { entry.destination = item->ifa_broadaddr && item->ifa_broadaddr->sa_family == AF_INET ? GuestAddress(item->ifa_broadaddr) : BroadcastAddress(entry.address, entry.netmask); } - entries.push_back(std::move(entry)); + interfaceFor(entry.name).addresses.push_back(std::move(entry)); } } catch (...) { ::freeifaddrs(host); throw; } ::freeifaddrs(host); + for (auto& owner : interfaces) { + if (owner.linked) entries.push_back(std::move(owner.link)); + for (auto& address : owner.addresses) entries.push_back(std::move(address)); + } return 0; } #endif @@ -212,7 +342,7 @@ int APS5_VABI getifaddrs_nid_postfix(GuestInterfaceAddress** list) { } std::size_t size = entries.size() * sizeof(GuestInterfaceAddress); for (const auto& entry : entries) - size += Aligned(entry.name.size() + 1) + Aligned(entry.address.size()) + Aligned(entry.netmask.size()) + Aligned(entry.destination.size()); + size += Aligned(entry.name.size() + 1) + Aligned(entry.address.size()) + Aligned(entry.netmask.size()) + Aligned(entry.destination.size()) + Aligned(entry.data.size()); auto* block = static_cast(ApplicationHeapAllocate_nid_no_patch(size)); if (!block) { *__error_nid_postfix() = 12; @@ -233,9 +363,9 @@ int APS5_VABI getifaddrs_nid_postfix(GuestInterfaceAddress** list) { item.name = static_cast(place(entry.name.c_str(), entry.name.size() + 1)); item.flags = entry.flags; item.address = place(entry.address.data(), entry.address.size()); - item.netmask = place(entry.netmask.data(), entry.netmask.size()); + item.netmask = entry.netmask.empty() ? nullptr : place(entry.netmask.data(), entry.netmask.size()); item.destination = entry.destination.empty() ? nullptr : place(entry.destination.data(), entry.destination.size()); - item.data = nullptr; + item.data = entry.data.empty() ? nullptr : place(entry.data.data(), entry.data.size()); } *list = items; return 0; diff --git a/core/libs/tests/GuestInterfaces.cpp b/core/libs/tests/GuestInterfaces.cpp index 4a7202cf4e..5b69f12016 100644 --- a/core/libs/tests/GuestInterfaces.cpp +++ b/core/libs/tests/GuestInterfaces.cpp @@ -4,8 +4,17 @@ #include #include #else +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include #endif #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -46,6 +55,12 @@ struct HostInterfaceAddress { std::uint8_t address[16]; std::uint8_t netmask[16]; std::uint32_t scope; + char name[16]; + unsigned int index; + std::uint8_t type; + std::uint8_t hardwareLength; + std::uint8_t hardware[8]; + bool loopbackUp; }; static HostInterfaceAddress HostAddress(const sockaddr* address) { @@ -61,6 +76,43 @@ static HostInterfaceAddress HostAddress(const sockaddr* address) { return entry; } +#ifndef _WIN32 +static std::uint64_t SysfsCounter(const char* name, const char* file) { + std::ifstream input(std::string("/sys/class/net/") + name + "/statistics/" + file); + unsigned long long value = 0; + Require(static_cast(input >> value)); + return value; +} + +static std::uint32_t HostMtu(const char* name) { + const int probe = ::socket(AF_INET, SOCK_DGRAM, 0); + Require(probe >= 0); + ifreq request{}; + std::strncpy(request.ifr_name, name, sizeof(request.ifr_name) - 1); + Require(::ioctl(probe, SIOCGIFMTU, &request) == 0); + ::close(probe); + return static_cast(request.ifr_mtu); +} + +static void SendLoopbackDatagram() { + const int receiver = ::socket(AF_INET, SOCK_DGRAM, 0); + const int sender = ::socket(AF_INET, SOCK_DGRAM, 0); + Require(receiver >= 0 && sender >= 0); + sockaddr_in address{}; + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + Require(::bind(receiver, reinterpret_cast(&address), sizeof(address)) == 0); + socklen_t length = sizeof(address); + Require(::getsockname(receiver, reinterpret_cast(&address), &length) == 0); + const char payload = 'x'; + Require(::sendto(sender, &payload, 1, 0, reinterpret_cast(&address), sizeof(address)) == 1); + char received = 0; + Require(::recv(receiver, &received, 1, 0) == 1); + ::close(sender); + ::close(receiver); +} +#endif + static std::vector HostSnapshot() { std::vector entries; #ifdef _WIN32 @@ -88,22 +140,47 @@ static std::vector HostSnapshot() { #else ifaddrs* host = nullptr; Require(::getifaddrs(&host) == 0); + std::vector names; + std::vector> groups; + const auto group = [&](const char* name) -> std::vector& { + for (std::size_t index = 0; index < names.size(); ++index) + if (names[index] == name) return groups[index]; + names.emplace_back(name); + return groups.emplace_back(); + }; try { for (auto* item = host; item; item = item->ifa_next) { - if (!item->ifa_addr || (item->ifa_addr->sa_family != AF_INET && item->ifa_addr->sa_family != AF_INET6)) continue; + if (!item->ifa_addr) continue; + if (item->ifa_addr->sa_family == AF_PACKET) { + const auto& link = *reinterpret_cast(item->ifa_addr); + HostInterfaceAddress entry{}; + entry.family = AF_PACKET; + std::strncpy(entry.name, item->ifa_name, sizeof(entry.name) - 1); + entry.index = static_cast(link.sll_ifindex); + entry.type = link.sll_hatype == ARPHRD_ETHER ? 0x6 : link.sll_hatype == ARPHRD_LOOPBACK ? 0x18 : link.sll_hatype == ARPHRD_PPP ? 0x17 : 0x1; + entry.hardwareLength = entry.type == 0x18 ? 0 : link.sll_halen; + std::memcpy(entry.hardware, link.sll_addr, entry.hardwareLength); + entry.loopbackUp = (item->ifa_flags & IFF_UP) != 0; + auto& members = group(item->ifa_name); + members.insert(members.begin(), entry); + continue; + } + if (item->ifa_addr->sa_family != AF_INET && item->ifa_addr->sa_family != AF_INET6) continue; Require(item->ifa_netmask != nullptr); HostInterfaceAddress entry = HostAddress(item->ifa_addr); + std::strncpy(entry.name, item->ifa_name, sizeof(entry.name) - 1); if (entry.family == AF_INET) std::memcpy(entry.netmask, &reinterpret_cast(item->ifa_netmask)->sin_addr, 4); else std::memcpy(entry.netmask, &reinterpret_cast(item->ifa_netmask)->sin6_addr, 16); - entries.push_back(entry); + group(item->ifa_name).push_back(entry); } } catch (...) { ::freeifaddrs(host); throw; } ::freeifaddrs(host); + for (const auto& members : groups) entries.insert(entries.end(), members.begin(), members.end()); #endif return entries; } @@ -115,6 +192,10 @@ int main() { const std::uint8_t loopbackAddress[] = {127, 0, 0, 1}; const std::uint8_t loopbackMask[] = {255, 0, 0, 0}; bool matched = false; +#ifndef _WIN32 + bool linkChecked = false; + SendLoopbackDatagram(); +#endif for (unsigned attempt = 0; attempt < 3 && !matched; ++attempt) { const std::vector host = HostSnapshot(); GuestInterfaceAddress* list = nullptr; @@ -128,8 +209,47 @@ int main() { const std::size_t nameLength = std::strlen(item->name); Require(nameLength >= 1 && nameLength <= 15); Require((item->flags & ~knownFlags) == 0); - Require(item->data == nullptr); const auto* address = static_cast(item->address); + Require(address != nullptr && reinterpret_cast(address) % 8 == 0); +#ifndef _WIN32 + if (address[1] == 18) { + Require(item->netmask == nullptr && item->destination == nullptr); + const auto* data = static_cast(item->data); + Require(data != nullptr && reinterpret_cast(data) % 8 == 0); + std::uint16_t index = 0; + std::uint16_t dataLength = 0; + std::uint32_t mtu = 0; + std::uint64_t counters[11] = {}; + std::memcpy(&index, address + 2, 2); + std::memcpy(&dataLength, data + 6, 2); + std::memcpy(&mtu, data + 8, 4); + std::memcpy(counters, data + 24, sizeof(counters)); + Require(address[0] >= 54 && address[0] % 8 == 0 && address[0] >= 24 + address[6]); + Require(address[5] == nameLength && std::memcmp(address + 8, item->name, nameLength) == 0 && address[7] == 0); + Require(dataLength == 152 && data[0] == address[4] && data[2] == address[6] && data[1] == 0 && data[5] == 0); + Require(data[4] == 1 || data[4] == 2); + Require(index == if_nametoindex(item->name)); + Require(mtu == HostMtu(item->name)); + Require(counters[0] <= SysfsCounter(item->name, "rx_packets") && counters[2] <= SysfsCounter(item->name, "tx_packets")); + Require(counters[5] <= SysfsCounter(item->name, "rx_bytes") && counters[6] <= SysfsCounter(item->name, "tx_bytes")); + if (entries <= host.size()) { + const HostInterfaceAddress& expected = host[entries - 1]; + equal = equal && expected.family == AF_PACKET && std::strcmp(expected.name, item->name) == 0 && expected.index == index && + expected.type == address[4] && expected.hardwareLength == address[6] && + std::memcmp(expected.hardware, address + 8 + nameLength, expected.hardwareLength) == 0; + if (expected.type == 0x18) { + Require(data[3] == 0 && data[4] == (expected.loopbackUp ? 2 : 1)); + if (expected.loopbackUp) Require(counters[0] > 0 && counters[2] > 0 && counters[5] > 0 && counters[6] > 0); + linkChecked = linkChecked || expected.loopbackUp; + } + if (expected.type == 0x6) Require(data[3] == 14 && address[6] == 6); + } else { + equal = false; + } + continue; + } +#endif + Require(item->data == nullptr); const auto* netmask = static_cast(item->netmask); Require(address != nullptr && netmask != nullptr); Require(reinterpret_cast(address) % 8 == 0); @@ -151,6 +271,9 @@ int main() { if (item->flags & 0x10) Require(item->destination != nullptr); if (entries <= host.size()) { const HostInterfaceAddress& expected = host[entries - 1]; +#ifndef _WIN32 + equal = equal && std::strcmp(expected.name, item->name) == 0; +#endif const bool ipv4 = address[1] == 2; const std::size_t offset = ipv4 ? 4 : 8; const std::size_t length = ipv4 ? 4 : 16; @@ -172,6 +295,9 @@ int main() { freeifaddrs_nid_postfix(list); } Require(matched); +#ifndef _WIN32 + Require(linkChecked); +#endif GuestInterfaceAddress* released = nullptr; Require(getifaddrs_nid_postfix(&released) == 0 && released != nullptr); free_nid_postfix(released); diff --git a/docs/dev/debt/libc.md b/docs/dev/debt/libc.md index cde40c7ff4..7cdfe88e19 100644 --- a/docs/dev/debt/libc.md +++ b/docs/dev/debt/libc.md @@ -89,7 +89,7 @@ - The [`_Unwind_Context`](../../../core/libs/prx/libc/include/exceptions/Unwind.hpp) that `_Unwind_Backtrace` passes to its callback starts with a table laid out like LLVM libunwind's `AbstractUnwindCursor` virtual table, because homebrew built with the ps5-payload-dev SDK links libunwind statically and reads the frames with its own `_Unwind_GetIP` (`__unw_get_reg`), which calls through that table. Only register reads and writes (`validReg`, `getReg`, `setReg`, with `UNW_REG_IP` and `UNW_REG_SP`), `validFloatReg` (always false) and `isSignalFrame` are answered; the other slots throw. The layout follows libunwind's `UnwindCursor.hpp`, not the console's own `libSceLibcInternal`, which is not checked - The Windows libc [exception unwinder](../../../core/libs/prx/libc/src/exception/Unwind.cpp) steps through frames of modules without the project's `.ehmeta` and `.ehfram` tables (system DLLs, and WinLibs GCC 15.2's `libstdc++-6.dll` and `libwinpthread-1.dll`, whose `.eh_frame` sections are empty) with their PE unwind data through `RtlVirtualUnwind`, but does not run Windows exception or termination handlers: it stops at a frame whose unwind data has one, such as a C++ cleanup through `__gxx_personality_seh0` in `libstdc++-6.dll` or a system DLL function like `qsort` in `ucrtbase.dll` (Windows 10 22H2), and the exception is then reported as unhandled - The libc [SSE4a trap emulation](../../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it. -- [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. +- [getifaddrs](../../../core/libs/prx/libc/src/Interfaces.cpp) (libc) lists host interfaces and IPv4/IPv6 addresses in FreeBSD's layouts. Windows omits per-interface AF_LINK entries and interfaces without an address; its flags are only `IFF_UP`, `IFF_RUNNING`, `IFF_LOOPBACK`, `IFF_BROADCAST` and `IFF_MULTICAST` (PPP/tunnel adapters omit broadcast and point-to-point, and an unspecified prefix length gets a host mask). On Linux, `AF_LINK` `if_data` maps non-Ethernet/loopback/PPP types to `IFT_OTHER` (1) and leaves `ifi_omcasts`, `ifi_noproto`, `ifi_hwassist`, `ifi_metric`, `ifi_epoch`, `ifi_lastchange`, unknown baud rates and address-entry `ifa_data` as 0/null; a point-to-point address without a peer loses `IFF_POINTOPOINT`, and an omitted IPv4 broadcast is derived from the netmask. Interface names are the host's (`if` on Windows above 15 bytes). - [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 - [c16rtomb, c32rtomb, mbrtoc16 and mbrtoc32](../../../core/libs/prx/libc/src/Multibyte.cpp) (libSceLibcInternal, implemented in libc) follow the console's `libSceLibcInternal.sprx` (SDK 4.5x build of `J03950001`, 0x7D480 to 0x7D920), which converts to and from UTF-8 whatever the locale, with the original sequences of up to six bytes and no check for surrogates or for values above 0x10FFFF when encoding. The conversion state keeps the value in its first eight bytes and the count of bytes still expected (or 0x10 for a pending low surrogate, 1 for a stored high surrogate) as 16 bits at offset 0xA; a null state, which the console dereferences, throws. As on the console, `mbrtoc16` does not store the complete code point when it returns a high surrogate, so the low surrogate of the next call is built from the value the state last held, which is the partial value of an earlier incomplete call or whatever the caller initialised it to, `c16rtomb` returns -1 without setting `errno` when a high surrogate is not followed by a low one, and `mbrtoc16` and `mbrtoc32` report `EILSEQ` as 86. Measured on a PS5 (firmware 13.60) from a homebrew payload, the system `libSceLibcInternal`'s default locale also converts UTF-8 in `wcrtomb`, `wctomb`, `mbrtowc` and `mblen` (`wctomb` of U+263A gives `E2 98 BA`, `mblen` of a lone 0xE9 gives -1); this libc keeps its single-byte C locale, so [wctomb and mblen](../../../core/libs/prx/libc/src/Multibyte.cpp), which call `wcrtomb` and `mbtowc` here as they call the same internal routines on the console, share that difference. Titles bundle their own `libc.prx`, whose locale was not measured