fix(libkernel): setsockopt sets options of libSceNet sockets

A title creating a socket with sceNetSocket sets its options with the BSD
setsockopt, which looked the descriptor up only among libkernel's own
sockets and returned EBADF. libSceNet now registers an option setter with
libkernel, as it does for close and select, and accepts SO_REUSEADDR,
SO_BROADCAST, SO_SNDBUF and SO_RCVBUF like libkernel's own sockets.

PPSA01289 (Sackboy: A Big Adventure) sets SO_BROADCAST on its UDP P2P
socket this way in UIpNetDriver::InitBase. The failure made every listen
fail, and the engine travelled back to its default map with ?closed,
reloading it about every two seconds behind the loading screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jdmagic 2026-10-10 00:22:26 -04:00
parent 12b18af550
commit d5805fb3e4
6 changed files with 45 additions and 1 deletions

View File

@ -74,6 +74,10 @@ 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_SO_REUSEADDR = 0x4;
constexpr int NET_SO_BROADCAST = 0x20;
constexpr int NET_SO_SNDBUF = 0x1001;
constexpr int NET_SO_RCVBUF = 0x1002;
constexpr int NET_MSG_PEEK = 0x2;
constexpr int NET_MSG_TRUNC = 0x10;
constexpr int NET_UIO_MAXIOV = 1024;
@ -452,6 +456,19 @@ bool CloseSocketForKernel(int descriptor, int* error) {
const bool g_socketCloserRegistered = (KernelSetSocketCloser_nid_no_patch(&CloseSocketForKernel), true);
extern "C" int APS5_VABI sceNetSetsockopt(int s, int level, int optname, const void* optval, uint32_t optlen);
bool SetSocketOptionForKernel(int descriptor, int level, int option, const void* value, std::uint32_t length, int* error) {
{
std::lock_guard lk(g_mutex);
if (g_socks.count(descriptor) == 0) return false;
}
*error = sceNetSetsockopt(descriptor, level, option, value, length) == 0 ? 0 : *errno_slot();
return true;
}
const bool g_socketOptionSetterRegistered = (KernelSetSocketOptionSetter_nid_no_patch(&SetSocketOptionForKernel), true);
int APS5_VABI sceNetInit_nid_postfix(void) {
std::lock_guard lk(g_mutex);
g_net_inited = true;
@ -844,6 +861,10 @@ int APS5_VABI sceNetSetsockopt(int s, int level, int optname, const void* optval
if (::setsockopt(socket.native->value, SOL_SOCKET, native_option, &timeout, sizeof(timeout)) != 0)
return fail(native_error());
#endif
} else if (optname == NET_SO_REUSEADDR || optname == NET_SO_BROADCAST || optname == NET_SO_SNDBUF || optname == NET_SO_RCVBUF) {
const int native_option = optname == NET_SO_REUSEADDR ? SO_REUSEADDR : optname == NET_SO_BROADCAST ? SO_BROADCAST : optname == NET_SO_SNDBUF ? SO_SNDBUF : SO_RCVBUF;
if (::setsockopt(socket.native->value, SOL_SOCKET, native_option, reinterpret_cast<const char*>(&value), sizeof(value)) != 0)
return fail(native_error());
} else {
return fail(NET_EOPNOTSUPP);
}

View File

@ -17,8 +17,11 @@ struct Entry {
using Poller = int (*)(Entry* entries, int count, int timeoutMilliseconds);
using Closer = bool (*)(int descriptor, int* error);
using OptionSetter = bool (*)(int descriptor, int level, int option, const void* value, std::uint32_t length, int* error);
}
extern "C" void KernelSetSocketPoller_nid_no_patch(KernelSocketPoll::Poller poller);
extern "C" void KernelSetSocketCloser_nid_no_patch(KernelSocketPoll::Closer closer);
extern "C" bool KernelCloseSocket_nid_no_patch(int descriptor, int* error);
extern "C" void KernelSetSocketOptionSetter_nid_no_patch(KernelSocketPoll::OptionSetter setter);
extern "C" bool KernelSetSocketOption_nid_no_patch(int descriptor, int level, int option, const void* value, std::uint32_t length, int* error);

View File

@ -32,6 +32,7 @@ struct GuestTimeval {
std::atomic<KernelSocketPoll::Poller> g_socketPoller{nullptr};
std::atomic<KernelSocketPoll::Closer> g_socketCloser{nullptr};
std::atomic<KernelSocketPoll::OptionSetter> g_socketOptionSetter{nullptr};
bool DescriptorSet(const void* set, int descriptor) {
return set != nullptr && ((static_cast<const std::uint64_t*>(set)[descriptor / 64] >> (descriptor % 64)) & 1u) != 0;
@ -115,6 +116,15 @@ bool KernelCloseSocket_nid_no_patch(int descriptor, int* error) {
return closer != nullptr && closer(descriptor, error);
}
void KernelSetSocketOptionSetter_nid_no_patch(KernelSocketPoll::OptionSetter setter) {
g_socketOptionSetter.store(setter);
}
bool KernelSetSocketOption_nid_no_patch(int descriptor, int level, int option, const void* value, std::uint32_t length, int* error) {
const auto setter = g_socketOptionSetter.load();
return setter != nullptr && setter(descriptor, level, option, value, length, error);
}
int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* exceptfds, const void* timeout) {
const auto* limit = static_cast<const GuestTimeval*>(timeout);
if (nfds < 0 || nfds > SelectDescriptorLimit || (limit != nullptr && (limit->seconds < 0 || limit->microseconds < 0 || limit->microseconds >= 1000000))) {

View File

@ -18,6 +18,7 @@
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include "prx/libkernel/Equeue/Equeue.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include "prx/libkernel/Socket/include/SocketPoll.hpp"
#include <algorithm>
#include <chrono>
#include <climits>
@ -463,6 +464,10 @@ int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...) {
}
int APS5_VABI setsockopt_nid_postfix(int descriptor, int level, int option,
const void* value, std::uint32_t length) {
int netError = 0;
if (descriptor < GuestSockets::FirstDescriptor && KernelSetSocketOption_nid_no_patch(descriptor, level, option, value, length, &netError)) {
return netError == 0 ? 0 : Fail(netError);
}
const auto socket = Lookup(descriptor);
if (!socket) return -1;
const int nativeOption = Option(option);

View File

@ -56,6 +56,7 @@ 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 setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t);
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);
@ -340,6 +341,10 @@ int main() {
const int closed = sceNetSocket("closed-by-kernel", 2, 2, 17);
Require(closed >= 0);
const int enabled = 1;
Require(setsockopt_nid_postfix(closed, 0xffff, 0x20, &enabled, sizeof(enabled)) == 0);
Require(sceNetSetsockopt(closed, 0xffff, 0x20, &enabled, sizeof(enabled)) == 0);
Require(sceNetSetsockopt(closed, 0xffff, 0x4, &enabled, sizeof(enabled)) == 0);
Require(close_nid_postfix(closed) == 0);
Require(Failed(sceNetSocketClose(closed), 9));

View File

@ -422,7 +422,7 @@ Throughout the project, every function at every stage either **does exactly what
- [Shared memory](../../core/libs/prx/libkernel/File/src/SharedMemory.cpp) (libkernel) on Linux implements anonymous and named objects, bounded descriptor writes and seeks, and `shm_unlink` with effective read/write permission checks from FreeBSD 11. Named paths up to 1023 bytes use `/anyps5-shm-` plus SHA-1; collisions are not detected, and the namespace is shared across processes in the host mount namespace. Named creation applies the guest umask without changing the host mask. Linux procfs and `/dev/shm` are required. Operations use an advisory directory lock; external host operations bypass it, unrelated objects serialize, and descriptor operations require two spare host descriptors and compare device/inode with the initial descriptor identity on both the pinned-object and ordinary-file fallback paths. [Guest mappings](../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) support shared and private mappings of these descriptors through the allocation tracker and pin a duplicate descriptor during validation and mapping; mapping requires one spare host descriptor; file-backed mappings use the write-watch scan fallback because aliases and descriptor writes bypass per-mapping tracking. Named objects and file-backed mappings on Windows remain unsupported. Invalid non-null name pointers, console behaviour and title compatibility remain unverified.
- [sceKernelAddReadEvent and sceKernelAddWriteEvent](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel): a write event reports the socket's send buffer size (`SO_SNDBUF`) in `data`, not the free space left in it, as Windows has no query for that space. Only guest sockets are supported; other descriptors and a low watermark above 1 throw
- [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
- [close](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) closes a [libSceNet](../../core/libs/prx/libSceNet/Export.cpp) socket through a hook libSceNet registers, as `select` polls them. libSceNet numbers its sockets from 32 in its own table, so a CRT file descriptor and a socket can share a number; `close` then closes the socket. On the console both are one kernel descriptor space
- [close](../../core/libs/prx/libkernel/File/src/Stdio.cpp) and [setsockopt](../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) reach a [libSceNet](../../core/libs/prx/libSceNet/Export.cpp) socket through hooks libSceNet registers, as `select` polls them; the other libkernel socket calls do not. libSceNet numbers its sockets from 32 in its own table, so a CRT file descriptor and a socket can share a number; `close` then closes the socket. On the console both are one kernel descriptor space
- [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; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries
- [Content export sessions](../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport): `sceContentExportStart` hands out positive session ids, at most 10 live, which `sceContentExportFinish` closes. The signatures follow [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json) and the limit `MaxExecution` [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Content/ContentExport.cs); the parameter layouts follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/443f022450f6857256e780daa3d5a14029a748f7/src/core/libraries/content_export/content_export.h). The host has no media library, so `sceContentExportFromDataWithThumbnail` throws once the session is checked, before calling either data callback, and no export ever runs. `sceContentExportGetProgress` and `sceContentExportCancel` throw for a live session, as their result with no running export is unknown. An eleventh `sceContentExportStart` and `sceContentExportTerm` with live sessions throw: whether the console answers `EXECUTION_MAX` or `BUSY` there, or ends the sessions, is unverified. Session id values and error precedence are unverified
- [Runtime-loaded modules](../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) that another module needs are initialized at startup with no start arguments, before the module that needs them is loaded; the console is assumed to start them when that module is loaded.