// Vita3K emulator project // Copyright (C) 2025 Vita3K team // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #include "SceNet.h" #include #include #include #include #include #include #include #include TRACY_MODULE_NAME(SceNet); template <> std::string to_debug_str(const MemState &mem, SceNetEpollControlFlag type) { switch (type) { case SCE_NET_EPOLL_CTL_ADD: return "SCE_NET_EPOLL_CTL_ADD"; case SCE_NET_EPOLL_CTL_MOD: return "SCE_NET_EPOLL_CTL_MOD"; case SCE_NET_EPOLL_CTL_DEL: return "SCE_NET_EPOLL_CTL_DEL"; } return std::to_string(type); } template <> std::string to_debug_str(const MemState &mem, SceNetProtocol type) { switch (type) { case SCE_NET_IPPROTO_IP: return "SCE_NET_IPPROTO_IP"; case SCE_NET_IPPROTO_ICMP: return "SCE_NET_IPPROTO_ICMP"; case SCE_NET_IPPROTO_IGMP: return "SCE_NET_IPPROTO_IGMP"; case SCE_NET_IPPROTO_TCP: return "SCE_NET_IPPROTO_TCP"; case SCE_NET_IPPROTO_UDP: return "SCE_NET_IPPROTO_UDP"; case SCE_NET_SOL_SOCKET: return "SCE_NET_SOL_SOCKET"; } return std::to_string(type); } template <> std::string to_debug_str(const MemState &mem, SceNetSocketType type) { switch (type) { case SCE_NET_SOCK_STREAM: return "SCE_NET_SOCK_STREAM"; case SCE_NET_SOCK_DGRAM: return "SCE_NET_SOCK_DGRAM"; case SCE_NET_SOCK_RAW: return "SCE_NET_SOCK_RAW"; case SCE_NET_SOCK_DGRAM_P2P: return "SCE_NET_SOCK_DGRAM_P2P"; case SCE_NET_SOCK_STREAM_P2P: return "SCE_NET_SOCK_STREAM_P2P"; } return std::to_string(type); } template <> std::string to_debug_str(const MemState &mem, SceNetSocketOption type) { switch (type) { /* IP */ case SCE_NET_IP_HDRINCL: return "SCE_NET_IP_HDRINCL or SCE_NET_TCP_MAXSEG"; case SCE_NET_IP_TOS: return "SCE_NET_IP_TOS or SCE_NET_TCP_MSS_TO_ADVERTISE"; case SCE_NET_IP_TTL: return "SCE_NET_IP_TTL or SCE_NET_SO_REUSEADDR"; case SCE_NET_IP_MULTICAST_IF: return "SCE_NET_IP_MULTICAST_IF"; case SCE_NET_IP_MULTICAST_TTL: return "SCE_NET_IP_MULTICAST_TTL"; case SCE_NET_IP_MULTICAST_LOOP: return "SCE_NET_IP_MULTICAST_LOOP"; case SCE_NET_IP_ADD_MEMBERSHIP: return "SCE_NET_IP_ADD_MEMBERSHIP"; case SCE_NET_IP_DROP_MEMBERSHIP: return "SCE_NET_IP_DROP_MEMBERSHIP"; case SCE_NET_IP_TTLCHK: return "SCE_NET_IP_TTLCHK"; case SCE_NET_IP_MAXTTL: return "SCE_NET_IP_MAXTTL"; /* TCP */ case SCE_NET_TCP_NODELAY: return "SCE_NET_TCP_NODELAY"; // case SCE_NET_TCP_MAXSEG: return "SCE_NET_TCP_MAXSEG"; // case SCE_NET_TCP_MSS_TO_ADVERTISE: return "SCE_NET_TCP_MSS_TO_ADVERTISE"; /* SOCKET */ // case SCE_NET_SO_REUSEADDR: return "SCE_NET_SO_REUSEADDR"; case SCE_NET_SO_KEEPALIVE: return "SCE_NET_SO_KEEPALIVE"; case SCE_NET_SO_BROADCAST: return "SCE_NET_SO_BROADCAST"; case SCE_NET_SO_LINGER: return "SCE_NET_SO_LINGER"; case SCE_NET_SO_OOBINLINE: return "SCE_NET_SO_OOBINLINE"; case SCE_NET_SO_REUSEPORT: return "SCE_NET_SO_REUSEPORT"; case SCE_NET_SO_ONESBCAST: return "SCE_NET_SO_ONESBCAST"; case SCE_NET_SO_USECRYPTO: return "SCE_NET_SO_USECRYPTO"; case SCE_NET_SO_USESIGNATURE: return "SCE_NET_SO_USESIGNATURE"; case SCE_NET_SO_SNDBUF: return "SCE_NET_SO_SNDBUF"; case SCE_NET_SO_RCVBUF: return "SCE_NET_SO_RCVBUF"; case SCE_NET_SO_SNDLOWAT: return "SCE_NET_SO_SNDLOWAT"; case SCE_NET_SO_RCVLOWAT: return "SCE_NET_SO_RCVLOWAT"; case SCE_NET_SO_SNDTIMEO: return "SCE_NET_SO_SNDTIMEO"; case SCE_NET_SO_RCVTIMEO: return "SCE_NET_SO_RCVTIMEO"; case SCE_NET_SO_ERROR: return "SCE_NET_SO_ERROR"; case SCE_NET_SO_TYPE: return "SCE_NET_SO_TYPE"; case SCE_NET_SO_NBIO: return "SCE_NET_SO_NBIO"; case SCE_NET_SO_TPPOLICY: return "SCE_NET_SO_TPPOLICY"; case SCE_NET_SO_NAME: return "SCE_NET_SO_NAME"; } return std::to_string(type); } EXPORT(int, sceNetAccept, int sid, SceNetSockaddr *addr, unsigned int *addrlen) { TRACY_FUNC(sceNetAccept, sid, addr, addrlen); auto sock = lock_and_find(sid, emuenv.net.socks, emuenv.kernel.mutex); if (!sock) { return RET_ERROR(SCE_NET_EBADF); } auto newsock = sock->accept(addr, addrlen); if (!newsock) { return RET_ERROR(-1); } auto id = ++emuenv.net.next_id; emuenv.net.socks.emplace(id, sock); return id; } EXPORT(int, sceNetBind, int sid, const SceNetSockaddr *addr, unsigned int addrlen) { TRACY_FUNC(sceNetBind, sid, addr, addrlen); auto sock = lock_and_find(sid, emuenv.net.socks, emuenv.kernel.mutex); if (!sock) { return RET_ERROR(SCE_NET_EBADF); } return sock->bind(addr, addrlen); } EXPORT(int, sceNetClearDnsCache) { TRACY_FUNC(sceNetClearDnsCache); return UNIMPLEMENTED(); } EXPORT(int, sceNetConnect, int sid, const SceNetSockaddr *addr, unsigned int addrlen) { TRACY_FUNC(sceNetConnect, sid, addr, addrlen); auto sock = lock_and_find(sid, emuenv.net.socks, emuenv.kernel.mutex); if (!sock) { return RET_ERROR(SCE_NET_EBADF); } return sock->connect(addr, addrlen); } EXPORT(int, sceNetDumpAbort) { TRACY_FUNC(sceNetDumpAbort); return UNIMPLEMENTED(); } EXPORT(int, sceNetDumpCreate) { TRACY_FUNC(sceNetDumpCreate); return UNIMPLEMENTED(); } EXPORT(int, sceNetDumpDestroy) { TRACY_FUNC(sceNetDumpDestroy); return UNIMPLEMENTED(); } EXPORT(int, sceNetDumpRead) { TRACY_FUNC(sceNetDumpRead); return UNIMPLEMENTED(); } EXPORT(int, sceNetEmulationGet) { TRACY_FUNC(sceNetEmulationGet); return UNIMPLEMENTED(); } EXPORT(int, sceNetEmulationSet) { TRACY_FUNC(sceNetEmulationSet); return UNIMPLEMENTED(); } EXPORT(int, sceNetEpollAbort) { TRACY_FUNC(sceNetEpollAbort); return UNIMPLEMENTED(); } EXPORT(int, sceNetEpollControl, int eid, SceNetEpollControlFlag op, int id, SceNetEpollEvent *ev) { TRACY_FUNC(sceNetEpollControl, eid, op, id, ev); auto epoll = lock_and_find(eid, emuenv.net.epolls, emuenv.kernel.mutex); if (!epoll) { return RET_ERROR(SCE_NET_ERROR_EBADF); } if (id == emuenv.net.resolver_id) { STUBBED("Async DNS resolve is not supported"); return 0; } auto sock = lock_and_find(id, emuenv.net.socks, emuenv.kernel.mutex); if (!sock) { return RET_ERROR(SCE_NET_ERROR_EBADF); } auto posixSocket = std::dynamic_pointer_cast(sock); if (!posixSocket) { return RET_ERROR(SCE_NET_ERROR_EBADF); } switch (op) { case SCE_NET_EPOLL_CTL_ADD: return epoll->add(id, posixSocket->sock, ev); case SCE_NET_EPOLL_CTL_DEL: return epoll->del(id, posixSocket->sock, ev); case SCE_NET_EPOLL_CTL_MOD: return epoll->mod(id, posixSocket->sock, ev); default: return RET_ERROR(SCE_NET_ERROR_EINVAL); } } EXPORT(int, sceNetEpollCreate, const char *name, int flags) { TRACY_FUNC(sceNetEpollCreate, name, flags); auto id = ++emuenv.net.next_epoll_id; auto epoll = std::make_shared(); const std::lock_guard lock(emuenv.kernel.mutex); emuenv.net.epolls.emplace(id, epoll); return id; } EXPORT(int, sceNetEpollDestroy, int eid) { TRACY_FUNC(sceNetEpollDestroy, eid); const std::lock_guard lock(emuenv.kernel.mutex); if (emuenv.net.epolls.erase(eid) == 0) { return RET_ERROR(SCE_NET_EBADF); } return 0; } EXPORT(int, sceNetEpollWait, int eid, SceNetEpollEvent *events, int maxevents, int timeout) { TRACY_FUNC(sceNetEpollWait, eid, events, maxevents, timeout); auto epoll = lock_and_find(eid, emuenv.net.epolls, emuenv.kernel.mutex); if (!epoll) { return RET_ERROR(SCE_NET_ERROR_EBADF); } return epoll->wait(events, maxevents, timeout); } EXPORT(int, sceNetEpollWaitCB) { TRACY_FUNC(sceNetEpollWaitCB); return UNIMPLEMENTED(); } EXPORT(Ptr, sceNetErrnoLoc) { TRACY_FUNC(sceNetErrnoLoc); // TLS id was taken from disasm source auto addr = emuenv.kernel.get_thread_tls_addr(emuenv.mem, thread_id, TLS_NET_ERRNO); return addr.cast(); } EXPORT(int, sceNetEtherNtostr, SceNetEtherAddr *n, char *str, unsigned int len) { TRACY_FUNC(sceNetEtherNtostr, n, str, len); if (!emuenv.net.inited) return RET_ERROR(SCE_NET_ERROR_ENOTINIT); if (!n || !str || len <= 0x11) return RET_ERROR(SCE_NET_ERROR_EINVAL); 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; } EXPORT(int, sceNetEtherStrton, const char *str, SceNetEtherAddr *n) { TRACY_FUNC(sceNetEtherStrton, str, n); if (!emuenv.net.inited) return RET_ERROR(SCE_NET_ERROR_ENOTINIT); if (!str || !n) return RET_ERROR(SCE_NET_ERROR_EINVAL); sscanf(str, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", &n->data[0], &n->data[1], &n->data[2], &n->data[3], &n->data[4], &n->data[5]); return 0; } EXPORT(int, sceNetGetMacAddress, SceNetEtherAddr *addr, int flags) { TRACY_FUNC(sceNetGetMacAddress, addr, flags); if (addr == nullptr) { return RET_ERROR(SCE_NET_EINVAL); } #ifdef _WIN32 IP_ADAPTER_INFO AdapterInfo[16]; DWORD dwBufLen = sizeof(AdapterInfo); if (GetAdaptersInfo(AdapterInfo, &dwBufLen) != ERROR_SUCCESS) { return RET_ERROR(SCE_NET_EINVAL); } else { memcpy(addr->data, AdapterInfo[0].Address, 6); } #else // TODO: Implement the function for non Windows OS return UNIMPLEMENTED(); #endif return 0; } EXPORT(int, sceNetGetSockIdInfo) { TRACY_FUNC(sceNetGetSockIdInfo); return UNIMPLEMENTED(); } EXPORT(int, sceNetGetSockInfo) { TRACY_FUNC(sceNetGetSockInfo); return UNIMPLEMENTED(); } EXPORT(int, sceNetGetStatisticsInfo) { TRACY_FUNC(sceNetGetStatisticsInfo); return UNIMPLEMENTED(); } EXPORT(int, sceNetGetpeername) { TRACY_FUNC(sceNetGetpeername); return UNIMPLEMENTED(); } EXPORT(int, sceNetGetsockname, int sid, SceNetSockaddr *name, unsigned int *namelen) { TRACY_FUNC(sceNetGetsockname, sid, name, namelen); auto sock = lock_and_find(sid, emuenv.net.socks, emuenv.kernel.mutex); if (!sock) { return RET_ERROR(SCE_NET_ERROR_EBADF); } return sock->get_socket_address(name, namelen); } EXPORT(int, sceNetGetsockopt, int sid, int level, int optname, void *optval, unsigned int *optlen) { TRACY_FUNC(sceNetGetsockopt, sid, level, optname, optval, optlen); auto sock = lock_and_find(sid, emuenv.net.socks, emuenv.kernel.mutex); if (!sock) { return RET_ERROR(SCE_NET_ERROR_EBADF); } return sock->get_socket_options(level, optname, optval, optlen); } EXPORT(unsigned int, sceNetHtonl, unsigned int n) { TRACY_FUNC(sceNetHtonl, n); return htonl(n); } EXPORT(int, sceNetHtonll, SceUInt64 n) { TRACY_FUNC(sceNetHtonll, n); return HTONLL(n); } EXPORT(unsigned short int, sceNetHtons, unsigned short int n) { TRACY_FUNC(sceNetHtons, n); return htons(n); } EXPORT(Ptr, sceNetInetNtop, int af, const void *src, Ptr dst, unsigned int size) { TRACY_FUNC(sceNetInetNtop, af, src, dst, size); char *dst_ptr = dst.get(emuenv.mem); #ifdef _WIN32 const char *res = InetNtop(af, src, dst_ptr, size); #else const char *res = inet_ntop(af, src, dst_ptr, size); #endif if (res == nullptr) { RET_ERROR(0x0); return Ptr(); } return dst; } EXPORT(int, sceNetInetPton, int af, const char *src, void *dst) { TRACY_FUNC(sceNetInetPton, af, src, dst); #ifdef _WIN32 int res = InetPton(af, src, dst); #else int res = inet_pton(af, src, dst); #endif if (res < 0) { return RET_ERROR(-1); } return res; } EXPORT(int, sceNetInit, SceNetInitParam *param) { TRACY_FUNC(sceNetInit, param); if (emuenv.net.inited) return RET_ERROR(SCE_NET_ERROR_EBUSY); if (!param || !param->memory.address() || param->size < 0x4000 || param->flags != 0) return RET_ERROR(SCE_NET_ERROR_EINVAL); #ifdef _WIN32 WORD versionWanted = MAKEWORD(2, 2); WSADATA wsaData; WSAStartup(versionWanted, &wsaData); #endif emuenv.net.state = 0; emuenv.net.inited = true; emuenv.net.resolver_id = ++emuenv.net.next_id; return 0; } EXPORT(int, sceNetListen, int sid, int backlog) { TRACY_FUNC(sceNetListen, sid, backlog); auto sock = lock_and_find(sid, emuenv.net.socks, emuenv.kernel.mutex); if (!sock) { return RET_ERROR(SCE_NET_ERROR_EBADF); } return sock->listen(backlog); } EXPORT(unsigned int, sceNetNtohl, unsigned int n) { TRACY_FUNC(sceNetNtohl, n); return ntohl(n); } EXPORT(int, sceNetNtohll, SceUInt64 n) { TRACY_FUNC(sceNetNtohll, n); return NTOHLL(n); } EXPORT(unsigned short int, sceNetNtohs, unsigned short int n) { TRACY_FUNC(sceNetNtohs, n); return ntohs(n); } EXPORT(int, sceNetRecv, int sid, void *buf, unsigned int len, int flags) { TRACY_FUNC(sceNetRecv, sid, buf, len, flags); auto sock = lock_and_find(sid, emuenv.net.socks, emuenv.kernel.mutex); if (!sock) { return RET_ERROR(SCE_NET_ERROR_EBADF); } return sock->recv_packet(buf, len, flags, nullptr, 0); } EXPORT(int, sceNetRecvfrom, int sid, void *buf, unsigned int len, int flags, SceNetSockaddr *from, unsigned int *fromlen) { TRACY_FUNC(sceNetRecvfrom, sid, buf, len, flags, from, fromlen); auto sock = lock_and_find(sid, emuenv.net.socks, emuenv.kernel.mutex); if (!sock) { return RET_ERROR(SCE_NET_ERROR_EBADF); } return sock->recv_packet(buf, len, flags, from, fromlen); } EXPORT(int, sceNetRecvmsg) { TRACY_FUNC(sceNetRecvmsg); return UNIMPLEMENTED(); } EXPORT(int, sceNetResolverAbort) { TRACY_FUNC(sceNetResolverAbort); return UNIMPLEMENTED(); } EXPORT(int, sceNetResolverCreate, const char *name, void *param, int flags) { TRACY_FUNC(sceNetResolverCreate, name, param, flags); STUBBED("Fake id"); return emuenv.net.resolver_id; } EXPORT(int, sceNetResolverDestroy, int rid) { TRACY_FUNC(sceNetResolverDestroy, rid); return UNIMPLEMENTED(); } EXPORT(int, sceNetResolverGetError) { TRACY_FUNC(sceNetResolverGetError); return UNIMPLEMENTED(); } EXPORT(int, sceNetResolverStartAton, int rid, const SceNetInAddr *addr, char *hostname, int len, int timeout, int retry, int flags) { TRACY_FUNC(sceNetResolverStartAton, rid, addr, hostname, len, timeout, retry, flags); struct hostent *resolved = gethostbyaddr((const char *)addr, len, AF_INET); strcpy(hostname, resolved->h_name); return 0; } EXPORT(int, sceNetResolverStartNtoa, int rid, const char *emuenvname, SceNetInAddr *addr, int timeout, int retry, int flags) { TRACY_FUNC(sceNetResolverStartNtoa, rid, emuenvname, addr, timeout, retry, flags); struct hostent *resolved = gethostbyname(emuenvname); if (resolved == nullptr) { memset(addr, 0, sizeof(*addr)); return RET_ERROR(-1); } memcpy(addr, resolved->h_addr, sizeof(uint32_t)); return 0; } EXPORT(int, sceNetSend, int sid, const void *msg, unsigned int len, int flags) { TRACY_FUNC(sceNetSend, sid, msg, len, flags); auto sock = lock_and_find(sid, emuenv.net.socks, emuenv.kernel.mutex); if (!sock) { return RET_ERROR(SCE_NET_EBADF); } return sock->send_packet(msg, len, flags, nullptr, 0); } EXPORT(int, sceNetSendmsg) { TRACY_FUNC(sceNetSendmsg); return UNIMPLEMENTED(); } EXPORT(int, sceNetSendto, int sid, const void *msg, unsigned int len, int flags, const SceNetSockaddr *to, unsigned int tolen) { TRACY_FUNC(sceNetSendto, sid, msg, len, flags, to, tolen); auto sock = lock_and_find(sid, emuenv.net.socks, emuenv.kernel.mutex); if (!sock) { return RET_ERROR(SCE_NET_EBADF); } return sock->send_packet(msg, len, flags, to, tolen); } EXPORT(int, sceNetSetDnsInfo) { TRACY_FUNC(sceNetSetDnsInfo); return UNIMPLEMENTED(); } EXPORT(int, sceNetSetsockopt, int sid, SceNetProtocol level, SceNetSocketOption optname, const int *optval, unsigned int optlen) { TRACY_FUNC(sceNetSetsockopt, sid, level, optname, *optval, optlen); auto sock = lock_and_find(sid, emuenv.net.socks, emuenv.kernel.mutex); if (!sock) { return RET_ERROR(SCE_NET_EBADF); } if (optname == 0x40000) { LOG_ERROR("Unknown socket option {}", log_hex(optname)); return 0; } return sock->set_socket_options(level, optname, optval, optlen); } EXPORT(int, sceNetShowIfconfig) { TRACY_FUNC(sceNetShowIfconfig); return UNIMPLEMENTED(); } EXPORT(int, sceNetShowNetstat) { TRACY_FUNC(sceNetShowNetstat); if (!emuenv.net.inited) { return RET_ERROR(SCE_NET_ERROR_ENOTINIT); } return 0; } EXPORT(int, sceNetShowRoute) { TRACY_FUNC(sceNetShowRoute); return UNIMPLEMENTED(); } EXPORT(int, sceNetShutdown) { TRACY_FUNC(sceNetShutdown); return UNIMPLEMENTED(); } EXPORT(int, sceNetSocket, const char *name, int domain, SceNetSocketType type, SceNetProtocol protocol) { TRACY_FUNC(sceNetSocket, name, domain, type, protocol); SocketPtr sock; if (type < SCE_NET_SOCK_STREAM || type > SCE_NET_SOCK_RAW) { sock = std::make_shared(domain, type, protocol); } else { sock = std::make_shared(domain, type, protocol); } auto id = ++emuenv.net.next_id; emuenv.net.socks.emplace(id, sock); return id; } EXPORT(int, sceNetSocketAbort) { TRACY_FUNC(sceNetSocketAbort); return UNIMPLEMENTED(); } EXPORT(int, sceNetSocketClose, int sid) { TRACY_FUNC(sceNetSocketClose, sid); auto sock = lock_and_find(sid, emuenv.net.socks, emuenv.kernel.mutex); if (!sock) { return RET_ERROR(SCE_NET_EBADF); } return sock->close(); } EXPORT(int, sceNetTerm) { TRACY_FUNC(sceNetTerm); if (!emuenv.net.inited) { return RET_ERROR(SCE_NET_ERROR_ENOTINIT); } #ifdef _WIN32 WSACleanup(); #endif emuenv.net.inited = false; return 0; }