From 31c3dfd2fa3e7de733b1e82a2643f295793899f5 Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Fri, 16 Jan 2026 17:41:32 +0900 Subject: [PATCH] [Net] Convert sockets to asio Makes the socket layer cross-platform while retaining mostly BSD socket style API. --- src/xenia/base/platform.h | 6 +- src/xenia/base/platform_win.h | 2 + src/xenia/kernel/premake5.lua | 13 + src/xenia/kernel/xam/apps/xlivebase_app.cc | 8 +- src/xenia/kernel/xam/xam_net.cc | 8 +- src/xenia/kernel/xsocket.cc | 601 +++++++++++++++------ src/xenia/kernel/xsocket.h | 37 +- 7 files changed, 483 insertions(+), 192 deletions(-) diff --git a/src/xenia/base/platform.h b/src/xenia/base/platform.h index 7525f3419..d02a5b4de 100644 --- a/src/xenia/base/platform.h +++ b/src/xenia/base/platform.h @@ -81,9 +81,13 @@ #endif #if XE_PLATFORM_WIN32 +#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX #define NOMINMAX // Don't want windows.h including min/max macros. -#endif // XE_PLATFORM_WIN32 +#endif +#endif // XE_PLATFORM_WIN32 #if XE_PLATFORM_WIN32 #include diff --git a/src/xenia/base/platform_win.h b/src/xenia/base/platform_win.h index 8a798a3ff..6a9f60f9d 100644 --- a/src/xenia/base/platform_win.h +++ b/src/xenia/base/platform_win.h @@ -19,7 +19,9 @@ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif +#ifndef NOMINMAX #define NOMINMAX +#endif #include #include #include diff --git a/src/xenia/kernel/premake5.lua b/src/xenia/kernel/premake5.lua index f478c9f22..96336a893 100644 --- a/src/xenia/kernel/premake5.lua +++ b/src/xenia/kernel/premake5.lua @@ -6,6 +6,19 @@ project("xenia-kernel") uuid("ae185c4a-1c4f-4503-9892-328e549e871a") kind("StaticLib") language("C++") + includedirs({ + project_root.."/third_party/asio/include", + }) + defines({ + "ASIO_STANDALONE", + "ASIO_NO_DEPRECATED", + }) + filter("platforms:Windows") + defines({ + "_WIN32_WINNT=0x0A00", -- Windows 10+ for Asio + "_WINSOCK_DEPRECATED_NO_WARNINGS", -- Suppress deprecated Winsock API warnings + }) + filter({}) links({ "aes_128", "fmt", diff --git a/src/xenia/kernel/xam/apps/xlivebase_app.cc b/src/xenia/kernel/xam/apps/xlivebase_app.cc index 32eb4d99c..a6407384d 100644 --- a/src/xenia/kernel/xam/apps/xlivebase_app.cc +++ b/src/xenia/kernel/xam/apps/xlivebase_app.cc @@ -11,13 +11,7 @@ #include "xenia/base/logging.h" -#ifdef XE_PLATFORM_WIN32 -// NOTE: must be included last as it expects windows.h to already be included. -#define _WINSOCK_DEPRECATED_NO_WARNINGS // inet_addr -#include // NOLINT(build/include_order) -#elif XE_PLATFORM_LINUX -#include -#endif +#include struct XONLINE_SERVICE_INFO { xe::be id; diff --git a/src/xenia/kernel/xam/xam_net.cc b/src/xenia/kernel/xam/xam_net.cc index ec4393740..29ff109ba 100644 --- a/src/xenia/kernel/xam/xam_net.cc +++ b/src/xenia/kernel/xam/xam_net.cc @@ -21,8 +21,10 @@ #ifdef XE_PLATFORM_WIN32 // NOTE: must be included last as it expects windows.h to already be included. +#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS #define _WINSOCK_DEPRECATED_NO_WARNINGS // inet_addr -#include // NOLINT(build/include_order) +#endif +#include // NOLINT(build/include_order) #elif XE_PLATFORM_LINUX #include #include @@ -323,9 +325,7 @@ DECLARE_XAM_EXPORT1(NetDll_WSACleanup, kNetworking, kImplemented); // Xbox shares space between normal error codes and WSA errors. // This under the hood returns directly value received from RtlGetLastError. dword_result_t NetDll_WSAGetLastError_entry() { - uint32_t last_error = XThread::GetLastError(); - XELOGD("NetDll_WSAGetLastError: {}", last_error); - return last_error; + return XThread::GetLastError(); } DECLARE_XAM_EXPORT1(NetDll_WSAGetLastError, kNetworking, kImplemented); diff --git a/src/xenia/kernel/xsocket.cc b/src/xenia/kernel/xsocket.cc index 72d7ccd2e..be4dd7fbb 100644 --- a/src/xenia/kernel/xsocket.cc +++ b/src/xenia/kernel/xsocket.cc @@ -7,7 +7,7 @@ ****************************************************************************** */ -#include "src/xenia/kernel/xsocket.h" +#include "xenia/kernel/xsocket.h" #include @@ -16,88 +16,130 @@ #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/xam/xam_module.h" -#ifdef XE_PLATFORM_WIN32 -// clang-format off -#include "xenia/base/platform_win.h" -#include -#include -// clang-format on -#else -#include -#include -#include -#include -#include -#include -#include -#endif - namespace xe { namespace kernel { +// Shared io_context for all sockets +static asio::io_context& GetIoContext() { + static asio::io_context io_context; + return io_context; +} + // Translate socket options to native // Note: // SO_DONTLINGER = ~SO_LINGER // SO_EXCLUSIVEADDRUSE = ~SO_REUSEADDR // TODO: Check SO_DONTLINGER and SO_EXCLUSIVEADDRUSE usage on linux -const std::map supported_socket_options = { +const std::map supported_socket_options = { {0x0004, SO_REUSEADDR}, {0x0020, SO_BROADCAST}, {0x0080, SO_LINGER}, {0x1001, SO_SNDBUF}, {0x1002, SO_RCVBUF}, {0x1005, SO_SNDTIMEO}, - {0x1006, SO_RCVTIMEO}, {~0x0080, ~SO_LINGER}, {~0x0004, ~SO_REUSEADDR}}; + {0x1006, SO_RCVTIMEO}, {~0x0080u, ~SO_LINGER}, {~0x0004u, ~SO_REUSEADDR}}; // Translate socket TCP options to native -const std::map supported_tcp_options = { - {0x0001, TCP_NODELAY}}; - -// Translate socket levels to native -const std::map supported_levels = {{0xFFFF, SOL_SOCKET}, - {0x6, IPPROTO_TCP}}; +const std::map supported_tcp_options = {{0x0001, TCP_NODELAY}}; // Translate ioctl commands to native const std::map supported_controls = { {0x8004667E, FIONBIO}, {0x4004667F, FIONREAD}}; +// Translate socket levels to native +const std::map supported_levels = {{0xFFFF, SOL_SOCKET}, + {0x6, IPPROTO_TCP}}; + +// TODO(Gliniak): Provide error mapping table. +// Xbox error codes might not match with what we receive from OS. +// TODO(has207): On Linux, asio returns POSIX errno values which games won't +// understand. Needs POSIX -> WSAError mapping for proper cross-platform +// support. +uint32_t AsioErrorToWSAError(const asio::error_code& ec) { + if (!ec) return 0; + return static_cast(ec.value()); +} + XSocket::XSocket(KernelState* kernel_state) : XObject(kernel_state, kObjectType) {} -XSocket::XSocket(KernelState* kernel_state, uint64_t native_handle) - : XObject(kernel_state, kObjectType), native_handle_(native_handle) {} +XSocket::XSocket(KernelState* kernel_state, asio::ip::tcp::socket socket) + : XObject(kernel_state, kObjectType), tcp_socket_(std::move(socket)) { + af_ = AddressFamily::X_AF_INET; + type_ = Type::X_SOCK_STREAM; + proto_ = Protocol::X_IPPROTO_TCP; +} XSocket::~XSocket() { Close(); } +uint64_t XSocket::native_handle() { + if (tcp_socket_ && tcp_socket_->is_open()) { + return static_cast(tcp_socket_->native_handle()); + } + if (udp_socket_ && udp_socket_->is_open()) { + return static_cast(udp_socket_->native_handle()); + } + if (acceptor_ && acceptor_->is_open()) { + return static_cast(acceptor_->native_handle()); + } + return static_cast(-1); +} + X_STATUS XSocket::Initialize(AddressFamily af, Type type, Protocol proto) { af_ = af; type_ = type; proto_ = proto; - if (proto == Protocol::IPPROTO_VDP) { + if (proto == Protocol::X_IPPROTO_VDP) { // VDP is a layer on top of UDP. - proto = Protocol::IPPROTO_UDP; + proto = Protocol::X_IPPROTO_UDP; } - native_handle_ = socket(af, type, proto); - if (native_handle_ == -1) { + asio::error_code ec; + + if (type == Type::X_SOCK_STREAM) { + tcp_socket_.emplace(GetIoContext()); + tcp_socket_->open(asio::ip::tcp::v4(), ec); + } else if (type == Type::X_SOCK_DGRAM) { + udp_socket_.emplace(GetIoContext()); + udp_socket_->open(asio::ip::udp::v4(), ec); + } else { + return X_STATUS_INVALID_PARAMETER; + } + + if (ec) { + last_error_ = AsioErrorToWSAError(ec); return X_STATUS_UNSUCCESSFUL; } // Allow port reuse so that in-process relaunches can rebind ports // immediately without waiting for TIME_WAIT to expire. int reuse = 1; - setsockopt(native_handle_, SOL_SOCKET, SO_REUSEADDR, + setsockopt(static_cast(native_handle()), SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuse), sizeof(reuse)); return X_STATUS_SUCCESS; } X_STATUS XSocket::Close() { -#if XE_PLATFORM_WIN32 - int ret = closesocket(native_handle_); -#elif XE_PLATFORM_LINUX - int ret = close(native_handle_); -#endif + asio::error_code ec; - if (ret != 0) { - return X_STATUS_UNSUCCESSFUL; + if (tcp_socket_) { + if (tcp_socket_->is_open()) { + tcp_socket_->shutdown(asio::socket_base::shutdown_both, ec); + tcp_socket_->close(ec); + } + tcp_socket_.reset(); + } + + if (udp_socket_) { + if (udp_socket_->is_open()) { + udp_socket_->close(ec); + } + udp_socket_.reset(); + } + + if (acceptor_) { + if (acceptor_->is_open()) { + acceptor_->close(ec); + } + acceptor_.reset(); } return X_STATUS_SUCCESS; @@ -105,15 +147,44 @@ X_STATUS XSocket::Close() { X_STATUS XSocket::GetOption(uint32_t level, uint32_t optname, void* optval_ptr, uint32_t* optlen) { - int ret = - getsockopt(native_handle_, level, optname, static_cast(optval_ptr), - reinterpret_cast(optlen)); + if (!tcp_socket_ && !udp_socket_) { + return X_STATUS_INVALID_HANDLE; + } + + // Map Xbox socket levels to native + int native_level = level; + if (supported_levels.contains(level)) { + native_level = supported_levels.at(level); + } + + // Map Xbox socket options to native + int native_optname = optname; + if (level == 0xFFFF && supported_socket_options.contains(optname)) { + native_optname = supported_socket_options.at(optname); + } else if (level == IPPROTO_TCP && supported_tcp_options.contains(optname)) { + native_optname = supported_tcp_options.at(optname); + } + + asio::error_code ec; + socklen_t native_optlen = static_cast(*optlen); + + int native_handle_val = static_cast(native_handle()); + if (native_handle_val == -1) { + return X_STATUS_INVALID_HANDLE; + } + + int ret = getsockopt(native_handle_val, native_level, native_optname, + static_cast(optval_ptr), &native_optlen); if (ret < 0) { - // TODO: WSAGetLastError() + last_error_ = AsioErrorToWSAError( + asio::error_code(errno, asio::error::get_system_category())); return X_STATUS_UNSUCCESSFUL; } + + *optlen = static_cast(native_optlen); return X_STATUS_SUCCESS; } + X_STATUS XSocket::SetOption(uint32_t level, uint32_t optname, void* optval_ptr, uint32_t optlen) { if (level == 0xFFFF && (optname == 0x5801 || optname == 0x5802)) { @@ -122,37 +193,35 @@ X_STATUS XSocket::SetOption(uint32_t level, uint32_t optname, void* optval_ptr, return X_STATUS_SUCCESS; } + if (!tcp_socket_ && !udp_socket_) { + return X_STATUS_INVALID_HANDLE; + } + + // Map Xbox socket levels to native int native_level = level; - - assert_false(!supported_levels.contains(level)); - if (supported_levels.contains(level)) { native_level = supported_levels.at(level); } + // Map Xbox socket options to native int native_optname = optname; - - if (level == 0xFFFF) { - assert_false(!supported_socket_options.contains(optname)); - - if (supported_socket_options.contains(optname)) { - native_optname = supported_socket_options.at(optname); - } + if (level == 0xFFFF && supported_socket_options.contains(optname)) { + native_optname = supported_socket_options.at(optname); + } else if (level == IPPROTO_TCP && supported_tcp_options.contains(optname)) { + native_optname = supported_tcp_options.at(optname); } - if (level == IPPROTO_TCP) { - assert_false(!supported_tcp_options.contains(optname)); - - if (supported_tcp_options.contains(optname)) { - native_optname = supported_tcp_options.at(optname); - } + int native_handle_val = static_cast(native_handle()); + if (native_handle_val == -1) { + return X_STATUS_INVALID_HANDLE; } - int ret = setsockopt(native_handle_, native_level, native_optname, + int ret = setsockopt(native_handle_val, native_level, native_optname, static_cast(optval_ptr), optlen); if (ret < 0) { - // TODO: WSAGetLastError() - XELOGE("XSocket::SetOption: failed with error {:08X}", GetLastWSAError()); + last_error_ = AsioErrorToWSAError( + asio::error_code(errno, asio::error::get_system_category())); + XELOGE("XSocket::SetOption: failed with error {:08X}", last_error_); return X_STATUS_UNSUCCESSFUL; } @@ -165,35 +234,73 @@ X_STATUS XSocket::SetOption(uint32_t level, uint32_t optname, void* optval_ptr, } X_STATUS XSocket::IOControl(uint32_t cmd, uint8_t* arg_ptr) { -#ifdef XE_PLATFORM_WIN32 - int ret = ioctlsocket(native_handle_, cmd, (u_long*)arg_ptr); - if (ret < 0) { - // TODO: Get last error - return X_STATUS_UNSUCCESSFUL; - } - return X_STATUS_SUCCESS; -#elif XE_PLATFORM_LINUX - int native_cmd = cmd; - - assert_false(!supported_controls.contains(cmd)); - - if (supported_controls.contains(cmd)) { - native_cmd = supported_controls.at(cmd); + if (!tcp_socket_ && !udp_socket_) { + return X_STATUS_INVALID_HANDLE; } - int ret = ioctl(native_handle_, native_cmd, arg_ptr); + asio::error_code ec; - if (ret < 0) { - return X_STATUS_UNSUCCESSFUL; + // FIONBIO - set non-blocking mode + if (cmd == 0x8004667E) { + uint32_t value = *reinterpret_cast(arg_ptr); + bool non_blocking = (value != 0); + + if (tcp_socket_) { + tcp_socket_->non_blocking(non_blocking, ec); + } else if (udp_socket_) { + udp_socket_->non_blocking(non_blocking, ec); + } + + if (ec) { + last_error_ = AsioErrorToWSAError(ec); + return X_STATUS_UNSUCCESSFUL; + } + return X_STATUS_SUCCESS; } - return X_STATUS_SUCCESS; -#endif + // FIONREAD - get bytes available + if (cmd == 0x4004667F) { + size_t available = 0; + if (tcp_socket_) { + available = tcp_socket_->available(ec); + } else if (udp_socket_) { + available = udp_socket_->available(ec); + } + + if (ec) { + last_error_ = AsioErrorToWSAError(ec); + return X_STATUS_UNSUCCESSFUL; + } + + *reinterpret_cast(arg_ptr) = static_cast(available); + return X_STATUS_SUCCESS; + } + + XELOGE("XSocket::IOControl: unsupported command {:08X}", cmd); + return X_STATUS_INVALID_PARAMETER; } X_STATUS XSocket::Connect(N_XSOCKADDR* name, int name_len) { - int ret = connect(native_handle_, (sockaddr*)name, name_len); - if (ret < 0) { + if (!tcp_socket_ && !udp_socket_) { + return X_STATUS_INVALID_HANDLE; + } + + auto* addr_in = reinterpret_cast(name); + asio::ip::address_v4 addr(addr_in->sin_addr); + uint16_t port = addr_in->sin_port; + + asio::error_code ec; + + if (tcp_socket_) { + asio::ip::tcp::endpoint endpoint(addr, port); + tcp_socket_->connect(endpoint, ec); + } else if (udp_socket_) { + asio::ip::udp::endpoint endpoint(addr, port); + udp_socket_->connect(endpoint, ec); + } + + if (ec) { + last_error_ = AsioErrorToWSAError(ec); return X_STATUS_UNSUCCESSFUL; } @@ -201,33 +308,79 @@ X_STATUS XSocket::Connect(N_XSOCKADDR* name, int name_len) { } X_STATUS XSocket::Bind(N_XSOCKADDR_IN* name, int name_len) { + if (!tcp_socket_ && !udp_socket_) { + return X_STATUS_INVALID_HANDLE; + } + // On Linux and Windows (when running under Wine), ports < 1024 require root // privileges. Remap to port + 10000 to avoid privilege issues. - // Note: sin_port is xe::be which automatically handles endianness, - // so we use it directly without ntohs/htons. - const uint16_t original_port = uint16_t(name->sin_port); - if (original_port < 1024) { + // Note: N_XSOCKADDR_IN uses xe::be<> which auto-converts to/from host endian. + const uint16_t original_port = static_cast(name->sin_port); + if (original_port < 1024 && original_port != 0) { uint16_t new_port = original_port + 10000; name->sin_port = new_port; XELOGW("XSocket::Bind: port {} requires privileges, remapping to port {}", original_port, new_port); } - int ret = bind(native_handle_, (sockaddr*)name, name_len); + asio::ip::address_v4 addr(name->sin_addr); + uint16_t port = name->sin_port; - if (ret < 0) { + asio::error_code ec; + + if (tcp_socket_) { + asio::ip::tcp::endpoint endpoint(addr, port); + tcp_socket_->bind(endpoint, ec); + } else if (udp_socket_) { + asio::ip::udp::endpoint endpoint(addr, port); + udp_socket_->bind(endpoint, ec); + } + + if (ec) { + last_error_ = AsioErrorToWSAError(ec); return X_STATUS_UNSUCCESSFUL; } bound_ = true; - bound_port_ = name->sin_port; + + // Get the actual bound port (important when binding to port 0) + if (tcp_socket_) { + bound_port_ = tcp_socket_->local_endpoint(ec).port(); + } else if (udp_socket_) { + bound_port_ = udp_socket_->local_endpoint(ec).port(); + } + + if (ec) { + bound_port_ = port; + } return X_STATUS_SUCCESS; } X_STATUS XSocket::Listen(int backlog) { - int ret = listen(native_handle_, backlog); - if (ret < 0) { + if (!tcp_socket_) { + return X_STATUS_INVALID_HANDLE; + } + + asio::error_code ec; + + // Create an acceptor and transfer the bound socket's native handle to it + acceptor_.emplace(GetIoContext()); + acceptor_->assign(asio::ip::tcp::v4(), tcp_socket_->native_handle(), ec); + if (ec) { + last_error_ = AsioErrorToWSAError(ec); + acceptor_.reset(); + return X_STATUS_UNSUCCESSFUL; + } + + // Release the socket's handle since the acceptor now owns it + tcp_socket_->release(); + tcp_socket_.reset(); + + // Start listening + acceptor_->listen(backlog, ec); + if (ec) { + last_error_ = AsioErrorToWSAError(ec); return X_STATUS_UNSUCCESSFUL; } @@ -235,104 +388,191 @@ X_STATUS XSocket::Listen(int backlog) { } object_ref XSocket::Accept(N_XSOCKADDR* name, int* name_len) { - sockaddr n_sockaddr; - socklen_t n_name_len = sizeof(sockaddr); - uintptr_t ret = accept(native_handle_, &n_sockaddr, &n_name_len); - if (ret == -1) { - std::memset(name, 0, *name_len); + if (!acceptor_) { + return nullptr; + } + + asio::error_code ec; + + // Accept a new connection + asio::ip::tcp::socket new_socket(GetIoContext()); + asio::ip::tcp::endpoint peer_endpoint; + acceptor_->accept(new_socket, peer_endpoint, ec); + + if (ec) { + last_error_ = AsioErrorToWSAError(ec); + if (name) { + std::memset(name, 0, *name_len); + } *name_len = 0; return nullptr; } - std::memcpy(name, &n_sockaddr, n_name_len); - *name_len = n_name_len; + // Fill in the client address + if (name && *name_len >= static_cast(sizeof(sockaddr_in))) { + sockaddr_in* addr = reinterpret_cast(name); + addr->sin_family = AF_INET; + addr->sin_port = htons(peer_endpoint.port()); + addr->sin_addr.s_addr = htonl(peer_endpoint.address().to_v4().to_uint()); + std::memset(reinterpret_cast(addr) + 8, 0, 8); // Zero sin_zero + } + *name_len = sizeof(sockaddr_in); - // Create a kernel object to represent the new socket, and copy parameters - // over. - auto socket = object_ref(new XSocket(kernel_state_, ret)); - socket->af_ = af_; - socket->type_ = type_; - socket->proto_ = proto_; + // Create a kernel object to represent the new socket + auto socket = + object_ref(new XSocket(kernel_state_, std::move(new_socket))); + socket->bound_ = true; return socket; } -int XSocket::Shutdown(int how) { return shutdown(native_handle_, how); } +int XSocket::Shutdown(int how) { + if (!tcp_socket_ && !udp_socket_) { + return -1; + } + + asio::error_code ec; + asio::socket_base::shutdown_type shutdown_type; + + switch (how) { + case 0: + shutdown_type = asio::socket_base::shutdown_receive; + break; + case 1: + shutdown_type = asio::socket_base::shutdown_send; + break; + case 2: + default: + shutdown_type = asio::socket_base::shutdown_both; + break; + } + + if (tcp_socket_) { + tcp_socket_->shutdown(shutdown_type, ec); + } + // UDP sockets don't support shutdown (connectionless protocol) + + if (ec) { + last_error_ = AsioErrorToWSAError(ec); + return -1; + } + + return 0; +} int XSocket::Recv(uint8_t* buf, uint32_t buf_len, uint32_t flags) { - return recv(native_handle_, reinterpret_cast(buf), buf_len, flags); + if (!tcp_socket_ && !udp_socket_) { + return -1; + } + + asio::error_code ec; + size_t bytes_received = 0; + + if (udp_socket_) { + bytes_received = + udp_socket_->receive(asio::buffer(buf, buf_len), flags, ec); + } else if (tcp_socket_) { + bytes_received = + tcp_socket_->receive(asio::buffer(buf, buf_len), flags, ec); + } + + if (ec) { + last_error_ = AsioErrorToWSAError(ec); + return -1; + } + + return static_cast(bytes_received); } int XSocket::RecvFrom(uint8_t* buf, uint32_t buf_len, uint32_t flags, N_XSOCKADDR_IN* from, uint32_t* from_len) { - // Pop from secure packets first - // TODO(DrChat): Enable when I commit XNet - /* - { - std::lock_guard lock(incoming_packet_mutex_); - if (incoming_packets_.size()) { - packet* pkt = (packet*)incoming_packets_.front(); - int data_len = pkt->data_len; - std::memcpy(buf, pkt->data, std::min((uint32_t)pkt->data_len, buf_len)); + if (!udp_socket_ && !tcp_socket_) { + return -1; + } - from->sin_family = 2; - from->sin_addr = pkt->src_ip; - from->sin_port = pkt->src_port; + asio::error_code ec; + size_t bytes_received = 0; - incoming_packets_.pop(); - uint8_t* pkt_ui8 = (uint8_t*)pkt; - delete[] pkt_ui8; + if (udp_socket_) { + asio::ip::udp::endpoint sender_endpoint; + bytes_received = udp_socket_->receive_from(asio::buffer(buf, buf_len), + sender_endpoint, 0, ec); - return data_len; + if (!ec && from) { + from->sin_family = AF_INET; + from->sin_addr = sender_endpoint.address().to_v4().to_uint(); + from->sin_port = sender_endpoint.port(); + std::memset(from->x_sin_zero, 0, sizeof(from->x_sin_zero)); } - } - */ - - sockaddr_in nfrom; - socklen_t nfromlen = sizeof(sockaddr_in); - int ret = recvfrom(native_handle_, reinterpret_cast(buf), buf_len, - flags, (sockaddr*)&nfrom, &nfromlen); - if (from) { - from->sin_family = nfrom.sin_family; - from->sin_addr = ntohl(nfrom.sin_addr.s_addr); // BE <- BE - from->sin_port = nfrom.sin_port; - std::memset(from->x_sin_zero, 0, sizeof(from->x_sin_zero)); + if (from_len) { + *from_len = sizeof(N_XSOCKADDR_IN); + } + } else if (tcp_socket_) { + bytes_received = + tcp_socket_->receive(asio::buffer(buf, buf_len), flags, ec); } - if (from_len) { - *from_len = nfromlen; + if (ec) { + last_error_ = AsioErrorToWSAError(ec); + return -1; } - return ret; + return static_cast(bytes_received); } int XSocket::Send(const uint8_t* buf, uint32_t buf_len, uint32_t flags) { - return send(native_handle_, reinterpret_cast(buf), buf_len, - flags); + if (!tcp_socket_ && !udp_socket_) { + return -1; + } + + asio::error_code ec; + size_t bytes_sent = 0; + + if (udp_socket_) { + bytes_sent = udp_socket_->send(asio::buffer(buf, buf_len), flags, ec); + } else if (tcp_socket_) { + bytes_sent = tcp_socket_->send(asio::buffer(buf, buf_len), flags, ec); + } + + if (ec) { + last_error_ = AsioErrorToWSAError(ec); + return -1; + } + + return static_cast(bytes_sent); } int XSocket::SendTo(uint8_t* buf, uint32_t buf_len, uint32_t flags, N_XSOCKADDR_IN* to, uint32_t to_len) { - // Send 2 copies of the packet: One to XNet (for network security) and an - // unencrypted copy for other Xenia hosts. - // TODO(DrChat): Enable when I commit XNet. - /* - auto xam = kernel_state()->GetKernelModule("xam.xex"); - auto xnet = xam->xnet(); - if (xnet) { - xnet->SendPacket(this, to, buf, buf_len); - } - */ - - sockaddr_in nto; - if (to) { - nto.sin_addr.s_addr = to->sin_addr; - nto.sin_family = to->sin_family; - nto.sin_port = to->sin_port; + if (!udp_socket_ && !tcp_socket_) { + return -1; } - return sendto(native_handle_, reinterpret_cast(buf), buf_len, flags, - to ? (sockaddr*)&nto : nullptr, to_len); + asio::error_code ec; + size_t bytes_sent = 0; + + if (udp_socket_) { + if (to) { + asio::ip::address_v4 addr(to->sin_addr); + uint16_t port = to->sin_port; + asio::ip::udp::endpoint endpoint(addr, port); + + bytes_sent = + udp_socket_->send_to(asio::buffer(buf, buf_len), endpoint, flags, ec); + } else { + // Send to connected endpoint + bytes_sent = udp_socket_->send(asio::buffer(buf, buf_len), flags, ec); + } + } else if (tcp_socket_) { + bytes_sent = tcp_socket_->send(asio::buffer(buf, buf_len), flags, ec); + } + + if (ec) { + last_error_ = AsioErrorToWSAError(ec); + return -1; + } + + return static_cast(bytes_sent); } bool XSocket::QueuePacket(uint32_t src_ip, uint16_t src_port, @@ -341,36 +581,55 @@ bool XSocket::QueuePacket(uint32_t src_ip, uint16_t src_port, pkt->src_ip = src_ip; pkt->src_port = src_port; - pkt->data_len = (uint16_t)len; + pkt->data_len = static_cast(len); std::memcpy(pkt->data, buf, len); std::lock_guard lock(incoming_packet_mutex_); - incoming_packets_.push((uint8_t*)pkt); + incoming_packets_.push(reinterpret_cast(pkt)); // TODO: Limit on number of incoming packets? return true; } X_STATUS XSocket::GetSockName(uint8_t* buf, int* buf_len) { - struct sockaddr sa = {}; + auto handle = native_handle(); + if (handle == static_cast(-1)) { + return X_STATUS_INVALID_HANDLE; + } - int ret = getsockname(native_handle_, &sa, (socklen_t*)buf_len); - if (ret < 0) { + socklen_t len = static_cast(*buf_len); + int result = getsockname(static_cast(handle), + reinterpret_cast(buf), &len); + if (result == -1) { + last_error_ = AsioErrorToWSAError( + asio::error_code(errno, asio::error::get_system_category())); return X_STATUS_UNSUCCESSFUL; } - std::memcpy(buf, &sa, *buf_len); + *buf_len = static_cast(len); return X_STATUS_SUCCESS; } -uint32_t XSocket::GetLastWSAError() const { - // Todo(Gliniak): Provide error mapping table - // Xbox error codes might not match with what we receive from OS -#ifdef XE_PLATFORM_WIN32 - return WSAGetLastError(); -#endif - return errno; +X_STATUS XSocket::GetPeerName(uint8_t* buf, int* buf_len) { + auto handle = native_handle(); + if (handle == static_cast(-1)) { + return X_STATUS_INVALID_HANDLE; + } + + socklen_t len = static_cast(*buf_len); + int result = getpeername(static_cast(handle), + reinterpret_cast(buf), &len); + if (result == -1) { + last_error_ = AsioErrorToWSAError( + asio::error_code(errno, asio::error::get_system_category())); + return X_STATUS_UNSUCCESSFUL; + } + + *buf_len = static_cast(len); + return X_STATUS_SUCCESS; } +uint32_t XSocket::GetLastWSAError() const { return last_error_; } + } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/xsocket.h b/src/xenia/kernel/xsocket.h index 10dcf42f1..441baae1d 100644 --- a/src/xenia/kernel/xsocket.h +++ b/src/xenia/kernel/xsocket.h @@ -10,7 +10,11 @@ #ifndef XENIA_KERNEL_XSOCKET_H_ #define XENIA_KERNEL_XSOCKET_H_ +// Asio must be included before Windows headers to avoid macro conflicts +#include + #include +#include #include #include "xenia/base/byte_order.h" @@ -80,29 +84,33 @@ class XSocket : public XObject { public: static const XObject::Type kObjectType = XObject::Type::Socket; + // Note: These enum values use X_ prefix to avoid conflicts with Windows + // macros enum AddressFamily { - AF_INET = 2, + X_AF_INET = 2, }; enum Type { - SOCK_STREAM = 1, - SOCK_DGRAM = 2, + X_SOCK_STREAM = 1, + X_SOCK_DGRAM = 2, }; enum Protocol { - IPPROTO_TCP = 6, - IPPROTO_UDP = 17, + X_IPPROTO_TCP = 6, + X_IPPROTO_UDP = 17, // LIVE Voice and Data Protocol // https://blog.csdn.net/baozi3026/article/details/4277227 // Format: [cbGameData][GameData(encrypted)][VoiceData(unencrypted)] - IPPROTO_VDP = 254, + X_IPPROTO_VDP = 254, }; XSocket(KernelState* kernel_state); ~XSocket(); - uint64_t native_handle() const { return native_handle_; } + // Returns the native socket handle for use with select() etc. + // Returns -1 if socket is not initialized. + uint64_t native_handle(); uint16_t bound_port() const { return bound_port_; } X_STATUS Initialize(AddressFamily af, Type type, Protocol proto); @@ -118,6 +126,7 @@ class XSocket : public XObject { X_STATUS Bind(N_XSOCKADDR_IN* name, int name_len); X_STATUS Listen(int backlog); X_STATUS GetSockName(uint8_t* buf, int* buf_len); + X_STATUS GetPeerName(uint8_t* buf, int* buf_len); object_ref Accept(N_XSOCKADDR* name, int* name_len); int Shutdown(int how); @@ -145,8 +154,15 @@ class XSocket : public XObject { size_t len); private: - XSocket(KernelState* kernel_state, uint64_t native_handle); - uint64_t native_handle_ = -1; + // Private constructor for accepted sockets + XSocket(KernelState* kernel_state, asio::ip::tcp::socket socket); + + // Socket storage - either TCP or UDP + std::optional tcp_socket_; + std::optional udp_socket_; + + // Acceptor for listening TCP sockets (created when Listen() is called) + std::optional acceptor_; AddressFamily af_; // Address family Type type_; // Type (DGRAM/Stream/etc) @@ -158,6 +174,9 @@ class XSocket : public XObject { bool broadcast_socket_ = false; + // Last error code for this socket + mutable uint32_t last_error_ = 0; + std::unique_ptr event_; std::mutex incoming_packet_mutex_; std::queue incoming_packets_;