From 1f50b912804cac00a6ecc2df1f0a785410d318f3 Mon Sep 17 00:00:00 2001 From: Abdulrahman Elkordy <196347165+Silverash-i8@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:23:39 +0300 Subject: [PATCH 1/3] fix(libkernel): report /dev/urandom descriptors ready in select select passed every descriptor it was given to the libSceNet socket poller and aborted on any it did not know. A title that selects on its /dev/urandom descriptor (Dungeons, PPSA16064, aborts right after opening it) stopped with "select on descriptors that are not libSceNet sockets". On FreeBSD the random device is always readable and writable, so these descriptors are now answered without the poller, and a ready one makes the socket poll return at once. Claude-Session: https://claude.ai/code/session_012SLQbhAiexxW6TtjYToW8a --- core/libs/prx/libkernel/Socket/src/Socket.cpp | 48 +++++++++++-------- core/libs/tests/GuestSceNet.cpp | 14 ++++++ 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/core/libs/prx/libkernel/Socket/src/Socket.cpp b/core/libs/prx/libkernel/Socket/src/Socket.cpp index 95da4bf78..63db392e0 100644 --- a/core/libs/prx/libkernel/Socket/src/Socket.cpp +++ b/core/libs/prx/libkernel/Socket/src/Socket.cpp @@ -2,6 +2,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/File/include/RandomDevice.hpp" #include "prx/libkernel/Socket/include/SocketPoll.hpp" #include "prx/libkernel/Socket/include/SocketSelectDeadline.hpp" @@ -59,13 +60,19 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* return -1; } std::vector entries; + std::vector devices; for (int descriptor = 0; descriptor < nfds; ++descriptor) { short events = 0; if (DescriptorSet(readfds, descriptor)) events |= KernelSocketPoll::Readable; if (DescriptorSet(writefds, descriptor)) events |= KernelSocketPoll::Writable; if (DescriptorSet(exceptfds, descriptor)) events |= KernelSocketPoll::Urgent; - if (events != 0) entries.push_back({descriptor, events, 0}); + if (events == 0) continue; + if (File::IsRandomDevice(descriptor)) devices.push_back({descriptor, events, static_cast(events & (KernelSocketPoll::Readable | KernelSocketPoll::Writable))}); + else entries.push_back({descriptor, events, 0}); } + const auto sockets = entries.size(); + const bool devicesReady = std::any_of(devices.begin(), devices.end(), [](const auto& entry) { return entry.revents != 0; }); + entries.insert(entries.end(), devices.begin(), devices.end()); const auto start = std::chrono::steady_clock::now(); const auto deadline = limit == nullptr ? std::chrono::steady_clock::time_point::max() : KernelSocketSelect::DeadlineAfter(start, limit->seconds, limit->microseconds); @@ -76,34 +83,35 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* const auto roundedMilliseconds = remaining > 0 ? remaining / 1000 + (remaining % 1000 != 0) : 0; waitMilliseconds = static_cast(std::clamp(roundedMilliseconds, 0, 100)); } - if (!entries.empty()) { + if (devicesReady) waitMilliseconds = 0; + if (sockets != 0) { const auto poller = g_socketPoller.load(); if (poller == nullptr) NotImplemented_nid_no_patch("select on descriptors that are not libSceNet sockets"); - for (auto& entry : entries) entry.revents = 0; - const int result = poller(entries.data(), static_cast(entries.size()), waitMilliseconds); + for (std::size_t index = 0; index < sockets; ++index) entries[index].revents = 0; + const int result = poller(entries.data(), static_cast(sockets), waitMilliseconds); if (result < 0) { *__error_nid_postfix() = -result; return -1; } - int ready = 0; - for (const auto& entry : entries) { - if ((entry.revents & KernelSocketPoll::Unknown) != 0) NotImplemented_nid_no_patch("select on descriptors that are not libSceNet sockets"); - const bool readable = (entry.events & KernelSocketPoll::Readable) != 0 && (entry.revents & (KernelSocketPoll::Readable | KernelSocketPoll::HangUp | KernelSocketPoll::Error)) != 0; - const bool writable = (entry.events & KernelSocketPoll::Writable) != 0 && (entry.revents & (KernelSocketPoll::Writable | KernelSocketPoll::Error)) != 0; - const bool urgent = (entry.events & KernelSocketPoll::Urgent) != 0 && (entry.revents & KernelSocketPoll::Urgent) != 0; - ready += static_cast(readable) + static_cast(writable) + static_cast(urgent); - } - if (ready > 0) { - for (const auto& entry : entries) { - if ((entry.revents & (KernelSocketPoll::Readable | KernelSocketPoll::HangUp | KernelSocketPoll::Error)) == 0) ClearDescriptor(readfds, entry.descriptor); - if ((entry.revents & (KernelSocketPoll::Writable | KernelSocketPoll::Error)) == 0) ClearDescriptor(writefds, entry.descriptor); - if ((entry.revents & KernelSocketPoll::Urgent) == 0) ClearDescriptor(exceptfds, entry.descriptor); - } - return ready; - } } else if (waitMilliseconds > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(waitMilliseconds)); } + int ready = 0; + for (const auto& entry : entries) { + if ((entry.revents & KernelSocketPoll::Unknown) != 0) NotImplemented_nid_no_patch("select on descriptors that are not libSceNet sockets"); + const bool readable = (entry.events & KernelSocketPoll::Readable) != 0 && (entry.revents & (KernelSocketPoll::Readable | KernelSocketPoll::HangUp | KernelSocketPoll::Error)) != 0; + const bool writable = (entry.events & KernelSocketPoll::Writable) != 0 && (entry.revents & (KernelSocketPoll::Writable | KernelSocketPoll::Error)) != 0; + const bool urgent = (entry.events & KernelSocketPoll::Urgent) != 0 && (entry.revents & KernelSocketPoll::Urgent) != 0; + ready += static_cast(readable) + static_cast(writable) + static_cast(urgent); + } + if (ready > 0) { + for (const auto& entry : entries) { + if ((entry.revents & (KernelSocketPoll::Readable | KernelSocketPoll::HangUp | KernelSocketPoll::Error)) == 0) ClearDescriptor(readfds, entry.descriptor); + if ((entry.revents & (KernelSocketPoll::Writable | KernelSocketPoll::Error)) == 0) ClearDescriptor(writefds, entry.descriptor); + if ((entry.revents & KernelSocketPoll::Urgent) == 0) ClearDescriptor(exceptfds, entry.descriptor); + } + return ready; + } if (limit != nullptr && std::chrono::steady_clock::now() >= deadline) { for (const auto& entry : entries) { ClearDescriptor(readfds, entry.descriptor); diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index c4b8c8004..42e336604 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -53,6 +53,8 @@ int APS5_VABI sceNetResolverDestroy(int); int APS5_VABI sceNetResolverGetError(int, int*); int APS5_VABI sceNetCtlGetState(int*); int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*); +int APS5_VABI open_nid_postfix(const char*, int, int); +int APS5_VABI close_nid_postfix(int); int* APS5_VABI __error_nid_postfix(); int APS5_VABI sceNetInetPton(int, const char*, void*); const char* APS5_VABI sceNetInetNtop(int, const void*, char*, std::uint32_t); @@ -422,6 +424,18 @@ int main() { const std::int64_t bad_timeout[2]{0, 1000000}; Require(select_nid_postfix(udp_receiver + 1, readable, nullptr, nullptr, bad_timeout) == -1 && *__error_nid_postfix() == 22); Require(select_nid_postfix(1025, nullptr, nullptr, nullptr, poll_now) == -1 && *__error_nid_postfix() == 22); + const int random = open_nid_postfix("/dev/urandom", 0, 0); + Require(random >= 0 && random < 1024); + std::uint64_t random_readable[16]{}; + std::uint64_t random_urgent[16]{}; + random_readable[random / 64] |= std::uint64_t{1} << (random % 64); + random_readable[udp_receiver / 64] |= std::uint64_t{1} << (udp_receiver % 64); + random_urgent[random / 64] |= std::uint64_t{1} << (random % 64); + Require(select_nid_postfix(std::max(random, udp_receiver) + 1, random_readable, nullptr, random_urgent, nullptr) == 1); + Require(((random_readable[random / 64] >> (random % 64)) & 1u) != 0); + Require(((random_readable[udp_receiver / 64] >> (udp_receiver % 64)) & 1u) == 0); + Require(random_urgent[random / 64] == 0); + Require(close_nid_postfix(random) == 0); CheckMessages(udp_receiver, udp_sender, address); Require(sceNetSocketClose(udp_sender) == 0); Require(sceNetSocketClose(udp_receiver) == 0); From cbbccb493085c42f7b02aa82c09bb9f50f2dbce5 Mon Sep 17 00:00:00 2001 From: Abdulrahman Elkordy <196347165+Silverash-i8@users.noreply.github.com> Date: Sat, 10 Oct 2026 22:55:15 +0300 Subject: [PATCH 2/3] fix(libkernel): name the descriptor select cannot poll The abort only said "select on descriptors that are not libSceNet sockets", which leaves no way to tell from a title's log what it waited on. It now names the descriptor and says whether libSceNet was loaded. Claude-Session: https://claude.ai/code/session_012SLQbhAiexxW6TtjYToW8a --- core/libs/prx/libkernel/Socket/src/Socket.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libkernel/Socket/src/Socket.cpp b/core/libs/prx/libkernel/Socket/src/Socket.cpp index 63db392e0..987bd497e 100644 --- a/core/libs/prx/libkernel/Socket/src/Socket.cpp +++ b/core/libs/prx/libkernel/Socket/src/Socket.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace { @@ -28,6 +29,11 @@ bool DescriptorSet(const void* set, int descriptor) { return set != nullptr && ((static_cast(set)[descriptor / 64] >> (descriptor % 64)) & 1u) != 0; } +void UnknownDescriptor(int descriptor, bool polled) { + const auto what = "select on descriptor " + std::to_string(descriptor) + (polled ? " (not a libSceNet socket)" : " (libSceNet is not loaded)"); + NotImplemented_nid_no_patch(what.c_str()); +} + void ClearDescriptor(void* set, int descriptor) { if (set != nullptr) static_cast(set)[descriptor / 64] &= ~(std::uint64_t{1} << (descriptor % 64)); } @@ -86,7 +92,7 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* if (devicesReady) waitMilliseconds = 0; if (sockets != 0) { const auto poller = g_socketPoller.load(); - if (poller == nullptr) NotImplemented_nid_no_patch("select on descriptors that are not libSceNet sockets"); + if (poller == nullptr) UnknownDescriptor(entries.front().descriptor, false); for (std::size_t index = 0; index < sockets; ++index) entries[index].revents = 0; const int result = poller(entries.data(), static_cast(sockets), waitMilliseconds); if (result < 0) { @@ -98,7 +104,7 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* } int ready = 0; for (const auto& entry : entries) { - if ((entry.revents & KernelSocketPoll::Unknown) != 0) NotImplemented_nid_no_patch("select on descriptors that are not libSceNet sockets"); + if ((entry.revents & KernelSocketPoll::Unknown) != 0) UnknownDescriptor(entry.descriptor, true); const bool readable = (entry.events & KernelSocketPoll::Readable) != 0 && (entry.revents & (KernelSocketPoll::Readable | KernelSocketPoll::HangUp | KernelSocketPoll::Error)) != 0; const bool writable = (entry.events & KernelSocketPoll::Writable) != 0 && (entry.revents & (KernelSocketPoll::Writable | KernelSocketPoll::Error)) != 0; const bool urgent = (entry.events & KernelSocketPoll::Urgent) != 0 && (entry.revents & KernelSocketPoll::Urgent) != 0; From abed1c4314270993457103a1f6a4ec8842a04bc6 Mon Sep 17 00:00:00 2001 From: Abdulrahman Elkordy <196347165+Silverash-i8@users.noreply.github.com> Date: Sat, 10 Oct 2026 23:31:50 +0300 Subject: [PATCH 3/3] fix(libkernel): answer select on the standard descriptors Dungeons (PPSA16064) calls select on descriptor 0 and stopped with "select on descriptor 0 (not a libSceNet socket) not implemented". Descriptor 0 is now never readable and descriptors 1 and 2 are always writable, as for a console title with no input. The assumption is in TechnicalDebt next to the termios entry for the same descriptors. Claude-Session: https://claude.ai/code/session_012SLQbhAiexxW6TtjYToW8a --- core/libs/prx/libkernel/Socket/src/Socket.cpp | 1 + core/libs/tests/GuestSceNet.cpp | 6 ++++++ docs/dev/TechnicalDebt.md | 1 + 3 files changed, 8 insertions(+) diff --git a/core/libs/prx/libkernel/Socket/src/Socket.cpp b/core/libs/prx/libkernel/Socket/src/Socket.cpp index 987bd497e..f7e393e53 100644 --- a/core/libs/prx/libkernel/Socket/src/Socket.cpp +++ b/core/libs/prx/libkernel/Socket/src/Socket.cpp @@ -74,6 +74,7 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* if (DescriptorSet(exceptfds, descriptor)) events |= KernelSocketPoll::Urgent; if (events == 0) continue; if (File::IsRandomDevice(descriptor)) devices.push_back({descriptor, events, static_cast(events & (KernelSocketPoll::Readable | KernelSocketPoll::Writable))}); + else if (descriptor <= 2) devices.push_back({descriptor, events, static_cast(descriptor == 0 ? 0 : events & KernelSocketPoll::Writable)}); else entries.push_back({descriptor, events, 0}); } const auto sockets = entries.size(); diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 42e336604..e71c5d65c 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -436,6 +436,12 @@ int main() { Require(((random_readable[udp_receiver / 64] >> (udp_receiver % 64)) & 1u) == 0); Require(random_urgent[random / 64] == 0); Require(close_nid_postfix(random) == 0); + std::uint64_t stdio_readable[16]{1}; + std::uint64_t stdio_writable[16]{6}; + Require(select_nid_postfix(3, stdio_readable, stdio_writable, nullptr, poll_now) == 2); + Require(stdio_readable[0] == 0 && stdio_writable[0] == 6); + stdio_readable[0] = 1; + Require(select_nid_postfix(1, stdio_readable, nullptr, nullptr, poll_now) == 0 && stdio_readable[0] == 0); CheckMessages(udp_receiver, udp_sender, address); Require(sceNetSocketClose(udp_sender) == 0); Require(sceNetSocketClose(udp_receiver) == 0); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 827203c81..526de788f 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -193,6 +193,7 @@ Throughout the project, every function at every stage either **does exactly what - [Dinkumware mutexes and conditions](../../core/libs/prx/libc/src/Threads.cpp) (libc) use pointer-to-handle arguments, matching the console aerolib signatures reported in [#3079](https://github.com/boykopovar/AnyPS5/pull/3079). The title's `_Mtx_lock` maps `0` to `_Thrd_success` (0), `0x8002000b` to `_Thrd_busy` (3), and other failures to `_Thrd_error` (4); its internal mutex init recognizes recursive flag `0x100`, while every other type uses default attributes. The observed relock failure for a plain mutex suggests those defaults are error-checking, but that has not been confirmed on hardware. The title's `_Mtx_unlock` and `_Cnd_wait` return success regardless of their internal result. The host implementation treats invalid ownership preconditions as no-ops, and throws for invalid handles, host wait errors and destroying a locked mutex as debug guards; those cases are not confirmed against the console. - [random, srandom, initstate, setstate](../../core/libs/prx/libc/src/Random.cpp) (libc) follow FreeBSD 11 [random.c](https://github.com/freebsd/freebsd-src/blob/4ab2e064d7950be84256d671a7ae93f87cc6aa36/lib/libc/stdlib/random.c): the additive feedback generator on a default 128-byte `TYPE_3` table, `good_rand` seeding, state sizes rounded down to 8, 32, 64, 128 or 256 bytes, and `setstate` returning the previous state or null for a rear position past the degree. `initstate` below 8 bytes returns null without setting errno, as that file and its manual do (glibc sets `EINVAL`). FreeBSD does not lock the generator; here every call is serialised by one lock, so concurrent callers draw the serial sequence between them. A null state throws, where FreeBSD faults. That the console's `libSceLibcInternal` uses this FreeBSD code and not another generator is unverified - [tcgetattr, tcsetattr](../../core/libs/prx/libkernel/File/src/Terminal.cpp) (libkernel) - every file and pipe descriptor is taken as not a terminal (`ENOTTY`), and a socket gets `EADDRNOTAVAIL` for `AF_INET` and `EOPNOTSUPP` otherwise, as FreeBSD's `in_control` and `pru_control_notsupp` answer a termios ioctl; what descriptors 0 to 2 of a title are on the console, and whether any of them is a terminal there, is unknown +- [select](../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) - descriptor 0 is never reported readable and descriptors 1 and 2 are always writable, as for a console title with no input; reads from descriptor 0 still go to the host stdin. `/dev/random` and `/dev/urandom` are always readable and writable, as FreeBSD reports a seeded random device. Other descriptors that are not libSceNet sockets throw. Dungeons (PPSA16064) selects on descriptor 0 - [__progname](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export - [environ](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - data export pointing to a fixed empty environment so titles that import it load; it is not the environment libc's `getenv` reads, so it lists none of those variables and `setenv`/`putenv` changes do not appear in it - [seteuid, setegid](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the guest process is assumed to run as root, with real, effective and saved user and group ids 0. As in FreeBSD's `sys_seteuid` and `sys_setegid`, id 0 is then accepted without a privilege check and changes no credential, so 0 is returned; any other id would change the effective id, which is not modelled, and throws. The ids and privileges a title has on a console are unverified