network: handle SO_NBIO on non-Windows hosts (#954)

* network: handle SO_NBIO on non-Windows hosts

Setsockopt only translated the guest SOL_SOCKET/SO_NBIO (0x1200) option
under _WIN32; other hosts rejected it with ENOPROTOOPT. FMOD Studio's Live
Update listener sets SO_NBIO, so on Linux its socket setup failed and the
Studio async manager latched FMOD_ERR_NET_CONNECT. Every blocking Studio
call then failed: loadBankFile returned FMOD_ERR_NOTREADY for all banks,
leaving the game silent and stuck on the splash screen waiting for
RuntimeManager.HaveAllBanksLoaded (PPSA28997).

Toggle O_NONBLOCK with fcntl on non-Windows hosts.

* network: map connection errors from POSIX socket calls

ConvertHostSocketError only covered a handful of errno values on
non-Windows hosts, so anything else reached the guest as EIO. With
SO_NBIO now honoured there, a nonblocking connect() that is still in
progress returned EIO instead of EINPROGRESS (and EALREADY on retry).

Map the same connection and network errors the Windows branch already
translates.

* network: share nonblocking socket error handling

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
This commit is contained in:
Aryan Sharma
2026-10-01 19:24:21 +05:30
committed by GitHub
parent b7a1fac898
commit 44798085bd
+29 -7
View File
@@ -997,14 +997,31 @@ static int ConvertHostSocketError(int error) {
case EAGAIN: posix_error = Posix::POSIX_EWOULDBLOCK; break;
case EBADF: posix_error = Posix::POSIX_EBADF; break;
case EFAULT: posix_error = Posix::POSIX_EFAULT; break;
case EINTR: posix_error = Posix::POSIX_EINTR; break;
case EINVAL: posix_error = Posix::POSIX_EINVAL; break;
case EISCONN: posix_error = Posix::POSIX_EISCONN; break;
case EMFILE: posix_error = Posix::POSIX_EMFILE; break;
case EMSGSIZE: posix_error = Posix::POSIX_EMSGSIZE; break;
case ENFILE: posix_error = Posix::POSIX_ENFILE; break;
case ENOBUFS: posix_error = Posix::POSIX_ENOBUFS; break;
case ENOMEM: posix_error = Posix::POSIX_ENOMEM; break;
case ENETDOWN: posix_error = Posix::POSIX_ENETDOWN; break;
case ENETRESET: posix_error = Posix::POSIX_ENETRESET; break;
case ENETUNREACH: posix_error = Posix::POSIX_ENETUNREACH; break;
case ENOTCONN: posix_error = Posix::POSIX_ENOTCONN; break;
case ENOTSOCK: posix_error = Posix::POSIX_ENOTSOCK; break;
case EOPNOTSUPP: posix_error = Posix::POSIX_EOPNOTSUPP; break;
case EPIPE: posix_error = Posix::POSIX_EPIPE; break;
case EPROTONOSUPPORT: posix_error = Posix::POSIX_EPROTONOSUPPORT; break;
case ESHUTDOWN: posix_error = Posix::POSIX_ESHUTDOWN; break;
case ETIMEDOUT: posix_error = Posix::POSIX_ETIMEDOUT; break;
case ECONNABORTED: posix_error = Posix::POSIX_ECONNABORTED; break;
case ECONNREFUSED: posix_error = Posix::POSIX_ECONNREFUSED; break;
case ECONNRESET: posix_error = Posix::POSIX_ECONNRESET; break;
case EDESTADDRREQ: posix_error = Posix::POSIX_EDESTADDRREQ; break;
case EHOSTUNREACH: posix_error = Posix::POSIX_EHOSTUNREACH; break;
case EINPROGRESS: posix_error = Posix::POSIX_EINPROGRESS; break;
case EALREADY: posix_error = Posix::POSIX_EALREADY; break;
default: break;
}
#endif
@@ -2098,17 +2115,22 @@ int KYTY_SYSV_ABI Setsockopt(int s, int level, int optname, const void* optval,
return 0;
}
#if defined(_WIN32)
constexpr int ORBIS_SO_NBIO = 0x1200;
if (ConvertSocketOptionLevel(level) == SOL_SOCKET && optname == ORBIS_SO_NBIO &&
optlen >= sizeof(int)) {
u_long enabled = (*static_cast<const int*>(optval) != 0 ? 1 : 0);
if (ioctlsocket(socket, FIONBIO, &enabled) == SOCKET_ERROR) {
return SetHostSocketError();
}
return 0;
}
const bool enabled = *static_cast<const int*>(optval) != 0;
#if defined(_WIN32)
u_long mode = enabled ? 1 : 0;
const bool failed = ioctlsocket(socket, FIONBIO, &mode) == SOCKET_ERROR;
#else
const int flags = ::fcntl(socket, F_GETFL, 0);
const bool failed =
flags < 0 ||
::fcntl(socket, F_SETFL, enabled ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)) != 0;
#endif
return failed ? SetHostSocketError() : 0;
}
#if !defined(_WIN32)
// Guest TCP options: IPPROTO_TCP=6, TCP_NODELAY=1.
if (level != 6 || optname != 1) {
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);