From b19a74f5bf6c3772c46b6fd5eba9caad5117b56d Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+yunusshaikh7@users.noreply.github.com> Date: Mon, 12 Oct 2026 14:54:51 +0000 Subject: [PATCH 1/3] fix(libSceNet): sceNetEpollWait takes 2 as the OUT event sceNetEpollWait used Linux's EPOLLOUT bit (4) for writability. The SCE value is 2 (shadPS4 net_types.h ORBIS_NET_EPOLLOUT, KytyPS5 network.cpp EPOLL_OUT, SharpProspero Socket.cs EpollOut); IN (1), ERR (8) and HUP (0x10) already match. A socket registered for OUT was never polled for writability, so the wait ran out its timeout or blocked forever, and an IN|OUT registration never reported OUT. --- core/libs/prx/libSceNet/Export.cpp | 8 ++++---- core/libs/tests/GuestSceNet.cpp | 33 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 79214e8e4..e3d4b17f5 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -1071,14 +1071,14 @@ int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int for (std::size_t index = 0; index < descriptors.size(); ++index) { descriptors[index].fd = native_sockets[index]->value; if (registered_events[index].events & 1) descriptors[index].events |= POLLRDNORM; - if (registered_events[index].events & 4) descriptors[index].events |= POLLWRNORM; + if (registered_events[index].events & 2) descriptors[index].events |= POLLWRNORM; } #else std::vector descriptors(registered_events.size()); for (std::size_t index = 0; index < descriptors.size(); ++index) { descriptors[index].fd = native_sockets[index]->value; if (registered_events[index].events & 1) descriptors[index].events |= POLLIN; - if (registered_events[index].events & 4) descriptors[index].events |= POLLOUT; + if (registered_events[index].events & 2) descriptors[index].events |= POLLOUT; } #endif const auto now = std::chrono::steady_clock::now(); @@ -1108,10 +1108,10 @@ int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int std::uint32_t guest_events = 0; #ifdef _WIN32 if (native_events & (POLLRDNORM | POLLPRI)) guest_events |= 1; - if (native_events & POLLWRNORM) guest_events |= 4; + if (native_events & POLLWRNORM) guest_events |= 2; #else if (native_events & (POLLIN | POLLPRI)) guest_events |= 1; - if (native_events & POLLOUT) guest_events |= 4; + if (native_events & POLLOUT) guest_events |= 2; #endif if (native_events & POLLERR) guest_events |= 8; if (native_events & POLLHUP) guest_events |= 16; diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 993e40205..6eb3854ea 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -149,6 +149,38 @@ static void CheckEmptyEpoll() { } } +static void CheckEpollWritable() { + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + const int receiver = sceNetSocket("epoll-receiver", 2, 2, 17); + const int sender = sceNetSocket("epoll-sender", 2, 2, 17); + Require(receiver >= 0 && sender >= 0); + Require(sceNetBind_nid_postfix(receiver, address.data(), address.size()) == 0); + std::uint32_t size = address.size(); + Require(sceNetGetsockname(receiver, address.data(), &size) == 0 && size == address.size()); + const int epoll = sceNetEpollCreate("epoll-writable", 0); + Require(epoll >= 0); + NetEpollEvent registration{}; + registration.events = 2; + registration.ident = static_cast(sender); + registration.data.u32 = 41; + Require(sceNetEpollControl(epoll, 1, sender, ®istration) == 0); + NetEpollEvent ready{}; + Require(sceNetEpollWait(epoll, &ready, 1, 1000000) == 1); + Require(ready.events == 2 && ready.ident == static_cast(sender) && ready.data.u32 == 41); + Require(sceNetEpollControl(epoll, 3, sender, nullptr) == 0); + const char payload[] = "epoll"; + char peeked[sizeof(payload)]{}; + Require(sceNetSendto(sender, payload, sizeof(payload), 0, address.data(), address.size()) == sizeof(payload)); + Require(sceNetRecv(receiver, peeked, sizeof(peeked), 2) == sizeof(payload)); + registration.events = 3; + registration.ident = static_cast(receiver); + Require(sceNetEpollControl(epoll, 1, receiver, ®istration) == 0); + Require(sceNetEpollWait(epoll, &ready, 1, 1000000) == 1); + Require(ready.events == 3 && ready.ident == static_cast(receiver) && ready.data.u32 == 41); + Require(sceNetEpollDestroy(epoll) == 0); + Require(sceNetSocketClose(sender) == 0 && sceNetSocketClose(receiver) == 0); +} + static void CheckReceiveTimeout(int receiver, int sender, const std::array& address) { const int timeout = 50000; Require(sceNetSetsockopt(receiver, 0xffff, 0x1006, &timeout, sizeof(timeout)) == 0); @@ -353,6 +385,7 @@ int main() { Require(sceNetInit_nid_postfix() == 0); CheckPoolStats(); CheckEmptyEpoll(); + CheckEpollWritable(); CheckAddressText(2, "127.0.0.1"); CheckAddressText(2, "255.255.255.255"); CheckAddressText(28, "::1"); From d0cef9afdb6cfdda09f62568a3d7b4c8a4ca1308 Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+yunusshaikh7@users.noreply.github.com> Date: Mon, 12 Oct 2026 11:57:27 -0700 Subject: [PATCH 2/3] test(libSceNet): define and run CheckEpollWritable before CheckPoolStats #4673 adds a test after CheckEmptyEpoll and its call after CheckEmptyEpoll(), so placing the new check there made the two pull requests conflict. --- core/libs/tests/GuestSceNet.cpp | 66 ++++++++++++++++----------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 195f1597a..c18b2b917 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -158,6 +158,38 @@ static void CheckStreamSender(int family) { for (const int descriptor : {client, accepted, listener}) Require(sceNetSocketClose(descriptor) == 0); } +static void CheckEpollWritable() { + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + const int receiver = sceNetSocket("epoll-receiver", 2, 2, 17); + const int sender = sceNetSocket("epoll-sender", 2, 2, 17); + Require(receiver >= 0 && sender >= 0); + Require(sceNetBind_nid_postfix(receiver, address.data(), address.size()) == 0); + std::uint32_t size = address.size(); + Require(sceNetGetsockname(receiver, address.data(), &size) == 0 && size == address.size()); + const int epoll = sceNetEpollCreate("epoll-writable", 0); + Require(epoll >= 0); + NetEpollEvent registration{}; + registration.events = 2; + registration.ident = static_cast(sender); + registration.data.u32 = 41; + Require(sceNetEpollControl(epoll, 1, sender, ®istration) == 0); + NetEpollEvent ready{}; + Require(sceNetEpollWait(epoll, &ready, 1, 1000000) == 1); + Require(ready.events == 2 && ready.ident == static_cast(sender) && ready.data.u32 == 41); + Require(sceNetEpollControl(epoll, 3, sender, nullptr) == 0); + const char payload[] = "epoll"; + char peeked[sizeof(payload)]{}; + Require(sceNetSendto(sender, payload, sizeof(payload), 0, address.data(), address.size()) == sizeof(payload)); + Require(sceNetRecv(receiver, peeked, sizeof(peeked), 2) == sizeof(payload)); + registration.events = 3; + registration.ident = static_cast(receiver); + Require(sceNetEpollControl(epoll, 1, receiver, ®istration) == 0); + Require(sceNetEpollWait(epoll, &ready, 1, 1000000) == 1); + Require(ready.events == 3 && ready.ident == static_cast(receiver) && ready.data.u32 == 41); + Require(sceNetEpollDestroy(epoll) == 0); + Require(sceNetSocketClose(sender) == 0 && sceNetSocketClose(receiver) == 0); +} + static void CheckPoolStats() { const int pool = sceNetPoolCreate("stats", 0x4000, 0); Require(pool > 0); @@ -207,38 +239,6 @@ static void CheckEmptyEpoll() { } } -static void CheckEpollWritable() { - std::array address{16, 2, 0, 0, 127, 0, 0, 1}; - const int receiver = sceNetSocket("epoll-receiver", 2, 2, 17); - const int sender = sceNetSocket("epoll-sender", 2, 2, 17); - Require(receiver >= 0 && sender >= 0); - Require(sceNetBind_nid_postfix(receiver, address.data(), address.size()) == 0); - std::uint32_t size = address.size(); - Require(sceNetGetsockname(receiver, address.data(), &size) == 0 && size == address.size()); - const int epoll = sceNetEpollCreate("epoll-writable", 0); - Require(epoll >= 0); - NetEpollEvent registration{}; - registration.events = 2; - registration.ident = static_cast(sender); - registration.data.u32 = 41; - Require(sceNetEpollControl(epoll, 1, sender, ®istration) == 0); - NetEpollEvent ready{}; - Require(sceNetEpollWait(epoll, &ready, 1, 1000000) == 1); - Require(ready.events == 2 && ready.ident == static_cast(sender) && ready.data.u32 == 41); - Require(sceNetEpollControl(epoll, 3, sender, nullptr) == 0); - const char payload[] = "epoll"; - char peeked[sizeof(payload)]{}; - Require(sceNetSendto(sender, payload, sizeof(payload), 0, address.data(), address.size()) == sizeof(payload)); - Require(sceNetRecv(receiver, peeked, sizeof(peeked), 2) == sizeof(payload)); - registration.events = 3; - registration.ident = static_cast(receiver); - Require(sceNetEpollControl(epoll, 1, receiver, ®istration) == 0); - Require(sceNetEpollWait(epoll, &ready, 1, 1000000) == 1); - Require(ready.events == 3 && ready.ident == static_cast(receiver) && ready.data.u32 == 41); - Require(sceNetEpollDestroy(epoll) == 0); - Require(sceNetSocketClose(sender) == 0 && sceNetSocketClose(receiver) == 0); -} - static void CheckReceiveTimeout(int receiver, int sender, const std::array& address) { const int timeout = 50000; Require(sceNetSetsockopt(receiver, 0xffff, 0x1006, &timeout, sizeof(timeout)) == 0); @@ -503,9 +503,9 @@ int main() { Require(in6addr_loopback_nid_postfix[i] == (i == 15 ? 1 : 0)); } Require(sceNetInit_nid_postfix() == 0); + CheckEpollWritable(); CheckPoolStats(); CheckEmptyEpoll(); - CheckEpollWritable(); CheckAddressText(2, "127.0.0.1"); CheckAddressText(2, "255.255.255.255"); CheckAddressText(28, "::1"); From a7e320d1f77a775eb3d514679a9493ec73881124 Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:09:09 -0700 Subject: [PATCH 3/3] test(libSceNet): define CheckEpollWritable after CheckMessages #4785 adds its netstat checks before CheckPoolStats, so defining CheckEpollWritable there made the two pull requests conflict. --- core/libs/tests/GuestSceNet.cpp | 64 ++++++++++++++++----------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index c18b2b917..67be9c47d 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -158,38 +158,6 @@ static void CheckStreamSender(int family) { for (const int descriptor : {client, accepted, listener}) Require(sceNetSocketClose(descriptor) == 0); } -static void CheckEpollWritable() { - std::array address{16, 2, 0, 0, 127, 0, 0, 1}; - const int receiver = sceNetSocket("epoll-receiver", 2, 2, 17); - const int sender = sceNetSocket("epoll-sender", 2, 2, 17); - Require(receiver >= 0 && sender >= 0); - Require(sceNetBind_nid_postfix(receiver, address.data(), address.size()) == 0); - std::uint32_t size = address.size(); - Require(sceNetGetsockname(receiver, address.data(), &size) == 0 && size == address.size()); - const int epoll = sceNetEpollCreate("epoll-writable", 0); - Require(epoll >= 0); - NetEpollEvent registration{}; - registration.events = 2; - registration.ident = static_cast(sender); - registration.data.u32 = 41; - Require(sceNetEpollControl(epoll, 1, sender, ®istration) == 0); - NetEpollEvent ready{}; - Require(sceNetEpollWait(epoll, &ready, 1, 1000000) == 1); - Require(ready.events == 2 && ready.ident == static_cast(sender) && ready.data.u32 == 41); - Require(sceNetEpollControl(epoll, 3, sender, nullptr) == 0); - const char payload[] = "epoll"; - char peeked[sizeof(payload)]{}; - Require(sceNetSendto(sender, payload, sizeof(payload), 0, address.data(), address.size()) == sizeof(payload)); - Require(sceNetRecv(receiver, peeked, sizeof(peeked), 2) == sizeof(payload)); - registration.events = 3; - registration.ident = static_cast(receiver); - Require(sceNetEpollControl(epoll, 1, receiver, ®istration) == 0); - Require(sceNetEpollWait(epoll, &ready, 1, 1000000) == 1); - Require(ready.events == 3 && ready.ident == static_cast(receiver) && ready.data.u32 == 41); - Require(sceNetEpollDestroy(epoll) == 0); - Require(sceNetSocketClose(sender) == 0 && sceNetSocketClose(receiver) == 0); -} - static void CheckPoolStats() { const int pool = sceNetPoolCreate("stats", 0x4000, 0); Require(pool > 0); @@ -314,6 +282,38 @@ static void CheckMessages(int receiver, int sender, const std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + const int receiver = sceNetSocket("epoll-receiver", 2, 2, 17); + const int sender = sceNetSocket("epoll-sender", 2, 2, 17); + Require(receiver >= 0 && sender >= 0); + Require(sceNetBind_nid_postfix(receiver, address.data(), address.size()) == 0); + std::uint32_t size = address.size(); + Require(sceNetGetsockname(receiver, address.data(), &size) == 0 && size == address.size()); + const int epoll = sceNetEpollCreate("epoll-writable", 0); + Require(epoll >= 0); + NetEpollEvent registration{}; + registration.events = 2; + registration.ident = static_cast(sender); + registration.data.u32 = 41; + Require(sceNetEpollControl(epoll, 1, sender, ®istration) == 0); + NetEpollEvent ready{}; + Require(sceNetEpollWait(epoll, &ready, 1, 1000000) == 1); + Require(ready.events == 2 && ready.ident == static_cast(sender) && ready.data.u32 == 41); + Require(sceNetEpollControl(epoll, 3, sender, nullptr) == 0); + const char payload[] = "epoll"; + char peeked[sizeof(payload)]{}; + Require(sceNetSendto(sender, payload, sizeof(payload), 0, address.data(), address.size()) == sizeof(payload)); + Require(sceNetRecv(receiver, peeked, sizeof(peeked), 2) == sizeof(payload)); + registration.events = 3; + registration.ident = static_cast(receiver); + Require(sceNetEpollControl(epoll, 1, receiver, ®istration) == 0); + Require(sceNetEpollWait(epoll, &ready, 1, 1000000) == 1); + Require(ready.events == 3 && ready.ident == static_cast(receiver) && ready.data.u32 == 41); + Require(sceNetEpollDestroy(epoll) == 0); + Require(sceNetSocketClose(sender) == 0 && sceNetSocketClose(receiver) == 0); +} + static void RequireTimeout(int socket, int option, int expected) { int microseconds = -1; std::uint32_t size = sizeof(microseconds);