Merge pull request #3728 from MarcZX100/codex/net-resolver-abort-3153

feat(libSceNet): implement resolver abort and multiple records
This commit is contained in:
Adrià 2026-10-12 22:17:43 +02:00 committed by GitHub
commit 9f8a1080aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 151 additions and 35 deletions

View File

@ -30,6 +30,7 @@
#include <set>
#include <stdexcept>
#include <string>
#include <utility>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/GuestArena.hpp"
@ -40,6 +41,7 @@
namespace {
// FreeBSD errno numbers as reported through sceNetErrnoLoc (SCE_NET_ERROR_* is 0x80410100 + errno).
constexpr int NET_ENOENT = 2;
constexpr int NET_EINTR = 4;
constexpr int NET_EBADF = 9;
constexpr int NET_EACCES = 13;
constexpr int NET_EFAULT = 14;
@ -66,6 +68,8 @@ constexpr int NET_ECONNREFUSED = 61;
constexpr int NET_EHOSTUNREACH = 65;
constexpr int NET_ERROR_BASE = static_cast<int>(0x80410100u);
constexpr int NET_ERROR_RESOLVER_ENODNS = static_cast<int>(0x804101E1u);
constexpr int NET_RESOLVER_ABORT_FLAG_NTOA_PRESERVATION = 0x1;
constexpr int NET_RESOLVER_ABORT_FLAG_ATON_PRESERVATION = 0x2;
constexpr int NET_AF_INET = 2;
constexpr int NET_AF_INET6 = 28;
@ -302,7 +306,14 @@ struct NetMemoryPoolStats {
std::size_t max_inuse_size;
std::size_t current_inuse_size;
};
std::map<int, int> g_resolvers;
struct Resolver {
int error = 0;
int running = 0;
std::uint64_t aborts = 0;
bool abort_ntoa = false;
bool abort_aton = false;
};
std::map<int, Resolver> g_resolvers;
int g_next_sock = 32;
int g_next_epoll = 0x4000;
int g_next_pool = 1;
@ -365,10 +376,27 @@ int set_kernel_timeout(int descriptor, int optname, const void* value, std::uint
return 0;
}
void set_resolver_error(int rid, int error) {
int begin_lookup(int rid, bool Resolver::*preservedAbort, std::uint64_t& generation) {
std::lock_guard lk(g_mutex);
const auto resolver = g_resolvers.find(rid);
if (resolver != g_resolvers.end()) resolver->second = error;
if (resolver == g_resolvers.end()) return fail(NET_EBADF);
if (std::exchange(resolver->second.*preservedAbort, false)) {
resolver->second.error = NET_ERROR_BASE | NET_EINTR;
return fail(NET_EINTR);
}
++resolver->second.running;
generation = resolver->second.aborts;
return 0;
}
int end_lookup(int rid, std::uint64_t generation, int error) {
std::lock_guard lk(g_mutex);
const auto resolver = g_resolvers.find(rid);
if (resolver == g_resolvers.end()) return error;
--resolver->second.running;
if (resolver->second.aborts != generation) error = fail(NET_EINTR);
resolver->second.error = error;
return error;
}
void log_soft(const char* func, const char* what) {
@ -1244,7 +1272,7 @@ int APS5_VABI sceNetResolverCreate(const char* name, int memid, int flags) {
(void)flags;
std::lock_guard lk(g_mutex);
const int id = g_next_resolver++;
g_resolvers[id] = 0;
g_resolvers[id] = {};
return id;
}
@ -1254,11 +1282,9 @@ int APS5_VABI sceNetResolverDestroy(int rid) {
}
int lookup_ipv4(int rid, const char* hostname, const char* func, std::vector<std::uint32_t>& addresses) {
{
std::lock_guard lk(g_mutex);
if (g_resolvers.count(rid) == 0) return fail(NET_EBADF);
}
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;
addrinfo* results = nullptr;
@ -1266,16 +1292,14 @@ int lookup_ipv4(int rid, const char* hostname, const char* func, std::vector<std
if (result != 0) {
set_errno(result == EAI_AGAIN ? NET_ETIMEDOUT : NET_ENOENT);
log_soft(func, "host DNS lookup failed");
set_resolver_error(rid, NET_ERROR_RESOLVER_ENODNS);
return NET_ERROR_RESOLVER_ENODNS;
return end_lookup(rid, generation, NET_ERROR_RESOLVER_ENODNS);
}
for (const addrinfo* entry = results; entry != nullptr; entry = entry->ai_next) {
const auto address = reinterpret_cast<const sockaddr_in*>(entry->ai_addr)->sin_addr.s_addr;
if (std::find(addresses.begin(), addresses.end(), address) == addresses.end()) addresses.push_back(address);
}
::freeaddrinfo(results);
set_resolver_error(rid, 0);
return 0;
return end_lookup(rid, generation, 0);
}
int APS5_VABI sceNetResolverStartNtoa(int rid, const char* hostname, void* addr, int timeout, int retry, int flags) {
@ -1305,14 +1329,11 @@ struct NetResolverInfo {
static_assert(sizeof(NetResolverRecord) == 32 && offsetof(NetResolverRecord, family) == 16);
static_assert(sizeof(NetResolverInfo) == 384 && offsetof(NetResolverInfo, count) == 320 && offsetof(NetResolverInfo, count4) == 324);
int APS5_VABI sceNetResolverStartNtoaMultipleRecordsEx(int rid, const char* hostname, NetResolverInfo* info, int timeout,
int retry, int flags) {
(void)timeout;
(void)retry;
if (flags != 0) throw std::runtime_error("sceNetResolverStartNtoaMultipleRecordsEx: flags " + std::to_string(flags) + " are not supported");
int lookup_records(int rid, const char* hostname, NetResolverInfo* info, int flags, const char* func) {
if (flags != 0) throw std::runtime_error(std::string(func) + ": flags " + std::to_string(flags) + " are not supported");
if (!hostname || !info) return fail(NET_EINVAL);
std::vector<std::uint32_t> addresses;
if (const int error = lookup_ipv4(rid, hostname, __func__, addresses)) return error;
if (const int error = lookup_ipv4(rid, hostname, func, addresses)) return error;
*info = {};
info->count = static_cast<std::int32_t>(std::min<std::size_t>(addresses.size(), std::size(info->records)));
info->count4 = info->count;
@ -1323,28 +1344,41 @@ int APS5_VABI sceNetResolverStartNtoaMultipleRecordsEx(int rid, const char* host
return 0;
}
int APS5_VABI sceNetResolverStartNtoaMultipleRecords(int rid, const char* hostname, NetResolverInfo* info, int timeout,
int retry, int flags) {
(void)timeout;
(void)retry;
return lookup_records(rid, hostname, info, flags, __func__);
}
int APS5_VABI sceNetResolverStartNtoaMultipleRecordsEx(int rid, const char* hostname, NetResolverInfo* info, int timeout,
int retry, int flags) {
(void)timeout;
(void)retry;
return lookup_records(rid, hostname, info, flags, __func__);
}
int APS5_VABI sceNetResolverStartAton(int rid, const void* addr, char* hostname, int len, int timeout, int retry, int flags) {
(void)timeout;
(void)retry;
(void)flags;
if (!addr || !hostname || len <= 0) return fail(NET_EINVAL);
{
std::lock_guard lk(g_mutex);
if (g_resolvers.count(rid) == 0) return fail(NET_EBADF);
}
if (!initialize_sockets()) return fail(5);
std::uint64_t generation = 0;
if (const int error = begin_lookup(rid, &Resolver::abort_aton, generation)) return error;
sockaddr_in address{};
address.sin_family = AF_INET;
std::memcpy(&address.sin_addr, addr, sizeof(address.sin_addr));
const int result = ::getnameinfo(reinterpret_cast<const sockaddr*>(&address), sizeof(address), hostname,
std::vector<char> name(static_cast<std::size_t>(len));
const int result = ::getnameinfo(reinterpret_cast<const sockaddr*>(&address), sizeof(address), name.data(),
static_cast<NativeLength>(len), nullptr, 0, NI_NAMEREQD);
if (result != 0) {
set_errno(result == EAI_AGAIN ? NET_ETIMEDOUT : NET_ENOENT);
log_soft(__func__, "host reverse DNS lookup failed");
set_resolver_error(rid, NET_ERROR_RESOLVER_ENODNS);
return NET_ERROR_RESOLVER_ENODNS;
return end_lookup(rid, generation, NET_ERROR_RESOLVER_ENODNS);
}
set_resolver_error(rid, 0);
if (const int error = end_lookup(rid, generation, 0)) return error;
std::memcpy(hostname, name.data(), std::strlen(name.data()) + 1);
return 0;
}
@ -1353,17 +1387,23 @@ int APS5_VABI sceNetResolverGetError(int rid, int* status) {
std::lock_guard lk(g_mutex);
const auto resolver = g_resolvers.find(rid);
if (resolver == g_resolvers.end()) return fail(NET_EBADF);
*status = resolver->second;
*status = resolver->second.error;
return 0;
}
int APS5_VABI sceNetResolverAbort(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceNetResolverStartNtoaMultipleRecords() {
NotImplemented_nid_no_patch(__func__);
int APS5_VABI sceNetResolverAbort(int rid, int flags) {
constexpr int preservation = NET_RESOLVER_ABORT_FLAG_NTOA_PRESERVATION | NET_RESOLVER_ABORT_FLAG_ATON_PRESERVATION;
std::lock_guard lk(g_mutex);
const auto resolver = g_resolvers.find(rid);
if (resolver == g_resolvers.end()) return fail(NET_EBADF);
if ((flags & ~preservation) != 0) throw std::runtime_error("sceNetResolverAbort: flags " + std::to_string(flags) + " are not supported");
if (resolver->second.running != 0) {
++resolver->second.aborts;
resolver->second.error = NET_ERROR_BASE | NET_EINTR;
return 0;
}
if ((flags & NET_RESOLVER_ABORT_FLAG_NTOA_PRESERVATION) != 0) resolver->second.abort_ntoa = true;
if ((flags & NET_RESOLVER_ABORT_FLAG_ATON_PRESERVATION) != 0) resolver->second.abort_aton = true;
return 0;
}

View File

@ -51,6 +51,9 @@ 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 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);
int APS5_VABI sceNetResolverAbort(int, int);
int APS5_VABI sceNetResolverDestroy(int);
int APS5_VABI sceNetResolverGetError(int, int*);
int APS5_VABI sceNetCtlGetState(int*);
@ -669,6 +672,75 @@ int main() {
static_cast<int>(0x80410116) && *sceNetErrnoLoc() == 22);
Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "guest-sce-net.invalid.", records.data(), 5000000, 1, 0) ==
static_cast<int>(0x804101E1) && records == resolved);
std::array<std::uint8_t, 512> plain{};
plain.fill(0xA5);
Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", plain.data(), 5000000, 1, 0) == 0);
Require(plain == resolved);
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0);
Require(sceNetResolverStartNtoaMultipleRecords(resolver, nullptr, plain.data(), 5000000, 1, 0) ==
static_cast<int>(0x80410116) && *sceNetErrnoLoc() == 22 && plain == resolved);
Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", nullptr, 5000000, 1, 0) ==
static_cast<int>(0x80410116) && *sceNetErrnoLoc() == 22);
Require(sceNetResolverStartNtoaMultipleRecords(resolver, "guest-sce-net.invalid", plain.data(), 5000000, 1, 0) ==
static_cast<int>(0x804101E1) && plain == resolved);
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == static_cast<int>(0x804101E1));
for (const int flags : {0x1, 0x10000}) {
bool threw = false;
try { sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", plain.data(), 5000000, 1, flags); }
catch (const std::runtime_error&) { threw = true; }
Require(threw && plain == resolved);
}
const std::array<std::uint8_t, 4> loopback{127, 0, 0, 1};
char reverse[256]{};
Require(sceNetResolverAbort(resolver, 0) == 0);
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == static_cast<int>(0x804101E1));
Require(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0) == 0);
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0);
Require(sceNetResolverAbort(resolver, 1) == 0);
Require(sceNetResolverAbort(resolver, 1) == 0);
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0);
const int reverse_result = sceNetResolverStartAton(resolver, loopback.data(), reverse, sizeof(reverse), 5000000, 1, 0);
Require(reverse_result == 0 || reverse_result == static_cast<int>(0x804101E1));
Require(sceNetResolverStartNtoa(resolver, nullptr, ipv4.data(), 5000000, 1, 0) == static_cast<int>(0x80410116));
ipv4.fill(0xA5);
Require(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0) == static_cast<int>(0x80410104) &&
*sceNetErrnoLoc() == 4 && ipv4[0] == 0xA5);
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == static_cast<int>(0x80410104));
Require(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0) == 0 && ipv4[0] == 127);
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0);
Require(sceNetResolverAbort(resolver, 1) == 0);
Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", plain.data(), 5000000, 1, 0) ==
static_cast<int>(0x80410104) && *sceNetErrnoLoc() == 4);
Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", plain.data(), 5000000, 1, 0) == 0 && plain == resolved);
Require(sceNetResolverAbort(resolver, 2) == 0);
Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", records.data(), 5000000, 1, 0) == 0 && records == resolved);
std::memset(reverse, 0x5A, sizeof(reverse));
Require(sceNetResolverStartAton(resolver, loopback.data(), reverse, sizeof(reverse), 5000000, 1, 0) ==
static_cast<int>(0x80410104) && *sceNetErrnoLoc() == 4 && reverse[0] == 0x5A);
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == static_cast<int>(0x80410104));
Require(sceNetResolverStartAton(resolver, loopback.data(), reverse, sizeof(reverse), 5000000, 1, 0) == reverse_result);
Require(sceNetResolverAbort(resolver, 3) == 0);
Require(sceNetResolverStartAton(resolver, loopback.data(), reverse, sizeof(reverse), 5000000, 1, 0) ==
static_cast<int>(0x80410104) && *sceNetErrnoLoc() == 4);
Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", records.data(), 5000000, 1, 0) ==
static_cast<int>(0x80410104) && *sceNetErrnoLoc() == 4 && records == resolved);
Require(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0) == 0);
for (const int flags : {0x4, -1}) {
bool threw = false;
try { sceNetResolverAbort(resolver, flags); } catch (const std::runtime_error&) { threw = true; }
Require(threw);
}
Require(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0) == 0);
const int other = sceNetResolverCreate("guest-sce-net-other", 0, 0);
Require(other >= 0 && other != resolver);
Require(sceNetResolverAbort(other, 3) == 0);
Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", plain.data(), 5000000, 1, 0) == 0);
Require(sceNetResolverDestroy(other) == 0);
Require(sceNetResolverAbort(other, 0) == static_cast<int>(0x80410109) && *sceNetErrnoLoc() == 9);
Require(sceNetResolverAbort(-1, 1) == static_cast<int>(0x80410109) && *sceNetErrnoLoc() == 9);
Require(sceNetResolverGetError(resolver, nullptr) == static_cast<int>(0x80410116) && *sceNetErrnoLoc() == 22);
Require(sceNetResolverDestroy(resolver) == 0);
resolver_error = -1;
@ -676,6 +748,9 @@ int main() {
*sceNetErrnoLoc() == 9 && resolver_error == -1);
Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", records.data(), 5000000, 1, 0) ==
static_cast<int>(0x80410109) && *sceNetErrnoLoc() == 9 && records == resolved);
Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", plain.data(), 5000000, 1, 0) ==
static_cast<int>(0x80410109) && *sceNetErrnoLoc() == 9 && plain == resolved);
Require(sceNetResolverAbort(resolver, 1) == static_cast<int>(0x80410109) && *sceNetErrnoLoc() == 9);
std::array<std::uint8_t, 16> ipv6{};
Require(sceNetInetPton(28, "::1", ipv6.data()) == 1);

View File

@ -2,7 +2,7 @@
### Unknown function info
- PPSA23566 imports declared without parameters, signatures unknown: [sceAudioOut2UserGetSupportedAttributes](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp), [sceConvertKeycodeGetCharacterFromKeyboardData](../../../core/libs/prx/libSceConvertKeycode/Export.cpp), [sce::Json::MemAllocator::notifyError](../../../core/libs/prx/libSceJson2/Export.cpp), [sceNetResolverAbort](../../../core/libs/prx/libSceNet/Export.cpp), [sceNpCommerceDialogOpen2](../../../core/libs/prx/libSceNpCommerce/Export.cpp), [sceNpEntitlementAccessGetPftFlag](../../../core/libs/prx/libSceNpEntitlementAccess/Export.cpp), [sceProprietaryVoiceChatHelper{Initialize,Terminate,SetVoiceChatState,GetVoiceChatUsageState}](../../../core/libs/prx/libSceProprietaryVoiceChatHelperPrx/Export.cpp), [sceVoice{GetResourceInfo,ResetPort,EnableChat,DisableChat}](../../../core/libs/prx/libSceVoice/Export.cpp). Names from the shadPS4 aerolib NID list
- PPSA23566 imports declared without parameters, signatures unknown: [sceAudioOut2UserGetSupportedAttributes](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp), [sceConvertKeycodeGetCharacterFromKeyboardData](../../../core/libs/prx/libSceConvertKeycode/Export.cpp), [sce::Json::MemAllocator::notifyError](../../../core/libs/prx/libSceJson2/Export.cpp), [sceNpCommerceDialogOpen2](../../../core/libs/prx/libSceNpCommerce/Export.cpp), [sceNpEntitlementAccessGetPftFlag](../../../core/libs/prx/libSceNpEntitlementAccess/Export.cpp), [sceProprietaryVoiceChatHelper{Initialize,Terminate,SetVoiceChatState,GetVoiceChatUsageState}](../../../core/libs/prx/libSceProprietaryVoiceChatHelperPrx/Export.cpp), [sceVoice{GetResourceInfo,ResetPort,EnableChat,DisableChat}](../../../core/libs/prx/libSceVoice/Export.cpp). Names from the shadPS4 aerolib NID list
- PPSA28416 imports declared without parameters, signatures unknown: [sceAudioOut2EnableChat](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp), imported by its bundled `party-ps5-shipping.prx`. Name from the shadPS4 aerolib NID list
- [sceAudioOutExClose, sceAudioOutExConfigureOutput, sceAudioOutExGetMonitorInfo, sceAudioOutExOpen](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - imported by PPSA99505, which fails to load without them; added without an implementation and declared without parameters. PPSA99505 calls them only for its HDMI bitstream output, an option that is off by default, to read the formats the TV or receiver lists, switch the HDMI output mode and open a port for IEC 61937 bursts ([Jelly5](https://github.com/02dnot/Jelly5/blob/552b866d89b2a58b27f7f7b0484dc743ce235526/app/src/jelly5_bitstream.cpp#L110-L179)); [EVO-PLAYER-PS5](https://github.com/sainsaji/EVO-PLAYER-PS5/blob/da0c0451333bab82e79fffd2de4718e917f9dcb0/projects/evoplayer/core/src/audio/evo_pt.cpp) also calls `sceAudioOutExOpen`, `sceAudioOutExConfigureOutput` and `sceAudioOutExClose` for HDMI bitstream output. The two declare the arguments of `sceAudioOutExConfigureOutput` differently and neither is confirmed on a console. Audio reaches the host device through SDL as PCM, so there is no encoded HDMI output to switch to
- [sceAudioOut2Set3DLatency](../../../core/libs/prx/libSceAudioOut/src/AudioOut2System.cpp) (libSceAudioOut) - assumed to take `(user_id, latency)`: KytyPS5 reads `(user_id, output, latency_us)`, but the known calls set just edi and esi; only `(0xFF, 2)` (PPSA26344) and `(0xFF, 1)` (PPSA14632) are accepted

View File

@ -10,3 +10,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