From df32a44f7990b215cc9ea9327000670a16b125ad Mon Sep 17 00:00:00 2001 From: ps54all Date: Tue, 13 Oct 2026 20:44:40 +0200 Subject: [PATCH] feat(libSceNet): implement sceNetResolverStartNtoa6 Resolve a host name to its first IPv6 address through the host resolver (getaddrinfo with AF_INET6) and write the 16-byte address. The lookup shares the IPv4 path's resolver bookkeeping (abort preservation, error state); a name without an IPv6 address fails with RESOLVER_ENODNS like a failed lookup. Nonzero flags throw. Metal Gear Solid 4 Master Collection (PPSA18194) imports it and failed to load without it (unresolved zl35YNs9jnI from libSceNet.prx). Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceNet/Export.cpp | 36 +++++++++++++++++++++++++++--- core/libs/tests/GuestSceNet.cpp | 10 +++++++++ docs/dev/debt/libSceNet.md | 1 + 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 7c3433484..27638ea00 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -14,6 +14,7 @@ #include #endif #include +#include #include #include #include @@ -1299,12 +1300,12 @@ int APS5_VABI sceNetResolverDestroy(int rid) { return g_resolvers.erase(rid) != 0 ? 0 : fail(NET_EBADF); } -int lookup_ipv4(int rid, const char* hostname, const char* func, std::vector& addresses) { +int lookup_host(int rid, const char* hostname, const char* func, int family, std::vector>& addresses) { if (!initialize_sockets()) return fail(5); std::uint64_t generation = 0; if (const int error = begin_lookup(rid, &Resolver::abort_ntoa, generation)) return error; addrinfo hints{}; - hints.ai_family = AF_INET; + hints.ai_family = family; addrinfo* results = nullptr; const int result = ::getaddrinfo(hostname, nullptr, &hints, &results); if (result != 0) { @@ -1313,13 +1314,31 @@ int lookup_ipv4(int rid, const char* hostname, const char* func, std::vectorai_next) { - const auto address = reinterpret_cast(entry->ai_addr)->sin_addr.s_addr; + if (entry->ai_family != family) continue; + std::array address{}; + if (family == AF_INET6) std::memcpy(address.data(), &reinterpret_cast(entry->ai_addr)->sin6_addr, 16); + else std::memcpy(address.data(), &reinterpret_cast(entry->ai_addr)->sin_addr, 4); if (std::find(addresses.begin(), addresses.end(), address) == addresses.end()) addresses.push_back(address); } ::freeaddrinfo(results); + if (addresses.empty()) { + set_errno(NET_ENOENT); + return end_lookup(rid, generation, NET_ERROR_RESOLVER_ENODNS); + } return end_lookup(rid, generation, 0); } +int lookup_ipv4(int rid, const char* hostname, const char* func, std::vector& addresses) { + std::vector> found; + if (const int error = lookup_host(rid, hostname, func, AF_INET, found)) return error; + for (const auto& address : found) { + std::uint32_t value = 0; + std::memcpy(&value, address.data(), sizeof(value)); + addresses.push_back(value); + } + return 0; +} + int APS5_VABI sceNetResolverStartNtoa(int rid, const char* hostname, void* addr, int timeout, int retry, int flags) { (void)timeout; (void)retry; @@ -1331,6 +1350,17 @@ int APS5_VABI sceNetResolverStartNtoa(int rid, const char* hostname, void* addr, return 0; } +int APS5_VABI sceNetResolverStartNtoa6(int rid, const char* hostname, void* addr, int timeout, int retry, int flags) { + (void)timeout; + (void)retry; + if (flags != 0) throw std::runtime_error("sceNetResolverStartNtoa6: flags " + std::to_string(flags) + " are not supported"); + if (!hostname || !addr) return fail(NET_EINVAL); + std::vector> addresses; + if (const int error = lookup_host(rid, hostname, __func__, AF_INET6, addresses)) return error; + std::memcpy(addr, addresses.front().data(), addresses.front().size()); + return 0; +} + struct NetResolverRecord { std::uint32_t address; std::uint8_t address6[12]; diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index e88dc22a8..05c06bd61 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -52,6 +52,7 @@ extern const std::uint32_t sce_net_in6addr_any[4]; extern const std::uint8_t sce_net_in6addr_linklocal_allnodes[16]; int APS5_VABI sceNetResolverCreate(const char*, int, int); int APS5_VABI sceNetResolverStartNtoa(int, const char*, void*, int, int, int); +int APS5_VABI sceNetResolverStartNtoa6(int, const char*, void*, int, int, int); int APS5_VABI sceNetResolverStartNtoaMultipleRecordsEx(int, const char*, void*, int, int, int); int APS5_VABI sceNetResolverStartNtoaMultipleRecords(int, const char*, void*, int, int, int); int APS5_VABI sceNetResolverStartAton(int, const void*, char*, int, int, int, int); @@ -837,6 +838,15 @@ int main() { resolver_error == static_cast(0x804101E1)); Require(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0) == 0); Require(ipv4[0] == 127); + std::array resolved6{}; + resolved6.fill(0xA5); + Require(sceNetResolverStartNtoa6(resolver, "::1", resolved6.data(), 5000000, 1, 0) == 0); + Require(std::all_of(resolved6.begin(), resolved6.end() - 1, [](std::uint8_t byte) { return byte == 0; }) && resolved6[15] == 1); + Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0); + Require(sceNetResolverStartNtoa6(resolver, nullptr, resolved6.data(), 5000000, 1, 0) == static_cast(0x80410116)); + Require(sceNetResolverStartNtoa6(resolver, "127.0.0.1", resolved6.data(), 5000000, 1, 0) == static_cast(0x804101E1)); + Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == static_cast(0x804101E1)); + Require(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0) == 0); Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0); std::array records{}; records.fill(0xA5); diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index c70d4323d..95103ae7a 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -11,3 +11,4 @@ - [sceNetSetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) sets `SO_RCVTIMEO` and `SO_SNDTIMEO` as the host socket's timeouts. On Windows a receive or send on a stream socket that times out fails with `ETIMEDOUT`, where FreeBSD returns `EAGAIN` or the partial count: Winsock leaves the connection in an indeterminate state after a timeout and reports no partial count ([SOL_SOCKET options](https://learn.microsoft.com/en-us/windows/win32/winsock/sol-socket-socket-options)), so FreeBSD's behaviour needs the timeout waited for without the host option. A receive on a datagram socket that times out fails with `EAGAIN` - [sceNetSendmsg and sceNetRecvmsg](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported - [sceNetResolverAbort and sceNetResolverStartNtoaMultipleRecords](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) follow shadPS4 ([net.cpp](https://github.com/shadps4-emu/shadPS4/blob/811bfcd7d17ed8ae975c477d9ff28abc744e1ca8/src/core/libraries/net/net.cpp), [net_resolver.cpp](https://github.com/shadps4-emu/shadPS4/blob/811bfcd7d17ed8ae975c477d9ff28abc744e1ca8/src/core/libraries/net/net_resolver.cpp)); the `(int rid, int flags)` signature of the abort also matches [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs). Unverified on hardware. Lookups run synchronously over the host resolver, which cannot be interrupted: an abort while a lookup runs on another thread returns at once, and that lookup fails with `EINTR` without writing its output only when the host call returns; the abort flags are then ignored, as shadPS4 does for a running lookup. With nothing running, abort flag 1 (NTOA preservation) or 2 (ATON preservation) makes the next `sceNetResolverStartNtoa*` or `sceNetResolverStartAton` call on that resolver fail with `EINTR`, reported by `sceNetResolverGetError`, after its arguments are checked. Unlike shadPS4, which ignores them, other abort flag bits throw, and `sceNetResolverStartNtoaMultipleRecords` throws for any flag, including async (1) and 0x10000 (IP address literals disabled), as `sceNetResolverStartNtoaMultipleRecordsEx` does +- [sceNetResolverStartNtoa6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) resolves a host name to its first IPv6 address through the host resolver (`getaddrinfo` with `AF_INET6`) and writes the 16-byte address; the `(rid, hostname, addr, timeout, retry, flags)` signature follows `sceNetResolverStartNtoa`. A name with no IPv6 address fails with `RESOLVER_ENODNS` (`0x804101E1`) like a failed lookup; whether the console returns IPv4-mapped addresses instead is not verified. Timeout and retry are ignored as in `sceNetResolverStartNtoa`, and any nonzero flags throw because their meaning for this call is not known. Unverified on hardware.