[Kernel] Add Linux socket support and ensure binding low ports is remapped.

Remapping ports on both Linux and Windows to ensure games boot under
wine as well.
This commit is contained in:
Herman S.
2025-10-03 10:15:03 +09:00
committed by Radosław Gliński
parent 344e8538e0
commit 4af2fd6513
2 changed files with 138 additions and 25 deletions

View File

@@ -248,38 +248,42 @@ DECLARE_XAM_EXPORT1(NetDll_XNetRandom, kNetworking, kStub);
dword_result_t NetDll_WSAStartup_entry(dword_t caller, word_t version,
pointer_t<X_WSADATA> data_ptr) {
// TODO(benvanik): abstraction layer needed.
// TODO(benvanik): abstraction layer needed.
int ret = 0;
#ifdef XE_PLATFORM_WIN32
WSADATA wsaData;
ZeroMemory(&wsaData, sizeof(WSADATA));
int ret = WSAStartup(version, &wsaData);
auto data_out = kernel_state()->memory()->TranslateVirtual(data_ptr);
ret = WSAStartup(version, &wsaData);
#endif
if (data_ptr) {
auto data_out = kernel_state()->memory()->TranslateVirtual(data_ptr);
#ifdef XE_PLATFORM_WIN32
data_ptr->version = wsaData.wVersion;
data_ptr->version_high = wsaData.wHighVersion;
std::memcpy(&data_ptr->description, wsaData.szDescription, 0x100);
std::memcpy(&data_ptr->system_status, wsaData.szSystemStatus, 0x80);
data_ptr->max_sockets = wsaData.iMaxSockets;
data_ptr->max_udpdg = wsaData.iMaxUdpDg;
std::memcpy(&data_ptr->description, wsaData.szDescription, 0x100);
std::memcpy(&data_ptr->system_status, wsaData.szSystemStatus, 0x80);
#else
// Match Windows behavior with reasonable values
data_ptr->version = version.value();
data_ptr->version_high = version.value();
data_ptr->max_sockets = 100;
data_ptr->max_udpdg = 1024;
// WinSock 2.2 typically returns empty strings for these fields
std::memset(&data_ptr->description, 0, 0x100);
std::memset(&data_ptr->system_status, 0, 0x80);
#endif
// Some games (5841099F) want this value round-tripped - they'll compare if
// it changes and bugcheck if it does.
uint32_t vendor_ptr = xe::load_and_swap<uint32_t>(data_out + 0x190);
xe::store_and_swap<uint32_t>(data_out + 0x190, vendor_ptr);
// vendor_info_ptr is at offset 0x18A (after max_udpdg at 0x188)
uint32_t vendor_ptr = xe::load_and_swap<uint32_t>(data_out + 0x18A);
xe::store_and_swap<uint32_t>(data_out + 0x18A, vendor_ptr);
}
#else
int ret = 0;
if (data_ptr) {
// Guess these values!
data_ptr->version = version.value();
data_ptr->description[0] = '\0';
data_ptr->system_status[0] = '\0';
data_ptr->max_sockets = 100;
data_ptr->max_udpdg = 1024;
}
#endif
// DEBUG
/*
@@ -659,6 +663,8 @@ dword_result_t NetDll_socket_entry(dword_t caller, dword_t af, dword_t type,
socket->Release();
XThread::SetLastError(socket->GetLastWSAError());
XELOGE("NetDll_socket: failed with error {:08X}",
socket->GetLastWSAError());
return -1;
}
@@ -742,6 +748,8 @@ dword_result_t NetDll_ioctlsocket_entry(dword_t caller, dword_t socket_handle,
X_STATUS status = socket->IOControl(cmd, arg_ptr);
if (XFAILED(status)) {
XThread::SetLastError(socket->GetLastWSAError());
XELOGE("NetDll_ioctlsocket: failed with error {:08X}",
socket->GetLastWSAError());
return -1;
}
@@ -757,6 +765,7 @@ dword_result_t NetDll_bind_entry(dword_t caller, dword_t socket_handle,
kernel_state()->object_table()->LookupObject<XSocket>(socket_handle);
if (!socket) {
XThread::SetLastError(uint32_t(X_WSAError::X_WSAENOTSOCK));
XELOGE("NetDll_bind: failed - invalid socket");
return -1;
}
@@ -764,6 +773,7 @@ dword_result_t NetDll_bind_entry(dword_t caller, dword_t socket_handle,
X_STATUS status = socket->Bind(&native_name, namelen);
if (XFAILED(status)) {
XThread::SetLastError(socket->GetLastWSAError());
XELOGE("NetDll_bind: failed with status {:08X}", status);
return -1;
}
@@ -1027,7 +1037,8 @@ dword_result_t NetDll_sendto_entry(dword_t caller, dword_t socket_handle,
}
N_XSOCKADDR_IN native_to(to_ptr);
return socket->SendTo(buf_ptr, buf_len, flags, &native_to, to_len);
int ret = socket->SendTo(buf_ptr, buf_len, flags, &native_to, to_len);
return ret;
}
DECLARE_XAM_EXPORT1(NetDll_sendto, kNetworking, kImplemented);

View File

@@ -11,6 +11,7 @@
#include <cstring>
#include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/xam/xam_module.h"
@@ -25,6 +26,7 @@
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
@@ -91,10 +93,73 @@ X_STATUS XSocket::SetOption(uint32_t level, uint32_t optname, void* optval_ptr,
return X_STATUS_SUCCESS;
}
int ret =
setsockopt(native_handle_, level, optname, (char*)optval_ptr, optlen);
// Translate Xbox socket level to native
int native_level = level;
if (level == 0xFFFF) { // Xbox SOL_SOCKET
native_level = SOL_SOCKET;
}
// Translate Xbox socket options to native
// Xbox uses Winsock constants which mostly match standard values
int native_optname = optname;
if (level == 0xFFFF) {
switch (optname) {
case 0x0001: // SO_DEBUG
native_optname = SO_DEBUG;
break;
case 0x0002: // SO_ACCEPTCONN
native_optname = SO_ACCEPTCONN;
break;
case 0x0004: // SO_REUSEADDR
native_optname = SO_REUSEADDR;
break;
case 0x0008: // SO_KEEPALIVE
native_optname = SO_KEEPALIVE;
break;
case 0x0010: // SO_DONTROUTE
native_optname = SO_DONTROUTE;
break;
case 0x0020: // SO_BROADCAST
native_optname = SO_BROADCAST;
break;
case 0x0080: // SO_LINGER
native_optname = SO_LINGER;
break;
case 0x0100: // SO_OOBINLINE
native_optname = SO_OOBINLINE;
break;
case 0x1001: // SO_SNDBUF
native_optname = SO_SNDBUF;
break;
case 0x1002: // SO_RCVBUF
native_optname = SO_RCVBUF;
break;
case 0x1003: // SO_SNDLOWAT
native_optname = SO_SNDLOWAT;
break;
case 0x1004: // SO_RCVLOWAT
native_optname = SO_RCVLOWAT;
break;
case 0x1005: // SO_SNDTIMEO
native_optname = SO_SNDTIMEO;
break;
case 0x1006: // SO_RCVTIMEO
native_optname = SO_RCVTIMEO;
break;
case 0x1007: // SO_ERROR
native_optname = SO_ERROR;
break;
case 0x1008: // SO_TYPE
native_optname = SO_TYPE;
break;
// Add more translations as needed
}
}
int ret = setsockopt(native_handle_, native_level, native_optname,
static_cast<char*>(optval_ptr), optlen);
if (ret < 0) {
// TODO: WSAGetLastError()
XELOGE("XSocket::SetOption: setsockopt failed, errno={}", errno);
return X_STATUS_UNSUCCESSFUL;
}
@@ -113,10 +178,29 @@ X_STATUS XSocket::IOControl(uint32_t cmd, uint8_t* arg_ptr) {
// TODO: Get last error
return X_STATUS_UNSUCCESSFUL;
}
return X_STATUS_SUCCESS;
#elif XE_PLATFORM_LINUX
return X_STATUS_UNSUCCESSFUL;
// Translate Xbox/Windows ioctl commands to Linux equivalents
int native_cmd = cmd;
switch (cmd) {
case 0x8004667E: // Windows FIONBIO - set non-blocking mode
native_cmd = FIONBIO;
break;
case 0x4004667F: // Windows FIONREAD - get bytes available
native_cmd = FIONREAD;
break;
default:
XELOGW("XSocket::IOControl: unknown cmd={:08X}, passing through", cmd);
break;
}
int ret = ioctl(native_handle_, native_cmd, arg_ptr);
if (ret < 0) {
XELOGE("XSocket::IOControl: ioctl failed, cmd={:08X} -> {:08X}, errno={}",
cmd, native_cmd, errno);
return X_STATUS_UNSUCCESSFUL;
}
return X_STATUS_SUCCESS;
#endif
}
@@ -130,8 +214,26 @@ X_STATUS XSocket::Connect(N_XSOCKADDR* name, int name_len) {
}
X_STATUS XSocket::Bind(N_XSOCKADDR_IN* name, int name_len) {
// 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<uint16_t> 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) {
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);
if (ret < 0) {
#ifdef XE_PLATFORM_WIN32
XELOGE("XSocket::Bind: bind() failed with WSA error {}", WSAGetLastError());
#else
XELOGE("XSocket::Bind: bind() failed with errno={}", errno);
#endif
return X_STATUS_UNSUCCESSFUL;
}