#include #include #include #include #include #include #include #include #include #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" // Offline network stack: no link is ever up. Sockets are local bookkeeping objects (create/bind/listen/options // succeed like they do on a console without a connection), anything that needs a peer fails with the same errno a // console with no route gives, and blocking calls return on their timeout, an abort or a close — infinite // ("wait forever") timeouts are serviced in bounded slices so the inert stack can never deadlock a caller. 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_EINVAL = 22; constexpr int NET_EAGAIN = 35; constexpr int NET_ENOTSOCK = 38; constexpr int NET_EOPNOTSUPP = 45; constexpr int NET_EPROTONOSUPPORT = 43; constexpr int NET_EAFNOSUPPORT = 47; constexpr int NET_EADDRINUSE = 48; constexpr int NET_ENETUNREACH = 51; constexpr int NET_ECONNABORTED = 53; constexpr int NET_ENOTCONN = 57; constexpr int NET_ETIMEDOUT = 60; constexpr int NET_ECONNREFUSED = 61; constexpr int NET_ERROR_RESOLVER_ENODNS = static_cast(0x804101E1u); constexpr int NET_AF_INET = 2; constexpr int NET_AF_INET6 = 28; constexpr int NET_SOCK_STREAM = 1; constexpr int NET_SOCK_DGRAM = 2; constexpr int NET_SOCK_RAW = 3; constexpr int NET_SOL_SOCKET = 0xFFFF; constexpr int NET_SO_SNDTIMEO = 0x1005; constexpr int NET_SO_RCVTIMEO = 0x1006; constexpr int NET_SO_NBIO = 0x1200; struct Sock { int family = 0; int type = 0; bool nonblock = false; bool bound = false; bool listening = false; bool aborted = false; std::uint16_t port = 0; // network byte order std::uint32_t addr = 0; // network byte order int rcv_timeout_us = 0; int snd_timeout_us = 0; }; std::mutex g_mutex; std::condition_variable g_cv; std::map g_socks; std::set g_epolls; std::set g_pools; std::set g_resolvers; int g_next_sock = 32; int g_next_epoll = 0x4000; int g_next_pool = 1; int g_next_resolver = 0x100; std::uint16_t g_next_port = 49152; bool g_net_inited = false; int* errno_slot() { static thread_local int e = 0; return &e; } int fail(int err) { *errno_slot() = err; return -1; } void log_soft(const char* func, const char* what) { static std::mutex mtx; static std::map hits; std::lock_guard lk(mtx); if (++hits[func] > 3) { return; } std::fprintf(stderr, "[SOFT-NET] %s -> %s\n", func, what); std::fflush(stderr); } std::uint16_t swap16(std::uint16_t v) { return static_cast((v << 8) | (v >> 8)); } std::uint32_t swap32(std::uint32_t v) { return (v << 24) | ((v & 0xFF00u) << 8) | ((v >> 8) & 0xFF00u) | (v >> 24); } // Waits on the socket until the timeout expires or it is closed/aborted. // timeout_us <= 0 nominally means "wait forever", but on an inert stack nothing can ever arrive, so such a // wait is bounded to INFINITE_WAIT_SLICE_US per iteration and then reports EAGAIN: the caller keeps correct // blocking-loop semantics without the process being able to deadlock on the network. // Returns 0 on timeout, -1 with errno set when the socket went away or was aborted. int block_on(std::unique_lock& lk, int fd, int timeout_us) { constexpr int INFINITE_WAIT_SLICE_US = 100000; // 100 ms const auto deadline = std::chrono::steady_clock::now() + std::chrono::microseconds(timeout_us > 0 ? timeout_us : INFINITE_WAIT_SLICE_US); for (;;) { auto it = g_socks.find(fd); if (it == g_socks.end()) { return fail(NET_EBADF); } if (it->second.aborted) { it->second.aborted = false; return fail(NET_ECONNABORTED); } if (g_cv.wait_until(lk, deadline) == std::cv_status::timeout) { return fail(NET_EAGAIN); } } } } // namespace extern "C" { int* APS5_VABI sceNetErrnoLoc(void) { return errno_slot(); } int APS5_VABI sceNetInit_nid_postfix(void) { std::lock_guard lk(g_mutex); g_net_inited = true; return 0; } int APS5_VABI sceNetTerm(void) { std::lock_guard lk(g_mutex); g_net_inited = false; return 0; } int APS5_VABI sceNetPoolCreate(const char* name, int size, int flags) { (void)name; (void)flags; if (size <= 0) { return fail(NET_EINVAL); } std::lock_guard lk(g_mutex); const int id = g_next_pool++; g_pools.insert(id); return id; } int APS5_VABI sceNetPoolDestroy(int memid) { std::lock_guard lk(g_mutex); return g_pools.erase(memid) != 0 ? 0 : fail(NET_EBADF); } int APS5_VABI sceNetSocket(const char* name, int family, int type, int protocol) { (void)name; (void)protocol; if (family != NET_AF_INET && family != NET_AF_INET6) { return fail(NET_EAFNOSUPPORT); } if (type != NET_SOCK_STREAM && type != NET_SOCK_DGRAM && type != NET_SOCK_RAW) { return fail(NET_EPROTONOSUPPORT); } std::lock_guard lk(g_mutex); const int fd = g_next_sock++; Sock s; s.family = family; s.type = type; g_socks[fd] = s; return fd; } int APS5_VABI sceNetSocketClose(int s) { std::lock_guard lk(g_mutex); if (g_socks.erase(s) == 0) { return fail(NET_EBADF); } g_cv.notify_all(); return 0; } int APS5_VABI sceNetSocketAbort(int s, int flags) { (void)flags; std::lock_guard lk(g_mutex); auto it = g_socks.find(s); if (it == g_socks.end()) { return fail(NET_EBADF); } it->second.aborted = true; g_cv.notify_all(); return 0; } int APS5_VABI sceNetBind_nid_postfix(int s, const void* addr, uint32_t addrlen) { std::lock_guard lk(g_mutex); auto it = g_socks.find(s); if (it == g_socks.end()) { return fail(NET_EBADF); } if (addr == nullptr || addrlen < 8) { return fail(NET_EINVAL); } if (it->second.bound) { return fail(NET_EINVAL); } // sockaddr_in: len, family, port (BE), addr (BE) std::uint16_t port = 0; std::uint32_t ip = 0; std::memcpy(&port, static_cast(addr) + 2, 2); std::memcpy(&ip, static_cast(addr) + 4, 4); if (port == 0) { port = swap16(g_next_port++); } else { for (const auto& kv : g_socks) { if (kv.first != s && kv.second.bound && kv.second.port == port && kv.second.type == it->second.type) { return fail(NET_EADDRINUSE); } } } it->second.bound = true; it->second.port = port; it->second.addr = ip; return 0; } int APS5_VABI sceNetListen(int s, int backlog) { (void)backlog; std::lock_guard lk(g_mutex); auto it = g_socks.find(s); if (it == g_socks.end()) { return fail(NET_EBADF); } if (it->second.type != NET_SOCK_STREAM) { return fail(NET_EOPNOTSUPP); } if (!it->second.bound) { it->second.bound = true; it->second.port = swap16(g_next_port++); } it->second.listening = true; return 0; } int APS5_VABI sceNetAccept(int s, void* addr, uint32_t* addrlen) { (void)addr; (void)addrlen; std::unique_lock lk(g_mutex); auto it = g_socks.find(s); if (it == g_socks.end()) { return fail(NET_EBADF); } if (!it->second.listening) { return fail(NET_EINVAL); } if (it->second.nonblock) { return fail(NET_EAGAIN); } // No peer can ever arrive: wait for close/abort like an idle listener. const int rc = block_on(lk, s, it->second.rcv_timeout_us); return rc; } int APS5_VABI sceNetConnect(int s, const void* addr, uint32_t addrlen) { std::lock_guard lk(g_mutex); auto it = g_socks.find(s); if (it == g_socks.end()) { return fail(NET_EBADF); } if (addr == nullptr || addrlen < 8) { return fail(NET_EINVAL); } std::uint32_t ip = 0; std::memcpy(&ip, static_cast(addr) + 4, 4); if ((swap32(ip) >> 24) == 127) { return fail(NET_ECONNREFUSED); // loopback with no listener } return fail(NET_ENETUNREACH); } static int recv_common(int s) { std::unique_lock lk(g_mutex); auto it = g_socks.find(s); if (it == g_socks.end()) { return fail(NET_EBADF); } if (it->second.type == NET_SOCK_STREAM) { return fail(NET_ENOTCONN); } if (it->second.nonblock) { return fail(NET_EAGAIN); } return block_on(lk, s, it->second.rcv_timeout_us); } int64_t APS5_VABI sceNetRecv(int s, void* buf, size_t len, int flags) { (void)buf; (void)len; (void)flags; return recv_common(s); } int64_t APS5_VABI sceNetRecvfrom(int s, void* buf, size_t len, int flags, void* from, uint32_t* fromlen) { (void)buf; (void)len; (void)flags; (void)from; (void)fromlen; return recv_common(s); } static int send_common(int s) { std::lock_guard lk(g_mutex); auto it = g_socks.find(s); if (it == g_socks.end()) { return fail(NET_EBADF); } return fail(it->second.type == NET_SOCK_STREAM ? NET_ENOTCONN : NET_ENETUNREACH); } int64_t APS5_VABI sceNetSend(int s, const void* buf, size_t len, int flags) { (void)buf; (void)len; (void)flags; return send_common(s); } int64_t APS5_VABI sceNetSendto(int s, const void* buf, size_t len, int flags, const void* to, uint32_t tolen) { (void)buf; (void)len; (void)flags; (void)to; (void)tolen; return send_common(s); } int APS5_VABI sceNetShutdown(int s, int how) { (void)how; std::lock_guard lk(g_mutex); auto it = g_socks.find(s); if (it == g_socks.end()) { return fail(NET_EBADF); } return it->second.type == NET_SOCK_STREAM ? fail(NET_ENOTCONN) : 0; } int APS5_VABI sceNetSetsockopt(int s, int level, int optname, const void* optval, uint32_t optlen) { std::lock_guard lk(g_mutex); auto it = g_socks.find(s); if (it == g_socks.end()) { return fail(NET_EBADF); } if (level == NET_SOL_SOCKET && optval != nullptr && optlen >= sizeof(int)) { int v = 0; std::memcpy(&v, optval, sizeof(v)); switch (optname) { case NET_SO_NBIO: it->second.nonblock = v != 0; break; case NET_SO_RCVTIMEO: it->second.rcv_timeout_us = v; break; case NET_SO_SNDTIMEO: it->second.snd_timeout_us = v; break; default: break; } } return 0; } int APS5_VABI sceNetGetsockopt(int s, int level, int optname, void* optval, uint32_t* optlen) { std::lock_guard lk(g_mutex); auto it = g_socks.find(s); if (it == g_socks.end()) { return fail(NET_EBADF); } if (optval == nullptr || optlen == nullptr || *optlen < sizeof(int)) { return fail(NET_EINVAL); } int v = 0; if (level == NET_SOL_SOCKET) { switch (optname) { case NET_SO_NBIO: v = it->second.nonblock ? 1 : 0; break; case NET_SO_RCVTIMEO: v = it->second.rcv_timeout_us; break; case NET_SO_SNDTIMEO: v = it->second.snd_timeout_us; break; default: break; } } std::memcpy(optval, &v, sizeof(v)); *optlen = sizeof(v); return 0; } int APS5_VABI sceNetGetsockname(int s, void* addr, uint32_t* addrlen) { std::lock_guard lk(g_mutex); auto it = g_socks.find(s); if (it == g_socks.end()) { return fail(NET_EBADF); } if (addr == nullptr || addrlen == nullptr) { return fail(NET_EINVAL); } std::uint8_t sa[16] = {}; sa[0] = 16; sa[1] = static_cast(NET_AF_INET); std::memcpy(sa + 2, &it->second.port, 2); std::memcpy(sa + 4, &it->second.addr, 4); const std::uint32_t n = *addrlen < sizeof(sa) ? *addrlen : static_cast(sizeof(sa)); std::memcpy(addr, sa, n); *addrlen = sizeof(sa); return 0; } int APS5_VABI sceNetGetSockInfo(int s, void* info, int n, int flags) { (void)info; (void)n; (void)flags; std::lock_guard lk(g_mutex); return g_socks.count(s) != 0 ? 0 : fail(NET_EBADF); } // Epoll: nothing can become ready without a network, so a wait sleeps for its timeout (microseconds; a // negative infinite timeout is serviced in bounded slices) and reports zero events. int APS5_VABI sceNetEpollCreate(const char* name, int flags) { (void)name; (void)flags; std::lock_guard lk(g_mutex); const int id = g_next_epoll++; g_epolls.insert(id); return id; } int APS5_VABI sceNetEpollDestroy(int eid) { std::lock_guard lk(g_mutex); if (g_epolls.erase(eid) == 0) { return fail(NET_EBADF); } g_cv.notify_all(); return 0; } int APS5_VABI sceNetEpollControl(int eid, int op, int id, const NetEpollEvent* event) { (void)event; std::lock_guard lk(g_mutex); if (g_epolls.count(eid) == 0) { return fail(NET_EBADF); } if (g_socks.count(id) == 0) { return fail(NET_EBADF); } if (op < 1 || op > 3) { return fail(NET_EINVAL); } return 0; } int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int timeout) { (void)events; if (maxevents <= 0) { return fail(NET_EINVAL); } std::unique_lock lk(g_mutex); if (g_epolls.count(eid) == 0) { return fail(NET_EBADF); } if (timeout == 0) { return 0; } // timeout < 0 nominally waits until the epoll object is destroyed; bound each wait slice so an // infinite-timeout waiter on the inert stack still returns periodically (0 events) instead of // deadlocking if nobody ever destroys the epoll. constexpr int INFINITE_WAIT_SLICE_US = 100000; // 100 ms const auto deadline = std::chrono::steady_clock::now() + std::chrono::microseconds(timeout > 0 ? timeout : INFINITE_WAIT_SLICE_US); for (;;) { if (g_cv.wait_until(lk, deadline) == std::cv_status::timeout) { return 0; } if (g_epolls.count(eid) == 0) { return fail(NET_EBADF); } } } // Byte order and text conversion. uint32_t APS5_VABI sceNetHtonl_nid_postfix(uint32_t host32) { return swap32(host32); } uint16_t APS5_VABI sceNetHtons_nid_postfix(uint16_t host16) { return swap16(host16); } uint32_t APS5_VABI sceNetNtohl_nid_postfix(uint32_t net32) { return swap32(net32); } uint16_t APS5_VABI sceNetNtohs_nid_postfix(uint16_t net16) { return swap16(net16); } int APS5_VABI sceNetInetPton(int af, const char* src, void* dst) { if (src == nullptr || dst == nullptr) { return fail(NET_EINVAL); } if (af != NET_AF_INET) { return fail(NET_EAFNOSUPPORT); } unsigned a = 0, b = 0, c = 0, d = 0; char tail = 0; if (std::sscanf(src, "%3u.%3u.%3u.%3u%c", &a, &b, &c, &d, &tail) != 4 || a > 255 || b > 255 || c > 255 || d > 255) { return 0; } const std::uint8_t out[4] = {static_cast(a), static_cast(b), static_cast(c), static_cast(d)}; std::memcpy(dst, out, 4); return 1; } const char* APS5_VABI sceNetInetNtop(int af, const void* src, char* dst, uint32_t size) { if (src == nullptr || dst == nullptr || af != NET_AF_INET) { *errno_slot() = af != NET_AF_INET ? NET_EAFNOSUPPORT : NET_EINVAL; return nullptr; } const auto* p = static_cast(src); char tmp[16]; const int n = std::snprintf(tmp, sizeof(tmp), "%u.%u.%u.%u", p[0], p[1], p[2], p[3]); if (n < 0 || static_cast(n) >= size) { *errno_slot() = 28; // ENOSPC return nullptr; } std::memcpy(dst, tmp, static_cast(n) + 1); return dst; } int APS5_VABI sceNetEtherNtostr(const NetEtherAddr* n, char* str, size_t len) { if (n == nullptr || str == nullptr || len < 18) { return fail(NET_EINVAL); } std::snprintf(str, len, "%02x:%02x:%02x:%02x:%02x:%02x", n->data[0], n->data[1], n->data[2], n->data[3], n->data[4], n->data[5]); return 0; } int APS5_VABI sceNetGetMacAddress(NetEtherAddr* addr, int flags) { (void)flags; if (addr == nullptr) { return fail(NET_EINVAL); } // Locally administered address; there is no adapter to read one from. const std::uint8_t mac[6] = {0x02, 0x50, 0x53, 0x35, 0x00, 0x01}; std::memcpy(addr->data, mac, 6); return 0; } // Resolver: there is no DNS server to ask. int APS5_VABI sceNetResolverCreate(const char* name, int memid, int flags) { (void)name; (void)memid; (void)flags; std::lock_guard lk(g_mutex); const int id = g_next_resolver++; g_resolvers.insert(id); return id; } int APS5_VABI sceNetResolverDestroy(int rid) { std::lock_guard lk(g_mutex); return g_resolvers.erase(rid) != 0 ? 0 : fail(NET_EBADF); } int APS5_VABI sceNetResolverStartNtoa(int rid, const char* hostname, void* addr, int timeout, int retry, int flags) { (void)hostname; (void)addr; (void)timeout; (void)retry; (void)flags; std::lock_guard lk(g_mutex); if (g_resolvers.count(rid) == 0) { return fail(NET_EBADF); } log_soft(__func__, "ENODNS (no network)"); *errno_slot() = NET_ETIMEDOUT; return NET_ERROR_RESOLVER_ENODNS; } int APS5_VABI sceNetResolverStartAton(int rid, const void* addr, char* hostname, int len, int timeout, int retry, int flags) { (void)addr; (void)hostname; (void)len; (void)timeout; (void)retry; (void)flags; std::lock_guard lk(g_mutex); if (g_resolvers.count(rid) == 0) { return fail(NET_EBADF); } log_soft(__func__, "ENODNS (no network)"); *errno_slot() = NET_ETIMEDOUT; return NET_ERROR_RESOLVER_ENODNS; } }