From 92d79996c777932b2ab1a5c8bbdc10da26d4cabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Tue, 6 Oct 2026 10:26:36 +0200 Subject: [PATCH] libSceNet: implement sceNetSendmsg and sceNetRecvmsg Co-Authored-By: Claude --- core/libs/prx/libSceNet/Export.cpp | 100 +++++++++++++++++++++++++++++ core/libs/tests/GuestSceNet.cpp | 93 +++++++++++++++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 3 files changed, 194 insertions(+) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 0aa78dcd6..bd02f7958 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -37,6 +38,7 @@ namespace { // FreeBSD errno numbers as reported through sceNetErrnoLoc (SCE_NET_ERROR_* is 0x80410100 + errno). constexpr int NET_ENOENT = 2; constexpr int NET_EBADF = 9; +constexpr int NET_EFAULT = 14; constexpr int NET_EINVAL = 22; constexpr int NET_ENOSPC = 28; constexpr int NET_EAGAIN = 35; @@ -62,6 +64,9 @@ constexpr int NET_SOL_SOCKET = 0xFFFF; constexpr int NET_SO_SNDTIMEO = 0x1005; constexpr int NET_SO_RCVTIMEO = 0x1006; constexpr int NET_SO_NBIO = 0x1200; +constexpr int NET_MSG_PEEK = 0x2; +constexpr int NET_MSG_TRUNC = 0x10; +constexpr int NET_UIO_MAXIOV = 1024; #ifdef _WIN32 using NativeSocket = SOCKET; @@ -247,6 +252,37 @@ std::uint64_t swap64(std::uint64_t v) { ((v >> 8) & 0xFF000000u) | ((v >> 24) & 0xFF0000u) | ((v >> 40) & 0xFF00u) | (v >> 56); } +struct NetIovec { + void* base; + std::uint64_t length; +}; + +struct NetMsghdr { + void* name; + std::uint32_t name_length; + NetIovec* iov; + int iov_length; + void* control; + std::uint32_t control_length; + int flags; +}; +static_assert(sizeof(NetMsghdr) == 48 && offsetof(NetMsghdr, iov) == 16 && offsetof(NetMsghdr, control) == 32 && + offsetof(NetMsghdr, flags) == 44); + +std::int64_t message_length(const NetMsghdr* message) { + if (!message) return fail(NET_EFAULT); + if (message->iov_length < 0 || message->iov_length > NET_UIO_MAXIOV) return fail(NET_EMSGSIZE); + if (message->iov_length && !message->iov) return fail(NET_EFAULT); + std::uint64_t total = 0; + for (int i = 0; i < message->iov_length; ++i) { + const auto& entry = message->iov[i]; + if (!entry.base && entry.length) return fail(NET_EFAULT); + if (entry.length > INT_MAX - total) return fail(NET_EINVAL); + total += entry.length; + } + return static_cast(total); +} + } // namespace extern "C" { @@ -525,6 +561,70 @@ int64_t APS5_VABI sceNetSendto(int s, const void* buf, size_t len, int flags, co return result >= 0 ? result : fail(native_error()); } +int64_t APS5_VABI sceNetSendmsg(int s, const NetMsghdr* msg, int flags) { + const auto total = message_length(msg); + if (total < 0) return total; + if (msg->control && msg->control_length) throw std::runtime_error("sceNetSendmsg: control data is not supported"); + std::vector buffer; + try { + buffer.resize(static_cast(total)); + } catch (const std::bad_alloc&) { + return fail(55); + } + std::size_t offset = 0; + for (int i = 0; i < msg->iov_length; ++i) { + if (msg->iov[i].length) std::memcpy(buffer.data() + offset, msg->iov[i].base, msg->iov[i].length); + offset += msg->iov[i].length; + } + return sceNetSendto(s, buffer.data(), buffer.size(), flags, msg->name, msg->name ? msg->name_length : 0u); +} + +int64_t APS5_VABI sceNetRecvmsg(int s, NetMsghdr* msg, int flags) { + const auto total = message_length(msg); + if (total < 0) return total; + if (flags != 0 && flags != NET_MSG_PEEK) return fail(NET_EOPNOTSUPP); + Sock socket; + { + std::lock_guard lk(g_mutex); + auto it = g_socks.find(s); + if (it == g_socks.end()) return fail(NET_EBADF); + socket = it->second; + } + const bool datagram = socket.type != NET_SOCK_STREAM; + std::vector buffer; + try { + buffer.resize(static_cast(total) + (datagram ? 1u : 0u)); + } catch (const std::bad_alloc&) { + return fail(55); + } + sockaddr_storage peer{}; + NativeLength peer_length = sizeof(peer); + std::int64_t received = ::recvfrom(socket.native->value, buffer.data(), static_cast(buffer.size()), + flags == NET_MSG_PEEK ? MSG_PEEK : 0, reinterpret_cast(&peer), &peer_length); + if (received < 0) { +#ifdef _WIN32 + if (!datagram || WSAGetLastError() != WSAEMSGSIZE) return fail(native_error()); + received = static_cast(buffer.size()); +#else + return fail(native_error()); +#endif + } + msg->flags = 0; + if (received > total) { + received = total; + msg->flags = NET_MSG_TRUNC; + } + std::size_t offset = 0; + for (int i = 0; i < msg->iov_length && offset < static_cast(received); ++i) { + const auto count = std::min(msg->iov[i].length, static_cast(received) - offset); + std::memcpy(msg->iov[i].base, buffer.data() + offset, count); + offset += count; + } + if (msg->name && !native_to_guest_address(peer, msg->name, &msg->name_length)) msg->name_length = 0; + msg->control_length = 0; + return received; +} + int APS5_VABI sceNetShutdown(int s, int how) { if (how < 0 || how > 2) return fail(NET_EINVAL); Sock socket; diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index fe213c179..4bbb122ce 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include extern "C" { @@ -36,10 +37,91 @@ const char* APS5_VABI sceNetInetNtop(int, const void*, char*, std::uint32_t); int* APS5_VABI sceNetErrnoLoc(void); } +struct NetIovec { + void* base; + std::uint64_t length; +}; + +struct NetMsghdr { + void* name; + std::uint32_t name_length; + NetIovec* iov; + int iov_length; + void* control; + std::uint32_t control_length; + int flags; +}; + +extern "C" { +std::int64_t APS5_VABI sceNetSendmsg(int, const NetMsghdr*, int); +std::int64_t APS5_VABI sceNetRecvmsg(int, NetMsghdr*, int); +} + static void Require(bool condition) { if (!condition) std::abort(); } +static bool Failed(std::int64_t result, int error) { + return result == static_cast(0x80410100u | static_cast(error)) && *sceNetErrnoLoc() == error; +} + +static void CheckMessages(int receiver, int sender, const std::array& address) { + char head[] = "scatter "; + char tail[] = "gather"; + NetIovec out[] = {{head, 8}, {nullptr, 0}, {tail, 6}}; + std::array destination = address; + NetMsghdr send{destination.data(), 16, out, 3, nullptr, 0, 0}; + Require(sceNetSendmsg(sender, &send, 0) == 14); + + char first[5]{}; + char second[20]{}; + NetIovec in[] = {{first, sizeof(first)}, {second, sizeof(second)}}; + std::array source{}; + char control[16]{}; + NetMsghdr receive{source.data(), static_cast(source.size()), in, 2, control, sizeof(control), -1}; + Require(sceNetRecvmsg(receiver, &receive, 2) == 14); + Require(std::memcmp(first, "scatt", 5) == 0 && std::memcmp(second, "er gather", 9) == 0); + Require(receive.flags == 0 && receive.control_length == 0 && receive.name_length == 16); + Require(source[0] == 16 && source[1] == 2 && source[4] == 127 && source[7] == 1); + std::memset(second, 0, sizeof(second)); + receive.iov_length = 1; + receive.flags = -1; + Require(sceNetRecvmsg(receiver, &receive, 0) == 5); + Require(receive.flags == 0x10 && std::memcmp(first, "scatt", 5) == 0 && second[0] == 0); + + NetMsghdr bad = send; + Require(Failed(sceNetSendmsg(sender, nullptr, 0), 14)); + Require(Failed(sceNetRecvmsg(receiver, nullptr, 0), 14)); + bad.iov_length = -1; + Require(Failed(sceNetSendmsg(sender, &bad, 0), 40)); + bad.iov_length = 1025; + Require(Failed(sceNetRecvmsg(receiver, &bad, 0), 40)); + bad.iov_length = 1; + bad.iov = nullptr; + Require(Failed(sceNetSendmsg(sender, &bad, 0), 14)); + NetIovec hole[] = {{nullptr, 1}}; + bad.iov = hole; + Require(Failed(sceNetRecvmsg(receiver, &bad, 0), 14)); + NetIovec huge[] = {{head, 0x7fffffff}, {tail, 1}}; + bad.iov = huge; + bad.iov_length = 2; + Require(Failed(sceNetSendmsg(sender, &bad, 0), 22)); + Require(Failed(sceNetRecvmsg(receiver, &receive, 1), 45)); + Require(Failed(sceNetSendmsg(sender, &send, 1), 45)); + Require(Failed(sceNetRecvmsg(12345, &receive, 0), 9)); + Require(Failed(sceNetSendmsg(12345, &send, 0), 9)); + NetMsghdr with_control = send; + with_control.control = control; + with_control.control_length = sizeof(control); + bool threw = false; + try { + sceNetSendmsg(sender, &with_control, 0); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw); +} + static void CheckAddressText(int family, const char* text) { std::array address{}; Require(sceNetInetPton(family, text, address.data()) == 1); @@ -103,6 +185,16 @@ int main() { Require(ready_count == 1 && (ready.events & 1) && ready.ident == static_cast(accepted)); Require(sceNetRecv(accepted, response, sizeof(response), 0) == sizeof(response)); Require(std::strcmp(request, response) == 0); + char reply[] = "stream reply"; + NetIovec reply_out[] = {{reply, 7}, {reply + 7, sizeof(reply) - 7}}; + NetMsghdr reply_send{nullptr, 0, reply_out, 2, nullptr, 0, 0}; + Require(sceNetSendmsg(accepted, &reply_send, 0) == sizeof(reply)); + char reply_result[sizeof(reply)]{}; + NetIovec reply_in[] = {{reply_result, sizeof(reply_result)}}; + std::array reply_name{}; + NetMsghdr reply_receive{reply_name.data(), 16, reply_in, 1, nullptr, 0, -1}; + Require(sceNetRecvmsg(client, &reply_receive, 0) == sizeof(reply)); + Require(std::strcmp(reply, reply_result) == 0 && reply_receive.flags == 0 && reply_receive.name_length == 0); Require(sceNetEpollDestroy(epoll) == 0); Require(sceNetSocketClose(accepted) == 0); bool send_failed = false; @@ -130,6 +222,7 @@ int main() { Require(sceNetRecvfrom(udp_receiver, datagram_result, sizeof(datagram_result), 0, source.data(), &address_size) == sizeof(datagram_result)); Require(std::strcmp(datagram, datagram_result) == 0 && source[1] == 2); + 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 645d976a4..b467e3ce3 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -135,6 +135,7 @@ Throughout the project, every function at every stage either **does exactly what ### Functional +- [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 - [Additional content](../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out - [libSceUlt queues](../../core/libs/prx/libSceUlt/Export.cpp) use preallocated host storage instead of the supplied guest work area; its existing size formula and the 512-byte object layouts remain unverified. Waiting-queue pool thread limits and waiter priority ordering are not implemented. Finalization wakes blocked queue callers with `ULT_ERROR_STATE`. - [MIMG `tfe` and `lwe`](../../core/shader/recompiler/RdnaDecoder/src/RdnaImageOpDecoder.cpp) never write the status VGPR after the data. On hardware it is written only when a texel fetch hits an unmapped page of a partially resident texture, or, for `lwe` on sample/gather, when the LOD is below the T# `MIN_LOD_WARN`; texture memory is always resident here, and `lwe` on sample, gather and `image_get_lod` throws.