Compare commits

...

2 Commits

Author SHA1 Message Date
nmzik e679486ed3 net: translate native UDP socket options and microsecond timeouts 2026-10-06 23:41:06 +04:00
Henrique Reis Neves 6ef406542c tests: make kyty_tests build and run every test without LTO (#1110)
avplayer_file_tests compiles libs/avPlayer.cpp into the test, and its
PRINT_NAME() calls Loader::Timer::GetTimeMs(). Add loader/timer.cpp to the
target, like the other tests that compile HLE library sources. CI links the
test with LTO, which drops the unused reference, so the failure only shows
in non-LTO builds such as the README's no-Qt instructions.

Clang with libstdc++ rejects optional<IndirectDescriptor>::emplace() with
no arguments in ShaderRecompilerComputeTests.cpp. Assign the descriptor the
same way the neighbouring test does.

trophy_system is registered with ctest but kyty_tests did not build its
executable, so ctest reported it as Not Run.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:56:48 +04:00
4 changed files with 181 additions and 15 deletions
+5 -1
View File
@@ -419,7 +419,10 @@ add_executable(trophy_system_tests EXCLUDE_FROM_ALL
target_link_libraries(trophy_system_tests common)
target_include_directories(trophy_system_tests PRIVATE ${inc_headers})
add_executable(avplayer_file_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/AvPlayerFileTests.cpp")
add_executable(avplayer_file_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/AvPlayerFileTests.cpp"
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
)
target_link_libraries(avplayer_file_tests common FFmpeg::ffmpeg ZArchive::zarchive)
target_include_directories(avplayer_file_tests PRIVATE ${inc_headers})
@@ -689,6 +692,7 @@ if(BUILD_TESTING)
memory_tracker_tests
page_manager_tests
archive_file_tests
trophy_system_tests
avplayer_file_tests
bit_array_tests
lru_cache_tests
+84 -12
View File
@@ -992,6 +992,7 @@ static int ConvertHostSocketError(int error) {
case WSAENETUNREACH: posix_error = Posix::POSIX_ENETUNREACH; break;
case WSAENOTCONN: posix_error = Posix::POSIX_ENOTCONN; break;
case WSAENOTSOCK: posix_error = Posix::POSIX_ENOTSOCK; break;
case WSAENOPROTOOPT: posix_error = Posix::POSIX_ENOPROTOOPT; break;
case WSAEOPNOTSUPP: posix_error = Posix::POSIX_EOPNOTSUPP; break;
case WSAEPROTONOSUPPORT: posix_error = Posix::POSIX_EPROTONOSUPPORT; break;
case WSAESHUTDOWN: posix_error = Posix::POSIX_ESHUTDOWN; break;
@@ -1028,6 +1029,7 @@ static int ConvertHostSocketError(int error) {
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 ENOPROTOOPT: posix_error = Posix::POSIX_ENOPROTOOPT; 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;
@@ -1066,6 +1068,26 @@ static int ConvertSocketOptionLevel(int level) {
return (level == 0xffff ? SOL_SOCKET : level);
}
static int ConvertSocketOptionName(int level, int option) {
if (level == 0xffff) {
switch (option) {
case 0x0020: return SO_BROADCAST;
case 0x1001: return SO_SNDBUF;
case 0x1002: return SO_RCVBUF;
case 0x1007: return SO_ERROR;
case 0x1105: return SO_SNDTIMEO;
default: break;
}
} else if (level == 6 && option == 1) {
return TCP_NODELAY;
}
#if defined(_WIN32)
return option;
#else
return -1;
#endif
}
static int* P2pSocketOption(P2pEndpoint& endpoint, int option) {
switch (option) {
case 0x0020: return &endpoint.broadcast;
@@ -2149,21 +2171,41 @@ int KYTY_SYSV_ABI Getsockopt(int s, int level, int optname, void* optval, uint32
return 0;
}
// Guest socket options: SOL_SOCKET=0xffff, SO_ERROR=0x1007.
const bool socket_error = (level == 0xffff && optname == 0x1007);
#if !defined(_WIN32)
if (!socket_error) {
const bool send_timeout = (level == 0xffff && optname == 0x1105);
optname = ConvertSocketOptionName(level, optname);
if (optname < 0) {
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
}
#if defined(_WIN32)
DWORD timeout {};
#else
timeval timeout {};
#endif
if (socket_error) {
optname = SO_ERROR;
}
SocketLength len = static_cast<SocketLength>(*optlen);
if (::getsockopt(socket, ConvertSocketOptionLevel(level), optname, static_cast<char*>(optval),
void* value = optval;
if (send_timeout) {
if (*optlen < sizeof(int)) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
value = &timeout;
len = sizeof(timeout);
}
if (::getsockopt(socket, ConvertSocketOptionLevel(level), optname, static_cast<char*>(value),
&len) != 0) {
return SetHostSocketError();
}
if (send_timeout) {
#if defined(_WIN32)
const int64_t usec = static_cast<int64_t>(timeout) * 1000;
#else
const int64_t usec = static_cast<int64_t>(timeout.tv_sec) * 1'000'000 + timeout.tv_usec;
#endif
const int guest_timeout =
static_cast<int>(std::min<int64_t>(usec, std::numeric_limits<int>::max()));
std::memcpy(optval, &guest_timeout, sizeof(guest_timeout));
len = sizeof(guest_timeout);
}
if (socket_error && len >= static_cast<SocketLength>(sizeof(int))) {
auto* error = static_cast<int*>(optval);
*error = ConvertHostSocketError(*error);
@@ -2215,6 +2257,18 @@ int KYTY_SYSV_ABI Setsockopt(int s, int level, int optname, const void* optval,
return 0;
}
#if defined(__linux__)
if (level == 0xffff && optname == 0x10000) {
if (optlen != sizeof(int)) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
int enabled = 0;
std::memcpy(&enabled, optval, sizeof(enabled));
// Native sends preserve 255.255.255.255 without changing broadcast permission.
return enabled != 0 ? 0 : SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
}
#endif
constexpr int ORBIS_SO_NBIO = 0x1200;
if (ConvertSocketOptionLevel(level) == SOL_SOCKET && optname == ORBIS_SO_NBIO &&
optlen >= sizeof(int)) {
@@ -2230,14 +2284,32 @@ int KYTY_SYSV_ABI Setsockopt(int s, int level, int optname, const void* optval,
#endif
return failed ? SetHostSocketError() : 0;
}
#if !defined(_WIN32)
// Guest TCP options: IPPROTO_TCP=6, TCP_NODELAY=1.
if (level != 6 || optname != 1) {
const bool send_timeout = (level == 0xffff && optname == 0x1105);
optname = ConvertSocketOptionName(level, optname);
if (optname < 0) {
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
}
level = IPPROTO_TCP;
optname = TCP_NODELAY;
#if defined(_WIN32)
DWORD timeout {};
#else
timeval timeout {};
#endif
if (send_timeout) {
if (optlen != sizeof(int)) {
return SetGuestSocketError(Posix::POSIX_EINVAL);
}
int usec = 0;
std::memcpy(&usec, optval, sizeof(usec));
usec = std::max(usec, 0);
#if defined(_WIN32)
timeout = static_cast<DWORD>((static_cast<int64_t>(usec) + 999) / 1000);
#else
timeout.tv_sec = usec / 1'000'000;
timeout.tv_usec = usec % 1'000'000;
#endif
optval = &timeout;
optlen = sizeof(timeout);
}
if (::setsockopt(socket, ConvertSocketOptionLevel(level), optname,
static_cast<const char*>(optval), static_cast<SocketLength>(optlen)) != 0) {
+90 -1
View File
@@ -34,6 +34,11 @@
#include <thread>
#include <vector>
#if defined(__linux__)
#include <sys/mman.h>
#include <unistd.h>
#endif
namespace Libs::LibKernelApr {
void InitLibKernel_1_Apr(Loader::SymbolDatabase *symbols);
}
@@ -1065,6 +1070,8 @@ void CheckSocketWakeup() {
{"OXXX4mUk3uk", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
const auto *getsockopt_symbol = symbols.Find(
{"xphrZusl78E", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
const auto *setsockopt_symbol = symbols.Find(
{"2mKX2Spso7I", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
const auto *send_symbol = symbols.Find(
{"beRjXBn-z+o", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
const auto *sendto_symbol = symbols.Find(
@@ -1075,11 +1082,12 @@ void CheckSocketWakeup() {
{"304ooNZxWDY", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
const auto *errno_symbol = symbols.Find(
{"HQOwnfMGipQ", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
Check(connect_symbol && getsockopt_symbol && send_symbol && sendto_symbol &&
Check(connect_symbol && getsockopt_symbol && setsockopt_symbol && send_symbol && sendto_symbol &&
recv_symbol && recvfrom_symbol && errno_symbol,
"Net socket and errno exports resolve with the guest ABI versions");
using Connect = int (KYTY_SYSV_ABI *)(int, const void *, uint32_t);
using Getsockopt = int (KYTY_SYSV_ABI *)(int, int, int, void *, uint32_t *);
using Setsockopt = int (KYTY_SYSV_ABI *)(int, int, int, const void *, uint32_t);
using Send = int (KYTY_SYSV_ABI *)(int, const void *, size_t, int);
using Sendto = int (KYTY_SYSV_ABI *)(int, const void *, size_t, int, const void *, uint32_t);
using Recv = int (KYTY_SYSV_ABI *)(int, void *, size_t, int);
@@ -1087,6 +1095,7 @@ void CheckSocketWakeup() {
using Errno = int *(KYTY_SYSV_ABI *)();
const auto net_connect = reinterpret_cast<Connect>(connect_symbol->vaddr);
const auto net_getsockopt = reinterpret_cast<Getsockopt>(getsockopt_symbol->vaddr);
const auto net_setsockopt = reinterpret_cast<Setsockopt>(setsockopt_symbol->vaddr);
const auto net_send = reinterpret_cast<Send>(send_symbol->vaddr);
const auto net_sendto = reinterpret_cast<Sendto>(sendto_symbol->vaddr);
const auto net_recv = reinterpret_cast<Recv>(recv_symbol->vaddr);
@@ -1157,6 +1166,86 @@ void CheckSocketWakeup() {
Net::Getsockname(datagram_writer, expected_peer.data(), &expected_peer_size) == 0 &&
Net::Setsockopt(datagram, 0xffff, 0x1200, &enabled, sizeof(enabled)) == 0,
"create nonblocking loopback datagrams for Net ABI verification");
*net_errno = Libs::Posix::POSIX_EINVAL;
*Libs::Posix::GetErrorAddr() = Libs::Posix::POSIX_EINVAL;
for (const int option : {0x1001, 0x1002}) {
constexpr int requested = 16384;
int actual = 0;
uint32_t size = sizeof(actual);
Check(net_setsockopt(datagram, 0xffff, option, &requested, sizeof(requested)) == 0 &&
net_getsockopt(datagram, 0xffff, option, &actual, &size) == 0 &&
actual >= requested && size == sizeof(actual),
"Net UDP send and receive buffers accept guest option numbers");
}
for (const int value : {1, 0}) {
int actual = -1;
uint32_t size = sizeof(actual);
Check(net_setsockopt(datagram, 0xffff, 0x20, &value, sizeof(value)) == 0 &&
net_getsockopt(datagram, 0xffff, 0x20, &actual, &size) == 0 &&
actual == value && size == sizeof(actual),
"Net UDP broadcast option can be enabled and disabled");
}
int timeout = 0;
int *timeout_value = &timeout;
#if defined(__linux__)
int broadcast = -1;
uint32_t broadcast_size = sizeof(broadcast);
Check(net_setsockopt(datagram, 0xffff, 0x10000, &enabled, sizeof(enabled)) == 0 &&
net_getsockopt(datagram, 0xffff, 0x20, &broadcast, &broadcast_size) == 0 &&
broadcast == 0,
"preserving the all-ones destination does not enable broadcast permission");
const long page_size = sysconf(_SC_PAGESIZE);
Check(page_size > 0, "get host page size for socket timeout boundary");
void *timeout_pages = mmap(nullptr, page_size * 2, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Check(timeout_pages != MAP_FAILED &&
mprotect(static_cast<char *>(timeout_pages) + page_size, page_size,
PROT_NONE) == 0,
"guard memory after the four-byte socket timeout");
timeout_value = reinterpret_cast<int *>(static_cast<char *>(timeout_pages) +
page_size - sizeof(int));
#endif
for (const int value : {1500000, 0, -1}) {
*timeout_value = value;
Check(net_setsockopt(datagram, 0xffff, 0x1105, timeout_value, sizeof(int)) == 0,
"Net send timeout reads a four-byte microsecond value");
*timeout_value = -2;
uint32_t size = sizeof(int);
Check(net_getsockopt(datagram, 0xffff, 0x1105, timeout_value, &size) == 0 &&
*timeout_value == std::max(value, 0) && size == sizeof(int),
"Net send timeout returns four-byte microseconds and disables nonpositive values");
}
#if defined(__linux__)
Check(munmap(timeout_pages, page_size * 2) == 0, "free socket timeout guard pages");
#endif
Check(*net_errno == Libs::Posix::POSIX_EINVAL &&
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EINVAL,
"successful Net socket option calls preserve both guest errno values");
Check(net_setsockopt(datagram, 0xffff, 0x1105, nullptr, sizeof(timeout)) ==
Libs::Network::NET_ERROR_EFAULT && *net_errno == Libs::Posix::POSIX_EFAULT &&
net_getsockopt(datagram, 0xffff, 0x1105, &timeout, nullptr) ==
Libs::Network::NET_ERROR_EFAULT && *net_errno == Libs::Posix::POSIX_EFAULT,
"Net socket options reject null value and length pointers");
uint32_t short_size = sizeof(timeout) - 1;
Check(net_setsockopt(datagram, 0xffff, 0x1105, &timeout, short_size) ==
Libs::Network::NET_ERROR_EINVAL && *net_errno == Libs::Posix::POSIX_EINVAL &&
net_getsockopt(datagram, 0xffff, 0x1105, &timeout, &short_size) ==
Libs::Network::NET_ERROR_EINVAL && *net_errno == Libs::Posix::POSIX_EINVAL,
"Net send timeout rejects undersized values");
uint32_t option_size = sizeof(timeout);
Check(net_setsockopt(datagram, 0xffff, 0x7fffffff, &timeout, option_size) ==
Libs::Network::NET_ERROR_ENOPROTOOPT &&
*net_errno == Libs::Posix::POSIX_ENOPROTOOPT &&
net_getsockopt(datagram, 0xffff, 0x7fffffff, &timeout, &option_size) ==
Libs::Network::NET_ERROR_ENOPROTOOPT &&
*net_errno == Libs::Posix::POSIX_ENOPROTOOPT,
"Net unknown socket options return protocol-option errors");
#if defined(__linux__)
Check(net_setsockopt(datagram, 0xffff, 0x1007, &timeout, option_size) ==
Libs::Network::NET_ERROR_ENOPROTOOPT &&
*net_errno == Libs::Posix::POSIX_ENOPROTOOPT,
"Net native protocol-option errors retain their guest error code");
#endif
received.fill(0);
*net_errno = Libs::Posix::POSIX_EINVAL;
Check(net_sendto(datagram_writer, payload, sizeof(payload), 0,
+2 -1
View File
@@ -33541,7 +33541,8 @@ void CheckIndirectBufferStore(VulkanHarness &vulkan) {
candidate.indirect_search_iterations = 0;
candidate.indirect_resources.clear();
program.descriptor_sources.resize(1);
program.descriptor_sources[0].indirect_descriptor.emplace().table_stride = 16;
program.descriptor_sources[0].indirect_descriptor =
DescriptorSource::IndirectDescriptor{.table_stride = 16};
ShaderComputeInputInfo compute;
compute.wave_size = 32;
compute.host_subgroup_size = vulkan.SubgroupSize();