summaryrefslogtreecommitdiffstats
path: root/src/core/hle/service/soc_u.cpp
diff options
context:
space:
mode:
authorbunnei <bunneidev@gmail.com>2014-12-31 17:31:53 +0100
committerbunnei <bunneidev@gmail.com>2014-12-31 17:31:53 +0100
commit08b6cf778da3240dc78cff748c38c1188c7e6b69 (patch)
treef4e336af1e5cf5d72c2665470a96a9db73711ae7 /src/core/hle/service/soc_u.cpp
parentMerge pull request #375 from lioncash/uops (diff)
parentSOC_U: Preliminary implementation of sockets. (diff)
downloadyuzu-08b6cf778da3240dc78cff748c38c1188c7e6b69.tar
yuzu-08b6cf778da3240dc78cff748c38c1188c7e6b69.tar.gz
yuzu-08b6cf778da3240dc78cff748c38c1188c7e6b69.tar.bz2
yuzu-08b6cf778da3240dc78cff748c38c1188c7e6b69.tar.lz
yuzu-08b6cf778da3240dc78cff748c38c1188c7e6b69.tar.xz
yuzu-08b6cf778da3240dc78cff748c38c1188c7e6b69.tar.zst
yuzu-08b6cf778da3240dc78cff748c38c1188c7e6b69.zip
Diffstat (limited to 'src/core/hle/service/soc_u.cpp')
-rw-r--r--src/core/hle/service/soc_u.cpp721
1 files changed, 700 insertions, 21 deletions
diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp
index ef4f9829d..9fbf18b26 100644
--- a/src/core/hle/service/soc_u.cpp
+++ b/src/core/hle/service/soc_u.cpp
@@ -2,40 +2,712 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/platform.h"
+
+#if EMU_PLATFORM == PLATFORM_WINDOWS
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <poll.h>
+#endif
+
#include "common/log.h"
+#include "common/scope_exit.h"
#include "core/hle/hle.h"
#include "core/hle/service/soc_u.h"
+#include <unordered_map>
+
+#if EMU_PLATFORM == PLATFORM_WINDOWS
+# define WSAEAGAIN WSAEWOULDBLOCK
+# define WSAEMULTIHOP -1 // Invalid dummy value
+# define ERRNO(x) WSA##x
+# define GET_ERRNO WSAGetLastError()
+# define poll(x, y, z) WSAPoll(x, y, z);
+#else
+# define ERRNO(x) x
+# define GET_ERRNO errno
+# define closesocket(x) close(x)
+#endif
+
+static const s32 SOCKET_ERROR_VALUE = -1;
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace SOC_U
namespace SOC_U {
+/// Holds the translation from system network errors to 3DS network errors
+static const std::unordered_map<int, int> error_map = { {
+ { E2BIG, 1 },
+ { ERRNO(EACCES), 2 },
+ { ERRNO(EADDRINUSE), 3 },
+ { ERRNO(EADDRNOTAVAIL), 4 },
+ { ERRNO(EAFNOSUPPORT), 5 },
+ { ERRNO(EAGAIN), 6 },
+ { ERRNO(EALREADY), 7 },
+ { ERRNO(EBADF), 8 },
+ { EBADMSG, 9 },
+ { EBUSY, 10 },
+ { ECANCELED, 11 },
+ { ECHILD, 12 },
+ { ERRNO(ECONNABORTED), 13 },
+ { ERRNO(ECONNREFUSED), 14 },
+ { ERRNO(ECONNRESET), 15 },
+ { EDEADLK, 16 },
+ { ERRNO(EDESTADDRREQ), 17 },
+ { EDOM, 18 },
+ { ERRNO(EDQUOT), 19 },
+ { EEXIST, 20 },
+ { ERRNO(EFAULT), 21 },
+ { EFBIG, 22 },
+ { ERRNO(EHOSTUNREACH), 23 },
+ { EIDRM, 24 },
+ { EILSEQ, 25 },
+ { ERRNO(EINPROGRESS), 26 },
+ { ERRNO(EINTR), 27 },
+ { ERRNO(EINVAL), 28 },
+ { EIO, 29 },
+ { ERRNO(EISCONN), 30 },
+ { EISDIR, 31 },
+ { ERRNO(ELOOP), 32 },
+ { ERRNO(EMFILE), 33 },
+ { EMLINK, 34 },
+ { ERRNO(EMSGSIZE), 35 },
+ { ERRNO(EMULTIHOP), 36 },
+ { ERRNO(ENAMETOOLONG), 37 },
+ { ERRNO(ENETDOWN), 38 },
+ { ERRNO(ENETRESET), 39 },
+ { ERRNO(ENETUNREACH), 40 },
+ { ENFILE, 41 },
+ { ERRNO(ENOBUFS), 42 },
+ { ENODATA, 43 },
+ { ENODEV, 44 },
+ { ENOENT, 45 },
+ { ENOEXEC, 46 },
+ { ENOLCK, 47 },
+ { ENOLINK, 48 },
+ { ENOMEM, 49 },
+ { ENOMSG, 50 },
+ { ERRNO(ENOPROTOOPT), 51 },
+ { ENOSPC, 52 },
+ { ENOSR, 53 },
+ { ENOSTR, 54 },
+ { ENOSYS, 55 },
+ { ERRNO(ENOTCONN), 56 },
+ { ENOTDIR, 57 },
+ { ERRNO(ENOTEMPTY), 58 },
+ { ERRNO(ENOTSOCK), 59 },
+ { ENOTSUP, 60 },
+ { ENOTTY, 61 },
+ { ENXIO, 62 },
+ { ERRNO(EOPNOTSUPP), 63 },
+ { EOVERFLOW, 64 },
+ { EPERM, 65 },
+ { EPIPE, 66 },
+ { EPROTO, 67 },
+ { ERRNO(EPROTONOSUPPORT), 68 },
+ { ERRNO(EPROTOTYPE), 69 },
+ { ERANGE, 70 },
+ { EROFS, 71 },
+ { ESPIPE, 72 },
+ { ESRCH, 73 },
+ { ERRNO(ESTALE), 74 },
+ { ETIME, 75 },
+ { ERRNO(ETIMEDOUT), 76 }
+}};
+
+/// Converts a network error from platform-specific to 3ds-specific
+static int TranslateError(int error) {
+ auto found = error_map.find(error);
+ if (found != error_map.end())
+ return -found->second;
+
+ return error;
+}
+
+/// Holds information about a particular socket
+struct SocketHolder {
+ u32 socket_fd; ///< The socket descriptor
+ bool blocking; ///< Whether the socket is blocking or not, it is only read on Windows.
+};
+
+/// Structure to represent the 3ds' pollfd structure, which is different than most implementations
+struct CTRPollFD {
+ u32 fd; ///< Socket handle
+
+ union Events {
+ u32 hex; ///< The complete value formed by the flags
+ BitField<0, 1, u32> pollin;
+ BitField<1, 1, u32> pollpri;
+ BitField<2, 1, u32> pollhup;
+ BitField<3, 1, u32> pollerr;
+ BitField<4, 1, u32> pollout;
+ BitField<5, 1, u32> pollnval;
+
+ Events& operator=(const Events& other) {
+ hex = other.hex;
+ return *this;
+ }
+
+ /// Translates the resulting events of a Poll operation from platform-specific to 3ds specific
+ static Events TranslateTo3DS(u32 input_event) {
+ Events ev = {};
+ if (input_event & POLLIN)
+ ev.pollin = 1;
+ if (input_event & POLLPRI)
+ ev.pollpri = 1;
+ if (input_event & POLLHUP)
+ ev.pollhup = 1;
+ if (input_event & POLLERR)
+ ev.pollerr = 1;
+ if (input_event & POLLOUT)
+ ev.pollout = 1;
+ if (input_event & POLLNVAL)
+ ev.pollnval = 1;
+ return ev;
+ }
+
+ /// Translates the resulting events of a Poll operation from 3ds specific to platform specific
+ static u32 TranslateToPlatform(Events input_event) {
+ u32 ret = 0;
+ if (input_event.pollin)
+ ret |= POLLIN;
+ if (input_event.pollpri)
+ ret |= POLLPRI;
+ if (input_event.pollhup)
+ ret |= POLLHUP;
+ if (input_event.pollerr)
+ ret |= POLLERR;
+ if (input_event.pollout)
+ ret |= POLLOUT;
+ if (input_event.pollnval)
+ ret |= POLLNVAL;
+ return ret;
+ }
+ };
+ Events events; ///< Events to poll for (input)
+ Events revents; ///< Events received (output)
+
+ /// Converts a platform-specific pollfd to a 3ds specific structure
+ static CTRPollFD FromPlatform(pollfd const& fd) {
+ CTRPollFD result;
+ result.events.hex = Events::TranslateTo3DS(fd.events).hex;
+ result.revents.hex = Events::TranslateTo3DS(fd.revents).hex;
+ result.fd = static_cast<u32>(fd.fd);
+ return result;
+ }
+
+ /// Converts a 3ds specific pollfd to a platform-specific structure
+ static pollfd ToPlatform(CTRPollFD const& fd) {
+ pollfd result;
+ result.events = Events::TranslateToPlatform(fd.events);
+ result.revents = Events::TranslateToPlatform(fd.revents);
+ result.fd = fd.fd;
+ return result;
+ }
+};
+
+/// Union to represent the 3ds' sockaddr structure
+union CTRSockAddr {
+ /// Structure to represent a raw sockaddr
+ struct {
+ u8 len; ///< The length of the entire structure, only the set fields count
+ u8 sa_family; ///< The address family of the sockaddr
+ u8 sa_data[0x1A]; ///< The extra data, this varies, depending on the address family
+ } raw;
+
+ /// Structure to represent the 3ds' sockaddr_in structure
+ struct CTRSockAddrIn {
+ u8 len; ///< The length of the entire structure
+ u8 sin_family; ///< The address family of the sockaddr_in
+ u16 sin_port; ///< The port associated with this sockaddr_in
+ u32 sin_addr; ///< The actual address of the sockaddr_in
+ } in;
+
+ /// Convert a 3DS CTRSockAddr to a platform-specific sockaddr
+ static sockaddr ToPlatform(CTRSockAddr const& ctr_addr) {
+ sockaddr result;
+ result.sa_family = ctr_addr.raw.sa_family;
+ memset(result.sa_data, 0, sizeof(result.sa_data));
+
+ // We can not guarantee ABI compatibility between platforms so we copy the fields manually
+ switch (result.sa_family) {
+ case AF_INET:
+ {
+ sockaddr_in* result_in = reinterpret_cast<sockaddr_in*>(&result);
+ result_in->sin_port = ctr_addr.in.sin_port;
+ result_in->sin_addr.s_addr = ctr_addr.in.sin_addr;
+ memset(result_in->sin_zero, 0, sizeof(result_in->sin_zero));
+ break;
+ }
+ default:
+ _dbg_assert_msg_(Service_SOC, false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform");
+ break;
+ }
+ return result;
+ }
+
+ /// Convert a platform-specific sockaddr to a 3DS CTRSockAddr
+ static CTRSockAddr FromPlatform(sockaddr const& addr) {
+ CTRSockAddr result;
+ result.raw.sa_family = static_cast<u8>(addr.sa_family);
+ // We can not guarantee ABI compatibility between platforms so we copy the fields manually
+ switch (result.raw.sa_family) {
+ case AF_INET:
+ {
+ sockaddr_in const* addr_in = reinterpret_cast<sockaddr_in const*>(&addr);
+ result.raw.len = sizeof(CTRSockAddrIn);
+ result.in.sin_port = addr_in->sin_port;
+ result.in.sin_addr = addr_in->sin_addr.s_addr;
+ break;
+ }
+ default:
+ _dbg_assert_msg_(Service_SOC, false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform");
+ break;
+ }
+ return result;
+ }
+};
+
+/// Holds info about the currently open sockets
+static std::unordered_map<u32, SocketHolder> open_sockets;
+
+/// Close all open sockets
+static void CleanupSockets() {
+ for (auto sock : open_sockets)
+ closesocket(sock.second.socket_fd);
+ open_sockets.clear();
+}
+
+static void Socket(Service::Interface* self) {
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ u32 domain = cmd_buffer[1]; // Address family
+ u32 type = cmd_buffer[2];
+ u32 protocol = cmd_buffer[3];
+
+ // Only 0 is allowed according to 3dbrew, using 0 will let the OS decide which protocol to use
+ if (protocol != 0) {
+ cmd_buffer[1] = UnimplementedFunction(ErrorModule::SOC).raw; // TODO(Subv): Correct error code
+ return;
+ }
+
+ if (domain != AF_INET) {
+ cmd_buffer[1] = UnimplementedFunction(ErrorModule::SOC).raw; // TODO(Subv): Correct error code
+ return;
+ }
+
+ if (type != SOCK_DGRAM && type != SOCK_STREAM) {
+ cmd_buffer[1] = UnimplementedFunction(ErrorModule::SOC).raw; // TODO(Subv): Correct error code
+ return;
+ }
+
+ u32 socket_handle = static_cast<u32>(::socket(domain, type, protocol));
+
+ if (socket_handle != SOCKET_ERROR_VALUE)
+ open_sockets[socket_handle] = { socket_handle, true };
+
+ int result = 0;
+ if (socket_handle == SOCKET_ERROR_VALUE)
+ result = TranslateError(GET_ERRNO);
+
+ cmd_buffer[1] = result;
+ cmd_buffer[2] = socket_handle;
+}
+
+static void Bind(Service::Interface* self) {
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ u32 socket_handle = cmd_buffer[1];
+ u32 len = cmd_buffer[2];
+ CTRSockAddr* ctr_sock_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[6]));
+
+ if (ctr_sock_addr == nullptr) {
+ cmd_buffer[1] = -1; // TODO(Subv): Correct code
+ return;
+ }
+
+ sockaddr sock_addr = CTRSockAddr::ToPlatform(*ctr_sock_addr);
+
+ int res = ::bind(socket_handle, &sock_addr, std::max<u32>(sizeof(sock_addr), len));
+
+ int result = 0;
+ if (res != 0)
+ result = TranslateError(GET_ERRNO);
+
+ cmd_buffer[2] = res;
+ cmd_buffer[1] = result;
+}
+
+static void Fcntl(Service::Interface* self) {
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ u32 socket_handle = cmd_buffer[1];
+ u32 ctr_cmd = cmd_buffer[2];
+ u32 ctr_arg = cmd_buffer[3];
+
+ int result = 0;
+ u32 posix_ret = 0; // TODO: Check what hardware returns for F_SETFL (unspecified by POSIX)
+ SCOPE_EXIT({
+ cmd_buffer[1] = result;
+ cmd_buffer[2] = posix_ret;
+ });
+
+ if (ctr_cmd == 3) { // F_GETFL
+#if EMU_PLATFORM == PLATFORM_WINDOWS
+ posix_ret = 0;
+ auto iter = open_sockets.find(socket_handle);
+ if (iter != open_sockets.end() && iter->second.blocking == false)
+ posix_ret |= 4; // O_NONBLOCK
+#else
+ int ret = ::fcntl(socket_handle, F_GETFL, 0);
+ if (ret == SOCKET_ERROR_VALUE) {
+ result = TranslateError(GET_ERRNO);
+ posix_ret = -1;
+ return;
+ }
+ posix_ret = 0;
+ if (ret & O_NONBLOCK)
+ posix_ret |= 4; // O_NONBLOCK
+#endif
+ } else if (ctr_cmd == 4) { // F_SETFL
+#if EMU_PLATFORM == PLATFORM_WINDOWS
+ unsigned long tmp = (ctr_arg & 4 /* O_NONBLOCK */) ? 1 : 0;
+ int ret = ioctlsocket(socket_handle, FIONBIO, &tmp);
+ if (ret == SOCKET_ERROR_VALUE) {
+ result = TranslateError(GET_ERRNO);
+ posix_ret = -1;
+ return;
+ }
+ auto iter = open_sockets.find(socket_handle);
+ if (iter != open_sockets.end())
+ iter->second.blocking = (tmp == 0);
+#else
+ int flags = ::fcntl(socket_handle, F_GETFL, 0);
+ if (flags == SOCKET_ERROR_VALUE) {
+ result = TranslateError(GET_ERRNO);
+ posix_ret = -1;
+ return;
+ }
+
+ flags &= ~O_NONBLOCK;
+ if (ctr_arg & 4) // O_NONBLOCK
+ flags |= O_NONBLOCK;
+
+ int ret = ::fcntl(socket_handle, F_SETFL, flags);
+ if (ret == SOCKET_ERROR_VALUE) {
+ result = TranslateError(GET_ERRNO);
+ posix_ret = -1;
+ return;
+ }
+#endif
+ } else {
+ LOG_ERROR(Service_SOC, "Unsupported command (%d) in fcntl call");
+ result = TranslateError(EINVAL); // TODO: Find the correct error
+ posix_ret = -1;
+ return;
+ }
+}
+
+static void Listen(Service::Interface* self) {
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ u32 socket_handle = cmd_buffer[1];
+ u32 backlog = cmd_buffer[2];
+
+ int ret = ::listen(socket_handle, backlog);
+ int result = 0;
+ if (ret != 0)
+ result = TranslateError(GET_ERRNO);
+
+ cmd_buffer[2] = ret;
+ cmd_buffer[1] = result;
+}
+
+static void Accept(Service::Interface* self) {
+ // TODO(Subv): Calling this function on a blocking socket will block the emu thread,
+ // preventing graceful shutdown when closing the emulator, this can be fixed by always
+ // performing nonblocking operations and spinlock until the data is available
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ u32 socket_handle = cmd_buffer[1];
+ socklen_t max_addr_len = static_cast<socklen_t>(cmd_buffer[2]);
+ sockaddr addr;
+ socklen_t addr_len = sizeof(addr);
+ u32 ret = static_cast<u32>(::accept(socket_handle, &addr, &addr_len));
+
+ if (ret != SOCKET_ERROR_VALUE)
+ open_sockets[ret] = { ret, true };
+
+ int result = 0;
+ if (ret == SOCKET_ERROR_VALUE) {
+ result = TranslateError(GET_ERRNO);
+ } else {
+ CTRSockAddr ctr_addr = CTRSockAddr::FromPlatform(addr);
+ Memory::WriteBlock(cmd_buffer[0x104 >> 2], (const u8*)&ctr_addr, max_addr_len);
+ }
+
+ cmd_buffer[2] = ret;
+ cmd_buffer[1] = result;
+}
+
+static void GetHostId(Service::Interface* self) {
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+
+ char name[128];
+ gethostname(name, sizeof(name));
+ hostent* host = gethostbyname(name);
+ in_addr* addr = reinterpret_cast<in_addr*>(host->h_addr);
+
+ cmd_buffer[2] = addr->s_addr;
+ cmd_buffer[1] = 0;
+}
+
+static void Close(Service::Interface* self) {
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ u32 socket_handle = cmd_buffer[1];
+
+ int ret = 0;
+ open_sockets.erase(socket_handle);
+
+ ret = closesocket(socket_handle);
+
+ int result = 0;
+ if (ret != 0)
+ result = TranslateError(GET_ERRNO);
+
+ cmd_buffer[2] = ret;
+ cmd_buffer[1] = result;
+}
+
+static void SendTo(Service::Interface* self) {
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ u32 socket_handle = cmd_buffer[1];
+ u32 len = cmd_buffer[2];
+ u32 flags = cmd_buffer[3];
+ u32 addr_len = cmd_buffer[4];
+
+ u8* input_buff = Memory::GetPointer(cmd_buffer[8]);
+ CTRSockAddr* ctr_dest_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[10]));
+
+ if (ctr_dest_addr == nullptr) {
+ cmd_buffer[1] = -1; // TODO(Subv): Find the right error code
+ return;
+ }
+
+ int ret = -1;
+ if (addr_len > 0) {
+ sockaddr dest_addr = CTRSockAddr::ToPlatform(*ctr_dest_addr);
+ ret = ::sendto(socket_handle, (const char*)input_buff, len, flags, &dest_addr, sizeof(dest_addr));
+ } else {
+ ret = ::sendto(socket_handle, (const char*)input_buff, len, flags, nullptr, 0);
+ }
+
+ int result = 0;
+ if (ret == SOCKET_ERROR_VALUE)
+ result = TranslateError(GET_ERRNO);
+
+ cmd_buffer[2] = ret;
+ cmd_buffer[1] = result;
+}
+
+static void RecvFrom(Service::Interface* self) {
+ // TODO(Subv): Calling this function on a blocking socket will block the emu thread,
+ // preventing graceful shutdown when closing the emulator, this can be fixed by always
+ // performing nonblocking operations and spinlock until the data is available
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ u32 socket_handle = cmd_buffer[1];
+ u32 len = cmd_buffer[2];
+ u32 flags = cmd_buffer[3];
+ socklen_t addr_len = static_cast<socklen_t>(cmd_buffer[4]);
+
+ u8* output_buff = Memory::GetPointer(cmd_buffer[0x104 >> 2]);
+ sockaddr src_addr;
+ socklen_t src_addr_len = sizeof(src_addr);
+ int ret = ::recvfrom(socket_handle, (char*)output_buff, len, flags, &src_addr, &src_addr_len);
+
+ if (cmd_buffer[0x1A0 >> 2] != 0) {
+ CTRSockAddr* ctr_src_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[0x1A0 >> 2]));
+ *ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
+ }
+
+ int result = 0;
+ int total_received = ret;
+ if (ret == SOCKET_ERROR_VALUE) {
+ result = TranslateError(GET_ERRNO);
+ total_received = 0;
+ }
+
+ cmd_buffer[1] = result;
+ cmd_buffer[2] = ret;
+ cmd_buffer[3] = total_received;
+}
+
+static void Poll(Service::Interface* self) {
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ u32 nfds = cmd_buffer[1];
+ int timeout = cmd_buffer[2];
+ CTRPollFD* input_fds = reinterpret_cast<CTRPollFD*>(Memory::GetPointer(cmd_buffer[6]));
+ CTRPollFD* output_fds = reinterpret_cast<CTRPollFD*>(Memory::GetPointer(cmd_buffer[0x104 >> 2]));
+
+ // The 3ds_pollfd and the pollfd structures may be different (Windows/Linux have different sizes)
+ // so we have to copy the data
+ pollfd* platform_pollfd = new pollfd[nfds];
+ for (unsigned current_fds = 0; current_fds < nfds; ++current_fds)
+ platform_pollfd[current_fds] = CTRPollFD::ToPlatform(input_fds[current_fds]);
+
+ int ret = ::poll(platform_pollfd, nfds, timeout);
+
+ // Now update the output pollfd structure
+ for (unsigned current_fds = 0; current_fds < nfds; ++current_fds)
+ output_fds[current_fds] = CTRPollFD::FromPlatform(platform_pollfd[current_fds]);
+
+ delete[] platform_pollfd;
+
+ int result = 0;
+ if (ret == SOCKET_ERROR_VALUE)
+ result = TranslateError(GET_ERRNO);
+
+ cmd_buffer[1] = result;
+ cmd_buffer[2] = ret;
+}
+
+static void GetSockName(Service::Interface* self) {
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ u32 socket_handle = cmd_buffer[1];
+ socklen_t ctr_len = cmd_buffer[2];
+
+ CTRSockAddr* ctr_dest_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[0x104 >> 2]));
+
+ sockaddr dest_addr;
+ socklen_t dest_addr_len = sizeof(dest_addr);
+ int ret = ::getsockname(socket_handle, &dest_addr, &dest_addr_len);
+
+ if (ctr_dest_addr != nullptr) {
+ *ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
+ } else {
+ cmd_buffer[1] = -1; // TODO(Subv): Verify error
+ return;
+ }
+
+ int result = 0;
+ if (ret != 0)
+ result = TranslateError(GET_ERRNO);
+
+ cmd_buffer[2] = ret;
+ cmd_buffer[1] = result;
+}
+
+static void Shutdown(Service::Interface* self) {
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ u32 socket_handle = cmd_buffer[1];
+ int how = cmd_buffer[2];
+
+ int ret = ::shutdown(socket_handle, how);
+ int result = 0;
+ if (ret != 0)
+ result = TranslateError(GET_ERRNO);
+ cmd_buffer[2] = ret;
+ cmd_buffer[1] = result;
+}
+
+static void GetPeerName(Service::Interface* self) {
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ u32 socket_handle = cmd_buffer[1];
+ socklen_t len = cmd_buffer[2];
+
+ CTRSockAddr* ctr_dest_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[0x104 >> 2]));
+
+ sockaddr dest_addr;
+ socklen_t dest_addr_len = sizeof(dest_addr);
+ int ret = ::getpeername(socket_handle, &dest_addr, &dest_addr_len);
+
+ if (ctr_dest_addr != nullptr) {
+ *ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
+ } else {
+ cmd_buffer[1] = -1;
+ return;
+ }
+
+ int result = 0;
+ if (ret != 0)
+ result = TranslateError(GET_ERRNO);
+
+ cmd_buffer[2] = ret;
+ cmd_buffer[1] = result;
+}
+
+static void Connect(Service::Interface* self) {
+ // TODO(Subv): Calling this function on a blocking socket will block the emu thread,
+ // preventing graceful shutdown when closing the emulator, this can be fixed by always
+ // performing nonblocking operations and spinlock until the data is available
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ u32 socket_handle = cmd_buffer[1];
+ socklen_t len = cmd_buffer[2];
+
+ CTRSockAddr* ctr_input_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[6]));
+ if (ctr_input_addr == nullptr) {
+ cmd_buffer[1] = -1; // TODO(Subv): Verify error
+ return;
+ }
+
+ sockaddr input_addr = CTRSockAddr::ToPlatform(*ctr_input_addr);
+ int ret = ::connect(socket_handle, &input_addr, sizeof(input_addr));
+ int result = 0;
+ if (ret != 0)
+ result = TranslateError(GET_ERRNO);
+ cmd_buffer[2] = ret;
+ cmd_buffer[1] = result;
+}
+
+static void InitializeSockets(Service::Interface* self) {
+ // TODO(Subv): Implement
+#if EMU_PLATFORM == PLATFORM_WINDOWS
+ WSADATA data;
+ WSAStartup(MAKEWORD(2, 2), &data);
+#endif
+
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ cmd_buffer[1] = 0;
+}
+
+static void ShutdownSockets(Service::Interface* self) {
+ // TODO(Subv): Implement
+ CleanupSockets();
+
+#if EMU_PLATFORM == PLATFORM_WINDOWS
+ WSACleanup();
+#endif
+
+ u32* cmd_buffer = Kernel::GetCommandBuffer();
+ cmd_buffer[1] = 0;
+}
+
const Interface::FunctionInfo FunctionTable[] = {
- {0x00010044, nullptr, "InitializeSockets"},
- {0x000200C2, nullptr, "socket"},
- {0x00030082, nullptr, "listen"},
- {0x00040082, nullptr, "accept"},
- {0x00050084, nullptr, "bind"},
- {0x00060084, nullptr, "connect"},
+ {0x00010044, InitializeSockets, "InitializeSockets"},
+ {0x000200C2, Socket, "Socket"},
+ {0x00030082, Listen, "Listen"},
+ {0x00040082, Accept, "Accept"},
+ {0x00050084, Bind, "Bind"},
+ {0x00060084, Connect, "Connect"},
{0x00070104, nullptr, "recvfrom_other"},
- {0x00080102, nullptr, "recvfrom"},
+ {0x00080102, RecvFrom, "RecvFrom"},
{0x00090106, nullptr, "sendto_other"},
- {0x000A0106, nullptr, "sendto"},
- {0x000B0042, nullptr, "close"},
- {0x000C0082, nullptr, "shutdown"},
- {0x000D0082, nullptr, "gethostbyname"},
- {0x000E00C2, nullptr, "gethostbyaddr"},
+ {0x000A0106, SendTo, "SendTo"},
+ {0x000B0042, Close, "Close"},
+ {0x000C0082, Shutdown, "Shutdown"},
+ {0x000D0082, nullptr, "GetHostByName"},
+ {0x000E00C2, nullptr, "GetHostByAddr"},
{0x000F0106, nullptr, "unknown_resolve_ip"},
- {0x00110102, nullptr, "getsockopt"},
- {0x00120104, nullptr, "setsockopt"},
- {0x001300C2, nullptr, "fcntl"},
- {0x00140084, nullptr, "poll"},
- {0x00150042, nullptr, "sockatmark"},
- {0x00160000, nullptr, "gethostid"},
- {0x00170082, nullptr, "getsockname"},
- {0x00180082, nullptr, "getpeername"},
- {0x00190000, nullptr, "ShutdownSockets"},
+ {0x00110102, nullptr, "GetSockOpt"},
+ {0x00120104, nullptr, "SetSockOpt"},
+ {0x001300C2, Fcntl, "Fcntl"},
+ {0x00140084, Poll, "Poll"},
+ {0x00150042, nullptr, "SockAtMark"},
+ {0x00160000, GetHostId, "GetHostId"},
+ {0x00170082, GetSockName, "GetSockName"},
+ {0x00180082, GetPeerName, "GetPeerName"},
+ {0x00190000, ShutdownSockets, "ShutdownSockets"},
{0x001A00C0, nullptr, "GetNetworkOpt"},
{0x001B0040, nullptr, "ICMPSocket"},
{0x001C0104, nullptr, "ICMPPing"},
@@ -52,4 +724,11 @@ Interface::Interface() {
Register(FunctionTable, ARRAY_SIZE(FunctionTable));
}
+Interface::~Interface() {
+ CleanupSockets();
+#if EMU_PLATFORM == PLATFORM_WINDOWS
+ WSACleanup();
+#endif
+}
+
} // namespace