diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 09f46bd6e..1ea3b505a 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #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(0x80410100u); constexpr int NET_ERROR_RESOLVER_ENODNS = static_cast(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 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 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& 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::vectorai_next) { const auto address = reinterpret_cast(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 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::min(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(&address), sizeof(address), hostname, + std::vector name(static_cast(len)); + const int result = ::getnameinfo(reinterpret_cast(&address), sizeof(address), name.data(), static_cast(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; } diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 583cb51cd..2ad9a8a60 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -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(0x80410116) && *sceNetErrnoLoc() == 22); Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "guest-sce-net.invalid.", records.data(), 5000000, 1, 0) == static_cast(0x804101E1) && records == resolved); + + std::array 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(0x80410116) && *sceNetErrnoLoc() == 22 && plain == resolved); + Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", nullptr, 5000000, 1, 0) == + static_cast(0x80410116) && *sceNetErrnoLoc() == 22); + Require(sceNetResolverStartNtoaMultipleRecords(resolver, "guest-sce-net.invalid", plain.data(), 5000000, 1, 0) == + static_cast(0x804101E1) && plain == resolved); + Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == static_cast(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 loopback{127, 0, 0, 1}; + char reverse[256]{}; + Require(sceNetResolverAbort(resolver, 0) == 0); + 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); + 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(0x804101E1)); + Require(sceNetResolverStartNtoa(resolver, nullptr, ipv4.data(), 5000000, 1, 0) == static_cast(0x80410116)); + ipv4.fill(0xA5); + Require(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0) == static_cast(0x80410104) && + *sceNetErrnoLoc() == 4 && ipv4[0] == 0xA5); + Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == static_cast(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(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(0x80410104) && *sceNetErrnoLoc() == 4 && reverse[0] == 0x5A); + Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == static_cast(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(0x80410104) && *sceNetErrnoLoc() == 4); + Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", records.data(), 5000000, 1, 0) == + static_cast(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(0x80410109) && *sceNetErrnoLoc() == 9); + Require(sceNetResolverAbort(-1, 1) == static_cast(0x80410109) && *sceNetErrnoLoc() == 9); Require(sceNetResolverGetError(resolver, nullptr) == static_cast(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(0x80410109) && *sceNetErrnoLoc() == 9 && records == resolved); + Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", plain.data(), 5000000, 1, 0) == + static_cast(0x80410109) && *sceNetErrnoLoc() == 9 && plain == resolved); + Require(sceNetResolverAbort(resolver, 1) == static_cast(0x80410109) && *sceNetErrnoLoc() == 9); std::array ipv6{}; Require(sceNetInetPton(28, "::1", ipv6.data()) == 1); diff --git a/docs/dev/debt/libSceAudioOut.md b/docs/dev/debt/libSceAudioOut.md index 176d82c4d..57bba2cc0 100644 --- a/docs/dev/debt/libSceAudioOut.md +++ b/docs/dev/debt/libSceAudioOut.md @@ -15,6 +15,7 @@ - [sceAudioOut2PortCreate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - 12 channels are assumed to be L R C LFE Ls Rs Lb Rb Ltf Rtf Ltb Rtb as in KytyPS5's 12-to-8 fold, and 6 and 8 channels to be its first 6 and 8; no title or SDK header confirms the order - [sceAudioOut2PortCreate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - the stereo fold is assumed, not the console's downmix: C at -3 dB into both sides, Ls/Rs, Lb/Rb and Ltf/Rtf at -3 dB and Ltb/Rtb at -6 dB into their own side, LFE dropped - [sceAudioOut2PortSetAttributes](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - each PCM buffer set (attribute 0) is taken as one grain: buffers set between two pushes are queued and played one per grain, at most 16 queued. A grain with no new buffer repeats the last one only when the port has always been given that same buffer (rewritten in place); a port whose buffers move on plays silence instead. Inferred from PPSA12544, whose object port (type 6) is given one grain of a 64-grain ring per set, often two or three between pushes and then none for a few; the console behaviour for several sets per push and for a push without a new buffer is not documented +- 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 ### Functional diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index 08c35423d..346d3b3e7 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -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