diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 3bfaa1229..798494847 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -439,6 +439,19 @@ int PollSockets(KernelSocketPoll::Entry* entries, int count, int timeoutMillisec const bool g_socketPollerRegistered = (KernelSetSocketPoller_nid_no_patch(&PollSockets), true); +extern "C" int APS5_VABI sceNetSocketClose(int s); + +bool CloseSocketForKernel(int descriptor, int* error) { + { + std::lock_guard lk(g_mutex); + if (g_socks.count(descriptor) == 0) return false; + } + *error = sceNetSocketClose(descriptor) == 0 ? 0 : *errno_slot(); + return true; +} + +const bool g_socketCloserRegistered = (KernelSetSocketCloser_nid_no_patch(&CloseSocketForKernel), true); + int APS5_VABI sceNetInit_nid_postfix(void) { std::lock_guard lk(g_mutex); g_net_inited = true; diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index fbe87450b..3e619ac9a 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -16,6 +16,7 @@ #include "prx/libkernel/File/include/RandomDevice.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/Socket/include/SocketRuntime.hpp" +#include "prx/libkernel/Socket/include/SocketPoll.hpp" #include #include #include @@ -466,6 +467,12 @@ int APS5_VABI chmod_nid_postfix(const char* path, int mode) { int APS5_VABI close_nid_postfix(int d) { if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Close(d); + int socketError = 0; + if (KernelCloseSocket_nid_no_patch(d, &socketError)) { + if (socketError == 0) return 0; + *__error_nid_postfix() = socketError; + return -1; + } File::ForgetRandomDevice(d); #ifdef _WIN32 File::ForgetDirectoryDescriptor(d); diff --git a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp index 71d5f62e7..41455ba03 100644 --- a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp +++ b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp @@ -16,6 +16,9 @@ struct Entry { }; using Poller = int (*)(Entry* entries, int count, int timeoutMilliseconds); +using Closer = bool (*)(int descriptor, 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); diff --git a/core/libs/prx/libkernel/Socket/src/Socket.cpp b/core/libs/prx/libkernel/Socket/src/Socket.cpp index 3f71f4f1c..09baf528f 100644 --- a/core/libs/prx/libkernel/Socket/src/Socket.cpp +++ b/core/libs/prx/libkernel/Socket/src/Socket.cpp @@ -31,6 +31,7 @@ struct GuestTimeval { }; std::atomic g_socketPoller{nullptr}; +std::atomic g_socketCloser{nullptr}; bool DescriptorSet(const void* set, int descriptor) { return set != nullptr && ((static_cast(set)[descriptor / 64] >> (descriptor % 64)) & 1u) != 0; @@ -105,6 +106,15 @@ void KernelSetSocketPoller_nid_no_patch(KernelSocketPoll::Poller poller) { g_socketPoller.store(poller); } +void KernelSetSocketCloser_nid_no_patch(KernelSocketPoll::Closer closer) { + g_socketCloser.store(closer); +} + +bool KernelCloseSocket_nid_no_patch(int descriptor, int* error) { + const auto closer = g_socketCloser.load(); + return closer != nullptr && closer(descriptor, error); +} + int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* exceptfds, const void* timeout) { const auto* limit = static_cast(timeout); if (nfds < 0 || nfds > SelectDescriptorLimit || (limit != nullptr && (limit->seconds < 0 || limit->microseconds < 0 || limit->microseconds >= 1000000))) { diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 1579e6645..e4978db04 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -338,6 +338,11 @@ int main() { CheckLinkLocalAllNodesIpv6(); CheckUnspecifiedIpv6(); + const int closed = sceNetSocket("closed-by-kernel", 2, 2, 17); + Require(closed >= 0); + Require(close_nid_postfix(closed) == 0); + Require(Failed(sceNetSocketClose(closed), 9)); + const int listener = sceNetSocket(nullptr, 2, 1, 6); Require(listener >= 0); std::array address{16, 2, 0, 0, 127, 0, 0, 1}; diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 0fa78fec4..497f97eae 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -422,6 +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 - [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.