From 530f1571d4c83e3c2ce6ab8ad588a0ace51575cb Mon Sep 17 00:00:00 2001 From: digant73 Date: Mon, 27 Apr 2026 23:03:20 +0200 Subject: [PATCH 1/8] Add support to play a disc game directly from a Blu-Ray Disc Drive --- Utilities/File.cpp | 100 ++++- Utilities/File.h | 6 + rpcs3/Emu/System.cpp | 20 +- rpcs3/Loader/ISO.cpp | 617 +++++++++++++++++++++++------- rpcs3/Loader/ISO.h | 43 ++- rpcs3/rpcs3qt/game_list_frame.cpp | 31 +- rpcs3/rpcs3qt/game_list_grid.cpp | 2 +- rpcs3/rpcs3qt/game_list_table.cpp | 8 +- rpcs3/rpcs3qt/main_window.cpp | 2 +- rpcs3/rpcs3qt/qt_utils.cpp | 16 +- rpcs3/rpcs3qt/qt_video_source.cpp | 12 +- rpcs3/rpcs3qt/shortcut_utils.cpp | 8 +- 12 files changed, 665 insertions(+), 200 deletions(-) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index aff4537dea7e..b26bdc51785a 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -38,6 +38,17 @@ static std::unique_ptr to_wchar(std::string_view source) // Buffer for max possible output length std::unique_ptr buffer(new wchar_t[buf_size + 8 + 32768]); + // If path points to an optical raw device, copy it AS IS + if (fs::is_optical_raw_device(std::string(source))) + { + ensure(MultiByteToWideChar(CP_UTF8, 0, source.data(), size, buffer.get() + 32768, size)); // "to_wchar" + + // Canonicalize wide path (replace '/', ".", "..", \\ repetitions, etc) + ensure(GetFullPathNameW(buffer.get() + 32768, 32768, buffer.get(), nullptr) - 1 < 32768 - 1); // "to_wchar" + + return buffer; + } + // Prepend wide path prefix (4 characters) std::memcpy(buffer.get() + 32768, L"\\\\\?\\", 4 * sizeof(wchar_t)); @@ -566,9 +577,18 @@ namespace fs { // NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso). LARGE_INTEGER size; - ensure(GetFileSizeEx(m_handle, &size)); // "file::size" - return size.QuadPart; + // It will fail for a raw device. + if (GetFileSizeEx(m_handle, &size)) // "file::size" + { + 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; } native_handle get_handle() override @@ -1091,6 +1111,71 @@ bool fs::is_symlink(const std::string& path) return true; } +bool fs::is_optical_raw_device(const std::string& path) +{ +#ifdef _WIN32 + if (path.starts_with("\\\\.\\")) + { + return true; + } + + return false; +#endif + return false; +} + +bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device) +{ + if (fs::is_optical_raw_device(path)) + { + if (raw_device) + { + *raw_device = path; + } + + return true; + } + +#ifdef _WIN32 + constexpr u32 BUF_SIZE = 1000; + WCHAR drive_list[MAX_PATH] = {0}; + + // GetLogicalDriveStrings() returns a double-null terminated list of null-terminated strings. + // E.g. A:\B:\C:\ + DWORD copied = GetLogicalDriveStrings(MAX_PATH, drive_list); + + if (copied > 0 && copied <= MAX_PATH) + { + WCHAR* drive = drive_list; + + while (*drive) + { + if (GetDriveType(drive) == DRIVE_CDROM) + { + std::wstring ws(drive); + std::string s = std::string(ws.begin(), ws.end() - 1); + + if (path.starts_with(s)) + { + if (raw_device) + { + *raw_device = "\\\\.\\" + s; + } + + return true; + } + } + + // Get the next drive + drive += wcslen(drive) + 1; + } + } + + return false; +#endif + return false; +} + bool fs::statfs(const std::string& path, fs::device_stat& info) { if (auto device = get_virtual_device(path)) @@ -1660,9 +1745,18 @@ fs::file::file(const std::string& path, bs_t mode) // Check if the handle is actually valid. // This can fail on empty mounted drives (e.g. with ERROR_NOT_READY or ERROR_INVALID_FUNCTION). + // It always fails on raw devices. BY_HANDLE_FILE_INFORMATION info{}; + if (!GetFileInformationByHandle(handle, &info)) { + // If path points to an optical raw device, complete the file opening + if (is_optical_raw_device(path)) + { + m_file = std::make_unique(handle); + return; + } + const DWORD last_error = GetLastError(); CloseHandle(handle); @@ -1671,7 +1765,7 @@ fs::file::file(const std::string& path, bs_t mode) g_tls_error = fs::error::isdir; return; } - + g_tls_error = to_error(last_error); return; } diff --git a/Utilities/File.h b/Utilities/File.h index 3d332dd0be0d..94d9b5fc0aad 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -213,6 +213,12 @@ 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 + bool is_optical_raw_device(const std::string& path); + + // Check whether the path points to an optical drive. If so, provide the raw device + bool get_optical_raw_device(const std::string& path, std::string* raw_device = nullptr); + // Get filesystem information bool statfs(const std::string& path, device_stat& info); diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 492aed799a59..0deae114b3ec 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -638,7 +638,7 @@ void Emulator::Init() const std::string games_common_dir = g_cfg_vfs.get(g_cfg_vfs.games_dir, emu_dir); - if (make_path_verbose(games_common_dir, true)) + if (!is_iso_file(games_common_dir) && make_path_verbose(games_common_dir, true)) { fs::write_file(games_common_dir + "/Disc Games Can Be Put Here For Automatic Detection.txt", fs::create + fs::excl + fs::write, ""s); @@ -985,7 +985,7 @@ game_boot_result Emulator::BootGame(const std::string& path, const std::string& m_db_config = db_config; // Handle files and special paths inside Load unmodified - if (direct || !fs::is_dir(path)) + if (direct || !fs::is_dir(path) || fs::get_optical_raw_device(path)) { m_path = path; @@ -1201,7 +1201,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, std::string disc_info; m_ar->serialize(argv.emplace_back(), disc_info, klic.emplace_back(), m_game_dir, hdd1); - launching_from_disc_archive = is_file_iso(disc_info); + launching_from_disc_archive = is_iso_file(disc_info); sys_log.notice("Savestate: is iso archive = %d ('%s')", launching_from_disc_archive, disc_info); @@ -1218,7 +1218,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, // Load /dev_bdvd/ from game list if available if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty()) { - if (is_file_iso(game_path)) + if (is_iso_file(game_path)) { game_path = iso_device::virtual_device_name + "/PS3_GAME/./"; } @@ -1389,7 +1389,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, title_path = std::move(game_path); } - if (is_file_iso(title_path)) + if (is_iso_file(title_path)) { m_path = std::move(title_path); ok = true; @@ -1480,7 +1480,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, } const std::string resolved_path = GetCallbacks().resolve_path(m_path); - if (!launching_from_disc_archive && is_file_iso(m_path)) + if (!launching_from_disc_archive && is_iso_file(m_path)) { sys_log.notice("Loading iso archive '%s'", m_path); @@ -1933,7 +1933,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, // Load /dev_bdvd/ from game list if available if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty()) { - if (is_file_iso(game_path)) + if (is_iso_file(game_path)) { sys_log.notice("Loading iso archive for patch ('%s')", game_path); @@ -4246,7 +4246,7 @@ u32 Emulator::AddGamesFromDir(const std::string& path) const std::string dir_path = path + '/' + dir_entry.name; - if (!dir_entry.is_directory && !is_file_iso(dir_path)) + if (!dir_entry.is_directory && !is_iso_file(dir_path)) { continue; } @@ -4283,7 +4283,7 @@ u32 Emulator::AddGamesFromDir(const std::string& path) game_boot_result Emulator::AddGame(const std::string& path) { // Handle files directly - if (!fs::is_dir(path)) + if (!fs::is_dir(path) || fs::get_optical_raw_device(path)) { return AddGameToYml(path); } @@ -4354,7 +4354,7 @@ game_boot_result Emulator::AddGameToYml(const std::string& path) } std::unique_ptr archive; - if (is_file_iso(path)) + if (is_iso_file(path)) { archive = std::make_unique(path); } diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index e79373fcb460..15e188282c85 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -10,6 +10,7 @@ #include #include #include +#include LOG_CHANNEL(sys_log, "SYS"); LOG_CHANNEL(iso_log, "ISO"); @@ -22,32 +23,49 @@ struct iso_sector u64 address_aligned; u64 offset_aligned; u64 size_aligned; - std::array buf; }; -bool is_file_iso(const std::string& path) +static bool is_iso_file(const fs::file& file, u64* size = nullptr) { - if (path.empty() || fs::is_dir(path)) + if (!file || file.size() < 32768ULL + 6) { return false; } - return is_file_iso(fs::file(path)); + char magic[5]; + + file.read_at(32768ULL + 1, magic, 5); + + bool ret = magic[0] == 'C' && magic[1] == 'D' && magic[2] == '0' && magic[3] == '0' && magic[4] == '1'; + + if (size && ret) + { + *size = file.size(); + } + + return ret; } -bool is_file_iso(const fs::file& file) +bool is_iso_file(const std::string& path, u64* size, bool* is_raw_device) { - if (!file || file.size() < 32768 + 6) + std::string new_path = path; + + // "new_path" is updated with the raw device path in case "path" points to a BD drive + bool raw_device = fs::get_optical_raw_device(path, &new_path); + + if (path.empty() || (!raw_device && fs::is_dir(path))) { return false; } - file.seek(32768); + if (is_raw_device) + { + *is_raw_device = raw_device; + } - char magic[5]; - file.read_at(32768 + 1, magic, 5); + fs::file file(std::make_unique(new_path, fs::read)); - return magic[0] == 'C' && magic[1] == 'D' && magic[2] == '0' && magic[3] == '0' && magic[4] == '1'; + return is_iso_file(file, size); } // Convert 4 bytes in big-endian format to an unsigned integer @@ -68,7 +86,7 @@ static void reset_iv(std::array& iv, u32 lba) } // Main function that will decrypt the sector(s) -static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u64 size) +static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, unsigned char* out_buffer, u64 size) { // The following preliminary checks are good to be provided. // Commented out to gain a bit of performance, just because we know the caller is providing values in the expected range @@ -80,7 +98,7 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 //if ((size % 16) != 0) //{ - // iso_log.error("decrypt_data: Requested ciphertext blocks' size must be a multiple of 16 (%ull)", size); + // iso_log.error("decrypt_data: Requested ciphertext blocks' size must be a multiple of 16 (%llu)", size); // return; //} @@ -110,7 +128,7 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 cur_size -= cur_offset; // Partial (or even full) first sector - if (aes_crypt_cbc(&aes, AES_DECRYPT, cur_size, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0) + if (aes_crypt_cbc(&aes, AES_DECRYPT, cur_size, iv.data(), &buffer[cur_offset], &out_buffer[cur_offset]) != 0) { iso_log.error("decrypt_data: Error decrypting data on first sector read"); return false; @@ -130,7 +148,7 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 { reset_iv(iv, ++cur_sector_lba); // Next sector's IV - if (aes_crypt_cbc(&aes, AES_DECRYPT, ISO_SECTOR_SIZE, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0) + if (aes_crypt_cbc(&aes, AES_DECRYPT, ISO_SECTOR_SIZE, iv.data(), &buffer[cur_offset], &out_buffer[cur_offset]) != 0) { iso_log.error("decrypt_data: Error decrypting data on inner sector(s) read"); return false; @@ -142,7 +160,7 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 reset_iv(iv, ++cur_sector_lba); // Next sector's IV // Partial (or even full) last sector - if (aes_crypt_cbc(&aes, AES_DECRYPT, size - cur_offset, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0) + if (aes_crypt_cbc(&aes, AES_DECRYPT, size - cur_offset, iv.data(), &buffer[cur_offset], &out_buffer[cur_offset]) != 0) { iso_log.error("decrypt_data: Error decrypting data on last sector read"); return false; @@ -151,45 +169,9 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 return true; } -void iso_file_decryption::reset() -{ - m_enc_type = iso_encryption_type::NONE; - m_region_info.clear(); -} - -iso_type_status iso_file_decryption::check_type(const std::string& path, std::string& key_path, aes_context* aes_ctx) +iso_type_status iso_file_decryption::get_key(const std::string& key_path, aes_context* aes_ctx) { - if (!is_file_iso(path)) - { - return iso_type_status::NOT_ISO; - } - - // Remove file extension from file path - const usz ext_pos = path.rfind('.'); - const std::string name_path = ext_pos == umax ? path : path.substr(0, ext_pos); - - // Detect file name (with no parent folder and no file extension) - const usz name_pos = name_path.rfind('/'); - const std::string name = name_pos == umax ? name_path : name_path.substr(name_pos); - fs::file key_file; - - const std::array key_paths { - name_path + ".dkey", - name_path + ".key", - rpcs3::utils::get_redump_key_dir() + name + ".dkey", - rpcs3::utils::get_redump_key_dir() + name + ".key" - }; - - for (const std::string& path : key_paths) - { - key_file = fs::file(path); - - if (key_file) - { - key_path = path; - break; - } - } + fs::file key_file(key_path); // If no ".dkey" and ".key" file exists if (!key_file) @@ -234,32 +216,152 @@ iso_type_status iso_file_decryption::check_type(const std::string& path, std::st return iso_type_status::ERROR_PROCESSING_KEY; } -bool iso_file_decryption::init(const std::string& path) +iso_type_status iso_file_decryption::retrieve_key(iso_archive& archive, std::string& key_path, aes_context& aes_ctx) { - reset(); + // + // Find the first existing file in the archive present on the list of well known encrypted files to use for testing a matching key + // + + const std::map dec_magics { + {"PS3_GAME/LICDIR/LIC.DAT", "PS3LICDA"}, + {"PS3_GAME/USRDIR/EBOOT.BIN", "SCE"} + }; - if (!is_file_iso(path)) + iso_fs_node* node = nullptr; + std::string magic_value; + + for (const auto& magic : dec_magics) { - return false; + if (node = archive.retrieve(magic.first)) + { + magic_value = magic.second; + break; + } + } + + if (!node) + { + return iso_type_status::ERROR_OPENING_KEY; + } + + // + // Read the first encrypted sector to use for testing a matching key + // + + std::array enc_sec; + std::array dec_sec; + iso_file iso_file(archive.path(), fs::read, *node); + + if (!iso_file || iso_file.read(enc_sec.data(), ISO_SECTOR_SIZE) != ISO_SECTOR_SIZE) + { + return iso_type_status::NOT_ISO; + } + + // + // Scan all the key files present in the redump keys folder, decrypt the read sector and test for a match with file's magic value + // + + std::vector entries; + + for (auto&& dir_entry : fs::dir(rpcs3::utils::get_redump_key_dir())) + { + // Prefetch entries, it is unsafe to keep fs::dir for a long time or for many operations + entries.emplace_back(std::move(dir_entry)); + } + + for (auto path_it = entries.begin(); path_it != entries.end(); path_it++) + { + auto dir_entry = std::move(*path_it); + + if (dir_entry.name == "." || dir_entry.name == ".." || dir_entry.is_directory) + { + continue; + } + + key_path = rpcs3::utils::get_redump_key_dir() + dir_entry.name; + + // If no valid key is present on the file + if (get_key(key_path, &aes_ctx) != iso_type_status::REDUMP_ISO) + { + continue; + } + + // If the decryption fails + if (!decrypt_data(aes_ctx, iso_file.file_offset(0), enc_sec.data(), dec_sec.data(), ISO_SECTOR_SIZE)) + { + continue; + } + + // If the decrypted data match the magic value + if (memcmp(magic_value.data(), dec_sec.data(), magic_value.size()) == 0) + { + return iso_type_status::REDUMP_ISO; + } } + return iso_type_status::ERROR_OPENING_KEY; +} + +iso_type_status iso_file_decryption::check_type(const std::string& path, std::string& key_path, aes_context* aes_ctx) +{ + if (!is_iso_file(path)) + { + return iso_type_status::NOT_ISO; + } + + // Remove file extension from file path + const usz ext_pos = path.rfind('.'); + const std::string name_path = ext_pos == umax ? path : path.substr(0, ext_pos); + + // Detect file name (with no parent folder and no file extension) + const usz name_pos = name_path.rfind('/'); + const std::string name = name_pos == umax ? name_path : name_path.substr(name_pos); + + const std::array key_paths { + name_path + ".dkey", + name_path + ".key", + rpcs3::utils::get_redump_key_dir() + name + ".dkey", + rpcs3::utils::get_redump_key_dir() + name + ".key" + }; + + for (const std::string& path : key_paths) + { + if (fs::is_file(path)) + { + key_path = path; + return get_key(key_path, aes_ctx); + } + } + + return iso_type_status::ERROR_OPENING_KEY; +} + +bool iso_file_decryption::init(const std::string& path, iso_archive* archive) +{ + // Reset attributes first + m_enc_type = iso_encryption_type::NONE; + m_region_info.clear(); + // // Store the ISO region information (needed by both the "Redump" type (only on "decrypt()" method) and "3k3y" type) // - fs::file iso_file(path); + fs::file iso_file(std::make_unique(path, fs::read)); - if (!iso_file) + if (!is_iso_file(iso_file)) { - iso_log.error("init: Failed to open file: %s", path); + iso_log.error("init: Failed to recognize ISO file: %s", path); return false; } - std::array sec0_sec1 {}; + // Reset the file position after it was changed by is_iso_file() + iso_file.seek(0); + + std::array sec0_sec1; if (iso_file.size() < sec0_sec1.size()) { - iso_log.error("init: Found only %ull sector(s) (minimum required is 2): %s", iso_file.size(), path); + iso_log.error("init: Found only %llu sector(s) (minimum required is 2): %s", iso_file.size(), path); return false; } @@ -279,13 +381,13 @@ bool iso_file_decryption::init(const std::string& path) // Ensure the region count is a proper value if (region_count < 1 || region_count > 127) // It's non-PS3ISO { - iso_log.error("init: Failed to read region information: '%s' (region_count=%d)", path, region_count); + iso_log.error("init: Failed to read region information (region_count=%lu): %s", region_count, path); return false; } m_region_info.resize(region_count * 2 - 1); - for (size_t i = 0; i < m_region_info.size(); ++i) + for (size_t i = 0; i < m_region_info.size(); i++) { // Store the region information in address format m_region_info[i].encrypted = (i % 2 == 1); @@ -298,15 +400,28 @@ bool iso_file_decryption::init(const std::string& path) // Check for Redump type // + iso_type_status status; std::string key_path; - // Try to detect the Redump type. If so, the decryption context is set into "m_aes_dec" - switch (check_type(path, key_path, &m_aes_dec)) + // If raw device and requested by the caller ("archive" provided), scan the redump keys folder and retrieve + // (if present) the first key that allows decrypting a sector of the ISO file + if (fs::is_optical_raw_device(path) && archive) + { + status = retrieve_key(*archive, key_path, m_aes_dec); + } + else + { + // Try to detect the Redump type. If so, the decryption context is set into "m_aes_dec" + status = check_type(path, key_path, &m_aes_dec); + } + + switch (status) { case iso_type_status::NOT_ISO: iso_log.warning("init: Failed to recognize ISO file: %s", path); break; case iso_type_status::REDUMP_ISO: + iso_log.warning("init: Found matching key file: %s", key_path); m_enc_type = iso_encryption_type::REDUMP; // SET ENCRYPTION TYPE: REDUMP break; case iso_type_status::ERROR_OPENING_KEY: @@ -370,16 +485,20 @@ bool iso_file_decryption::init(const std::string& path) switch (m_enc_type) { case iso_encryption_type::REDUMP: - iso_log.warning("init: Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_info.size(), path); + sys_log.success("init(): Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_info.size(), path); + // sys_log.warning("init(): Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_info.size(), path); break; case iso_encryption_type::ENC_3K3Y: - iso_log.warning("init: Set 'enc type': ENC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); + sys_log.success("init(): Set 'enc type': ENC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); + // sys_log.warning("init(): Set 'enc type': ENC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); break; case iso_encryption_type::DEC_3K3Y: - iso_log.warning("init: Set 'enc type': DEC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); + sys_log.success("init(): Set 'enc type': DEC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); + // sys_log.warning("init(): Set 'enc type': DEC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); break; case iso_encryption_type::NONE: // If encryption type was not set for any reason - iso_log.warning("init: Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path); + sys_log.success("init(): Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path); + // sys_log.warning("init(): Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path); break; } @@ -425,7 +544,7 @@ bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std: } // Decrypt the region before sending it back - decrypt_data(m_aes_dec, offset, reinterpret_cast(buffer), size); + decrypt_data(m_aes_dec, offset, reinterpret_cast(buffer), reinterpret_cast(buffer), size); return true; } @@ -441,6 +560,165 @@ bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std: return true; } +iso_file_encrypted::iso_file_encrypted(const std::string& path, bs_t mode, const iso_fs_node& node, std::shared_ptr dec) + : iso_file(path, mode, node), m_dec(dec) +{ +} + +u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) +{ + // IMPORTANT NOTES: + // - For a raw device, we must use a support buffer aligned (probably enough on multiple of 4), otherwise any read from file will fail. + // For that reason, we don't use directly "buffer" (not guaranteeing any alignment) + // - "iso_file_decryption::decrypt()" method requires that offset and size are multiple of 16 bytes (ciphertext block's size) + // and that a previous ciphertext block (used as IV) is read in case offset is not a multiple of ISO_SECTOR_SIZE + // + // ---------------------------------------------------------------------- + // file on ISO archive: | ' ' | + // ---------------------------------------------------------------------- + // ' ' + // --------------------------------------------- + // buffer: | | + // --------------------------------------------- + // ' ' ' ' + // ------------------------------------------------------------------------------------------------------------------------------------- + // ISO archive: | sec 0 | sec 1 |xxxxx######'###########'###########'###########'##xxxxxxxxx| | ... | sec n-1 | sec n | + // ------------------------------------------------------------------------------------------------------------------------------------- + // 16 Bytes x block read: | | | | | | | '#######'###########'###########'###########'###| | | | | | | | | | | | | | | + // ' ' ' ' + // | first sec | inner sec(s) | last sec | + + const u64 max_size = std::min(size, local_extent_remaining(offset)); + + if (max_size == 0) + { + return 0; + } + + const u64 archive_first_offset = file_offset(offset); + const u64 archive_last_offset = archive_first_offset + max_size - 1; + u64 total_read; + + iso_sector first_sec, last_sec; + u64 offset_aligned; + u64 offset_aligned_first_out; + + first_sec.lba_address = (archive_first_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; + first_sec.offset = archive_first_offset % ISO_SECTOR_SIZE; + first_sec.size = first_sec.offset + max_size <= ISO_SECTOR_SIZE ? max_size : ISO_SECTOR_SIZE - first_sec.offset; + + last_sec.lba_address = last_sec.address_aligned = (archive_last_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; + // last_sec.offset = last_sec.offset_aligned = 0; // Always 0 so no need to set and use those attributes + last_sec.size = (archive_last_offset % ISO_SECTOR_SIZE) + 1; + + // + // First sector + // + + if (!m_raw_device) + { + offset_aligned = first_sec.offset & ~0xF; + offset_aligned_first_out = (first_sec.offset + first_sec.size) & ~0xF; + + first_sec.offset_aligned = offset_aligned != 0 ? offset_aligned - 16 : 0; // Eventually include the previous block (used as IV) + first_sec.size_aligned = offset_aligned_first_out != (first_sec.offset + first_sec.size) ? + offset_aligned_first_out + 16 - first_sec.offset_aligned : + offset_aligned_first_out - first_sec.offset_aligned; + first_sec.address_aligned = first_sec.lba_address + first_sec.offset_aligned; + } + else + { + first_sec.offset_aligned = 0; + first_sec.size_aligned = ISO_SECTOR_SIZE; + first_sec.address_aligned = first_sec.lba_address; + } + + total_read = m_file.read_at(first_sec.address_aligned, &reinterpret_cast(m_buf)[first_sec.offset_aligned], first_sec.size_aligned); + + m_dec->decrypt(first_sec.address_aligned, &reinterpret_cast(m_buf)[first_sec.offset_aligned], first_sec.size_aligned, m_meta.name); + memcpy(buffer, &reinterpret_cast(m_buf)[first_sec.offset], first_sec.size); + + u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1; + + if (sector_count < 2) // If no more sector(s) + { + if (total_read != first_sec.size_aligned) + { + iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, total_read, first_sec.size_aligned); + + seek(m_pos, fs::seek_set); + return 0; + } + + m_pos += max_size; + return max_size; + } + + // + // Inner sector(s), if any + // + + if (sector_count > 2) // If inner sector(s) are present + { + if (!m_raw_device) + { + u64 inner_sector_size = (sector_count - 2) * ISO_SECTOR_SIZE; + + total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast(buffer)[first_sec.size], inner_sector_size); + + m_dec->decrypt(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast(buffer)[first_sec.size], inner_sector_size, m_meta.name); + } + else + { + u64 inner_sector_offset = 0; + + for (u64 i = 0; i < sector_count - 2; i++, inner_sector_offset += ISO_SECTOR_SIZE) + { + total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE + inner_sector_offset, m_buf, ISO_SECTOR_SIZE); + + m_dec->decrypt(first_sec.lba_address + ISO_SECTOR_SIZE + inner_sector_offset, m_buf, ISO_SECTOR_SIZE, m_meta.name); + memcpy(&reinterpret_cast(buffer)[first_sec.size + inner_sector_offset], m_buf, ISO_SECTOR_SIZE); + } + } + } + + // + // Last sector + // + + if (!m_raw_device) + { + offset_aligned_first_out = last_sec.size & ~0xF; + + last_sec.size_aligned = offset_aligned_first_out != last_sec.size ? offset_aligned_first_out + 16 : offset_aligned_first_out; + } + else + { + last_sec.size_aligned = ISO_SECTOR_SIZE; + } + + total_read += m_file.read_at(last_sec.address_aligned, m_buf, last_sec.size_aligned); + + m_dec->decrypt(last_sec.address_aligned, m_buf, last_sec.size_aligned, m_meta.name); + memcpy(&reinterpret_cast(buffer)[max_size - last_sec.size], m_buf, last_sec.size); + + // + // As last, check for an unlikely reading error (decoding also failed due to use of partially initialized buffer) + // + + if (total_read != first_sec.size_aligned + last_sec.size_aligned + (sector_count - 2) * ISO_SECTOR_SIZE) + { + iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, + total_read, ISO_SECTOR_SIZE + ISO_SECTOR_SIZE + (sector_count - 2) * ISO_SECTOR_SIZE); + + seek(m_pos, fs::seek_set); + return 0; + } + + m_pos += max_size; + return max_size; +} + template inline T retrieve_endian_int(const u8* buf) { @@ -539,7 +817,7 @@ static std::optional iso_read_directory_entry(fs::file& entry, utf16.resize(header.file_name_length / 2); - for (usz i = 0; i < utf16.size(); ++i, raw++) + for (usz i = 0; i < utf16.size(); i++, raw++) { utf16[i] = *reinterpret_cast*>(raw); } @@ -661,23 +939,30 @@ u64 iso_fs_metadata::size() const iso_archive::iso_archive(const std::string& path) { m_path = path; - m_file = fs::file(path); - m_dec = std::make_shared(); - if (!m_dec->init(path)) + // "m_path" is updated with the raw device path in case "path" points to a BD drive + fs::get_optical_raw_device(path, &m_path); + + fs::file iso_file(std::make_unique(m_path, fs::read)); + + if (!iso_file || !is_iso_file(iso_file)) { - // Not ISO... TODO: throw something?? + // Not ISO... TODO: throw something? + iso_log.error("iso_archive: Failed to recognize ISO file: %s", path); return; } + // Reset the file position after it was changed by is_iso_file() + iso_file.seek(0); + u8 descriptor_type = -2; bool use_ucs2_decoding = false; do { - const auto descriptor_start = m_file.pos(); + const auto descriptor_start = iso_file.pos(); - descriptor_type = m_file.read(); + descriptor_type = iso_file.read(); // 1 = primary vol descriptor, 2 = joliet SVD if (descriptor_type == 1 || descriptor_type == 2) @@ -685,9 +970,9 @@ iso_archive::iso_archive(const std::string& path) use_ucs2_decoding = descriptor_type == 2; // Skip the rest of descriptor's data - m_file.seek(155, fs::seek_cur); + iso_file.seek(155, fs::seek_cur); - const auto node = iso_read_directory_entry(m_file, use_ucs2_decoding); + const auto node = iso_read_directory_entry(iso_file, use_ucs2_decoding); if (node) { @@ -698,11 +983,20 @@ iso_archive::iso_archive(const std::string& path) } } - m_file.seek(descriptor_start + ISO_SECTOR_SIZE); + iso_file.seek(descriptor_start + ISO_SECTOR_SIZE); } while (descriptor_type != 255); - iso_form_hierarchy(m_file, m_root, use_ucs2_decoding); + iso_form_hierarchy(iso_file, m_root, use_ucs2_decoding); + + // Only when the archive object is fully set, we can finally initialize the decryption object needing the archive object + m_dec = std::make_shared(); + + if (!m_dec->init(m_path, this)) + { + // TODO: throw something? + return; + } } iso_fs_node* iso_archive::retrieve(const std::string& passed_path) @@ -799,29 +1093,92 @@ bool iso_archive::is_file(const std::string& path) return !file_node->metadata.is_directory; } -iso_file iso_archive::open(const std::string& path) +std::unique_ptr iso_archive::get_iso_file(const std::string& path, bs_t mode, const iso_fs_node& node) { - return iso_file(fs::file(m_path), m_dec, *ensure(retrieve(path))); + if (m_dec->get_enc_type() == iso_encryption_type::NONE) + { + return std::make_unique(path, mode, node); + } + + return std::make_unique(path, mode, node, m_dec); +} + +std::unique_ptr iso_archive::open(const std::string& path) +{ + return get_iso_file(m_path, fs::read, *ensure(retrieve(path))); } psf::registry iso_archive::open_psf(const std::string& path) { - auto* archive_file = retrieve(path); + const auto node = retrieve(path); - if (!archive_file) + if (!node) { return psf::registry(); } - const fs::file psf_file(std::make_unique(fs::file(m_path), m_dec, *archive_file)); + const fs::file psf_file(get_iso_file(m_path, fs::read, *node)); return psf::load_object(psf_file, path); } -iso_file::iso_file(fs::file&& iso_handle, std::shared_ptr iso_dec, const iso_fs_node& node) - : m_file(std::move(iso_handle)), m_dec(iso_dec), m_meta(node.metadata) +iso_file::iso_file(const std::string& path, bs_t mode) { - m_file.seek(node.metadata.extents[0].start * ISO_SECTOR_SIZE); + m_file = std::move(fs::file(path, mode)); + + if (!m_file) + { + // Should never happen... TODO: throw something? + iso_log.error("iso_file: Failed to open file: %s", path); + return; + } + + m_meta.name = path; + m_meta.extents.push_back({0, m_file.size()}); + + m_file.seek(m_meta.extents[0].start * ISO_SECTOR_SIZE); + + m_raw_device = fs::is_optical_raw_device(path) ? true : false; + + // IMPORTANT NOTE: It must be aligned (probably enough on multiple of 4) to support raw device, otherwise any read from file will fail +#if defined(_WIN32) + m_buf = _aligned_malloc(ISO_SECTOR_SIZE, ISO_SECTOR_SIZE * 2); +#else + m_buf = std::aligned_alloc(ISO_SECTOR_SIZE * 2, ISO_SECTOR_SIZE); +#endif +} + +iso_file::iso_file(const std::string& path, bs_t mode, const iso_fs_node& node) + : m_meta(node.metadata) +{ + m_file = std::move(fs::file(path, mode)); + + if (!m_file) + { + // Should never happen... TODO: throw something? + iso_log.error("iso_file: Failed to open file: %s", path); + return; + } + + m_file.seek(m_meta.extents[0].start * ISO_SECTOR_SIZE); + + m_raw_device = fs::is_optical_raw_device(path) ? true : false; + + // IMPORTANT NOTE: It must be aligned (probably enough on multiple of 4) to support raw device, otherwise any read from file will fail +#if defined(_WIN32) + m_buf = _aligned_malloc(ISO_SECTOR_SIZE, ISO_SECTOR_SIZE * 2); +#else + m_buf = std::aligned_alloc(ISO_SECTOR_SIZE * 2, ISO_SECTOR_SIZE); +#endif +} + +iso_file::~iso_file() +{ +#if defined(_WIN32) + _aligned_free(m_buf); +#else + std::free(m_buf); +#endif } fs::stat_t iso_file::get_stat() @@ -882,10 +1239,7 @@ u64 iso_file::file_offset(u64 pos) const u64 iso_file::read(void* buffer, u64 size) { - const auto r = read_at(m_pos, buffer, size); - - m_pos += r; - return r; + return read_at(m_pos, buffer, size); } u64 iso_file::read_at(u64 offset, void* buffer, u64 size) @@ -898,29 +1252,31 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) } const u64 archive_first_offset = file_offset(offset); - const u64 total_size = this->size(); u64 total_read; - // If it's a non-encrypted type - if (m_dec->get_enc_type() == iso_encryption_type::NONE) + // If it's not a raw device + if (!m_raw_device) { total_read = m_file.read_at(archive_first_offset, buffer, max_size); - if (size > total_read && (offset + total_read) < total_size) + if (total_read != max_size) { - total_read += read_at(offset + total_read, reinterpret_cast(buffer) + total_read, size - total_read); + iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, total_read, max_size); + + seek(m_pos, fs::seek_set); + return 0; } - return total_read; + m_pos += max_size; + return max_size; } - // If it's an encrypted type + // If it's a raw device // IMPORTANT NOTE: // - // "iso_file_decryption::decrypt()" method requires that offset and size are multiple of 16 bytes - // (ciphertext block's size) and that a previous ciphertext block (used as IV) is read in case - // offset is not a multiple of ISO_SECTOR_SIZE + // For a raw device, we must use a support buffer aligned (probably enough on multiple of 4), otherwise any read from file will fail. + // For that reason, we don't use directly "buffer" (not guaranteeing any alignment) // // ---------------------------------------------------------------------- // file on ISO archive: | ' ' | @@ -933,53 +1289,41 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) // ------------------------------------------------------------------------------------------------------------------------------------- // ISO archive: | sec 0 | sec 1 |xxxxx######'###########'###########'###########'##xxxxxxxxx| | ... | sec n-1 | sec n | // ------------------------------------------------------------------------------------------------------------------------------------- - // 16 Bytes x block read: | | | | | | | '#######'###########'###########'###########'###| | | | | | | | | | | | | | | // ' ' ' ' // | first sec | inner sec(s) | last sec | const u64 archive_last_offset = archive_first_offset + max_size - 1; iso_sector first_sec, last_sec; - u64 offset_aligned; - u64 offset_aligned_first_out; first_sec.lba_address = (archive_first_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; first_sec.offset = archive_first_offset % ISO_SECTOR_SIZE; first_sec.size = first_sec.offset + max_size <= ISO_SECTOR_SIZE ? max_size : ISO_SECTOR_SIZE - first_sec.offset; last_sec.lba_address = last_sec.address_aligned = (archive_last_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; - //last_sec.offset = last_sec.offset_aligned = 0; // Always 0 so no need to set and use those attributes + // last_sec.offset = last_sec.offset_aligned = 0; // Always 0 so no need to set and use those attributes last_sec.size = (archive_last_offset % ISO_SECTOR_SIZE) + 1; // // First sector // - offset_aligned = first_sec.offset & ~0xF; - offset_aligned_first_out = (first_sec.offset + first_sec.size) & ~0xF; - - first_sec.offset_aligned = offset_aligned != 0 ? offset_aligned - 16 : 0; // Eventually include the previous block (used as IV) - first_sec.size_aligned = offset_aligned_first_out != (first_sec.offset + first_sec.size) ? - offset_aligned_first_out + 16 - first_sec.offset_aligned : - offset_aligned_first_out - first_sec.offset_aligned; - first_sec.address_aligned = first_sec.lba_address + first_sec.offset_aligned; - - total_read = m_file.read_at(first_sec.address_aligned, &first_sec.buf.data()[first_sec.offset_aligned], first_sec.size_aligned); + total_read = m_file.read_at(first_sec.lba_address, m_buf, ISO_SECTOR_SIZE); - m_dec->decrypt(first_sec.address_aligned, &first_sec.buf.data()[first_sec.offset_aligned], first_sec.size_aligned, m_meta.name); - memcpy(buffer, &first_sec.buf.data()[first_sec.offset], first_sec.size); + memcpy(buffer, &reinterpret_cast(m_buf)[first_sec.offset], first_sec.size); u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1; if (sector_count < 2) // If no more sector(s) { - if (total_read != first_sec.size_aligned) + if (total_read != ISO_SECTOR_SIZE) { - iso_log.error("read_at: %s: Error reading from file", m_meta.name); + iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, total_read, ISO_SECTOR_SIZE); seek(m_pos, fs::seek_set); return 0; } + m_pos += max_size; return max_size; } @@ -987,41 +1331,40 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) // Inner sector(s), if any // - u64 expected_inner_sector_read = 0; - if (sector_count > 2) // If inner sector(s) are present { - u64 inner_sector_size = expected_inner_sector_read = (sector_count - 2) * ISO_SECTOR_SIZE; + u64 sector_offset = 0; - total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast(buffer)[first_sec.size], inner_sector_size); + for (u64 i = 0; i < sector_count - 2; i++, sector_offset += ISO_SECTOR_SIZE) + { + total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE + sector_offset, m_buf, ISO_SECTOR_SIZE); - m_dec->decrypt(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast(buffer)[first_sec.size], inner_sector_size, m_meta.name); + memcpy(&reinterpret_cast(buffer)[first_sec.size + sector_offset], m_buf, ISO_SECTOR_SIZE); + } } // // Last sector // - offset_aligned_first_out = last_sec.size & ~0xF; - last_sec.size_aligned = offset_aligned_first_out != last_sec.size ? offset_aligned_first_out + 16 : offset_aligned_first_out; + total_read += m_file.read_at(last_sec.address_aligned, m_buf, ISO_SECTOR_SIZE); - total_read += m_file.read_at(last_sec.address_aligned, last_sec.buf.data(), last_sec.size_aligned); - - m_dec->decrypt(last_sec.address_aligned, last_sec.buf.data(), last_sec.size_aligned, m_meta.name); - memcpy(&reinterpret_cast(buffer)[max_size - last_sec.size], last_sec.buf.data(), last_sec.size); + memcpy(&reinterpret_cast(buffer)[max_size - last_sec.size], m_buf, last_sec.size); // - // As last, check for an unlikely reading error (decoding also failed due to use of partially initialized buffer) + // As last, check for an unlikely reading error // - if (total_read != first_sec.size_aligned + last_sec.size_aligned + expected_inner_sector_read) + if (total_read != ISO_SECTOR_SIZE + ISO_SECTOR_SIZE + (sector_count - 2) * ISO_SECTOR_SIZE) { - iso_log.error("read_at: %s: Error reading from file", m_meta.name); + iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, + total_read, ISO_SECTOR_SIZE + ISO_SECTOR_SIZE + (sector_count - 2) * ISO_SECTOR_SIZE); seek(m_pos, fs::seek_set); return 0; } + m_pos += max_size; return max_size; } @@ -1169,7 +1512,7 @@ std::unique_ptr iso_device::open(const std::string& path, bs_t(fs::file(m_path, mode), m_archive.get_dec(), *node); + return m_archive.get_iso_file(m_archive.path(), mode, *node); } std::unique_ptr iso_device::open_dir(const std::string& path) diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index 52cc33065a01..5c542cd28659 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -6,8 +6,7 @@ #include "util/types.hpp" #include "Crypto/aes.h" -bool is_file_iso(const std::string& path); -bool is_file_iso(const fs::file& path); +bool is_iso_file(const std::string& path, u64* size = nullptr, bool* is_raw_device = nullptr); void load_iso(const std::string& path); void unload_iso(); @@ -58,6 +57,8 @@ enum class iso_type_status ERROR_PROCESSING_KEY }; +class iso_archive; + // ISO file decryption class class iso_file_decryption { @@ -66,14 +67,15 @@ class iso_file_decryption iso_encryption_type m_enc_type = iso_encryption_type::NONE; std::vector m_region_info; - void reset(); + static iso_type_status get_key(const std::string& key_path, aes_context* aes_ctx = nullptr); + static iso_type_status retrieve_key(iso_archive& archive, std::string& key_path, aes_context& aes_ctx); public: static iso_type_status check_type(const std::string& path, std::string& key_path, aes_context* aes_ctx = nullptr); iso_encryption_type get_enc_type() const { return m_enc_type; } - bool init(const std::string& path); + bool init(const std::string& path, iso_archive* archive = nullptr); bool decrypt(u64 offset, void* buffer, u64 size, const std::string& name); }; @@ -102,10 +104,11 @@ struct iso_fs_node class iso_file : public fs::file_base { -private: +protected: fs::file m_file; - std::shared_ptr m_dec; iso_fs_metadata m_meta; + bool m_raw_device = false; + void* m_buf = nullptr; u64 m_pos = 0; std::pair get_extent_pos(u64 pos) const; @@ -114,7 +117,11 @@ class iso_file : public fs::file_base u64 file_offset(u64 pos) const; public: - iso_file(fs::file&& iso_handle, std::shared_ptr iso_dec, const iso_fs_node& node); + iso_file(const std::string& path, bs_t mode); + iso_file(const std::string& path, bs_t mode, const iso_fs_node& node); + virtual ~iso_file(); + + explicit operator bool() const { return m_file.operator bool(); } fs::stat_t get_stat() override; bool trunc(u64 length) override; @@ -125,6 +132,19 @@ class iso_file : public fs::file_base u64 size() override; void release() override; + + friend class iso_file_decryption; +}; + +class iso_file_encrypted : public iso_file +{ +private: + std::shared_ptr m_dec; + +public: + iso_file_encrypted(const std::string& path, bs_t mode, const iso_fs_node& node, std::shared_ptr dec); + + u64 read_at(u64 offset, void* buffer, u64 size) override; }; class iso_dir : public fs::dir_base @@ -147,22 +167,23 @@ class iso_archive { private: std::string m_path; - fs::file m_file; - std::shared_ptr m_dec; iso_fs_node m_root {}; + std::shared_ptr m_dec; public: iso_archive(const std::string& path); const std::string& path() const { return m_path; } - const std::shared_ptr get_dec() { return m_dec; } iso_fs_node* retrieve(const std::string& path); bool exists(const std::string& path); bool is_file(const std::string& path); - iso_file open(const std::string& path); + std::unique_ptr get_iso_file(const std::string& path, bs_t mode, const iso_fs_node& node); + std::unique_ptr open(const std::string& path); psf::registry open_psf(const std::string& path); + + friend class iso_file; }; class iso_device : public fs::device_base diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 32d36d7bd1ab..6e745f448c5d 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -551,13 +551,14 @@ void game_list_frame::OnParsingFinished() { std::unique_ptr archive; iso_metadata_cache_entry cache_entry{}; - const bool is_iso = is_file_iso(dir_or_elf); + bool is_raw_device = false; + const bool is_archive = is_iso_file(dir_or_elf, nullptr, &is_raw_device); - if (is_iso) + if (is_archive) { - // Only construct iso_archive (which walks the full directory tree) - // when no valid cache entry exists for this ISO path + mtime. - if (!iso_cache::load(dir_or_elf, cache_entry)) + // Only construct iso_archive (which walks the full directory tree) in case of raw device or + // when no valid cache entry exists for this ISO path + mtime + if (is_raw_device || !iso_cache::load(dir_or_elf, cache_entry)) { archive = std::make_unique(dir_or_elf); } @@ -738,9 +739,9 @@ void game_list_frame::OnParsingFinished() } } - // On cache miss for an ISO, persist the resolved metadata so subsequent - // launches skip iso_archive construction entirely. - if (archive && is_iso) + // With the exception of raw device, on cache miss for an ISO, persist the resolved metadata so subsequent + // launches skip iso_archive construction entirely + if (archive && is_archive && !is_raw_device) { fs::stat_t iso_stat{}; if (fs::get_stat(dir_or_elf, iso_stat)) @@ -755,11 +756,11 @@ void game_list_frame::OnParsingFinished() if (game.icon_in_archive) { auto icon_file = archive->open(game.info.icon_path); - const auto icon_size = icon_file.size(); + const auto icon_size = icon_file->size(); if (icon_size > 0) { cache_entry.icon_data.resize(icon_size); - icon_file.read(cache_entry.icon_data.data(), icon_size); + icon_file->read(cache_entry.icon_data.data(), icon_size); } } @@ -854,7 +855,11 @@ void game_list_frame::OnParsingFinished() if (entry.is_from_yml) { - if (fs::is_file(entry.path + "/PARAM.SFO")) + if (is_iso_file(entry.path)) + { + push_path(entry.path, legit_paths); + } + else if (fs::is_file(entry.path + "/PARAM.SFO")) { push_path(entry.path, legit_paths); } @@ -887,10 +892,6 @@ void game_list_frame::OnParsingFinished() add_disc_dir(entry.path, legit_paths); } - else if (is_file_iso(entry.path)) - { - push_path(entry.path, legit_paths); - } else { game_list_log.trace("Invalid game path registered: %s", entry.path); diff --git a/rpcs3/rpcs3qt/game_list_grid.cpp b/rpcs3/rpcs3qt/game_list_grid.cpp index 903fdf016aa9..04dd3a4e98e2 100644 --- a/rpcs3/rpcs3qt/game_list_grid.cpp +++ b/rpcs3/rpcs3qt/game_list_grid.cpp @@ -124,7 +124,7 @@ void game_list_grid::populate( check_iso |= !fs::exists(game->info.audio_path); } - if (check_iso && is_file_iso(game->info.path)) + if (check_iso && is_iso_file(game->info.path)) { item->set_iso_path(game->info.path); } diff --git a/rpcs3/rpcs3qt/game_list_table.cpp b/rpcs3/rpcs3qt/game_list_table.cpp index ec88561580a9..ca157c5183ed 100644 --- a/rpcs3/rpcs3qt/game_list_table.cpp +++ b/rpcs3/rpcs3qt/game_list_table.cpp @@ -283,12 +283,8 @@ void game_list_table::populate( // Do not report size of apps inside /dev_flash (it does not make sense to do so) game->info.size_on_disk = 0; } - else if (is_file_iso(game->info.path)) + else if (is_iso_file(game->info.path, &game->info.size_on_disk)) // If iso file, game->info.size_on_disk is also set { - fs::stat_t iso_stat; - fs::get_stat(game->info.path, iso_stat); - - game->info.size_on_disk = iso_stat.size; } else { @@ -317,7 +313,7 @@ void game_list_table::populate( check_iso |= !fs::exists(game->info.audio_path); } - if (check_iso && is_file_iso(game->info.path)) + if (check_iso && is_iso_file(game->info.path)) { icon_item->set_iso_path(game->info.path); } diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index e44bed7f81d2..b651ea8799ff 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -4080,7 +4080,7 @@ main_window::drop_type main_window::IsValidFile(const QMimeData& md, QStringList const QString suffix_lo = info.suffix().toLower(); // check for directories first, only valid if all other paths led to directories until now. - if (info.isDir() || is_file_iso(path.toStdString())) + if (info.isDir() || is_iso_file(path.toStdString())) { if (type != drop_type::drop_dir && type != drop_type::drop_error) { diff --git a/rpcs3/rpcs3qt/qt_utils.cpp b/rpcs3/rpcs3qt/qt_utils.cpp index 7ef0317ec232..b648688bf8a8 100644 --- a/rpcs3/rpcs3qt/qt_utils.cpp +++ b/rpcs3/rpcs3qt/qt_utils.cpp @@ -403,7 +403,7 @@ namespace gui // Get Icon for the gs_frame from path. this handles presumably all possible use cases std::vector path_list; - const bool is_archive = is_file_iso(path); + const bool is_archive = is_iso_file(path); if (is_archive) { icon_path = "PS3_GAME/ICON0.PNG"; @@ -708,11 +708,15 @@ namespace gui bool load_iso_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path) { if (icon_path.empty() || archive_path.empty()) return false; - if (!is_file_iso(archive_path)) return false; - // Check cache first — avoids constructing a full iso_archive just for the icon. + bool is_raw_device = false; + const bool is_archive = is_iso_file(archive_path, nullptr, &is_raw_device); + + if (!is_archive) return false; + + // With the exception of raw device, check cache first — avoids constructing a full iso_archive just for the icon. iso_metadata_cache_entry cache_entry{}; - if (iso_cache::load(archive_path, cache_entry) && !cache_entry.icon_data.empty()) + if (!is_raw_device && iso_cache::load(archive_path, cache_entry) && !cache_entry.icon_data.empty()) { const QByteArray data(reinterpret_cast(cache_entry.icon_data.data()), static_cast(cache_entry.icon_data.size())); @@ -723,11 +727,11 @@ namespace gui if (!archive.exists(icon_path)) return false; auto icon_file = archive.open(icon_path); - const auto icon_size = icon_file.size(); + const auto icon_size = icon_file->size(); if (icon_size == 0) return false; QByteArray data(icon_size, 0); - icon_file.read(data.data(), icon_size); + icon_file->read(data.data(), icon_size); return icon.loadFromData(data); } diff --git a/rpcs3/rpcs3qt/qt_video_source.cpp b/rpcs3/rpcs3qt/qt_video_source.cpp index 760369ad742c..14cda830fbfa 100644 --- a/rpcs3/rpcs3qt/qt_video_source.cpp +++ b/rpcs3/rpcs3qt/qt_video_source.cpp @@ -113,11 +113,11 @@ void qt_video_source::init_movie() { iso_archive archive(m_iso_path); auto movie_file = archive.open(m_video_path.toStdString()); - const auto movie_size = movie_file.size(); + const auto movie_size = movie_file->size(); if (movie_size == 0) return; m_video_data = QByteArray(movie_size, 0); - movie_file.read(m_video_data.data(), movie_size); + movie_file->read(m_video_data.data(), movie_size); m_video_buffer = std::make_unique(&m_video_data); m_video_buffer->open(QIODevice::ReadOnly); @@ -156,14 +156,14 @@ void qt_video_source::init_movie() { iso_archive archive(m_iso_path); auto movie_file = archive.open(m_video_path.toStdString()); - const auto movie_size = movie_file.size(); + const auto movie_size = movie_file->size(); if (movie_size == 0) { return; } m_video_data = QByteArray(movie_size, 0); - movie_file.read(m_video_data.data(), movie_size); + movie_file->read(m_video_data.data(), movie_size); } if (m_video_data.isEmpty()) @@ -280,12 +280,12 @@ void qt_video_source::start_audio() { iso_archive archive(m_iso_path); auto audio_file = archive.open(m_audio_path.toStdString()); - const auto audio_size = audio_file.size(); + const auto audio_size = audio_file->size(); if (audio_size == 0) return; std::unique_ptr old_audio_data = std::move(audio.data); audio.data = std::make_unique(audio_size, 0); - audio_file.read(audio.data->data(), audio_size); + audio_file->read(audio.data->data(), audio_size); if (!audio.buffer) { diff --git a/rpcs3/rpcs3qt/shortcut_utils.cpp b/rpcs3/rpcs3qt/shortcut_utils.cpp index d7b5586e7592..2112dfe0e954 100644 --- a/rpcs3/rpcs3qt/shortcut_utils.cpp +++ b/rpcs3/rpcs3qt/shortcut_utils.cpp @@ -72,7 +72,7 @@ namespace gui::utils return false; } - const bool is_archive = is_file_iso(path); + const bool is_archive = is_iso_file(path); QPixmap icon; if (!load_icon(icon, src_icon_path, is_archive ? path : "")) @@ -478,7 +478,7 @@ namespace gui::utils std::string gameid_token_value; const std::string dev_flash = g_cfg_vfs.get_dev_flash(); - const bool is_iso = is_file_iso(game->info.path); + const bool is_archive = is_iso_file(game->info.path); std::shared_ptr archive; const auto file_exists = [&archive](const std::string& path) @@ -486,7 +486,7 @@ namespace gui::utils return archive ? archive->is_file(path) : fs::is_file(path); }; - if (is_iso) + if (is_archive) { gameid_token_value = game->info.serial; archive = std::make_shared(game->info.path); @@ -542,7 +542,7 @@ namespace gui::utils if (location == shortcut_location::steam) { // Try to find a nice banner for steam - const std::string sfo_dir = is_iso ? "PS3_GAME" : rpcs3::utils::get_sfo_dir_from_game_path(game->info.path); + const std::string sfo_dir = is_archive ? "PS3_GAME" : rpcs3::utils::get_sfo_dir_from_game_path(game->info.path); for (const std::string& filename : {"PIC1.PNG"s, "PIC3.PNG"s, "PIC0.PNG"s, "PIC2.PNG"s, "ICON0.PNG"s}) { From 5124119b984b7eb0ec11aaa625cd679857e339a8 Mon Sep 17 00:00:00 2001 From: digant73 Date: Mon, 27 Apr 2026 23:32:39 +0200 Subject: [PATCH 2/8] minor cleanup --- rpcs3/Loader/ISO.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 15e188282c85..f223fed2d9cb 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -485,20 +485,16 @@ bool iso_file_decryption::init(const std::string& path, iso_archive* archive) switch (m_enc_type) { case iso_encryption_type::REDUMP: - sys_log.success("init(): Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_info.size(), path); - // sys_log.warning("init(): Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_info.size(), path); + iso_log.warning("init: Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_info.size(), path); break; case iso_encryption_type::ENC_3K3Y: - sys_log.success("init(): Set 'enc type': ENC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); - // sys_log.warning("init(): Set 'enc type': ENC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); + iso_log.warning("init: Set 'enc type': ENC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); break; case iso_encryption_type::DEC_3K3Y: - sys_log.success("init(): Set 'enc type': DEC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); - // sys_log.warning("init(): Set 'enc type': DEC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); + iso_log.warning("init: Set 'enc type': DEC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); break; case iso_encryption_type::NONE: // If encryption type was not set for any reason - sys_log.success("init(): Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path); - // sys_log.warning("init(): Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path); + iso_log.warning("init: Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path); break; } From 9b2ac3f03d4668cf6227eaf08e855d2f6a7887a5 Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:00:18 +0200 Subject: [PATCH 3/8] apply requested changes --- Utilities/File.cpp | 35 +++++++++++------------ Utilities/File.h | 2 +- rpcs3/Emu/System.cpp | 2 +- rpcs3/Loader/ISO.cpp | 68 +++++++++++++++++++++++--------------------- 4 files changed, 53 insertions(+), 54 deletions(-) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index b26bdc51785a..3176d9a4ea52 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -1138,36 +1138,33 @@ bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device #ifdef _WIN32 constexpr u32 BUF_SIZE = 1000; - WCHAR drive_list[MAX_PATH] = {0}; + WCHAR drive_list[BUF_SIZE] = {0}; // GetLogicalDriveStrings() returns a double-null terminated list of null-terminated strings. // E.g. A:\B:\C:\ - DWORD copied = GetLogicalDriveStrings(MAX_PATH, drive_list); + const DWORD copied = GetLogicalDriveStrings(BUF_SIZE, drive_list); - if (copied > 0 && copied <= MAX_PATH) + if (copied == 0 || copied > BUF_SIZE) { - WCHAR* drive = drive_list; + return false; + } - while (*drive) + for (const WCHAR* drive = drive_list; drive && *drive; drive += wcslen(drive) + 1) + { + if (GetDriveType(drive) == DRIVE_CDROM) { - if (GetDriveType(drive) == DRIVE_CDROM) - { - std::wstring ws(drive); - std::string s = std::string(ws.begin(), ws.end() - 1); + const std::wstring ws(drive); + const std::string s = std::string(ws.begin(), ws.end() - 1); - if (path.starts_with(s)) + if (path.starts_with(s)) + { + if (raw_device) { - if (raw_device) - { - *raw_device = "\\\\.\\" + s; - } - - return true; + *raw_device = "\\\\.\\" + s; } - } - // Get the next drive - drive += wcslen(drive) + 1; + return true; + } } } diff --git a/Utilities/File.h b/Utilities/File.h index 94d9b5fc0aad..778bfacd4888 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -216,7 +216,7 @@ namespace fs // Check whether the path points to a raw device bool is_optical_raw_device(const std::string& path); - // Check whether the path points to an optical drive. If so, provide the raw device + // Check whether the path points to an optical drive. 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/Emu/System.cpp b/rpcs3/Emu/System.cpp index 0deae114b3ec..7f0437648b28 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -4237,7 +4237,7 @@ u32 Emulator::AddGamesFromDir(const std::string& path) // search direct subdirectories, that way we can drop one folder containing all games for (; path_it != entries.end(); ++path_it) { - auto dir_entry = std::move(*path_it); + const auto dir_entry = std::move(*path_it); if (dir_entry.name == "." || dir_entry.name == "..") { diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index f223fed2d9cb..ed80144c6bea 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -36,7 +36,7 @@ static bool is_iso_file(const fs::file& file, u64* size = nullptr) file.read_at(32768ULL + 1, magic, 5); - bool ret = magic[0] == 'C' && magic[1] == 'D' && magic[2] == '0' && magic[3] == '0' && magic[4] == '1'; + const bool ret = magic[0] == 'C' && magic[1] == 'D' && magic[2] == '0' && magic[3] == '0' && magic[4] == '1'; if (size && ret) { @@ -48,12 +48,17 @@ static bool is_iso_file(const fs::file& file, u64* size = nullptr) bool is_iso_file(const std::string& path, u64* size, bool* is_raw_device) { + if (path.empty()) + { + return false; + } + std::string new_path = path; // "new_path" is updated with the raw device path in case "path" points to a BD drive - bool raw_device = fs::get_optical_raw_device(path, &new_path); + const bool raw_device = fs::get_optical_raw_device(path, &new_path); - if (path.empty() || (!raw_device && fs::is_dir(path))) + if (!raw_device && fs::is_dir(path)) { return false; } @@ -86,7 +91,7 @@ static void reset_iv(std::array& iv, u32 lba) } // Main function that will decrypt the sector(s) -static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, unsigned char* out_buffer, u64 size) +static bool decrypt_data(aes_context& aes, u64 offset, const unsigned char* buffer, unsigned char* out_buffer, u64 size) { // The following preliminary checks are good to be provided. // Commented out to gain a bit of performance, just because we know the caller is providing values in the expected range @@ -115,7 +120,7 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, un // Otherwise, the IV is based on sector's LBA if (sector_offset != 0) { - memcpy(iv.data(), buffer, 16); + std::memcpy(iv.data(), buffer, 16); cur_offset = 16; } else @@ -190,7 +195,7 @@ iso_type_status iso_file_decryption::get_key(const std::string& key_path, aes_co // binary (".key") and so not needing any further conversion from hex string to bytes if (key_len == sizeof(key)) { - memcpy(key.data(), key_str, sizeof(key)); + std::memcpy(key.data(), key_str, sizeof(key)); } else { @@ -271,7 +276,7 @@ iso_type_status iso_file_decryption::retrieve_key(iso_archive& archive, std::str for (auto path_it = entries.begin(); path_it != entries.end(); path_it++) { - auto dir_entry = std::move(*path_it); + const auto dir_entry = std::move(*path_it); if (dir_entry.name == "." || dir_entry.name == ".." || dir_entry.is_directory) { @@ -293,7 +298,7 @@ iso_type_status iso_file_decryption::retrieve_key(iso_archive& archive, std::str } // If the decrypted data match the magic value - if (memcmp(magic_value.data(), dec_sec.data(), magic_value.size()) == 0) + if (std::memcmp(magic_value.data(), dec_sec.data(), magic_value.size()) == 0) { return iso_type_status::REDUMP_ISO; } @@ -447,12 +452,12 @@ bool iso_file_decryption::init(const std::string& path, iso_archive* archive) static const unsigned char k3k3y_dec_watermark[16] = {0x44, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x33, 0x4B, 0x20, 0x42, 0x4C, 0x44}; - if (memcmp(&k3k3y_enc_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_enc_watermark)) == 0) + if (std::memcmp(&k3k3y_enc_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_enc_watermark)) == 0) { // Grab D1 from the 3k3y sector unsigned char key[16]; - memcpy(key, &sec0_sec1[0xF80], 0x10); + std::memcpy(key, &sec0_sec1[0xF80], 0x10); // Convert D1 to KEY and generate the "m_aes_dec" context unsigned char key_d1[] = {0x38, 11, 0xcf, 11, 0x53, 0x45, 0x5b, 60, 120, 0x17, 0xab, 0x4f, 0xa3, 0xba, 0x90, 0xed}; @@ -476,7 +481,7 @@ bool iso_file_decryption::init(const std::string& path, iso_archive* archive) iso_log.error("init: Failed to set encryption type to ENC_3K3Y: %s", path); } } - else if (memcmp(&k3k3y_dec_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_dec_watermark)) == 0) + else if (std::memcmp(&k3k3y_dec_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_dec_watermark)) == 0) { m_enc_type = iso_encryption_type::DEC_3K3Y; // SET ENCRYPTION TYPE: DEC_3K3Y } @@ -592,12 +597,10 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) } const u64 archive_first_offset = file_offset(offset); - const u64 archive_last_offset = archive_first_offset + max_size - 1; - u64 total_read; + const u64 archive_last_offset = archive_first_offset + max_size - 1;; iso_sector first_sec, last_sec; - u64 offset_aligned; - u64 offset_aligned_first_out; + u64 offset_aligned_first_out = 0; first_sec.lba_address = (archive_first_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; first_sec.offset = archive_first_offset % ISO_SECTOR_SIZE; @@ -613,7 +616,7 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) if (!m_raw_device) { - offset_aligned = first_sec.offset & ~0xF; + const u64 offset_aligned = first_sec.offset & ~0xF; offset_aligned_first_out = (first_sec.offset + first_sec.size) & ~0xF; first_sec.offset_aligned = offset_aligned != 0 ? offset_aligned - 16 : 0; // Eventually include the previous block (used as IV) @@ -629,12 +632,12 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) first_sec.address_aligned = first_sec.lba_address; } - total_read = m_file.read_at(first_sec.address_aligned, &reinterpret_cast(m_buf)[first_sec.offset_aligned], first_sec.size_aligned); + u64 total_read = m_file.read_at(first_sec.address_aligned, &reinterpret_cast(m_buf)[first_sec.offset_aligned], first_sec.size_aligned); m_dec->decrypt(first_sec.address_aligned, &reinterpret_cast(m_buf)[first_sec.offset_aligned], first_sec.size_aligned, m_meta.name); - memcpy(buffer, &reinterpret_cast(m_buf)[first_sec.offset], first_sec.size); + std::memcpy(buffer, &reinterpret_cast(m_buf)[first_sec.offset], first_sec.size); - u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1; + const u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1; if (sector_count < 2) // If no more sector(s) { @@ -658,7 +661,7 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) { if (!m_raw_device) { - u64 inner_sector_size = (sector_count - 2) * ISO_SECTOR_SIZE; + const u64 inner_sector_size = (sector_count - 2) * ISO_SECTOR_SIZE; total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast(buffer)[first_sec.size], inner_sector_size); @@ -673,7 +676,7 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE + inner_sector_offset, m_buf, ISO_SECTOR_SIZE); m_dec->decrypt(first_sec.lba_address + ISO_SECTOR_SIZE + inner_sector_offset, m_buf, ISO_SECTOR_SIZE, m_meta.name); - memcpy(&reinterpret_cast(buffer)[first_sec.size + inner_sector_offset], m_buf, ISO_SECTOR_SIZE); + std::memcpy(&reinterpret_cast(buffer)[first_sec.size + inner_sector_offset], m_buf, ISO_SECTOR_SIZE); } } } @@ -696,7 +699,7 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) total_read += m_file.read_at(last_sec.address_aligned, m_buf, last_sec.size_aligned); m_dec->decrypt(last_sec.address_aligned, m_buf, last_sec.size_aligned, m_meta.name); - memcpy(&reinterpret_cast(buffer)[max_size - last_sec.size], m_buf, last_sec.size); + std::memcpy(&reinterpret_cast(buffer)[max_size - last_sec.size], m_buf, last_sec.size); // // As last, check for an unlikely reading error (decoding also failed due to use of partially initialized buffer) @@ -1120,7 +1123,7 @@ psf::registry iso_archive::open_psf(const std::string& path) iso_file::iso_file(const std::string& path, bs_t mode) { - m_file = std::move(fs::file(path, mode)); + m_file = fs::file(path, mode); if (!m_file) { @@ -1134,7 +1137,7 @@ iso_file::iso_file(const std::string& path, bs_t mode) m_file.seek(m_meta.extents[0].start * ISO_SECTOR_SIZE); - m_raw_device = fs::is_optical_raw_device(path) ? true : false; + m_raw_device = fs::is_optical_raw_device(path); // IMPORTANT NOTE: It must be aligned (probably enough on multiple of 4) to support raw device, otherwise any read from file will fail #if defined(_WIN32) @@ -1147,7 +1150,7 @@ iso_file::iso_file(const std::string& path, bs_t mode) iso_file::iso_file(const std::string& path, bs_t mode, const iso_fs_node& node) : m_meta(node.metadata) { - m_file = std::move(fs::file(path, mode)); + m_file = fs::file(path, mode); if (!m_file) { @@ -1158,7 +1161,7 @@ iso_file::iso_file(const std::string& path, bs_t mode, const iso_ m_file.seek(m_meta.extents[0].start * ISO_SECTOR_SIZE); - m_raw_device = fs::is_optical_raw_device(path) ? true : false; + m_raw_device = fs::is_optical_raw_device(path); // IMPORTANT NOTE: It must be aligned (probably enough on multiple of 4) to support raw device, otherwise any read from file will fail #if defined(_WIN32) @@ -1248,12 +1251,11 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) } const u64 archive_first_offset = file_offset(offset); - u64 total_read; // If it's not a raw device if (!m_raw_device) { - total_read = m_file.read_at(archive_first_offset, buffer, max_size); + u64 total_read = m_file.read_at(archive_first_offset, buffer, max_size); if (total_read != max_size) { @@ -1303,11 +1305,11 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) // First sector // - total_read = m_file.read_at(first_sec.lba_address, m_buf, ISO_SECTOR_SIZE); + u64 total_read = m_file.read_at(first_sec.lba_address, m_buf, ISO_SECTOR_SIZE); - memcpy(buffer, &reinterpret_cast(m_buf)[first_sec.offset], first_sec.size); + std::memcpy(buffer, &reinterpret_cast(m_buf)[first_sec.offset], first_sec.size); - u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1; + const u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1; if (sector_count < 2) // If no more sector(s) { @@ -1335,7 +1337,7 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) { total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE + sector_offset, m_buf, ISO_SECTOR_SIZE); - memcpy(&reinterpret_cast(buffer)[first_sec.size + sector_offset], m_buf, ISO_SECTOR_SIZE); + std::memcpy(&reinterpret_cast(buffer)[first_sec.size + sector_offset], m_buf, ISO_SECTOR_SIZE); } } @@ -1345,7 +1347,7 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) total_read += m_file.read_at(last_sec.address_aligned, m_buf, ISO_SECTOR_SIZE); - memcpy(&reinterpret_cast(buffer)[max_size - last_sec.size], m_buf, last_sec.size); + std::memcpy(&reinterpret_cast(buffer)[max_size - last_sec.size], m_buf, last_sec.size); // // As last, check for an unlikely reading error From 29abcfbeb9516c3b1f12f53048a93a86c2036907 Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:52:14 +0200 Subject: [PATCH 4/8] apply requested change --- Utilities/File.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index 3176d9a4ea52..d58331e0e2ad 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -411,11 +411,12 @@ namespace fs class windows_file final : public file_base { HANDLE m_handle; + bool m_raw_device; atomic_t m_pos {0}; public: - windows_file(HANDLE handle) - : m_handle(handle) + windows_file(HANDLE handle, bool raw_device = false) + : m_handle(handle), m_raw_device(raw_device) { } @@ -575,12 +576,12 @@ namespace fs u64 size() override { - // NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso). - LARGE_INTEGER size; - - // It will fail for a raw device. - if (GetFileSizeEx(m_handle, &size)) // "file::size" + if (!m_raw_device) { + // NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso). + LARGE_INTEGER size; + + ensure(GetFileSizeEx(m_handle, &size)); // "file::size" return size.QuadPart; } @@ -1740,20 +1741,20 @@ fs::file::file(const std::string& path, bs_t mode) return; } + // If path points to an optical raw device, complete the file opening + // (the following GetFileInformationByHandle() would always fail on a raw device). + if (is_optical_raw_device(path)) + { + m_file = std::make_unique(handle, true); + return; + } + // Check if the handle is actually valid. // This can fail on empty mounted drives (e.g. with ERROR_NOT_READY or ERROR_INVALID_FUNCTION). - // It always fails on raw devices. BY_HANDLE_FILE_INFORMATION info{}; if (!GetFileInformationByHandle(handle, &info)) { - // If path points to an optical raw device, complete the file opening - if (is_optical_raw_device(path)) - { - m_file = std::make_unique(handle); - return; - } - const DWORD last_error = GetLastError(); CloseHandle(handle); From 2501c17a76286f5e3b0b023695a7a5b363f90179 Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Tue, 28 Apr 2026 17:47:38 +0200 Subject: [PATCH 5/8] properly set m_pos on read_at() (for ISO) --- rpcs3/Loader/ISO.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index ed80144c6bea..ec36eab80696 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -649,7 +649,7 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) return 0; } - m_pos += max_size; + m_pos = offset + max_size; return max_size; } @@ -714,7 +714,7 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) return 0; } - m_pos += max_size; + m_pos = offset + max_size; return max_size; } @@ -1265,7 +1265,7 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) return 0; } - m_pos += max_size; + m_pos = offset + max_size; return max_size; } @@ -1321,7 +1321,7 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) return 0; } - m_pos += max_size; + m_pos = offset + max_size; return max_size; } @@ -1362,7 +1362,7 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) return 0; } - m_pos += max_size; + m_pos = offset + max_size; return max_size; } From 0c9aa3d79f3285c87de3cf804b9a9b8a02b48c59 Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Tue, 28 Apr 2026 18:02:47 +0200 Subject: [PATCH 6/8] minor cleanup --- rpcs3/Loader/ISO.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index ec36eab80696..ee9cadde5939 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -1374,11 +1374,10 @@ u64 iso_file::write(const void* /*buffer*/, u64 /*size*/) u64 iso_file::seek(s64 offset, fs::seek_mode whence) { - const s64 total_size = size(); const s64 new_pos = whence == fs::seek_set ? offset : whence == fs::seek_cur ? offset + m_pos : - whence == fs::seek_end ? offset + total_size : -1; + whence == fs::seek_end ? offset + size() : -1; if (new_pos < 0) { From 9d0b2f1b9f49438bcc36067e12fa61802796076e Mon Sep 17 00:00:00 2001 From: digant73 Date: Tue, 28 Apr 2026 19:30:42 +0200 Subject: [PATCH 7/8] rollback changes on m_pos on read() and read_at() --- rpcs3/Loader/ISO.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index ee9cadde5939..dc0c9693c9a7 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -597,7 +597,7 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) } const u64 archive_first_offset = file_offset(offset); - const u64 archive_last_offset = archive_first_offset + max_size - 1;; + const u64 archive_last_offset = archive_first_offset + max_size - 1; iso_sector first_sec, last_sec; u64 offset_aligned_first_out = 0; @@ -649,7 +649,6 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) return 0; } - m_pos = offset + max_size; return max_size; } @@ -714,7 +713,6 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) return 0; } - m_pos = offset + max_size; return max_size; } @@ -1238,7 +1236,10 @@ u64 iso_file::file_offset(u64 pos) const u64 iso_file::read(void* buffer, u64 size) { - return read_at(m_pos, buffer, size); + const auto r = read_at(m_pos, buffer, size); + + m_pos += r; + return r; } u64 iso_file::read_at(u64 offset, void* buffer, u64 size) @@ -1265,7 +1266,6 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) return 0; } - m_pos = offset + max_size; return max_size; } @@ -1321,7 +1321,6 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) return 0; } - m_pos = offset + max_size; return max_size; } @@ -1362,7 +1361,6 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) return 0; } - m_pos = offset + max_size; return max_size; } From e15fe158c029ed8aba4e0ab1dd3672b779ce4f6f Mon Sep 17 00:00:00 2001 From: digant73 Date: Tue, 28 Apr 2026 21:24:49 +0200 Subject: [PATCH 8/8] use thread-safe buffer on read_at() --- rpcs3/Loader/ISO.cpp | 96 ++++++++++++++++++++++---------------------- rpcs3/Loader/ISO.h | 2 - 2 files changed, 48 insertions(+), 50 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index dc0c9693c9a7..f5ba9f839caf 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -25,6 +25,35 @@ struct iso_sector u64 size_aligned; }; +static void* get_aligned_buf() +{ + static thread_local struct aligned_buf + { + void* 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 +#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); +#endif + } + + ~aligned_buf() noexcept + { +#if defined(_WIN32) + _aligned_free(buf); +#else + std::free(buf); +#endif + } + } s_aligned_buf {}; + + return s_aligned_buf.buf; +} + static bool is_iso_file(const fs::file& file, u64* size = nullptr) { if (!file || file.size() < 32768ULL + 6) @@ -598,9 +627,8 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) const u64 archive_first_offset = file_offset(offset); const u64 archive_last_offset = archive_first_offset + max_size - 1; - iso_sector first_sec, last_sec; - u64 offset_aligned_first_out = 0; + void* aligned_buf = get_aligned_buf(); // thread-safe buffer first_sec.lba_address = (archive_first_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; first_sec.offset = archive_first_offset % ISO_SECTOR_SIZE; @@ -614,6 +642,8 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) // First sector // + u64 offset_aligned_first_out = 0; + if (!m_raw_device) { const u64 offset_aligned = first_sec.offset & ~0xF; @@ -632,10 +662,10 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) first_sec.address_aligned = first_sec.lba_address; } - u64 total_read = m_file.read_at(first_sec.address_aligned, &reinterpret_cast(m_buf)[first_sec.offset_aligned], first_sec.size_aligned); + u64 total_read = m_file.read_at(first_sec.address_aligned, &reinterpret_cast(aligned_buf)[first_sec.offset_aligned], first_sec.size_aligned); - m_dec->decrypt(first_sec.address_aligned, &reinterpret_cast(m_buf)[first_sec.offset_aligned], first_sec.size_aligned, m_meta.name); - std::memcpy(buffer, &reinterpret_cast(m_buf)[first_sec.offset], first_sec.size); + m_dec->decrypt(first_sec.address_aligned, &reinterpret_cast(aligned_buf)[first_sec.offset_aligned], first_sec.size_aligned, m_meta.name); + std::memcpy(buffer, &reinterpret_cast(aligned_buf)[first_sec.offset], first_sec.size); const u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1; @@ -644,8 +674,6 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) if (total_read != first_sec.size_aligned) { iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, total_read, first_sec.size_aligned); - - seek(m_pos, fs::seek_set); return 0; } @@ -672,10 +700,10 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) for (u64 i = 0; i < sector_count - 2; i++, inner_sector_offset += ISO_SECTOR_SIZE) { - total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE + inner_sector_offset, m_buf, ISO_SECTOR_SIZE); + total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE + inner_sector_offset, aligned_buf, ISO_SECTOR_SIZE); - m_dec->decrypt(first_sec.lba_address + ISO_SECTOR_SIZE + inner_sector_offset, m_buf, ISO_SECTOR_SIZE, m_meta.name); - std::memcpy(&reinterpret_cast(buffer)[first_sec.size + inner_sector_offset], m_buf, ISO_SECTOR_SIZE); + m_dec->decrypt(first_sec.lba_address + ISO_SECTOR_SIZE + inner_sector_offset, aligned_buf, ISO_SECTOR_SIZE, m_meta.name); + std::memcpy(&reinterpret_cast(buffer)[first_sec.size + inner_sector_offset], aligned_buf, ISO_SECTOR_SIZE); } } } @@ -695,10 +723,10 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) last_sec.size_aligned = ISO_SECTOR_SIZE; } - total_read += m_file.read_at(last_sec.address_aligned, m_buf, last_sec.size_aligned); + total_read += m_file.read_at(last_sec.address_aligned, aligned_buf, last_sec.size_aligned); - m_dec->decrypt(last_sec.address_aligned, m_buf, last_sec.size_aligned, m_meta.name); - std::memcpy(&reinterpret_cast(buffer)[max_size - last_sec.size], m_buf, last_sec.size); + m_dec->decrypt(last_sec.address_aligned, aligned_buf, last_sec.size_aligned, m_meta.name); + std::memcpy(&reinterpret_cast(buffer)[max_size - last_sec.size], aligned_buf, last_sec.size); // // As last, check for an unlikely reading error (decoding also failed due to use of partially initialized buffer) @@ -709,7 +737,6 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, total_read, ISO_SECTOR_SIZE + ISO_SECTOR_SIZE + (sector_count - 2) * ISO_SECTOR_SIZE); - seek(m_pos, fs::seek_set); return 0; } @@ -1136,13 +1163,6 @@ iso_file::iso_file(const std::string& path, bs_t mode) m_file.seek(m_meta.extents[0].start * ISO_SECTOR_SIZE); m_raw_device = fs::is_optical_raw_device(path); - - // IMPORTANT NOTE: It must be aligned (probably enough on multiple of 4) to support raw device, otherwise any read from file will fail -#if defined(_WIN32) - m_buf = _aligned_malloc(ISO_SECTOR_SIZE, ISO_SECTOR_SIZE * 2); -#else - m_buf = std::aligned_alloc(ISO_SECTOR_SIZE * 2, ISO_SECTOR_SIZE); -#endif } iso_file::iso_file(const std::string& path, bs_t mode, const iso_fs_node& node) @@ -1160,22 +1180,6 @@ iso_file::iso_file(const std::string& path, bs_t mode, const iso_ m_file.seek(m_meta.extents[0].start * ISO_SECTOR_SIZE); m_raw_device = fs::is_optical_raw_device(path); - - // IMPORTANT NOTE: It must be aligned (probably enough on multiple of 4) to support raw device, otherwise any read from file will fail -#if defined(_WIN32) - m_buf = _aligned_malloc(ISO_SECTOR_SIZE, ISO_SECTOR_SIZE * 2); -#else - m_buf = std::aligned_alloc(ISO_SECTOR_SIZE * 2, ISO_SECTOR_SIZE); -#endif -} - -iso_file::~iso_file() -{ -#if defined(_WIN32) - _aligned_free(m_buf); -#else - std::free(m_buf); -#endif } fs::stat_t iso_file::get_stat() @@ -1261,8 +1265,6 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) if (total_read != max_size) { iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, total_read, max_size); - - seek(m_pos, fs::seek_set); return 0; } @@ -1292,6 +1294,7 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) const u64 archive_last_offset = archive_first_offset + max_size - 1; iso_sector first_sec, last_sec; + void* aligned_buf = get_aligned_buf(); // thread-safe buffer first_sec.lba_address = (archive_first_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; first_sec.offset = archive_first_offset % ISO_SECTOR_SIZE; @@ -1305,9 +1308,9 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) // First sector // - u64 total_read = m_file.read_at(first_sec.lba_address, m_buf, ISO_SECTOR_SIZE); + u64 total_read = m_file.read_at(first_sec.lba_address, aligned_buf, ISO_SECTOR_SIZE); - std::memcpy(buffer, &reinterpret_cast(m_buf)[first_sec.offset], first_sec.size); + std::memcpy(buffer, &reinterpret_cast(aligned_buf)[first_sec.offset], first_sec.size); const u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1; @@ -1316,8 +1319,6 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) if (total_read != ISO_SECTOR_SIZE) { iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, total_read, ISO_SECTOR_SIZE); - - seek(m_pos, fs::seek_set); return 0; } @@ -1334,9 +1335,9 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) for (u64 i = 0; i < sector_count - 2; i++, sector_offset += ISO_SECTOR_SIZE) { - total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE + sector_offset, m_buf, ISO_SECTOR_SIZE); + total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE + sector_offset, aligned_buf, ISO_SECTOR_SIZE); - std::memcpy(&reinterpret_cast(buffer)[first_sec.size + sector_offset], m_buf, ISO_SECTOR_SIZE); + std::memcpy(&reinterpret_cast(buffer)[first_sec.size + sector_offset], aligned_buf, ISO_SECTOR_SIZE); } } @@ -1344,9 +1345,9 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) // Last sector // - total_read += m_file.read_at(last_sec.address_aligned, m_buf, ISO_SECTOR_SIZE); + total_read += m_file.read_at(last_sec.address_aligned, aligned_buf, ISO_SECTOR_SIZE); - std::memcpy(&reinterpret_cast(buffer)[max_size - last_sec.size], m_buf, last_sec.size); + std::memcpy(&reinterpret_cast(buffer)[max_size - last_sec.size], aligned_buf, last_sec.size); // // As last, check for an unlikely reading error @@ -1357,7 +1358,6 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, total_read, ISO_SECTOR_SIZE + ISO_SECTOR_SIZE + (sector_count - 2) * ISO_SECTOR_SIZE); - seek(m_pos, fs::seek_set); return 0; } diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index 5c542cd28659..ba3995623928 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -108,7 +108,6 @@ class iso_file : public fs::file_base fs::file m_file; iso_fs_metadata m_meta; bool m_raw_device = false; - void* m_buf = nullptr; u64 m_pos = 0; std::pair get_extent_pos(u64 pos) const; @@ -119,7 +118,6 @@ class iso_file : public fs::file_base public: iso_file(const std::string& path, bs_t mode); iso_file(const std::string& path, bs_t mode, const iso_fs_node& node); - virtual ~iso_file(); explicit operator bool() const { return m_file.operator bool(); }