From f745f4db5862c2f1684e7fb2eadf9b3c88e8611a Mon Sep 17 00:00:00 2001 From: digant73 Date: Sat, 15 Aug 2026 22:46:25 +0200 Subject: [PATCH 1/9] Add remaining BD Drive support --- Utilities/File.cpp | 215 +++++++++++++++++++++++++++++++++++++++++++-- Utilities/File.h | 5 +- 2 files changed, 212 insertions(+), 8 deletions(-) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index bb635592d43d..ff27cefd944a 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -148,6 +148,7 @@ static fs::error to_error(DWORD e) #include #include #include +#include #include #include #include @@ -159,12 +160,19 @@ static fs::error to_error(DWORD e) #include #include #include +#include +#include +#include #elif defined(__linux__) || defined(__sun) #include #include +#include #include #else #include +#include +#include +#include #endif static fs::error to_error(int e) @@ -185,6 +193,59 @@ static fs::error to_error(int e) } } +// Check whether the provided device node is an optical drive (e.g. a Blu-Ray Disc drive) +static bool is_optical_device_node(const struct ::stat& file_info) +{ +#if defined(__linux__) + // An optical drive is exposed as a block device using the SCSI CD-ROM major number (e.g. "/dev/sr0", "/dev/scd0" or the "/dev/cdrom" link) + return S_ISBLK(file_info.st_mode) && major(file_info.st_rdev) == 11; +#elif defined(__APPLE__) + // The raw (unbuffered) device is a character device (e.g. "/dev/rdisk2"). + // NOTE: there is no cheap way to tell an optical drive from any other raw disk here, so any raw disk node is accepted + return S_ISCHR(file_info.st_mode); +#else + // On the BSDs an optical drive is a character device (e.g. "/dev/cd0") + return S_ISCHR(file_info.st_mode) || S_ISBLK(file_info.st_mode); +#endif +} + +// Retrieve the size of a raw device: "fstat()" reports no size (0) on such a device +static u64 get_raw_device_size(int fd) +{ +#if defined(BLKGETSIZE64) + if (u64 size = 0; ::ioctl(fd, BLKGETSIZE64, &size) == 0) + { + return size; + } +#endif +#if defined(DKIOCGETBLOCKCOUNT) && defined(DKIOCGETBLOCKSIZE) + if (u64 block_count = 0; ::ioctl(fd, DKIOCGETBLOCKCOUNT, &block_count) == 0) + { + if (u32 block_size = 0; ::ioctl(fd, DKIOCGETBLOCKSIZE, &block_size) == 0) + { + return block_count * block_size; + } + } +#endif +#if defined(DIOCGMEDIASIZE) + if (off_t media_size = 0; ::ioctl(fd, DIOCGMEDIASIZE, &media_size) == 0) + { + return static_cast(media_size); + } +#endif + + // Fallback: seek to the end of the device (restoring the current position afterwards) + const off_t old_pos = ::lseek(fd, 0, SEEK_CUR); + const off_t end_pos = ::lseek(fd, 0, SEEK_END); + + if (old_pos >= 0) + { + ::lseek(fd, old_pos, SEEK_SET); + } + + return end_pos > 0 ? static_cast(end_pos) : 0; +} + #endif static std::string path_append(std::string_view path, std::string_view more) @@ -629,10 +690,11 @@ namespace fs class unix_file final : public file_base { int m_fd; + bool m_raw_device; public: - unix_file(int fd) - : m_fd(fd) + unix_file(int fd, bool raw_device = false) + : m_fd(fd), m_raw_device(raw_device) { } @@ -651,8 +713,8 @@ namespace fs stat_t info {}; info.is_directory = S_ISDIR(file_info.st_mode); - info.is_writable = file_info.st_mode & 0200; // HACK: approximation - info.size = file_info.st_size; + info.is_writable = !m_raw_device && (file_info.st_mode & 0200); // HACK: approximation + info.size = m_raw_device ? get_raw_device_size(m_fd) : file_info.st_size; // A raw device reports no size through "fstat()" info.atime = file_info.st_atime; info.mtime = file_info.st_mtime; info.ctime = info.mtime; @@ -758,6 +820,12 @@ namespace fs u64 size() override { + if (m_raw_device) + { + // For a raw device, we need to use an ioctl ("fstat()" would always report a null size) + return get_raw_device_size(m_fd); + } + struct ::stat file_info; ensure(::fstat(m_fd, &file_info) == 0); // "file::size" @@ -1120,8 +1188,24 @@ bool fs::is_optical_raw_device([[maybe_unused]] const std::string& path) { return true; } -#endif + return false; +#else + // Skip a useless check if the path cannot point to a device node (device nodes always live in "/dev") + if (!path.starts_with("/dev/")) + { + return false; + } + + struct ::stat file_info; + + if (::stat(path.c_str(), &file_info) != 0) + { + return false; + } + + return is_optical_device_node(file_info); +#endif } bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device) @@ -1158,8 +1242,109 @@ bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device return true; } + + return false; +#elif defined(__linux__) + // Here the path points to a mounted optical disc (e.g. "/media/user/PS3_DISC"), so retrieve the device backing it. + // NOTE: "st_dev" of anything stored on a filesystem backed by a block device matches "st_rdev" of the device itself + struct ::stat file_info; + + if (::stat(path.c_str(), &file_info) != 0) + { + return false; + } + + const dev_t device_id = S_ISBLK(file_info.st_mode) ? file_info.st_rdev : file_info.st_dev; + + // Skip a useless check to detect an optical raw device if the device is not using the SCSI CD-ROM major number + if (major(device_id) != 11) + { + return false; + } + + const std::string dev_major = std::to_string(major(device_id)); + const std::string dev_minor = std::to_string(minor(device_id)); + + // Retrieve the device name from sysfs (e.g. "/sys/dev/block/11:0" -> "../../devices/[...]/block/sr0") + std::string device_path; + char link_target[1024]{}; + + if (const ssize_t len = ::readlink(("/sys/dev/block/" + dev_major + ":" + dev_minor).c_str(), link_target, sizeof(link_target) - 1); len > 0) + { + const std::string_view target{link_target, static_cast(len)}; + + device_path = "/dev/" + std::string(target.substr(target.find_last_of('/') + 1)); + } + else + { + // Fallback: the minor number matches the index of the SCSI CD-ROM device + device_path = "/dev/sr" + dev_minor; + } + + // Ensure the resolved node really points to the same optical device + struct ::stat device_info; + + if (::stat(device_path.c_str(), &device_info) != 0 || device_info.st_rdev != device_id || !is_optical_device_node(device_info)) + { + return false; + } + + if (raw_device) + { + *raw_device = device_path; + } + + return true; +#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) + // Here the path points to a mounted optical disc, so retrieve the device backing the filesystem it belongs to + struct ::statfs mount_info; + + if (::statfs(path.c_str(), &mount_info) != 0) + { + return false; + } + + // Skip a useless check to detect an optical raw device if the filesystem is not one of those used by a PS3 disc + const std::string_view fs_type = mount_info.f_fstypename; + + if (fs_type != "cd9660" && fs_type != "udf") + { + return false; + } + + std::string device_path = mount_info.f_mntfromname; // e.g. "/dev/disk2" + + if (!device_path.starts_with("/dev/")) + { + return false; + } + +#ifdef __APPLE__ + // Use the raw (unbuffered) device (e.g. "/dev/disk2" -> "/dev/rdisk2") + if (!device_path.starts_with("/dev/r")) + { + device_path.insert(5, 1, 'r'); + } #endif + + // Ensure the resolved node really points to a raw device + struct ::stat device_info; + + if (::stat(device_path.c_str(), &device_info) != 0 || !is_optical_device_node(device_info)) + { + return false; + } + + if (raw_device) + { + *raw_device = device_path; + } + + return true; +#else + // Not supported on this platform return false; +#endif } bool fs::statfs(const std::string& path, fs::device_stat& info) @@ -1790,6 +1975,9 @@ fs::file::file(const std::string& path, bs_t mode) m_file = std::make_unique(handle); #else + // An optical raw device can only be read: any write related flag makes the opening fail (handled as any other error) + const bool raw_device = is_optical_raw_device(path); + int flags = O_CLOEXEC; // Ensures all files are closed on execl for auto updater if (mode & fs::read && mode & fs::write) flags |= O_RDWR; @@ -1805,7 +1993,7 @@ fs::file::file(const std::string& path, bs_t mode) if (mode & fs::write && mode & fs::unread) { - if (!(mode & (fs::excl + fs::lock)) && mode & fs::trunc) + if (!raw_device && !(mode & (fs::excl + fs::lock)) && mode & fs::trunc) { // Alternative to truncation for "unread" flag (TODO) if (mode & fs::create) @@ -1838,6 +2026,21 @@ fs::file::file(const std::string& path, bs_t mode) ensure(::ftruncate(fd, 0) == 0); } + // If path points to an optical raw device, complete the file opening + if (raw_device) + { + // Try to retrieve the size of the content. If it fails, no disc is probably mounted so abort the file opening + if (!get_raw_device_size(fd)) + { + ::close(fd); + g_tls_error = fs::error::noent; + return; + } + + m_file = std::make_unique(fd, true); + return; + } + m_file = std::make_unique(fd); if (mode & fs::isfile && !(mode & fs::write) && get_stat().is_directory) diff --git a/Utilities/File.h b/Utilities/File.h index 23e4756bd5a9..574ad079465b 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -213,10 +213,11 @@ namespace fs // Check whether the path points to an existing symlink bool is_symlink(const std::string& path); - // Check whether the path points to a raw device + // Check whether the path points to a raw device (e.g. "\\.\E:" on Windows, "/dev/sr0" on Linux, "/dev/rdisk2" on macOS) bool is_optical_raw_device(const std::string& path); - // Check whether the path points to an optical drive. If so, provide the raw device in "raw_device" if requested + // Check whether the path points to an optical drive (either the raw device itself or the mount point of the disc + // inserted in it). If so, provide the raw device in "raw_device" if requested bool get_optical_raw_device(const std::string& path, std::string* raw_device = nullptr); // Get filesystem information From 2e51ee476ba65f89acce989c9737774f8dc67009 Mon Sep 17 00:00:00 2001 From: digant73 Date: Sat, 15 Aug 2026 23:04:14 +0200 Subject: [PATCH 2/9] minor cleanup --- Utilities/File.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index ff27cefd944a..dd748b0f93a0 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -1181,7 +1181,7 @@ bool fs::is_symlink(const std::string& path) return true; } -bool fs::is_optical_raw_device([[maybe_unused]] const std::string& path) +bool fs::is_optical_raw_device(const std::string& path) { #ifdef _WIN32 if (path.starts_with("\\\\.\\")) From 5b4cea6d3d21b7fd818fc16049ab14ae3e1479dc Mon Sep 17 00:00:00 2001 From: digant73 Date: Sat, 15 Aug 2026 23:14:31 +0200 Subject: [PATCH 3/9] minor cleanup --- Utilities/File.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index dd748b0f93a0..a342801ee018 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -820,16 +820,16 @@ namespace fs u64 size() override { - if (m_raw_device) + if (!m_raw_device) { - // For a raw device, we need to use an ioctl ("fstat()" would always report a null size) - return get_raw_device_size(m_fd); - } + struct ::stat file_info; + ensure(::fstat(m_fd, &file_info) == 0); // "file::size" - struct ::stat file_info; - ensure(::fstat(m_fd, &file_info) == 0); // "file::size" + return file_info.st_size; + } - return file_info.st_size; + // For a raw device, we need to use an ioctl ("fstat()" would always report a null size) + return get_raw_device_size(m_fd); } native_handle get_handle() override From 54bbe4f33e65f4dce45d7e000c101d3356d3d718 Mon Sep 17 00:00:00 2001 From: digant73 Date: Sun, 16 Aug 2026 16:19:51 +0200 Subject: [PATCH 4/9] add sanity checks + fix on ISO.cpp (macOS only) --- Utilities/File.cpp | 75 ++++++++++++++++++++++++++++++++++---------- Utilities/File.h | 4 +-- rpcs3/Loader/ISO.cpp | 4 ++- 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index a342801ee018..dfdbd978065e 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -193,12 +193,27 @@ static fs::error to_error(int e) } } -// Check whether the provided device node is an optical drive (e.g. a Blu-Ray Disc drive) +#if defined(__linux__) +// Major numbers of the block devices an optical disc can be accessed through (see "Documentation/admin-guide/devices.txt") +constexpr unsigned int s_scsi_cdrom_major = 11; // Optical drive (e.g. "/dev/sr0") +constexpr unsigned int s_loopback_major = 7; // Disc image attached with "mount"/"losetup" (e.g. "/dev/loop0") +#endif + +// Check whether the provided device node is an optical drive (e.g. a Blu-Ray Disc drive) or a mounted disc image static bool is_optical_device_node(const struct ::stat& file_info) { #if defined(__linux__) - // An optical drive is exposed as a block device using the SCSI CD-ROM major number (e.g. "/dev/sr0", "/dev/scd0" or the "/dev/cdrom" link) - return S_ISBLK(file_info.st_mode) && major(file_info.st_rdev) == 11; + // An optical drive is exposed as a block device using the SCSI CD-ROM major number (e.g. "/dev/sr0", "/dev/scd0" or the "/dev/cdrom" link). + // A disc image attached through "mount -o loop" (or "losetup") is exposed as a loopback block device instead (e.g. "/dev/loop0"): + // it is the counterpart of an ISO file mounted on Windows, where the resulting virtual drive is reported as DRIVE_CDROM + if (!S_ISBLK(file_info.st_mode)) + { + return false; + } + + const unsigned int device_major = major(file_info.st_rdev); + + return device_major == s_scsi_cdrom_major || device_major == s_loopback_major; #elif defined(__APPLE__) // The raw (unbuffered) device is a character device (e.g. "/dev/rdisk2"). // NOTE: there is no cheap way to tell an optical drive from any other raw disk here, so any raw disk node is accepted @@ -209,6 +224,20 @@ static bool is_optical_device_node(const struct ::stat& file_info) #endif } +// Check whether the path is the root of a mounted filesystem (e.g. the mount point of a disc): "st_dev" changes when crossing a mount point +[[maybe_unused]] static bool is_mount_point(const std::string& path, const struct ::stat& file_info) +{ + struct ::stat parent_info; + + // NOTE: it also fails (ENOTDIR) if the path is a file, which is never a mount point + if (::stat((path + "/..").c_str(), &parent_info) != 0) + { + return false; + } + + return parent_info.st_dev != file_info.st_dev; +} + // Retrieve the size of a raw device: "fstat()" reports no size (0) on such a device static u64 get_raw_device_size(int fd) { @@ -1245,8 +1274,8 @@ bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device return false; #elif defined(__linux__) - // Here the path points to a mounted optical disc (e.g. "/media/user/PS3_DISC"), so retrieve the device backing it. - // NOTE: "st_dev" of anything stored on a filesystem backed by a block device matches "st_rdev" of the device itself + // Here the path points to a mounted optical disc (e.g. "/media/user/PS3_DISC") or to a mounted disc image + // (e.g. "mount -o loop game.iso /mnt/game"), so retrieve the device backing the filesystem it belongs to struct ::stat file_info; if (::stat(path.c_str(), &file_info) != 0) @@ -1254,14 +1283,15 @@ bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device return false; } - const dev_t device_id = S_ISBLK(file_info.st_mode) ? file_info.st_rdev : file_info.st_dev; - - // Skip a useless check to detect an optical raw device if the device is not using the SCSI CD-ROM major number - if (major(device_id) != 11) + // Skip a useless check to detect an optical raw device if the path is not the mount point of the disc, it means we are + // navigating on subfolders (on Windows, in the same way, only the drive root is accepted) + if (!is_mount_point(path, file_info)) { return false; } + // "st_dev" of anything stored on a filesystem backed by a block device matches "st_rdev" of the device itself + const dev_t device_id = file_info.st_dev; const std::string dev_major = std::to_string(major(device_id)); const std::string dev_minor = std::to_string(minor(device_id)); @@ -1275,11 +1305,15 @@ bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device device_path = "/dev/" + std::string(target.substr(target.find_last_of('/') + 1)); } - else + else if (major(device_id) == s_scsi_cdrom_major) { - // Fallback: the minor number matches the index of the SCSI CD-ROM device + // Fallback (sysfs not available): the minor number matches the index of the SCSI CD-ROM device device_path = "/dev/sr" + dev_minor; } + else + { + return false; + } // Ensure the resolved node really points to the same optical device struct ::stat device_info; @@ -1296,18 +1330,25 @@ bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device return true; #elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) - // Here the path points to a mounted optical disc, so retrieve the device backing the filesystem it belongs to - struct ::statfs mount_info; + // Here the path points to a mounted optical disc or to a mounted disc image (e.g. attached with "hdiutil"/"mdconfig"), + // so retrieve the device backing the filesystem it belongs to + struct ::stat file_info; - if (::statfs(path.c_str(), &mount_info) != 0) + if (::stat(path.c_str(), &file_info) != 0) + { + return false; + } + + // Skip a useless check to detect an optical raw device if the path is not the mount point of the disc, it means we are + // navigating on subfolders (on Windows, in the same way, only the drive root is accepted) + if (!is_mount_point(path, file_info)) { return false; } - // Skip a useless check to detect an optical raw device if the filesystem is not one of those used by a PS3 disc - const std::string_view fs_type = mount_info.f_fstypename; + struct ::statfs mount_info; - if (fs_type != "cd9660" && fs_type != "udf") + if (::statfs(path.c_str(), &mount_info) != 0) { return false; } diff --git a/Utilities/File.h b/Utilities/File.h index 574ad079465b..39df1f16806e 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -216,8 +216,8 @@ namespace fs // Check whether the path points to a raw device (e.g. "\\.\E:" on Windows, "/dev/sr0" on Linux, "/dev/rdisk2" on macOS) bool is_optical_raw_device(const std::string& path); - // Check whether the path points to an optical drive (either the raw device itself or the mount point of the disc - // inserted in it). If so, provide the raw device in "raw_device" if requested + // Check whether the path points to an optical drive or to a mounted disc image (either the raw device itself or the + // mount point of the disc/image). If so, provide the raw device in "raw_device" if requested bool get_optical_raw_device(const std::string& path, std::string* raw_device = nullptr); // Get filesystem information diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 609d4e2b4a57..a857adee1be9 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -39,7 +39,9 @@ static void* get_aligned_buf() #if defined(_WIN32) buf = _aligned_malloc(ISO_SECTOR_SIZE, ISO_SECTOR_SIZE * 2); #else - buf = std::aligned_alloc(ISO_SECTOR_SIZE * 2, ISO_SECTOR_SIZE); + // NOTE: unlike "_aligned_malloc", "aligned_alloc" requires the size to be a multiple of the alignment + // (macOS returns NULL with EINVAL otherwise), so the size is rounded up: only ISO_SECTOR_SIZE bytes are used + buf = std::aligned_alloc(ISO_SECTOR_SIZE * 2, ISO_SECTOR_SIZE * 2); #endif } From 235510ce3c43f7379ae233c4371196b4898ace47 Mon Sep 17 00:00:00 2001 From: digant73 Date: Sun, 23 Aug 2026 19:18:31 +0200 Subject: [PATCH 5/9] reviewed and updated code --- Utilities/File.cpp | 88 +++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index dfdbd978065e..a39a3f2d4f7b 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -227,9 +227,14 @@ static bool is_optical_device_node(const struct ::stat& file_info) // Check whether the path is the root of a mounted filesystem (e.g. the mount point of a disc): "st_dev" changes when crossing a mount point [[maybe_unused]] static bool is_mount_point(const std::string& path, const struct ::stat& file_info) { + // A file is never a mount point: discard it without any syscall (the "stat()" below would fail with ENOTDIR anyway) + if (!S_ISDIR(file_info.st_mode)) + { + return false; + } + struct ::stat parent_info; - // NOTE: it also fails (ENOTDIR) if the path is a file, which is never a mount point if (::stat((path + "/..").c_str(), &parent_info) != 0) { return false; @@ -241,22 +246,20 @@ static bool is_optical_device_node(const struct ::stat& file_info) // Retrieve the size of a raw device: "fstat()" reports no size (0) on such a device static u64 get_raw_device_size(int fd) { -#if defined(BLKGETSIZE64) +#if defined(__linux__) if (u64 size = 0; ::ioctl(fd, BLKGETSIZE64, &size) == 0) { return size; } -#endif -#if defined(DKIOCGETBLOCKCOUNT) && defined(DKIOCGETBLOCKSIZE) - if (u64 block_count = 0; ::ioctl(fd, DKIOCGETBLOCKCOUNT, &block_count) == 0) +#elif defined(__APPLE__) + u64 block_count = 0; + u32 block_size = 0; + + if (::ioctl(fd, DKIOCGETBLOCKCOUNT, &block_count) == 0 && ::ioctl(fd, DKIOCGETBLOCKSIZE, &block_size) == 0) { - if (u32 block_size = 0; ::ioctl(fd, DKIOCGETBLOCKSIZE, &block_size) == 0) - { - return block_count * block_size; - } + return block_count * block_size; } -#endif -#if defined(DIOCGMEDIASIZE) +#elif defined(__FreeBSD__) || defined(__DragonFly__) if (off_t media_size = 0; ::ioctl(fd, DIOCGMEDIASIZE, &media_size) == 0) { return static_cast(media_size); @@ -667,20 +670,20 @@ namespace fs u64 size() override { - if (!m_raw_device) + if (m_raw_device) { - // NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso). - LARGE_INTEGER size; + // For a raw device, we need to use DeviceIoControl. + DISK_GEOMETRY_EX geometry; - ensure(GetFileSizeEx(m_handle, &size)); // "file::size" - return size.QuadPart; + ensure(DeviceIoControl(m_handle, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nullptr, 0, &geometry, sizeof(geometry), nullptr, nullptr)); + return geometry.DiskSize.QuadPart; } - // For a raw device, we need to use DeviceIoControl. - DISK_GEOMETRY_EX geometry; + // NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso). + LARGE_INTEGER size; - ensure(DeviceIoControl(m_handle, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nullptr, 0, &geometry, sizeof(geometry), nullptr, nullptr)); - return geometry.DiskSize.QuadPart; + ensure(GetFileSizeEx(m_handle, &size)); // "file::size" + return size.QuadPart; } native_handle get_handle() override @@ -849,16 +852,16 @@ namespace fs u64 size() override { - if (!m_raw_device) + if (m_raw_device) { - struct ::stat file_info; - ensure(::fstat(m_fd, &file_info) == 0); // "file::size" - - return file_info.st_size; + // For a raw device, we need to use an ioctl ("fstat()" would always report a null size) + return get_raw_device_size(m_fd); } - // For a raw device, we need to use an ioctl ("fstat()" would always report a null size) - return get_raw_device_size(m_fd); + struct ::stat file_info; + ensure(::fstat(m_fd, &file_info) == 0); // "file::size" + + return file_info.st_size; } native_handle get_handle() override @@ -1283,6 +1286,17 @@ bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device return false; } + // "st_dev" of anything stored on a filesystem backed by a block device matches "st_rdev" of the device itself + const dev_t device_id = file_info.st_dev; + const unsigned int device_major = major(device_id); + + // Discard at once anything which is not backed by an optical drive or by a loopback device, so that the checks + // below (each one costing at least a syscall) only run on an actual candidate + if (device_major != s_scsi_cdrom_major && device_major != s_loopback_major) + { + return false; + } + // Skip a useless check to detect an optical raw device if the path is not the mount point of the disc, it means we are // navigating on subfolders (on Windows, in the same way, only the drive root is accepted) if (!is_mount_point(path, file_info)) @@ -1290,25 +1304,23 @@ bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device return false; } - // "st_dev" of anything stored on a filesystem backed by a block device matches "st_rdev" of the device itself - const dev_t device_id = file_info.st_dev; - const std::string dev_major = std::to_string(major(device_id)); - const std::string dev_minor = std::to_string(minor(device_id)); + const unsigned int device_minor = minor(device_id); // Retrieve the device name from sysfs (e.g. "/sys/dev/block/11:0" -> "../../devices/[...]/block/sr0") std::string device_path; - char link_target[1024]{}; + char link_target[1024]; // NOTE: "readlink()" never null terminates, so the buffer is used through the returned length only - if (const ssize_t len = ::readlink(("/sys/dev/block/" + dev_major + ":" + dev_minor).c_str(), link_target, sizeof(link_target) - 1); len > 0) + if (const ssize_t len = ::readlink(("/sys/dev/block/" + std::to_string(device_major) + ":" + std::to_string(device_minor)).c_str(), link_target, sizeof(link_target)); len > 0) { const std::string_view target{link_target, static_cast(len)}; - device_path = "/dev/" + std::string(target.substr(target.find_last_of('/') + 1)); + device_path = "/dev/"; + device_path += target.substr(target.find_last_of('/') + 1); } - else if (major(device_id) == s_scsi_cdrom_major) + else if (device_major == s_scsi_cdrom_major) { // Fallback (sysfs not available): the minor number matches the index of the SCSI CD-ROM device - device_path = "/dev/sr" + dev_minor; + device_path = "/dev/sr" + std::to_string(device_minor); } else { @@ -1340,7 +1352,9 @@ bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device } // Skip a useless check to detect an optical raw device if the path is not the mount point of the disc, it means we are - // navigating on subfolders (on Windows, in the same way, only the drive root is accepted) + // navigating on subfolders (on Windows, in the same way, only the drive root is accepted). There is no cheap way to + // discard a device here (unlike the major number on Linux), so this is the filter which must come first: it rejects + // any file without a single syscall and any other folder with just one more if (!is_mount_point(path, file_info)) { return false; From 1cfb39e44091ffdcb9d206b894828bcf36618839 Mon Sep 17 00:00:00 2001 From: digant73 Date: Sun, 23 Aug 2026 19:43:36 +0200 Subject: [PATCH 6/9] reswap if (m_raw_device) --- Utilities/File.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index a39a3f2d4f7b..2c4a7031a34a 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -670,20 +670,20 @@ namespace fs u64 size() override { - if (m_raw_device) + if (!m_raw_device) { - // For a raw device, we need to use DeviceIoControl. - DISK_GEOMETRY_EX geometry; + // NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso). + LARGE_INTEGER size; - ensure(DeviceIoControl(m_handle, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nullptr, 0, &geometry, sizeof(geometry), nullptr, nullptr)); - return geometry.DiskSize.QuadPart; + ensure(GetFileSizeEx(m_handle, &size)); // "file::size" + return size.QuadPart; } - // NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso). - LARGE_INTEGER size; + // For a raw device, we need to use DeviceIoControl. + DISK_GEOMETRY_EX geometry; - ensure(GetFileSizeEx(m_handle, &size)); // "file::size" - return size.QuadPart; + ensure(DeviceIoControl(m_handle, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nullptr, 0, &geometry, sizeof(geometry), nullptr, nullptr)); + return geometry.DiskSize.QuadPart; } native_handle get_handle() override @@ -852,16 +852,16 @@ namespace fs u64 size() override { - if (m_raw_device) + if (!m_raw_device) { - // For a raw device, we need to use an ioctl ("fstat()" would always report a null size) - return get_raw_device_size(m_fd); - } + struct ::stat file_info; + ensure(::fstat(m_fd, &file_info) == 0); // "file::size" - struct ::stat file_info; - ensure(::fstat(m_fd, &file_info) == 0); // "file::size" + return file_info.st_size; + } - return file_info.st_size; + // For a raw device, we need to use an ioctl ("fstat()" would always report a null size) + return get_raw_device_size(m_fd); } native_handle get_handle() override From f7703a2371a1aa27a467623129b7ff5fa758899e Mon Sep 17 00:00:00 2001 From: digant73 Date: Mon, 24 Aug 2026 21:38:53 +0200 Subject: [PATCH 7/9] cache size for raw device --- Utilities/File.cpp | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index 2c4a7031a34a..1ee30c724bf0 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -257,6 +257,11 @@ static u64 get_raw_device_size(int fd) if (::ioctl(fd, DKIOCGETBLOCKCOUNT, &block_count) == 0 && ::ioctl(fd, DKIOCGETBLOCKSIZE, &block_size) == 0) { + if (block_size && block_count > u64{umax} / block_size) + { + fmt::throw_exception("Raw device size overflow (block count: 0x%x, block size: 0x%x)", block_count, block_size); + } + return block_count * block_size; } #elif defined(__FreeBSD__) || defined(__DragonFly__) @@ -505,12 +510,12 @@ namespace fs class windows_file final : public file_base { HANDLE m_handle; - bool m_raw_device; + u64 m_raw_device_size; // Size of the raw device, 0 if it's not one: it never changes, so it's retrieved once when the file is opened atomic_t m_pos {0}; public: - windows_file(HANDLE handle, bool raw_device = false) - : m_handle(handle), m_raw_device(raw_device) + windows_file(HANDLE handle, u64 raw_device_size = 0) + : m_handle(handle), m_raw_device_size(raw_device_size) { } @@ -670,7 +675,7 @@ namespace fs u64 size() override { - if (!m_raw_device) + if (!m_raw_device_size) { // NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso). LARGE_INTEGER size; @@ -679,11 +684,8 @@ namespace fs return size.QuadPart; } - // For a raw device, we need to use DeviceIoControl. - DISK_GEOMETRY_EX geometry; - - ensure(DeviceIoControl(m_handle, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nullptr, 0, &geometry, sizeof(geometry), nullptr, nullptr)); - return geometry.DiskSize.QuadPart; + // For a raw device, the size was already retrieved with DeviceIoControl() when the file was opened + return m_raw_device_size; } native_handle get_handle() override @@ -722,11 +724,11 @@ namespace fs class unix_file final : public file_base { int m_fd; - bool m_raw_device; + u64 m_raw_device_size; // Size of the raw device, 0 if it's not one: it never changes, so it's retrieved once when the file is opened public: - unix_file(int fd, bool raw_device = false) - : m_fd(fd), m_raw_device(raw_device) + unix_file(int fd, u64 raw_device_size = 0) + : m_fd(fd), m_raw_device_size(raw_device_size) { } @@ -745,8 +747,8 @@ namespace fs stat_t info {}; info.is_directory = S_ISDIR(file_info.st_mode); - info.is_writable = !m_raw_device && (file_info.st_mode & 0200); // HACK: approximation - info.size = m_raw_device ? get_raw_device_size(m_fd) : file_info.st_size; // A raw device reports no size through "fstat()" + info.is_writable = !m_raw_device_size && (file_info.st_mode & 0200); // HACK: approximation + info.size = m_raw_device_size ? m_raw_device_size : static_cast(file_info.st_size); // A raw device reports no size through "fstat()" info.atime = file_info.st_atime; info.mtime = file_info.st_mtime; info.ctime = info.mtime; @@ -852,7 +854,7 @@ namespace fs u64 size() override { - if (!m_raw_device) + if (!m_raw_device_size) { struct ::stat file_info; ensure(::fstat(m_fd, &file_info) == 0); // "file::size" @@ -860,8 +862,8 @@ namespace fs return file_info.st_size; } - // For a raw device, we need to use an ioctl ("fstat()" would always report a null size) - return get_raw_device_size(m_fd); + // For a raw device, the size was already retrieved with an ioctl when the file was opened + return m_raw_device_size; } native_handle get_handle() override @@ -1984,7 +1986,7 @@ fs::file::file(const std::string& path, bs_t mode) return; } - m_file = std::make_unique(handle, true); + m_file = std::make_unique(handle, static_cast(geometry.DiskSize.QuadPart)); return; } @@ -2085,14 +2087,16 @@ fs::file::file(const std::string& path, bs_t mode) if (raw_device) { // Try to retrieve the size of the content. If it fails, no disc is probably mounted so abort the file opening - if (!get_raw_device_size(fd)) + const u64 raw_device_size = get_raw_device_size(fd); + + if (!raw_device_size) { ::close(fd); g_tls_error = fs::error::noent; return; } - m_file = std::make_unique(fd, true); + m_file = std::make_unique(fd, raw_device_size); return; } From 2ce085a4b679d1db052a2a710f4ac84950070804 Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:33:18 +0200 Subject: [PATCH 8/9] ensure raw_device_size is not zero for raw device opening --- Utilities/File.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index 1ee30c724bf0..7302dda0c2e9 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -1986,7 +1986,7 @@ fs::file::file(const std::string& path, bs_t mode) return; } - m_file = std::make_unique(handle, static_cast(geometry.DiskSize.QuadPart)); + m_file = std::make_unique(handle, static_cast(ensure(geometry.DiskSize.QuadPart))); return; } From a7aa67fae137caf69fcb47d88c254d5ae37bd982 Mon Sep 17 00:00:00 2001 From: digant73 Date: Sat, 29 Aug 2026 15:46:35 +0200 Subject: [PATCH 9/9] relax buffer alignment to 2048 --- rpcs3/Loader/ISO.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 7fbdb5fd5a80..b4d871407236 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -36,13 +36,12 @@ static void* get_aligned_buf() aligned_buf() noexcept { - // IMPORTANT NOTE: It must be aligned (probably enough on multiple of 4) to support raw device, otherwise any read from file will fail + // IMPORTANT NOTE: it must be aligned on the sector size of the volume to support a raw device, otherwise any read from + // file will fail (an optical medium always uses ISO_SECTOR_SIZE, so allocating a sector aligned on itself is enough) #if defined(_WIN32) - buf = _aligned_malloc(ISO_SECTOR_SIZE, ISO_SECTOR_SIZE * 2); + buf = _aligned_malloc(ISO_SECTOR_SIZE, ISO_SECTOR_SIZE); #else - // NOTE: unlike "_aligned_malloc", "aligned_alloc" requires the size to be a multiple of the alignment - // (macOS returns NULL with EINVAL otherwise), so the size is rounded up: only ISO_SECTOR_SIZE bytes are used - buf = std::aligned_alloc(ISO_SECTOR_SIZE * 2, ISO_SECTOR_SIZE * 2); + buf = std::aligned_alloc(ISO_SECTOR_SIZE, ISO_SECTOR_SIZE); #endif }