* select

* select for windows

* fixed windows only function

* windows error converts

* fixed up

* fixed compile

* another implementation for windows

* draft rewrite for windows

* implementation for windows

* added some debugging info

* more debugging

* extensive log

* Windows: Add device files to output fd_sets

Cyberpunk 2077 breaks without this.
Also added some formatting changes, to bring the implementation closer to our typical coding standards.

* Clang

* Formatting + cleanup

Removed some excessive logging used for debugging, and did some cleanup on the non-Windows implementation.

---------

Co-authored-by: georgemoralis <giorgosmrls@gmail.com>
Co-authored-by: Stephen Miller <millerste004@gmail.com>
This commit is contained in:
Marcin Mikołajczyk
2025-08-07 23:20:49 +02:00
committed by GitHub
parent f9ab6c48e3
commit befc5ec17b
3 changed files with 307 additions and 1 deletions

View File

@@ -213,13 +213,13 @@ public:
IOFile out(path, FileAccessMode::Write);
return out.Write(data);
}
std::FILE* file = nullptr;
private:
std::filesystem::path file_path;
FileAccessMode file_access_mode{};
FileType file_type{};
std::FILE* file = nullptr;
uintptr_t file_mapping = 0;
};

View File

@@ -5,6 +5,7 @@
#include <ranges>
#include "common/assert.h"
#include "common/error.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/singleton.h"
@@ -24,6 +25,13 @@
#include "core/memory.h"
#include "kernel.h"
#ifdef _WIN32
#include <io.h>
#include <winsock2.h>
#else
#include <sys/select.h>
#endif
namespace D = Core::Devices;
using FactoryDevice = std::function<std::shared_ptr<D::BaseDevice>(u32, const char*, int, u16)>;
@@ -1068,6 +1076,295 @@ s32 PS4_SYSV_ABI sceKernelUnlink(const char* path) {
return result;
}
#ifdef _WIN32
#define __FD_SETSIZE 1024
typedef struct {
unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(unsigned long))];
} fd_set_posix;
#define FD_SET_POSIX(fd, set) \
((set)->fds_bits[(fd) / (8 * sizeof(unsigned long))] |= \
(1UL << ((fd) % (8 * sizeof(unsigned long)))))
#define FD_CLR_POSIX(fd, set) \
((set)->fds_bits[(fd) / (8 * sizeof(unsigned long))] &= \
~(1UL << ((fd) % (8 * sizeof(unsigned long)))))
#define FD_ISSET_POSIX(fd, set) \
(((set)->fds_bits[(fd) / (8 * sizeof(unsigned long))] & \
(1UL << ((fd) % (8 * sizeof(unsigned long))))) != 0)
#define FD_ZERO_POSIX(set) memset((set), 0, sizeof(fd_set_posix))
s32 PS4_SYSV_ABI posix_select(s32 nfds, fd_set_posix* readfds, fd_set_posix* writefds,
fd_set_posix* exceptfds, OrbisKernelTimeval* timeout) {
LOG_DEBUG(Kernel_Fs, "nfds = {}, readfds = {}, writefds = {}, exceptfds = {}, timeout = {}",
nfds, fmt::ptr(readfds), fmt::ptr(writefds), fmt::ptr(exceptfds), fmt::ptr(timeout));
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
fd_set read_host = {}, write_host = {}, except_host = {};
FD_ZERO(&read_host);
FD_ZERO(&write_host);
FD_ZERO(&except_host);
fd_set_posix read_ready, write_ready, except_ready;
FD_ZERO_POSIX(&read_ready);
FD_ZERO_POSIX(&write_ready);
FD_ZERO_POSIX(&except_ready);
std::map<s32, s32> host_to_guest;
s32 socket_max_fd = -1;
for (s32 i = 0; i < nfds; ++i) {
bool want_read = readfds && FD_ISSET_POSIX(i, readfds);
bool want_write = writefds && FD_ISSET_POSIX(i, writefds);
bool want_except = exceptfds && FD_ISSET_POSIX(i, exceptfds);
if (!(want_read || want_write || want_except)) {
continue;
}
auto* file = h->GetFile(i);
if (!file || ((file->type == Core::FileSys::FileType::Regular && !file->f.IsOpen()) ||
(file->type == Core::FileSys::FileType::Socket && !file->is_opened))) {
LOG_ERROR(Kernel_Fs, "fd {} is null or not opened", i);
*__Error() = POSIX_EBADF;
return -1;
}
s32 native_fd = -1;
switch (file->type) {
case Core::FileSys::FileType::Regular:
native_fd = static_cast<s32>(file->f.GetFileMapping());
break;
case Core::FileSys::FileType::Socket: {
auto sock = file->socket->Native();
native_fd = sock ? static_cast<s32>(*sock) : -1;
break;
}
case Core::FileSys::FileType::Device:
native_fd = -1;
break;
default:
UNREACHABLE();
break;
}
if (file->type == Core::FileSys::FileType::Regular ||
file->type == Core::FileSys::FileType::Device) {
// Disk files always ready
if (want_read) {
FD_SET_POSIX(i, &read_ready);
}
if (want_write) {
FD_SET_POSIX(i, &write_ready);
}
// exceptfds not supported on regular files
} else if (file->type == Core::FileSys::FileType::Socket) {
if (want_read) {
FD_SET(native_fd, &read_host);
}
if (want_write) {
FD_SET(native_fd, &write_host);
}
if (want_except) {
FD_SET(native_fd, &except_host);
}
socket_max_fd = std::max(socket_max_fd, native_fd);
}
if (native_fd == -1) {
continue;
}
host_to_guest[native_fd] = i;
}
LOG_DEBUG(Kernel_Fs,
"Before select(): read_host.fd_count = {}, write_host.fd_count = {}, "
"except_host.fd_count = {}",
read_host.fd_count, write_host.fd_count, except_host.fd_count);
if (read_host.fd_count == 0 && write_host.fd_count == 0 && except_host.fd_count == 0) {
LOG_WARNING(Kernel_Fs, "No sockets in fd_sets, select() will return immediately");
}
if (readfds) {
FD_ZERO_POSIX(readfds);
}
if (writefds) {
FD_ZERO_POSIX(writefds);
}
if (exceptfds) {
FD_ZERO_POSIX(exceptfds);
}
s32 result = 0;
if (socket_max_fd != -1) {
timeval tv = {};
timeval* tv_ptr = nullptr;
if (timeout) {
tv.tv_sec = timeout->tv_sec;
tv.tv_usec = timeout->tv_usec;
tv_ptr = &tv;
}
result = select(0, read_host.fd_count > 0 ? &read_host : nullptr,
write_host.fd_count > 0 ? &write_host : nullptr,
except_host.fd_count > 0 ? &except_host : nullptr, tv_ptr);
if (result == SOCKET_ERROR) {
s32 err = WSAGetLastError();
LOG_ERROR(Kernel_Fs, "select() failed with error {}", err);
switch (err) {
case WSAEFAULT:
*__Error() = POSIX_EFAULT;
break;
case WSAEINVAL:
*__Error() = POSIX_EINVAL;
break;
case WSAENOBUFS:
*__Error() = POSIX_ENOBUFS;
break;
default:
LOG_ERROR(Kernel_Fs, "Unhandled error case {}", err);
break;
}
return -1;
}
for (s32 i = 0; i < read_host.fd_count; ++i) {
s32 fd = static_cast<s32>(read_host.fd_array[i]);
FD_SET_POSIX(host_to_guest[fd], readfds);
}
for (s32 i = 0; i < write_host.fd_count; ++i) {
s32 fd = static_cast<s32>(write_host.fd_array[i]);
FD_SET_POSIX(host_to_guest[fd], writefds);
}
for (s32 i = 0; i < except_host.fd_count; ++i) {
s32 fd = static_cast<s32>(except_host.fd_array[i]);
FD_SET_POSIX(host_to_guest[fd], exceptfds);
}
}
// Add regular/device files ready count
s32 disk_ready = 0;
for (s32 i = 0; i < nfds; ++i) {
if (FD_ISSET_POSIX(i, &read_ready)) {
FD_SET_POSIX(i, readfds);
disk_ready++;
}
if (FD_ISSET_POSIX(i, &write_ready)) {
FD_SET_POSIX(i, writefds);
disk_ready++;
}
}
return result + disk_ready;
}
#else
s32 PS4_SYSV_ABI posix_select(s32 nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
OrbisKernelTimeval* timeout) {
LOG_DEBUG(Kernel_Fs, "nfds = {}, readfds = {}, writefds = {}, exceptfds = {}, timeout = {}",
nfds, fmt::ptr(readfds), fmt::ptr(writefds), fmt::ptr(exceptfds), fmt::ptr(timeout));
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
fd_set read_host, write_host, except_host;
FD_ZERO(&read_host);
FD_ZERO(&write_host);
FD_ZERO(&except_host);
std::map<s32, s32> host_to_guest;
s32 max_fd = -1;
for (s32 i = 0; i < nfds; ++i) {
auto read = readfds && FD_ISSET(i, readfds);
auto write = writefds && FD_ISSET(i, writefds);
auto except = exceptfds && FD_ISSET(i, exceptfds);
if (read || write || except) {
auto* file = h->GetFile(i);
if (file == nullptr ||
((file->type == Core::FileSys::FileType::Regular && !file->f.IsOpen()) ||
(file->type == Core::FileSys::FileType::Socket && !file->is_opened))) {
LOG_ERROR(Kernel_Fs, "fd {} is null or not opened", i);
*__Error() = POSIX_EBADF;
return -1;
}
s32 native_fd = [&] {
switch (file->type) {
case Core::FileSys::FileType::Regular:
return static_cast<s32>(file->f.GetFileMapping());
case Core::FileSys::FileType::Device:
return -1;
case Core::FileSys::FileType::Socket: {
auto sock = file->socket->Native();
// until P2P sockets contain a proper socket
return sock ? static_cast<s32>(*sock) : -1;
}
default:
UNREACHABLE();
}
}();
if (native_fd == -1) {
continue;
}
host_to_guest.emplace(native_fd, i);
max_fd = std::max(max_fd, native_fd);
if (read) {
FD_SET(native_fd, &read_host);
}
if (write) {
FD_SET(native_fd, &write_host);
}
if (except) {
FD_SET(native_fd, &except_host);
}
}
}
if (max_fd == -1) {
LOG_WARNING(Kernel_Fs, "all requested file descriptors are unsupported");
return 0;
}
s32 ret = select(max_fd + 1, &read_host, &write_host, &except_host, (timeval*)timeout);
if (ret > 0) {
if (readfds) {
FD_ZERO(readfds);
}
if (writefds) {
FD_ZERO(writefds);
}
if (exceptfds) {
FD_ZERO(exceptfds);
}
for (s32 i = 0; i < max_fd + 1; ++i) {
if (readfds && FD_ISSET(i, &read_host)) {
FD_SET(host_to_guest[i], readfds);
}
if (writefds && FD_ISSET(i, &write_host)) {
FD_SET(host_to_guest[i], writefds);
}
if (exceptfds && FD_ISSET(i, &except_host)) {
FD_SET(host_to_guest[i], exceptfds);
}
}
}
if (ret < 0) {
s32 error = errno;
LOG_ERROR(Kernel_Fs, "native select call failed with {} ({})", error,
Common::NativeErrorToString(error));
SetPosixErrno(error);
}
return ret;
}
#endif
void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("6c3rCVE-fTU", "libkernel", 1, "libkernel", 1, 1, open);
LIB_FUNCTION("wuCroIGjt2g", "libScePosix", 1, "libkernel", 1, 1, posix_open);
@@ -1125,6 +1422,8 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("nKWi-N2HBV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPwrite);
LIB_FUNCTION("mBd4AfLP+u8", "libkernel", 1, "libkernel", 1, 1, sceKernelPwritev);
LIB_FUNCTION("AUXVxWeJU-A", "libkernel", 1, "libkernel", 1, 1, sceKernelUnlink);
LIB_FUNCTION("T8fER+tIGgk", "libScePosix", 1, "libkernel", 1, 1, posix_select);
LIB_FUNCTION("T8fER+tIGgk", "libkernel", 1, "libkernel", 1, 1, posix_select);
}
} // namespace Libraries::Kernel

View File

@@ -58,6 +58,7 @@ struct Socket {
virtual int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) = 0;
virtual int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) = 0;
virtual int fstat(Libraries::Kernel::OrbisKernelStat* stat) = 0;
virtual std::optional<net_socket> Native() = 0;
std::mutex m_mutex;
std::mutex receive_mutex;
};
@@ -93,6 +94,9 @@ struct PosixSocket : public Socket {
int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override;
int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override;
int fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
std::optional<net_socket> Native() override {
return sock;
}
};
struct P2PSocket : public Socket {
@@ -113,6 +117,9 @@ struct P2PSocket : public Socket {
int GetSocketAddress(OrbisNetSockaddr* name, u32* namelen) override;
int GetPeerName(OrbisNetSockaddr* addr, u32* namelen) override;
int fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
std::optional<net_socket> Native() override {
return {};
}
};
} // namespace Libraries::Net