From a4d2b6165599c159510c3831af52189bd978b8ca Mon Sep 17 00:00:00 2001 From: digant73 Date: Fri, 3 Apr 2026 00:05:51 +0200 Subject: [PATCH 01/17] Add support to encrypted ISO --- rpcs3/Loader/ISO.cpp | 628 +++++++++++++++++++++++++++++++++++++------ rpcs3/Loader/ISO.h | 95 ++++++- 2 files changed, 629 insertions(+), 94 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 0ddc3e39d9..4406a53d74 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -13,28 +13,353 @@ LOG_CHANNEL(sys_log, "SYS"); bool is_file_iso(const std::string& path) { - if (path.empty()) return false; - if (fs::is_dir(path)) return false; + if (path.empty() || fs::is_dir(path)) + { + return false; + } return is_file_iso(fs::file(path)); } bool is_file_iso(const fs::file& file) { - if (!file) return false; - if (file.size() < 32768 + 6) return false; + if (!file || file.size() < 32768 + 6) + { + return false; + } file.seek(32768); char magic[5]; file.read_at(32768 + 1, magic, 5); - return magic[0] == 'C' && magic[1] == 'D' - && magic[2] == '0' && magic[3] == '0' - && magic[4] == '1'; + return magic[0] == 'C' && magic[1] == 'D' && magic[2] == '0' && magic[3] == '0' && magic[4] == '1'; } -const int ISO_BLOCK_SIZE = 2048; +// keystr_to_keyarr (&& asciischar_to_byte): Convert a hex string into a byte array +static unsigned char asciischar_to_byte(char input) +{ + if (input >= '0' && input <= '9') + return input - '0'; + if (input >= 'A' && input <= 'F') + return input - 'A' + 10; + if (input >= 'a' && input <= 'f') + return input - 'a' + 10; + + sys_log.error("asciischar_to_byte(): Error converting data to byte array"); + + return 0x00; +} + +static void keystr_to_keyarr(const char (&str)[32], unsigned char (&arr)[16]) +{ + for (size_t i = 0; i < sizeof(arr); ++i) + { + arr[i] = (asciischar_to_byte(str[i * 2]) * 16) + asciischar_to_byte(str[(i * 2) + 1]); + } +} + +// Convert 4 bytes in big-endian format to an unsigned integer +static u32 char_arr_BE_to_uint(unsigned char* arr) +{ + return arr[0] << 24 | arr[1] << 16 | arr[2] << 8 | arr[3]; +} + +// Reset the iv to a particular lba +static void reset_iv(unsigned char (&iv)[16], u32 lba) +{ + memset(iv, 0, 12); + + iv[12] = (lba & 0xFF000000) >> 24; + iv[13] = (lba & 0x00FF0000) >> 16; + iv[14] = (lba & 0x0000FF00) >> 8; + iv[15] = (lba & 0x000000FF) >> 0; +} + +// Main function that will decrypt the sector(s) (needs to be a multiple of 2048) +static void decrypt_data(aes_context& aes, unsigned char* data, u32 sector_count, u32 start_lba) +{ + // Micro optimization, only ever used by decrypt_data(...) + unsigned char iv_[16]; + + for (u32 i = 0; i < sector_count; ++i) + { + reset_iv(iv_, start_lba + i); + + if (aes_crypt_cbc(&aes, AES_DECRYPT, ISO_SECTOR_SIZE, &iv_[0], &data[ISO_SECTOR_SIZE * i], &data[ISO_SECTOR_SIZE * i]) != 0) + { + sys_log.error("decrypt_data(): Error decrypting data (decrypt_data() > aes_crypt_cbc())"); + + return; + } + } +} + +void iso_file_decryption::reset(void) +{ + m_enc_type = ENC_TYPE_NONE; + m_region_count = 0; + + if (m_region_info != NULL) + { + delete[] m_region_info; + m_region_info = NULL; + } +} + +iso_file_decryption::~iso_file_decryption() +{ + reset(); +} + +bool iso_file_decryption::init(const std::string& path) +{ + reset(); + + if (!is_file_iso(path)) + { + return false; + } + + unsigned char sec0_sec1[ISO_SECTOR_SIZE * 2] = {0}; + + // + // Store the ISO region information (needed by both the Redump type (only on "decrypt()" method) and 3k3y type) + // + + bool region_info_stored = false; + + if (fs::file iso_file(path); iso_file) + { + if (iso_file.size() >= (u64)(ISO_SECTOR_SIZE) * 2) + { + if (iso_file.read(sec0_sec1, sizeof(sec0_sec1)) == sizeof(sec0_sec1)) + { + // NOTE: + // + // Following checks and assigned values are based on PS3 ISO specification. + // E.g. all even regions (0, 2, 4 etc.) are always unencrypted while the odd ones are encrypted + + size_t region_count = char_arr_BE_to_uint(sec0_sec1); + + // Ensure the region count is a proper value + if (region_count > 0 && region_count < 32) + { + m_region_count = region_count * 2 - 1; + m_region_info = new iso_region_info[m_region_count]; + + for (size_t i = 0; i < m_region_count; ++i) + { + // Store the region information in address format + m_region_info[i].encrypted = (i % 2 == 1); + m_region_info[i].region_first_addr = (i == 0 ? 0LL : m_region_info[i - 1].region_last_addr + 1LL); + m_region_info[i].region_last_addr = static_cast(char_arr_BE_to_uint(sec0_sec1 + 12 + (i * 4)) + - (i % 2 == 1 ? 1LL : 0LL)) * ISO_SECTOR_SIZE + ISO_SECTOR_SIZE - 1LL; + + region_info_stored = true; + } + } + else // It's non-PS3ISO + { + sys_log.error("init(): Failed to read region information: %s", path); + } + } + else + { + sys_log.error("init(): Failed to read file: %s", path); + } + } + else + { + sys_log.error("init(): Found only %ull sector(s) (minimum required is 2): %s", iso_file.size(), path); + } + } + else + { + sys_log.error("init(): Failed to open file: %s", path); + } + + if (!region_info_stored) + { + return false; + } + + // + // Check for Redump type + // + + std::string key_path; + size_t ext_pos = path.rfind('.'); + + // If no file extension is provided, set "key_path" appending ".dkey" to "path". + // Otherwise, replace the extension (e.g. ".iso") with ".dkey" + key_path = ext_pos == umax ? path + ".dkey" : path.substr(0, ext_pos) + ".dkey"; + + // Check if "key_path" exists and create the "m_aes_dec" context if so + if (fs::file key_file(key_path); key_file) + { + char key_str[32]; + unsigned char key[16]; + + u64 key_len = key_file.read(key_str, sizeof(key_str)); + + if (key_len == sizeof(key_str) || key_len == sizeof(key)) + { + if (key_len == sizeof(key)) + { + memcpy(key, key_str, sizeof(key)); + } + else + { + keystr_to_keyarr(key_str, key); + } + + if (aes_setkey_dec(&m_aes_dec, key, 128) == 0) + { + m_enc_type = ENC_TYPE_REDUMP; // SET ENCRYPTION TYPE: REDUMP + } + } + + if (m_enc_type == ENC_TYPE_NONE) // If encryption type was not set to REDUMP for any reason + { + sys_log.error("init(): Failed to process key file: %s", key_path); + } + } + else + { + sys_log.warning("init(): Failed to open, or missing, key file: %s", key_path); + } + + // + // Check for 3k3y type + // + + // If encryption type is still set to none (sec0_sec1 will be zeroed out if it didn't succeed above) + if (m_enc_type == ENC_TYPE_NONE) + { + // The 3k3y watermarks located at 0xF70: (D|E)ncrypted 3K BLD + static const unsigned char k3k3y_enc_watermark[16] = + {0x45, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x33, 0x4B, 0x20, 0x42, 0x4C, 0x44}; + 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) + { + // Grab D1 from the 3k3y sector + unsigned char key[16]; + + 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}; + unsigned char iv_d1[] = {0x69, 0x47, 0x47, 0x72, 0xaf, 0x6f, 0xda, 0xb3, 0x42, 0x74, 0x3a, 0xef, 170, 0x18, 0x62, 0x87}; + + aes_context aes_d1; + + if (aes_setkey_enc(&aes_d1, key_d1, 128) == 0) + { + if (aes_crypt_cbc(&aes_d1, AES_ENCRYPT, 16, &iv_d1[0], key, key) == 0) + { + if (aes_setkey_dec(&m_aes_dec, key, 128) == 0) + { + m_enc_type = ENC_TYPE_3K3Y_ENC; // SET ENCRYPTION TYPE: 3K3Y_ENC + } + } + } + + if (m_enc_type == ENC_TYPE_NONE) // If encryption type was not set to 3K3Y_ENC for any reason + { + sys_log.error("init(): Failed to set encryption type to 3K3Y_ENC: %s", path); + } + } + else if (memcmp(&k3k3y_dec_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_dec_watermark)) == 0) + { + m_enc_type = ENC_TYPE_3K3Y_DEC; // SET ENCRYPTION TYPE: 3K3Y_DEC + } + } + + if (m_enc_type == ENC_TYPE_REDUMP) + sys_log.warning("init(): Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_count, path); + else if (m_enc_type == ENC_TYPE_3K3Y_ENC) + sys_log.warning("init(): Set 'enc type': 3K3Y_ENC, 'reg count': %u: %s", m_region_count, path); + else if (m_enc_type == ENC_TYPE_3K3Y_DEC) + sys_log.warning("init(): Set 'enc type': 3K3Y_DEC, 'reg count': %u: %s", m_region_count, path); + else if (m_enc_type == ENC_TYPE_NONE) // If encryption type was not set for any reason + sys_log.warning("init(): Set 'enc type': NONE, 'reg count': %u: %s", m_region_count, path); + + return true; +} + +bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std::string& name) +{ + // If it's a non-encrypted type, nothing more to do + if (m_enc_type == ENC_TYPE_NONE) + { + return true; + } + + // If it's a 3k3y iso and 0xF70 data is being requested, we should null it out + if (m_enc_type == ENC_TYPE_3K3Y_DEC || m_enc_type == ENC_TYPE_3K3Y_ENC) + { + if (offset + size >= 0xF70LL && offset <= 0x1070LL) + { + // Zero out the 0xF70 - 0x1070 overlap + unsigned char* bufb = reinterpret_cast(buffer); + unsigned char* buf_overlap_start = offset < 0xF70LL ? bufb + 0xF70LL - offset : bufb; + + memset(buf_overlap_start, 0x00, offset + size < 0x1070LL ? size - (buf_overlap_start - bufb) : 0x100 - (buf_overlap_start - bufb)); + } + + // If it's a decrypted iso then return, otherwise go on to the decryption logic + if (m_enc_type == ENC_TYPE_3K3Y_DEC) + { + return true; + } + } + + // If it's an encrypted type, check if the request lies in an encrypted range + for (size_t i = 0; i < m_region_count; ++i) + { + if (offset >= m_region_info[i].region_first_addr && offset <= m_region_info[i].region_last_addr) + { + // We found the region, decrypt if needed + if (!m_region_info[i].encrypted) + { + return true; + } + + // Decrypt the region before sending it back + decrypt_data(m_aes_dec, reinterpret_cast(buffer), + static_cast(size / ISO_SECTOR_SIZE), static_cast(offset / ISO_SECTOR_SIZE)); + + // TODO: Sanity check. + // We are assuming that reads always start at the beginning of a sector and that all reads will be + // multiples of ISO_SECTOR_SIZE + // + // NOTE: Both are easy fixes, but, code can be more simple + efficient if these two conditions are true + // (which they look to be from initial testing) + + /*if (size % ISO_SECTOR_SIZE != 0 || offset % ISO_SECTOR_SIZE != 0) + { + sys_log.error("decrypt(): %s: Encryption assumptions were not met, code needs to be updated, your game is probably about to crash - offset: 0x%lx, size: 0x%lx", + name, + (unsigned long int)offset, + (unsigned long int)size); + }*/ + + return true; + } + } + + sys_log.error("decrypt(): %s: LBA request wasn't in the 'm_region_info' for an encrypted iso? - RP: 0x%lx, RC: 0x%lx, LR: (0x%016lx - 0x%016lx)", + name, + offset, + (unsigned long int)m_region_count, + (unsigned long int)((m_region_count > 0) ? m_region_info[m_region_count - 1].region_first_addr : 0), + (unsigned long int)((m_region_count > 0) ? m_region_info[m_region_count - 1].region_last_addr : 0)); + + return true; +} template inline T read_both_endian_int(fs::file& file) @@ -55,30 +380,33 @@ inline T read_both_endian_int(fs::file& file) return out; } -// assumed that directory_entry is at file head -std::optional iso_read_directory_entry(fs::file& file, bool names_in_ucs2 = false) +// Assumed that directory_entry is at file head +static std::optional iso_read_directory_entry(fs::file& file, bool names_in_ucs2 = false) { const auto start_pos = file.pos(); const u8 entry_length = file.read(); - if (entry_length == 0) return std::nullopt; + if (entry_length == 0) + { + return std::nullopt; + } file.seek(1, fs::seek_cur); + const u32 start_sector = read_both_endian_int(file); const u32 file_size = read_both_endian_int(file); - std::tm file_date = {}; + file_date.tm_year = file.read(); file_date.tm_mon = file.read() - 1; file_date.tm_mday = file.read(); file_date.tm_hour = file.read(); file_date.tm_min = file.read(); file_date.tm_sec = file.read(); + const s16 timezone_value = file.read(); const s16 timezone_offset = (timezone_value - 50) * 15 * 60; - const std::time_t date_time = std::mktime(&file_date) + timezone_offset; - const u8 flags = file.read(); // 2nd flag bit indicates whether a given fs node is a directory @@ -88,8 +416,8 @@ std::optional iso_read_directory_entry(fs::file& file, bool nam file.seek(6, fs::seek_cur); const u8 file_name_length = file.read(); - std::string file_name; + file.read(file_name, file_name_length); if (file_name_length == 1 && file_name[0] == 0) @@ -100,13 +428,14 @@ std::optional iso_read_directory_entry(fs::file& file, bool nam { file_name = ".."; } - else if (names_in_ucs2) // for strings in joliet descriptor + else if (names_in_ucs2) // For strings in joliet descriptor { - // characters are stored in big endian format. + // Characters are stored in big endian format + const u16* raw = reinterpret_cast(file_name.data()); std::u16string utf16; + utf16.resize(file_name_length / 2); - const u16* raw = reinterpret_cast(file_name.data()); for (size_t i = 0; i < utf16.size(); ++i, raw++) { utf16[i] = *reinterpret_cast*>(raw); @@ -125,7 +454,7 @@ std::optional iso_read_directory_entry(fs::file& file, bool nam file_name.pop_back(); } - // skip the rest of the entry. + // Skip the rest of the entry file.seek(entry_length + start_pos); return iso_fs_metadata @@ -145,49 +474,57 @@ std::optional iso_read_directory_entry(fs::file& file, bool nam }; } -void iso_form_hierarchy(fs::file& file, iso_fs_node& node, bool use_ucs2_decoding = false, const std::string& parent_path = "") +static void iso_form_hierarchy(fs::file& file, iso_fs_node& node, bool use_ucs2_decoding = false, const std::string& parent_path = "") { - if (!node.metadata.is_directory) return; + if (!node.metadata.is_directory) + { + return; + } std::vector multi_extent_node_indices; - // assuming the directory spans a single extent + // Assuming the directory spans a single extent const auto& directory_extent = node.metadata.extents[0]; + const u64 end_pos = directory_extent.size + (directory_extent.start * ISO_SECTOR_SIZE); - file.seek(directory_extent.start * ISO_BLOCK_SIZE); + file.seek(directory_extent.start * ISO_SECTOR_SIZE); - const u64 end_pos = directory_extent.size + (directory_extent.start * ISO_BLOCK_SIZE); - - while(file.pos() < end_pos) + while (file.pos() < end_pos) { auto entry = iso_read_directory_entry(file, use_ucs2_decoding); + if (!entry) { - const u64 new_sector = (file.pos() / ISO_BLOCK_SIZE) + 1; - file.seek(new_sector * ISO_BLOCK_SIZE); + const u64 new_sector = (file.pos() / ISO_SECTOR_SIZE) + 1; + + file.seek(new_sector * ISO_SECTOR_SIZE); continue; } bool extent_added = false; - // find previous extent and merge into it, otherwise we push this node's index + // Find previous extent and merge into it, otherwise we push this node's index for (usz index : multi_extent_node_indices) { auto& selected_node = ::at32(node.children, index); + if (selected_node->metadata.name == entry->name) { - // merge into selected_node + // Merge into selected_node selected_node->metadata.extents.push_back(entry->extents[0]); extent_added = true; } } - if (extent_added) continue; + if (extent_added) + { + continue; + } if (entry->has_multiple_extents) { - // haven't pushed entry to node.children yet so node.children::size() == entry_index + // Haven't pushed entry to node.children yet so node.children::size() == entry_index multi_extent_node_indices.push_back(node.children.size()); } @@ -208,6 +545,7 @@ void iso_form_hierarchy(fs::file& file, iso_fs_node& node, bool use_ucs2_decodin u64 iso_fs_metadata::size() const { u64 total_size = 0; + for (const auto& extent : extents) { total_size += extent.size; @@ -220,10 +558,11 @@ iso_archive::iso_archive(const std::string& path) { m_path = path; m_file = fs::file(path); + m_dec = std::make_shared(); - if (!is_file_iso(m_file)) + if (!m_dec->init(path)) { - // not iso... TODO: throw something?? + // Not iso... TODO: throw something?? return; } @@ -241,7 +580,7 @@ iso_archive::iso_archive(const std::string& path) { use_ucs2_decoding = descriptor_type == 2; - // skip the rest of descriptor's data + // Skip the rest of descriptor's data m_file.seek(155, fs::seek_cur); m_root = iso_fs_node @@ -250,7 +589,7 @@ iso_archive::iso_archive(const std::string& path) }; } - m_file.seek(descriptor_start + ISO_BLOCK_SIZE); + m_file.seek(descriptor_start + ISO_SECTOR_SIZE); } while (descriptor_type != 255); @@ -259,20 +598,27 @@ iso_archive::iso_archive(const std::string& path) iso_fs_node* iso_archive::retrieve(const std::string& passed_path) { - if (passed_path.empty()) return nullptr; + if (passed_path.empty()) + { + return nullptr; + } const std::string path = std::filesystem::path(passed_path).string(); const std::string_view path_sv = path; size_t start = 0; size_t end = path_sv.find_first_of(fs::delim); - std::stack search_stack; + search_stack.push(&m_root); do { - if (search_stack.empty()) return nullptr; + if (search_stack.empty()) + { + return nullptr; + } + const auto* top_entry = search_stack.top(); if (end == umax) @@ -280,8 +626,7 @@ iso_fs_node* iso_archive::retrieve(const std::string& passed_path) end = path.size(); } - const std::string_view path_component = path_sv.substr(start, end-start); - + const std::string_view path_component = path_sv.substr(start, end - start); bool found = false; if (path_component == ".") @@ -291,6 +636,7 @@ iso_fs_node* iso_archive::retrieve(const std::string& passed_path) else if (path_component == "..") { search_stack.pop(); + found = true; } else @@ -307,14 +653,20 @@ iso_fs_node* iso_archive::retrieve(const std::string& passed_path) } } - if (!found) return nullptr; + if (!found) + { + return nullptr; + } start = end + 1; end = path_sv.find_first_of(fs::delim, start); } while (start < path.size()); - if (search_stack.empty()) return nullptr; + if (search_stack.empty()) + { + return nullptr; + } return search_stack.top(); } @@ -327,30 +679,40 @@ bool iso_archive::exists(const std::string& path) bool iso_archive::is_file(const std::string& path) { const auto file_node = retrieve(path); - if (!file_node) return false; + + if (!file_node) + { + return false; + } return !file_node->metadata.is_directory; } iso_file iso_archive::open(const std::string& path) { - return iso_file(fs::file(m_path), *ensure(retrieve(path))); + const auto file_node = retrieve(path); + + return iso_file(fs::file(m_path), m_dec, *ensure(retrieve(path))); } psf::registry iso_archive::open_psf(const std::string& path) { auto* archive_file = retrieve(path); - if (!archive_file) return psf::registry(); - const fs::file psf_file(std::make_unique(fs::file(m_path), *archive_file)); + if (!archive_file) + { + return psf::registry(); + } + + const fs::file psf_file(std::make_unique(fs::file(m_path), m_dec, *archive_file)); return psf::load_object(psf_file, path); } -iso_file::iso_file(fs::file&& iso_handle, const iso_fs_node& node) - : m_file(std::move(iso_handle)), m_meta(node.metadata) +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) { - m_file.seek(ISO_BLOCK_SIZE * node.metadata.extents[0].start); + m_file.seek(ISO_SECTOR_SIZE * node.metadata.extents[0].start); } fs::stat_t iso_file::get_stat() @@ -389,14 +751,6 @@ std::pair iso_file::get_extent_pos(u64 pos) const return {pos, *it}; } -// assumed valid and in bounds. -u64 iso_file::file_offset(u64 pos) const -{ - const auto [local_pos, extent] = get_extent_pos(pos); - - return (extent.start * ISO_BLOCK_SIZE) + local_pos; -} - u64 iso_file::local_extent_remaining(u64 pos) const { const auto [local_pos, extent] = get_extent_pos(pos); @@ -409,29 +763,134 @@ u64 iso_file::local_extent_size(u64 pos) const return get_extent_pos(pos).second.size; } +// Assumed valid and in bounds +u64 iso_file::file_offset(u64 pos) const +{ + const auto [local_pos, extent] = get_extent_pos(pos); + + return (extent.start * ISO_SECTOR_SIZE) + local_pos; +} + u64 iso_file::read(void* buffer, u64 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) { - const u64 local_remaining = local_extent_remaining(offset); + const u64 max_size = std::min(size, local_extent_remaining(offset)); - const u64 total_read = m_file.read_at(file_offset(offset), buffer, std::min(size, local_remaining)); - - const auto total_size = this->size(); - - if (size > total_read && (offset + total_read) < total_size) + if (max_size == 0) { - const u64 second_total_read = read_at(offset + total_read, reinterpret_cast(buffer) + total_read, size - total_read); - - return total_read + second_total_read; + return 0; } - return total_read; + const u64 archive_first_offset = file_offset(offset); + const u64 total_size = this->size(); + + // If it's a non-encrypted type + if (m_dec->get_enc_type() == ENC_TYPE_NONE) + { + u64 total_read = m_file.read_at(archive_first_offset, buffer, max_size); + + if (size > total_read && (offset + total_read) < total_size) + { + total_read += read_at(offset + total_read, reinterpret_cast(buffer) + total_read, size - total_read); + } + + return total_read; + } + + // If it's an encrypted type + + // IMPORTANT NOTE (temporary implementation): + // + // Current "iso_file_decryption::decrypt()" method requires that reads always start at the beginning + // of a sector and that all reads will be multiples of ISO_SECTOR_SIZE. + // Both are easy fixes, but, code can be more simple + efficient if these two conditions are true + // (which they look to be from initial testing) + // + // + // TODO: + // + // The local buffers storing the first and last sector of the reads could be replaced by caches. + // That will allow to avoid to read and decrypt again the entire sector on a further call to "read_at()" method + // if trying to read the previously not requested bytes (marked with "x" in the picture below) + // + // --------------------------------------- + // | buffer | + // --------------------------------------- + // ' ' ' ' + // --------------------------------------------------------------------------------------------------------------- + // | sec 0 | sec 1 |xxx'#####|#########|#########|#########|#'xxxxxxx| | | sec n-1 | sec n | + // --------------------------------------------------------------------------------------------------------------- + // ' ' ' ' + // |first sec| |last sec | + + const u64 archive_last_offset = archive_first_offset + max_size - 1; + iso_sector first_sec, last_sec; + + first_sec.archive_first_addr = (archive_first_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; + first_sec.offset = archive_first_offset % ISO_SECTOR_SIZE; + first_sec.size = archive_last_offset < (first_sec.archive_first_addr + ISO_SECTOR_SIZE) ? max_size : ISO_SECTOR_SIZE - first_sec.offset; + last_sec.archive_first_addr = (archive_last_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; + last_sec.offset = 0; + last_sec.size = (archive_last_offset % ISO_SECTOR_SIZE) + 1; + + u64 sec_count = (last_sec.archive_first_addr - first_sec.archive_first_addr) / ISO_SECTOR_SIZE + 1; + u64 sec_inner_size = (sec_count - 2) * ISO_SECTOR_SIZE; + + if (m_file.read_at(first_sec.archive_first_addr, first_sec.buf, ISO_SECTOR_SIZE) != ISO_SECTOR_SIZE) + { + sys_log.error("read_at(): %s: Error reading from file", m_meta.name); + + seek(m_pos, fs::seek_set); + return 0; + } + + // Decrypt read buffer (if needed) and copy to destination buffer + m_dec->decrypt(first_sec.archive_first_addr, first_sec.buf, ISO_SECTOR_SIZE, m_meta.name); + memcpy(buffer, first_sec.buf + first_sec.offset, first_sec.size); + + // If the sector was already read, decrypted and copied to destination buffer, nothing more to do + if (sec_count < 2) + { + return max_size; + } + + if (m_file.read_at(last_sec.archive_first_addr, last_sec.buf, ISO_SECTOR_SIZE) != ISO_SECTOR_SIZE) + { + sys_log.error("read_at(): %s: Error reading from file", m_meta.name); + + seek(m_pos, fs::seek_set); + return 0; + } + + // Decrypt read buffer (if needed) and copy to destination buffer + m_dec->decrypt(last_sec.archive_first_addr, last_sec.buf, ISO_SECTOR_SIZE, m_meta.name); + memcpy(reinterpret_cast(buffer) + max_size - last_sec.size, last_sec.buf, last_sec.size); + + // If the sector was already read, decrypted and copied to destination buffer, nothing more to do + if (sec_count < 3) + { + return max_size; + } + + if (m_file.read_at(first_sec.archive_first_addr + ISO_SECTOR_SIZE, reinterpret_cast(buffer) + first_sec.size, sec_inner_size) != sec_inner_size) + { + sys_log.error("read_at(): %s: Error reading from file", m_meta.name); + + seek(m_pos, fs::seek_set); + return 0; + } + + // Decrypt read buffer (if needed) and copy to destination buffer + m_dec->decrypt(first_sec.archive_first_addr + ISO_SECTOR_SIZE, reinterpret_cast(buffer) + first_sec.size, sec_inner_size, m_meta.name); + + return max_size; } u64 iso_file::write(const void* /*buffer*/, u64 /*size*/) @@ -454,8 +913,10 @@ u64 iso_file::seek(s64 offset, fs::seek_mode whence) return -1; } - const u64 result = m_file.seek(file_offset(m_pos)); - if (result == umax) return umax; + if (m_file.seek(file_offset(new_pos)) == umax) + { + return umax; + } m_pos = new_pos; return m_pos; @@ -464,6 +925,7 @@ u64 iso_file::seek(s64 offset, fs::seek_mode whence) u64 iso_file::size() { u64 extent_sizes = 0; + for (const auto& extent : m_meta.extents) { extent_sizes += extent.size; @@ -493,18 +955,22 @@ bool iso_dir::read(fs::dir_entry& entry) entry.size = selected.size(); m_pos++; - return true; } return false; } +void iso_dir::rewind() +{ + m_pos = 0; +} + bool iso_device::stat(const std::string& path, fs::stat_t& info) { const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string(); - const auto node = m_archive.retrieve(relative_path); + if (!node) { fs::g_tls_error = fs::error::noent; @@ -530,16 +996,15 @@ bool iso_device::stat(const std::string& path, fs::stat_t& info) bool iso_device::statfs(const std::string& path, fs::device_stat& info) { const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string(); - const auto node = m_archive.retrieve(relative_path); + if (!node) { fs::g_tls_error = fs::error::noent; return false; } - const auto& meta = node->metadata; - const u64 size = meta.size(); + const u64 size = node->metadata.size(); info = fs::device_stat { @@ -549,14 +1014,14 @@ bool iso_device::statfs(const std::string& path, fs::device_stat& info) .avail_free = 0 }; - return false; + return true; } std::unique_ptr iso_device::open(const std::string& path, bs_t mode) { const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string(); - const auto node = m_archive.retrieve(relative_path); + if (!node) { fs::g_tls_error = fs::error::noent; @@ -569,14 +1034,14 @@ std::unique_ptr iso_device::open(const std::string& path, bs_t(fs::file(iso_path, mode), *node); + return std::make_unique(fs::file(m_path, mode), m_archive.get_dec(), *node); } std::unique_ptr iso_device::open_dir(const std::string& path) { const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string(); - const auto node = m_archive.retrieve(relative_path); + if (!node) { fs::g_tls_error = fs::error::noent; @@ -593,11 +1058,6 @@ std::unique_ptr iso_device::open_dir(const std::string& path) return std::make_unique(*node); } -void iso_dir::rewind() -{ - m_pos = 0; -} - void load_iso(const std::string& path) { sys_log.notice("Loading iso '%s'", path); diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index 2665ec29e5..9f1df8420a 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -4,6 +4,9 @@ #include "Utilities/File.h" #include "util/types.hpp" +#include "Crypto/aes.h" + +const int ISO_SECTOR_SIZE = 2048; bool is_file_iso(const std::string& path); bool is_file_iso(const fs::file& path); @@ -11,6 +14,75 @@ bool is_file_iso(const fs::file& path); void load_iso(const std::string& path); void unload_iso(); +/* +- Hijacked the "iso_archive::iso_archive" method to test if the ".iso" file is encrypted and sets a flag. + The flag is set according to the first matching encryption type in the order below: + - Redump type: ".dkey" file, with the same name of the ".iso" file, exists in the same folder of the ".iso" file + - 3k3y type: 3k3y watermark exists at 0xF70 + If the flag is set then the "iso_file::read" method will decrypt the data on the fly + +- Supported ISO encryption type: + - Decrypted (.iso) + - 3k3y (decrypted / encrypted) (.iso) + - Redump (encrypted) (.iso & .dkey) + +- Unsupported ISO encryption type: + - Encrypted split ISO files +*/ + +// Struct to store ISO region information (storing addrs instead of lba since we need to compare the addr anyway, +// so would have to multiply or divide every read if storing lba) +struct iso_region_info +{ + bool encrypted; + u64 region_first_addr; + u64 region_last_addr; +}; + +// Enum to decide ISO encryption type +enum iso_encryption_type +{ + ENC_TYPE_NONE, + ENC_TYPE_3K3Y_DEC, + ENC_TYPE_3K3Y_ENC, + ENC_TYPE_REDUMP +}; + +struct iso_sector +{ + u64 archive_first_addr = 0; + u64 offset = 0; + u64 size = 0; + u8 buf[ISO_SECTOR_SIZE]; +}; + +// ISO file decryption functions +unsigned char asciischar_to_byte(char input); +void keystr_to_keyarr(const char (&str)[32], unsigned char (&arr)[16]); +u32 char_arr_BE_to_uint(unsigned char* arr); +void reset_iv(unsigned char (&iv)[16], u32 lba); +void decrypt_data(aes_context& aes, unsigned char* data, u32 sector_count, u32 start_lba); + +// ISO file decryption class +class iso_file_decryption +{ +private: + aes_context m_aes_dec; + iso_encryption_type m_enc_type = ENC_TYPE_NONE; + size_t m_region_count = 0; + iso_region_info* m_region_info = nullptr; + + void reset(void); + +public: + ~iso_file_decryption(); + + iso_encryption_type get_enc_type() const { return m_enc_type; } + + bool init(const std::string& path); + bool decrypt(u64 offset, void* buffer, u64 size, const std::string& name); +}; + struct iso_extent_info { u64 start = 0; @@ -38,16 +110,17 @@ class iso_file : public fs::file_base { private: fs::file m_file; + std::shared_ptr m_dec; iso_fs_metadata m_meta {}; u64 m_pos = 0; std::pair get_extent_pos(u64 pos) const; - u64 file_offset(u64 pos) const; u64 local_extent_remaining(u64 pos) const; u64 local_extent_size(u64 pos) const; + u64 file_offset(u64 pos) const; public: - iso_file(fs::file&& iso_handle, const iso_fs_node& node); + iso_file(fs::file&& iso_handle, std::shared_ptr iso_dec, const iso_fs_node& node); fs::stat_t get_stat() override; bool trunc(u64 length) override; @@ -75,45 +148,47 @@ public: void rewind() override; }; -// represents the .iso file itself. +// Represents the .iso file itself class iso_archive { private: std::string m_path; - iso_fs_node m_root {}; fs::file m_file; + std::shared_ptr m_dec; + iso_fs_node m_root {}; 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); - psf::registry open_psf(const std::string& path); - - const std::string& path() const { return m_path; } }; class iso_device : public fs::device_base { private: + std::string m_path; iso_archive m_archive; - std::string iso_path; public: inline static std::string virtual_device_name = "/vfsv0_virtual_iso_overlay_fs_dev"; iso_device(const std::string& iso_path, const std::string& device_name = virtual_device_name) - : m_archive(iso_path), iso_path(iso_path) + : m_path(iso_path), m_archive(iso_path) { fs_prefix = device_name; } + ~iso_device() override = default; - const std::string& get_loaded_iso() const { return iso_path; } + const std::string& get_loaded_iso() const { return m_path; } bool stat(const std::string& path, fs::stat_t& info) override; bool statfs(const std::string& path, fs::device_stat& info) override; From e88833838c93fbb88339ff05c96e76d5b4296759 Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Fri, 3 Apr 2026 11:08:54 +0200 Subject: [PATCH 02/17] fix compile errors and suppress minor warnings --- rpcs3/Loader/ISO.cpp | 61 ++++++++++++++++++++++++++------------------ rpcs3/Loader/ISO.h | 17 ------------ 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 4406a53d74..ebbece153f 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -11,6 +11,16 @@ LOG_CHANNEL(sys_log, "SYS"); +const int ISO_SECTOR_SIZE = 2048; + +struct iso_sector +{ + u64 archive_first_addr = 0; + u64 offset = 0; + u64 size = 0; + u8 buf[ISO_SECTOR_SIZE]; +}; + bool is_file_iso(const std::string& path) { if (path.empty() || fs::is_dir(path)) @@ -131,7 +141,7 @@ bool iso_file_decryption::init(const std::string& path) if (fs::file iso_file(path); iso_file) { - if (iso_file.size() >= (u64)(ISO_SECTOR_SIZE) * 2) + if (iso_file.size() >= static_cast(ISO_SECTOR_SIZE) * 2) { if (iso_file.read(sec0_sec1, sizeof(sec0_sec1)) == sizeof(sec0_sec1)) { @@ -304,10 +314,10 @@ bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std: if (offset + size >= 0xF70LL && offset <= 0x1070LL) { // Zero out the 0xF70 - 0x1070 overlap - unsigned char* bufb = reinterpret_cast(buffer); - unsigned char* buf_overlap_start = offset < 0xF70LL ? bufb + 0xF70LL - offset : bufb; + unsigned char* buf = reinterpret_cast(buffer); + unsigned char* buf_overlap_start = offset < 0xF70LL ? buf + 0xF70LL - offset : buf; - memset(buf_overlap_start, 0x00, offset + size < 0x1070LL ? size - (buf_overlap_start - bufb) : 0x100 - (buf_overlap_start - bufb)); + memset(buf_overlap_start, 0x00, offset + size < 0x1070LL ? size - (buf_overlap_start - buf) : 0x100 - (buf_overlap_start - buf)); } // If it's a decrypted iso then return, otherwise go on to the decryption logic @@ -343,8 +353,8 @@ bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std: { sys_log.error("decrypt(): %s: Encryption assumptions were not met, code needs to be updated, your game is probably about to crash - offset: 0x%lx, size: 0x%lx", name, - (unsigned long int)offset, - (unsigned long int)size); + static_cast(offset), + static_cast(size); }*/ return true; @@ -354,9 +364,9 @@ bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std: sys_log.error("decrypt(): %s: LBA request wasn't in the 'm_region_info' for an encrypted iso? - RP: 0x%lx, RC: 0x%lx, LR: (0x%016lx - 0x%016lx)", name, offset, - (unsigned long int)m_region_count, - (unsigned long int)((m_region_count > 0) ? m_region_info[m_region_count - 1].region_first_addr : 0), - (unsigned long int)((m_region_count > 0) ? m_region_info[m_region_count - 1].region_last_addr : 0)); + static_cast(m_region_count), + static_cast(m_region_count > 0 ? m_region_info[m_region_count - 1].region_first_addr : 0), + static_cast(m_region_count > 0 ? m_region_info[m_region_count - 1].region_last_addr : 0)); return true; } @@ -690,8 +700,6 @@ bool iso_archive::is_file(const std::string& path) iso_file iso_archive::open(const std::string& path) { - const auto file_node = retrieve(path); - return iso_file(fs::file(m_path), m_dec, *ensure(retrieve(path))); } @@ -806,12 +814,11 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) // If it's an encrypted type - // IMPORTANT NOTE (temporary implementation): + // IMPORTANT NOTE: // - // Current "iso_file_decryption::decrypt()" method requires that reads always start at the beginning - // of a sector and that all reads will be multiples of ISO_SECTOR_SIZE. - // Both are easy fixes, but, code can be more simple + efficient if these two conditions are true - // (which they look to be from initial testing) + // For performance reasons, current "iso_file_decryption::decrypt()" method requires that reads always start at the beginning of a sector and + // that all reads will be multiples of ISO_SECTOR_SIZE. + // Both are easy fixes, but, code can be more simple + efficient if these two conditions are true (which they look to be from initial testing) // // // TODO: @@ -820,15 +827,19 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) // That will allow to avoid to read and decrypt again the entire sector on a further call to "read_at()" method // if trying to read the previously not requested bytes (marked with "x" in the picture below) // - // --------------------------------------- - // | buffer | - // --------------------------------------- - // ' ' ' ' - // --------------------------------------------------------------------------------------------------------------- - // | sec 0 | sec 1 |xxx'#####|#########|#########|#########|#'xxxxxxx| | | sec n-1 | sec n | - // --------------------------------------------------------------------------------------------------------------- - // ' ' ' ' - // |first sec| |last sec | + // ------------------------------------------------------------------- + // file on iso archive: | ' ' | + // ------------------------------------------------------------------- + // ' ' + // --------------------------------------- + // buffer: | | + // --------------------------------------- + // ' ' ' ' + // --------------------------------------------------------------------------------------------------------------- + // iso archive: | sec 0 | sec 1 |xxx'#####|#########|#########|#########|#'xxxxxxx| | ... | sec n-1 | sec n | + // --------------------------------------------------------------------------------------------------------------- + // ' ' ' ' + // |first sec| |last sec | const u64 archive_last_offset = archive_first_offset + max_size - 1; iso_sector first_sec, last_sec; diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index 9f1df8420a..bbb1cf0065 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -6,8 +6,6 @@ #include "util/types.hpp" #include "Crypto/aes.h" -const int ISO_SECTOR_SIZE = 2048; - bool is_file_iso(const std::string& path); bool is_file_iso(const fs::file& path); @@ -48,21 +46,6 @@ enum iso_encryption_type ENC_TYPE_REDUMP }; -struct iso_sector -{ - u64 archive_first_addr = 0; - u64 offset = 0; - u64 size = 0; - u8 buf[ISO_SECTOR_SIZE]; -}; - -// ISO file decryption functions -unsigned char asciischar_to_byte(char input); -void keystr_to_keyarr(const char (&str)[32], unsigned char (&arr)[16]); -u32 char_arr_BE_to_uint(unsigned char* arr); -void reset_iv(unsigned char (&iv)[16], u32 lba); -void decrypt_data(aes_context& aes, unsigned char* data, u32 sector_count, u32 start_lba); - // ISO file decryption class class iso_file_decryption { From 501323eda85a3794cd3c3bf56f22d6c2ace28db3 Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Fri, 3 Apr 2026 12:01:12 +0200 Subject: [PATCH 03/17] strip minor bug fixes and cleanup --- rpcs3/Loader/ISO.cpp | 108 ++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 67 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index ebbece153f..a2edeb7120 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -23,27 +23,25 @@ struct iso_sector bool is_file_iso(const std::string& path) { - if (path.empty() || fs::is_dir(path)) - { - return false; - } + if (path.empty()) return false; + if (fs::is_dir(path)) return false; return is_file_iso(fs::file(path)); } bool is_file_iso(const fs::file& file) { - if (!file || file.size() < 32768 + 6) - { - return false; - } + if (!file) return false; + if (file.size() < 32768 + 6) return false; file.seek(32768); char magic[5]; file.read_at(32768 + 1, magic, 5); - return magic[0] == 'C' && magic[1] == 'D' && magic[2] == '0' && magic[3] == '0' && magic[4] == '1'; + return magic[0] == 'C' && magic[1] == 'D' + && magic[2] == '0' && magic[3] == '0' + && magic[4] == '1'; } // keystr_to_keyarr (&& asciischar_to_byte): Convert a hex string into a byte array @@ -390,33 +388,30 @@ inline T read_both_endian_int(fs::file& file) return out; } -// Assumed that directory_entry is at file head -static std::optional iso_read_directory_entry(fs::file& file, bool names_in_ucs2 = false) +// assumed that directory_entry is at file head +std::optional iso_read_directory_entry(fs::file& file, bool names_in_ucs2 = false) { const auto start_pos = file.pos(); const u8 entry_length = file.read(); - if (entry_length == 0) - { - return std::nullopt; - } + if (entry_length == 0) return std::nullopt; file.seek(1, fs::seek_cur); - const u32 start_sector = read_both_endian_int(file); const u32 file_size = read_both_endian_int(file); - std::tm file_date = {}; + std::tm file_date = {}; file_date.tm_year = file.read(); file_date.tm_mon = file.read() - 1; file_date.tm_mday = file.read(); file_date.tm_hour = file.read(); file_date.tm_min = file.read(); file_date.tm_sec = file.read(); - const s16 timezone_value = file.read(); const s16 timezone_offset = (timezone_value - 50) * 15 * 60; + const std::time_t date_time = std::mktime(&file_date) + timezone_offset; + const u8 flags = file.read(); // 2nd flag bit indicates whether a given fs node is a directory @@ -426,8 +421,8 @@ static std::optional iso_read_directory_entry(fs::file& file, b file.seek(6, fs::seek_cur); const u8 file_name_length = file.read(); - std::string file_name; + std::string file_name; file.read(file_name, file_name_length); if (file_name_length == 1 && file_name[0] == 0) @@ -438,14 +433,13 @@ static std::optional iso_read_directory_entry(fs::file& file, b { file_name = ".."; } - else if (names_in_ucs2) // For strings in joliet descriptor + else if (names_in_ucs2) // for strings in joliet descriptor { - // Characters are stored in big endian format - const u16* raw = reinterpret_cast(file_name.data()); + // characters are stored in big endian format. std::u16string utf16; - utf16.resize(file_name_length / 2); + const u16* raw = reinterpret_cast(file_name.data()); for (size_t i = 0; i < utf16.size(); ++i, raw++) { utf16[i] = *reinterpret_cast*>(raw); @@ -464,7 +458,7 @@ static std::optional iso_read_directory_entry(fs::file& file, b file_name.pop_back(); } - // Skip the rest of the entry + // skip the rest of the entry. file.seek(entry_length + start_pos); return iso_fs_metadata @@ -555,7 +549,6 @@ static void iso_form_hierarchy(fs::file& file, iso_fs_node& node, bool use_ucs2_ u64 iso_fs_metadata::size() const { u64 total_size = 0; - for (const auto& extent : extents) { total_size += extent.size; @@ -608,27 +601,20 @@ iso_archive::iso_archive(const std::string& path) iso_fs_node* iso_archive::retrieve(const std::string& passed_path) { - if (passed_path.empty()) - { - return nullptr; - } + if (passed_path.empty()) return nullptr; const std::string path = std::filesystem::path(passed_path).string(); const std::string_view path_sv = path; size_t start = 0; size_t end = path_sv.find_first_of(fs::delim); - std::stack search_stack; + std::stack search_stack; search_stack.push(&m_root); do { - if (search_stack.empty()) - { - return nullptr; - } - + if (search_stack.empty()) return nullptr; const auto* top_entry = search_stack.top(); if (end == umax) @@ -636,7 +622,8 @@ iso_fs_node* iso_archive::retrieve(const std::string& passed_path) end = path.size(); } - const std::string_view path_component = path_sv.substr(start, end - start); + const std::string_view path_component = path_sv.substr(start, end-start); + bool found = false; if (path_component == ".") @@ -646,7 +633,6 @@ iso_fs_node* iso_archive::retrieve(const std::string& passed_path) else if (path_component == "..") { search_stack.pop(); - found = true; } else @@ -663,20 +649,14 @@ iso_fs_node* iso_archive::retrieve(const std::string& passed_path) } } - if (!found) - { - return nullptr; - } + if (!found) return nullptr; start = end + 1; end = path_sv.find_first_of(fs::delim, start); } while (start < path.size()); - if (search_stack.empty()) - { - return nullptr; - } + if (search_stack.empty()) return nullptr; return search_stack.top(); } @@ -689,11 +669,7 @@ bool iso_archive::exists(const std::string& path) bool iso_archive::is_file(const std::string& path) { const auto file_node = retrieve(path); - - if (!file_node) - { - return false; - } + if (!file_node) return false; return !file_node->metadata.is_directory; } @@ -782,7 +758,6 @@ 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; } @@ -924,10 +899,8 @@ u64 iso_file::seek(s64 offset, fs::seek_mode whence) return -1; } - if (m_file.seek(file_offset(new_pos)) == umax) - { - return umax; - } + const u64 result = m_file.seek(file_offset(m_pos)); + if (result == umax) return umax; m_pos = new_pos; return m_pos; @@ -936,7 +909,6 @@ u64 iso_file::seek(s64 offset, fs::seek_mode whence) u64 iso_file::size() { u64 extent_sizes = 0; - for (const auto& extent : m_meta.extents) { extent_sizes += extent.size; @@ -966,22 +938,18 @@ bool iso_dir::read(fs::dir_entry& entry) entry.size = selected.size(); m_pos++; + return true; } return false; } -void iso_dir::rewind() -{ - m_pos = 0; -} - bool iso_device::stat(const std::string& path, fs::stat_t& info) { const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string(); - const auto node = m_archive.retrieve(relative_path); + const auto node = m_archive.retrieve(relative_path); if (!node) { fs::g_tls_error = fs::error::noent; @@ -1007,15 +975,16 @@ bool iso_device::stat(const std::string& path, fs::stat_t& info) bool iso_device::statfs(const std::string& path, fs::device_stat& info) { const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string(); - const auto node = m_archive.retrieve(relative_path); + const auto node = m_archive.retrieve(relative_path); if (!node) { fs::g_tls_error = fs::error::noent; return false; } - const u64 size = node->metadata.size(); + const auto& meta = node->metadata; + const u64 size = meta.size(); info = fs::device_stat { @@ -1025,14 +994,14 @@ bool iso_device::statfs(const std::string& path, fs::device_stat& info) .avail_free = 0 }; - return true; + return false; } std::unique_ptr iso_device::open(const std::string& path, bs_t mode) { const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string(); - const auto node = m_archive.retrieve(relative_path); + const auto node = m_archive.retrieve(relative_path); if (!node) { fs::g_tls_error = fs::error::noent; @@ -1051,8 +1020,8 @@ std::unique_ptr iso_device::open(const std::string& path, bs_t iso_device::open_dir(const std::string& path) { const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string(); - const auto node = m_archive.retrieve(relative_path); + const auto node = m_archive.retrieve(relative_path); if (!node) { fs::g_tls_error = fs::error::noent; @@ -1069,6 +1038,11 @@ std::unique_ptr iso_device::open_dir(const std::string& path) return std::make_unique(*node); } +void iso_dir::rewind() +{ + m_pos = 0; +} + void load_iso(const std::string& path) { sys_log.notice("Loading iso '%s'", path); From cbc742c58c9000203e4ec13f52b6d4c1ec46ae77 Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Fri, 3 Apr 2026 12:13:56 +0200 Subject: [PATCH 04/17] minor cleanup --- rpcs3/Loader/ISO.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index a2edeb7120..01f36d2366 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -44,7 +44,7 @@ bool is_file_iso(const fs::file& file) && magic[4] == '1'; } -// keystr_to_keyarr (&& asciischar_to_byte): Convert a hex string into a byte array +// asciischar_to_byte() and keystr_to_keyarr(): Convert a hex string into a byte array static unsigned char asciischar_to_byte(char input) { if (input >= '0' && input <= '9') @@ -84,10 +84,10 @@ static void reset_iv(unsigned char (&iv)[16], u32 lba) iv[15] = (lba & 0x000000FF) >> 0; } -// Main function that will decrypt the sector(s) (needs to be a multiple of 2048) +// Main function that will decrypt the sector(s) (needs to be a multiple of ISO_SECTOR_SIZE) static void decrypt_data(aes_context& aes, unsigned char* data, u32 sector_count, u32 start_lba) { - // Micro optimization, only ever used by decrypt_data(...) + // Micro optimization, only ever used by "decrypt_data()" unsigned char iv_[16]; for (u32 i = 0; i < sector_count; ++i) @@ -132,7 +132,7 @@ bool iso_file_decryption::init(const std::string& path) unsigned char sec0_sec1[ISO_SECTOR_SIZE * 2] = {0}; // - // Store the ISO region information (needed by both the Redump type (only on "decrypt()" method) and 3k3y type) + // Store the ISO region information (needed by both the "Redump" type (only on "decrypt()" method) and "3k3y" type) // bool region_info_stored = false; From c613aca2c48628b2d17a3f00e10c44769af71fda Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Fri, 3 Apr 2026 15:02:12 +0200 Subject: [PATCH 05/17] minor cleanup --- rpcs3/Loader/ISO.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 01f36d2366..1a26843e80 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -11,7 +11,7 @@ LOG_CHANNEL(sys_log, "SYS"); -const int ISO_SECTOR_SIZE = 2048; +const u64 ISO_SECTOR_SIZE = 2048; struct iso_sector { @@ -139,7 +139,7 @@ bool iso_file_decryption::init(const std::string& path) if (fs::file iso_file(path); iso_file) { - if (iso_file.size() >= static_cast(ISO_SECTOR_SIZE) * 2) + if (iso_file.size() >= ISO_SECTOR_SIZE * 2) { if (iso_file.read(sec0_sec1, sizeof(sec0_sec1)) == sizeof(sec0_sec1)) { @@ -160,9 +160,9 @@ bool iso_file_decryption::init(const std::string& path) { // Store the region information in address format m_region_info[i].encrypted = (i % 2 == 1); - m_region_info[i].region_first_addr = (i == 0 ? 0LL : m_region_info[i - 1].region_last_addr + 1LL); - m_region_info[i].region_last_addr = static_cast(char_arr_BE_to_uint(sec0_sec1 + 12 + (i * 4)) - - (i % 2 == 1 ? 1LL : 0LL)) * ISO_SECTOR_SIZE + ISO_SECTOR_SIZE - 1LL; + m_region_info[i].region_first_addr = (i == 0 ? 0ULL : m_region_info[i - 1].region_last_addr + 1ULL); + m_region_info[i].region_last_addr = (static_cast(char_arr_BE_to_uint(sec0_sec1 + 12 + (i * 4))) + - (i % 2 == 1 ? 1ULL : 0ULL)) * ISO_SECTOR_SIZE + ISO_SECTOR_SIZE - 1ULL; region_info_stored = true; } @@ -309,13 +309,13 @@ bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std: // If it's a 3k3y iso and 0xF70 data is being requested, we should null it out if (m_enc_type == ENC_TYPE_3K3Y_DEC || m_enc_type == ENC_TYPE_3K3Y_ENC) { - if (offset + size >= 0xF70LL && offset <= 0x1070LL) + if (offset + size >= 0xF70ULL && offset <= 0x1070ULL) { // Zero out the 0xF70 - 0x1070 overlap unsigned char* buf = reinterpret_cast(buffer); - unsigned char* buf_overlap_start = offset < 0xF70LL ? buf + 0xF70LL - offset : buf; + unsigned char* buf_overlap_start = offset < 0xF70ULL ? buf + 0xF70ULL - offset : buf; - memset(buf_overlap_start, 0x00, offset + size < 0x1070LL ? size - (buf_overlap_start - buf) : 0x100 - (buf_overlap_start - buf)); + memset(buf_overlap_start, 0x00, offset + size < 0x1070ULL ? size - (buf_overlap_start - buf) : 0x100ULL - (buf_overlap_start - buf)); } // If it's a decrypted iso then return, otherwise go on to the decryption logic From 2fa716445cb85a10b0c65ad3bbe9f5ce794c876c Mon Sep 17 00:00:00 2001 From: digant73 Date: Sat, 4 Apr 2026 01:08:55 +0200 Subject: [PATCH 06/17] Fixed crash at boot parsing an ISO with a empty directory entry at file head --- rpcs3/Loader/ISO.cpp | 26 ++++++++++++++++---------- rpcs3/Loader/ISO.h | 4 ++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 1a26843e80..26a05cb4f4 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -388,8 +388,8 @@ inline T read_both_endian_int(fs::file& file) return out; } -// assumed that directory_entry is at file head -std::optional iso_read_directory_entry(fs::file& file, bool names_in_ucs2 = false) +// Assumed that directory entry is at file head +static std::optional iso_read_directory_entry(fs::file& file, bool names_in_ucs2 = false) { const auto start_pos = file.pos(); const u8 entry_length = file.read(); @@ -489,7 +489,7 @@ static void iso_form_hierarchy(fs::file& file, iso_fs_node& node, bool use_ucs2_ // Assuming the directory spans a single extent const auto& directory_extent = node.metadata.extents[0]; - const u64 end_pos = directory_extent.size + (directory_extent.start * ISO_SECTOR_SIZE); + const u64 end_pos = (directory_extent.start * ISO_SECTOR_SIZE) + directory_extent.size; file.seek(directory_extent.start * ISO_SECTOR_SIZE); @@ -586,10 +586,15 @@ iso_archive::iso_archive(const std::string& path) // Skip the rest of descriptor's data m_file.seek(155, fs::seek_cur); - m_root = iso_fs_node + const auto node = iso_read_directory_entry(m_file, use_ucs2_decoding); + + if (node) { - .metadata = iso_read_directory_entry(m_file, use_ucs2_decoding).value(), - }; + m_root = iso_fs_node + { + .metadata = node.value() + }; + } } m_file.seek(descriptor_start + ISO_SECTOR_SIZE); @@ -696,7 +701,7 @@ psf::registry iso_archive::open_psf(const std::string& 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) { - m_file.seek(ISO_SECTOR_SIZE * node.metadata.extents[0].start); + m_file.seek(node.metadata.extents[0].start * ISO_SECTOR_SIZE); } fs::stat_t iso_file::get_stat() @@ -791,9 +796,10 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) // IMPORTANT NOTE: // - // For performance reasons, current "iso_file_decryption::decrypt()" method requires that reads always start at the beginning of a sector and - // that all reads will be multiples of ISO_SECTOR_SIZE. - // Both are easy fixes, but, code can be more simple + efficient if these two conditions are true (which they look to be from initial testing) + // For performance reasons, current "iso_file_decryption::decrypt()" method requires that reads always + // start at the beginning of a sector and that all reads will be multiples of ISO_SECTOR_SIZE. + // Both are easy fixes, but, code can be more simple + efficient if these two conditions are true + // (which they look to be from initial testing) // // // TODO: diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index bbb1cf0065..605f138428 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -14,7 +14,7 @@ void unload_iso(); /* - Hijacked the "iso_archive::iso_archive" method to test if the ".iso" file is encrypted and sets a flag. - The flag is set according to the first matching encryption type in the order below: + The flag is set according to the first matching encryption type found in the following order: - Redump type: ".dkey" file, with the same name of the ".iso" file, exists in the same folder of the ".iso" file - 3k3y type: 3k3y watermark exists at 0xF70 If the flag is set then the "iso_file::read" method will decrypt the data on the fly @@ -94,7 +94,7 @@ class iso_file : public fs::file_base private: fs::file m_file; std::shared_ptr m_dec; - iso_fs_metadata m_meta {}; + iso_fs_metadata m_meta; u64 m_pos = 0; std::pair get_extent_pos(u64 pos) const; From dcb9df6d1d7970a1d198e5448d26bd58f613fd94 Mon Sep 17 00:00:00 2001 From: digant73 Date: Sat, 4 Apr 2026 17:44:44 +0200 Subject: [PATCH 07/17] applied suggested changes --- rpcs3/Loader/ISO.cpp | 112 +++++++++++++++++++++---------------------- rpcs3/Loader/ISO.h | 15 +++--- 2 files changed, 61 insertions(+), 66 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 26a05cb4f4..c6b9efd7cb 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -11,14 +11,14 @@ LOG_CHANNEL(sys_log, "SYS"); -const u64 ISO_SECTOR_SIZE = 2048; +constexpr u64 ISO_SECTOR_SIZE = 2048; struct iso_sector { u64 archive_first_addr = 0; u64 offset = 0; u64 size = 0; - u8 buf[ISO_SECTOR_SIZE]; + std::array buf {}; }; bool is_file_iso(const std::string& path) @@ -47,7 +47,7 @@ bool is_file_iso(const fs::file& file) // asciischar_to_byte() and keystr_to_keyarr(): Convert a hex string into a byte array static unsigned char asciischar_to_byte(char input) { - if (input >= '0' && input <= '9') + if (std::isdigit(input)) return input - '0'; if (input >= 'A' && input <= 'F') return input - 'A' + 10; @@ -68,15 +68,15 @@ static void keystr_to_keyarr(const char (&str)[32], unsigned char (&arr)[16]) } // Convert 4 bytes in big-endian format to an unsigned integer -static u32 char_arr_BE_to_uint(unsigned char* arr) +static u32 char_arr_BE_to_uint(const u8* arr) { return arr[0] << 24 | arr[1] << 16 | arr[2] << 8 | arr[3]; } // Reset the iv to a particular lba -static void reset_iv(unsigned char (&iv)[16], u32 lba) +static void reset_iv(std::array& iv, u32 lba) { - memset(iv, 0, 12); + memset(iv.data(), 0, 12); iv[12] = (lba & 0xFF000000) >> 24; iv[13] = (lba & 0x00FF0000) >> 16; @@ -88,13 +88,13 @@ static void reset_iv(unsigned char (&iv)[16], u32 lba) static void decrypt_data(aes_context& aes, unsigned char* data, u32 sector_count, u32 start_lba) { // Micro optimization, only ever used by "decrypt_data()" - unsigned char iv_[16]; + std::array iv; for (u32 i = 0; i < sector_count; ++i) { - reset_iv(iv_, start_lba + i); + reset_iv(iv, start_lba + i); - if (aes_crypt_cbc(&aes, AES_DECRYPT, ISO_SECTOR_SIZE, &iv_[0], &data[ISO_SECTOR_SIZE * i], &data[ISO_SECTOR_SIZE * i]) != 0) + if (aes_crypt_cbc(&aes, AES_DECRYPT, ISO_SECTOR_SIZE, iv.data(), &data[ISO_SECTOR_SIZE * i], &data[ISO_SECTOR_SIZE * i]) != 0) { sys_log.error("decrypt_data(): Error decrypting data (decrypt_data() > aes_crypt_cbc())"); @@ -103,16 +103,10 @@ static void decrypt_data(aes_context& aes, unsigned char* data, u32 sector_count } } -void iso_file_decryption::reset(void) +void iso_file_decryption::reset() { - m_enc_type = ENC_TYPE_NONE; - m_region_count = 0; - - if (m_region_info != NULL) - { - delete[] m_region_info; - m_region_info = NULL; - } + m_enc_type = iso_encryption_type::ENC_TYPE_NONE; + m_region_info.clear(); } iso_file_decryption::~iso_file_decryption() @@ -129,7 +123,7 @@ bool iso_file_decryption::init(const std::string& path) return false; } - unsigned char sec0_sec1[ISO_SECTOR_SIZE * 2] = {0}; + std::array sec0_sec1 {}; // // Store the ISO region information (needed by both the "Redump" type (only on "decrypt()" method) and "3k3y" type) @@ -139,29 +133,28 @@ bool iso_file_decryption::init(const std::string& path) if (fs::file iso_file(path); iso_file) { - if (iso_file.size() >= ISO_SECTOR_SIZE * 2) + if (iso_file.size() >= sec0_sec1.size()) { - if (iso_file.read(sec0_sec1, sizeof(sec0_sec1)) == sizeof(sec0_sec1)) + if (iso_file.read(sec0_sec1.data(), sec0_sec1.size()) == sec0_sec1.size()) { // NOTE: // // Following checks and assigned values are based on PS3 ISO specification. // E.g. all even regions (0, 2, 4 etc.) are always unencrypted while the odd ones are encrypted - size_t region_count = char_arr_BE_to_uint(sec0_sec1); + size_t region_count = char_arr_BE_to_uint(sec0_sec1.data()); // Ensure the region count is a proper value if (region_count > 0 && region_count < 32) { - m_region_count = region_count * 2 - 1; - m_region_info = new iso_region_info[m_region_count]; + m_region_info.resize(region_count * 2 - 1); - for (size_t i = 0; i < m_region_count; ++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); m_region_info[i].region_first_addr = (i == 0 ? 0ULL : m_region_info[i - 1].region_last_addr + 1ULL); - m_region_info[i].region_last_addr = (static_cast(char_arr_BE_to_uint(sec0_sec1 + 12 + (i * 4))) + m_region_info[i].region_last_addr = (static_cast(char_arr_BE_to_uint(sec0_sec1.data() + 12 + (i * 4))) - (i % 2 == 1 ? 1ULL : 0ULL)) * ISO_SECTOR_SIZE + ISO_SECTOR_SIZE - 1ULL; region_info_stored = true; @@ -197,7 +190,7 @@ bool iso_file_decryption::init(const std::string& path) // std::string key_path; - size_t ext_pos = path.rfind('.'); + const usz ext_pos = path.rfind('.'); // If no file extension is provided, set "key_path" appending ".dkey" to "path". // Otherwise, replace the extension (e.g. ".iso") with ".dkey" @@ -209,7 +202,7 @@ bool iso_file_decryption::init(const std::string& path) char key_str[32]; unsigned char key[16]; - u64 key_len = key_file.read(key_str, sizeof(key_str)); + const u64 key_len = key_file.read(key_str, sizeof(key_str)); if (key_len == sizeof(key_str) || key_len == sizeof(key)) { @@ -224,11 +217,11 @@ bool iso_file_decryption::init(const std::string& path) if (aes_setkey_dec(&m_aes_dec, key, 128) == 0) { - m_enc_type = ENC_TYPE_REDUMP; // SET ENCRYPTION TYPE: REDUMP + m_enc_type = iso_encryption_type::ENC_TYPE_REDUMP; // SET ENCRYPTION TYPE: REDUMP } } - if (m_enc_type == ENC_TYPE_NONE) // If encryption type was not set to REDUMP for any reason + if (m_enc_type == iso_encryption_type::ENC_TYPE_NONE) // If encryption type was not set to REDUMP for any reason { sys_log.error("init(): Failed to process key file: %s", key_path); } @@ -243,7 +236,7 @@ bool iso_file_decryption::init(const std::string& path) // // If encryption type is still set to none (sec0_sec1 will be zeroed out if it didn't succeed above) - if (m_enc_type == ENC_TYPE_NONE) + if (m_enc_type == iso_encryption_type::ENC_TYPE_NONE) { // The 3k3y watermarks located at 0xF70: (D|E)ncrypted 3K BLD static const unsigned char k3k3y_enc_watermark[16] = @@ -270,30 +263,33 @@ bool iso_file_decryption::init(const std::string& path) { if (aes_setkey_dec(&m_aes_dec, key, 128) == 0) { - m_enc_type = ENC_TYPE_3K3Y_ENC; // SET ENCRYPTION TYPE: 3K3Y_ENC + m_enc_type = iso_encryption_type::ENC_TYPE_3K3Y_ENC; // SET ENCRYPTION TYPE: 3K3Y_ENC } } } - if (m_enc_type == ENC_TYPE_NONE) // If encryption type was not set to 3K3Y_ENC for any reason + if (m_enc_type == iso_encryption_type::ENC_TYPE_NONE) // If encryption type was not set to 3K3Y_ENC for any reason { sys_log.error("init(): Failed to set encryption type to 3K3Y_ENC: %s", path); } } else if (memcmp(&k3k3y_dec_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_dec_watermark)) == 0) { - m_enc_type = ENC_TYPE_3K3Y_DEC; // SET ENCRYPTION TYPE: 3K3Y_DEC + m_enc_type = iso_encryption_type::ENC_TYPE_3K3Y_DEC; // SET ENCRYPTION TYPE: 3K3Y_DEC } } - if (m_enc_type == ENC_TYPE_REDUMP) - sys_log.warning("init(): Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_count, path); - else if (m_enc_type == ENC_TYPE_3K3Y_ENC) - sys_log.warning("init(): Set 'enc type': 3K3Y_ENC, 'reg count': %u: %s", m_region_count, path); - else if (m_enc_type == ENC_TYPE_3K3Y_DEC) - sys_log.warning("init(): Set 'enc type': 3K3Y_DEC, 'reg count': %u: %s", m_region_count, path); - else if (m_enc_type == ENC_TYPE_NONE) // If encryption type was not set for any reason - sys_log.warning("init(): Set 'enc type': NONE, 'reg count': %u: %s", m_region_count, path); + switch (m_enc_type) + { + case iso_encryption_type::ENC_TYPE_REDUMP: + sys_log.warning("init(): Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_info.size(), path); + case iso_encryption_type::ENC_TYPE_3K3Y_ENC: + sys_log.warning("init(): Set 'enc type': 3K3Y_ENC, 'reg count': %u: %s", m_region_info.size(), path); + case iso_encryption_type::ENC_TYPE_3K3Y_DEC: + sys_log.warning("init(): Set 'enc type': 3K3Y_DEC, 'reg count': %u: %s", m_region_info.size(), path); + default: // If encryption type was not set for any reason + sys_log.warning("init(): Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path); + } return true; } @@ -301,13 +297,13 @@ bool iso_file_decryption::init(const std::string& path) bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std::string& name) { // If it's a non-encrypted type, nothing more to do - if (m_enc_type == ENC_TYPE_NONE) + if (m_enc_type == iso_encryption_type::ENC_TYPE_NONE) { return true; } // If it's a 3k3y iso and 0xF70 data is being requested, we should null it out - if (m_enc_type == ENC_TYPE_3K3Y_DEC || m_enc_type == ENC_TYPE_3K3Y_ENC) + if (m_enc_type == iso_encryption_type::ENC_TYPE_3K3Y_DEC || m_enc_type == iso_encryption_type::ENC_TYPE_3K3Y_ENC) { if (offset + size >= 0xF70ULL && offset <= 0x1070ULL) { @@ -319,19 +315,19 @@ bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std: } // If it's a decrypted iso then return, otherwise go on to the decryption logic - if (m_enc_type == ENC_TYPE_3K3Y_DEC) + if (m_enc_type == iso_encryption_type::ENC_TYPE_3K3Y_DEC) { return true; } } // If it's an encrypted type, check if the request lies in an encrypted range - for (size_t i = 0; i < m_region_count; ++i) + for (const iso_region_info& info : m_region_info) { - if (offset >= m_region_info[i].region_first_addr && offset <= m_region_info[i].region_last_addr) + if (offset >= info.region_first_addr && offset <= info.region_last_addr) { // We found the region, decrypt if needed - if (!m_region_info[i].encrypted) + if (!info.encrypted) { return true; } @@ -362,9 +358,9 @@ bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std: sys_log.error("decrypt(): %s: LBA request wasn't in the 'm_region_info' for an encrypted iso? - RP: 0x%lx, RC: 0x%lx, LR: (0x%016lx - 0x%016lx)", name, offset, - static_cast(m_region_count), - static_cast(m_region_count > 0 ? m_region_info[m_region_count - 1].region_first_addr : 0), - static_cast(m_region_count > 0 ? m_region_info[m_region_count - 1].region_last_addr : 0)); + static_cast(m_region_info.size()), + static_cast(!m_region_info.empty() ? m_region_info.back().region_first_addr : 0), + static_cast(!m_region_info.empty() ? m_region_info.back().region_last_addr : 0)); return true; } @@ -780,7 +776,7 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) const u64 total_size = this->size(); // If it's a non-encrypted type - if (m_dec->get_enc_type() == ENC_TYPE_NONE) + if (m_dec->get_enc_type() == iso_encryption_type::ENC_TYPE_NONE) { u64 total_read = m_file.read_at(archive_first_offset, buffer, max_size); @@ -835,7 +831,7 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) u64 sec_count = (last_sec.archive_first_addr - first_sec.archive_first_addr) / ISO_SECTOR_SIZE + 1; u64 sec_inner_size = (sec_count - 2) * ISO_SECTOR_SIZE; - if (m_file.read_at(first_sec.archive_first_addr, first_sec.buf, ISO_SECTOR_SIZE) != ISO_SECTOR_SIZE) + if (m_file.read_at(first_sec.archive_first_addr, first_sec.buf.data(), ISO_SECTOR_SIZE) != ISO_SECTOR_SIZE) { sys_log.error("read_at(): %s: Error reading from file", m_meta.name); @@ -844,8 +840,8 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) } // Decrypt read buffer (if needed) and copy to destination buffer - m_dec->decrypt(first_sec.archive_first_addr, first_sec.buf, ISO_SECTOR_SIZE, m_meta.name); - memcpy(buffer, first_sec.buf + first_sec.offset, first_sec.size); + m_dec->decrypt(first_sec.archive_first_addr, first_sec.buf.data(), ISO_SECTOR_SIZE, m_meta.name); + memcpy(buffer, first_sec.buf.data() + first_sec.offset, first_sec.size); // If the sector was already read, decrypted and copied to destination buffer, nothing more to do if (sec_count < 2) @@ -853,7 +849,7 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) return max_size; } - if (m_file.read_at(last_sec.archive_first_addr, last_sec.buf, ISO_SECTOR_SIZE) != ISO_SECTOR_SIZE) + if (m_file.read_at(last_sec.archive_first_addr, last_sec.buf.data(), ISO_SECTOR_SIZE) != ISO_SECTOR_SIZE) { sys_log.error("read_at(): %s: Error reading from file", m_meta.name); @@ -862,8 +858,8 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) } // Decrypt read buffer (if needed) and copy to destination buffer - m_dec->decrypt(last_sec.archive_first_addr, last_sec.buf, ISO_SECTOR_SIZE, m_meta.name); - memcpy(reinterpret_cast(buffer) + max_size - last_sec.size, last_sec.buf, last_sec.size); + m_dec->decrypt(last_sec.archive_first_addr, last_sec.buf.data(), ISO_SECTOR_SIZE, m_meta.name); + memcpy(reinterpret_cast(buffer) + max_size - last_sec.size, last_sec.buf.data(), last_sec.size); // If the sector was already read, decrypted and copied to destination buffer, nothing more to do if (sec_count < 3) diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index 605f138428..3aabe36715 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -32,13 +32,13 @@ void unload_iso(); // so would have to multiply or divide every read if storing lba) struct iso_region_info { - bool encrypted; - u64 region_first_addr; - u64 region_last_addr; + bool encrypted = false; + u64 region_first_addr = 0; + u64 region_last_addr = 0; }; // Enum to decide ISO encryption type -enum iso_encryption_type +enum class iso_encryption_type { ENC_TYPE_NONE, ENC_TYPE_3K3Y_DEC, @@ -51,11 +51,10 @@ class iso_file_decryption { private: aes_context m_aes_dec; - iso_encryption_type m_enc_type = ENC_TYPE_NONE; - size_t m_region_count = 0; - iso_region_info* m_region_info = nullptr; + iso_encryption_type m_enc_type = iso_encryption_type::ENC_TYPE_NONE; + std::vector m_region_info; - void reset(void); + void reset(); public: ~iso_file_decryption(); From b78e5a7b36d253b879f3688f044dc51035d6d5c1 Mon Sep 17 00:00:00 2001 From: digant73 Date: Sat, 4 Apr 2026 18:30:19 +0200 Subject: [PATCH 08/17] added missing break in switch --- rpcs3/Loader/ISO.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index c6b9efd7cb..041bfdeac9 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -283,12 +283,16 @@ bool iso_file_decryption::init(const std::string& path) { case iso_encryption_type::ENC_TYPE_REDUMP: sys_log.warning("init(): Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_info.size(), path); + break; case iso_encryption_type::ENC_TYPE_3K3Y_ENC: sys_log.warning("init(): Set 'enc type': 3K3Y_ENC, 'reg count': %u: %s", m_region_info.size(), path); + break; case iso_encryption_type::ENC_TYPE_3K3Y_DEC: sys_log.warning("init(): Set 'enc type': 3K3Y_DEC, 'reg count': %u: %s", m_region_info.size(), path); + break; default: // If encryption type was not set for any reason sys_log.warning("init(): Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path); + break; } return true; From b790a504a8aab84cc623de0194527ceec24dea71 Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Sat, 4 Apr 2026 18:40:12 +0200 Subject: [PATCH 09/17] Update rpcs3/Loader/ISO.cpp Co-authored-by: Megamouse --- rpcs3/Loader/ISO.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 041bfdeac9..8c47e415ad 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -290,7 +290,7 @@ bool iso_file_decryption::init(const std::string& path) case iso_encryption_type::ENC_TYPE_3K3Y_DEC: sys_log.warning("init(): Set 'enc type': 3K3Y_DEC, 'reg count': %u: %s", m_region_info.size(), path); break; - default: // If encryption type was not set for any reason + case iso_encryption_type::ENC_TYPE_NONE: // If encryption type was not set for any reason sys_log.warning("init(): Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path); break; } From 20dd4cef76310a953ca894a37077a9f740e33d6a Mon Sep 17 00:00:00 2001 From: digant73 Date: Sat, 4 Apr 2026 20:53:41 +0200 Subject: [PATCH 10/17] use hex_to_bytes() instead of self made functions --- rpcs3/Loader/ISO.cpp | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 8c47e415ad..82f9679a3b 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -2,6 +2,7 @@ #include "ISO.h" #include "Emu/VFS.h" +#include "Crypto/utils.h" #include #include @@ -44,29 +45,6 @@ bool is_file_iso(const fs::file& file) && magic[4] == '1'; } -// asciischar_to_byte() and keystr_to_keyarr(): Convert a hex string into a byte array -static unsigned char asciischar_to_byte(char input) -{ - if (std::isdigit(input)) - return input - '0'; - if (input >= 'A' && input <= 'F') - return input - 'A' + 10; - if (input >= 'a' && input <= 'f') - return input - 'a' + 10; - - sys_log.error("asciischar_to_byte(): Error converting data to byte array"); - - return 0x00; -} - -static void keystr_to_keyarr(const char (&str)[32], unsigned char (&arr)[16]) -{ - for (size_t i = 0; i < sizeof(arr); ++i) - { - arr[i] = (asciischar_to_byte(str[i * 2]) * 16) + asciischar_to_byte(str[(i * 2) + 1]); - } -} - // Convert 4 bytes in big-endian format to an unsigned integer static u32 char_arr_BE_to_uint(const u8* arr) { @@ -206,13 +184,15 @@ bool iso_file_decryption::init(const std::string& path) if (key_len == sizeof(key_str) || key_len == sizeof(key)) { + // If the key read from the key file is 16 bytes long instead of 32, consider the file as + // binary (".key") and so not needing any further conversion from hex string to bytes if (key_len == sizeof(key)) { memcpy(key, key_str, sizeof(key)); } else { - keystr_to_keyarr(key_str, key); + hex_to_bytes(key, std::string_view(key_str, key_len), key_len); } if (aes_setkey_dec(&m_aes_dec, key, 128) == 0) From 389dd38a55653f1d698aa5bb085081e4014a41cb Mon Sep 17 00:00:00 2001 From: digant73 Date: Sat, 4 Apr 2026 23:39:20 +0200 Subject: [PATCH 11/17] minor cleanup --- rpcs3/Loader/ISO.cpp | 36 ++++++++++++++++++------------------ rpcs3/Loader/ISO.h | 10 +++++----- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 82f9679a3b..cb32b941f0 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -83,7 +83,7 @@ static void decrypt_data(aes_context& aes, unsigned char* data, u32 sector_count void iso_file_decryption::reset() { - m_enc_type = iso_encryption_type::ENC_TYPE_NONE; + m_enc_type = iso_encryption_type::NONE; m_region_info.clear(); } @@ -197,11 +197,11 @@ bool iso_file_decryption::init(const std::string& path) if (aes_setkey_dec(&m_aes_dec, key, 128) == 0) { - m_enc_type = iso_encryption_type::ENC_TYPE_REDUMP; // SET ENCRYPTION TYPE: REDUMP + m_enc_type = iso_encryption_type::REDUMP; // SET ENCRYPTION TYPE: REDUMP } } - if (m_enc_type == iso_encryption_type::ENC_TYPE_NONE) // If encryption type was not set to REDUMP for any reason + if (m_enc_type == iso_encryption_type::NONE) // If encryption type was not set to REDUMP for any reason { sys_log.error("init(): Failed to process key file: %s", key_path); } @@ -216,7 +216,7 @@ bool iso_file_decryption::init(const std::string& path) // // If encryption type is still set to none (sec0_sec1 will be zeroed out if it didn't succeed above) - if (m_enc_type == iso_encryption_type::ENC_TYPE_NONE) + if (m_enc_type == iso_encryption_type::NONE) { // The 3k3y watermarks located at 0xF70: (D|E)ncrypted 3K BLD static const unsigned char k3k3y_enc_watermark[16] = @@ -243,34 +243,34 @@ bool iso_file_decryption::init(const std::string& path) { if (aes_setkey_dec(&m_aes_dec, key, 128) == 0) { - m_enc_type = iso_encryption_type::ENC_TYPE_3K3Y_ENC; // SET ENCRYPTION TYPE: 3K3Y_ENC + m_enc_type = iso_encryption_type::ENC_3K3Y; // SET ENCRYPTION TYPE: ENC_3K3Y } } } - if (m_enc_type == iso_encryption_type::ENC_TYPE_NONE) // If encryption type was not set to 3K3Y_ENC for any reason + if (m_enc_type == iso_encryption_type::NONE) // If encryption type was not set to ENC_3K3Y for any reason { - sys_log.error("init(): Failed to set encryption type to 3K3Y_ENC: %s", path); + sys_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) { - m_enc_type = iso_encryption_type::ENC_TYPE_3K3Y_DEC; // SET ENCRYPTION TYPE: 3K3Y_DEC + m_enc_type = iso_encryption_type::DEC_3K3Y; // SET ENCRYPTION TYPE: DEC_3K3Y } } switch (m_enc_type) { - case iso_encryption_type::ENC_TYPE_REDUMP: + case iso_encryption_type::REDUMP: sys_log.warning("init(): Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_info.size(), path); break; - case iso_encryption_type::ENC_TYPE_3K3Y_ENC: - sys_log.warning("init(): Set 'enc type': 3K3Y_ENC, 'reg count': %u: %s", m_region_info.size(), path); + case iso_encryption_type::ENC_3K3Y: + sys_log.warning("init(): Set 'enc type': ENC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); break; - case iso_encryption_type::ENC_TYPE_3K3Y_DEC: - sys_log.warning("init(): Set 'enc type': 3K3Y_DEC, 'reg count': %u: %s", m_region_info.size(), path); + case iso_encryption_type::DEC_3K3Y: + sys_log.warning("init(): Set 'enc type': DEC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); break; - case iso_encryption_type::ENC_TYPE_NONE: // If encryption type was not set for any reason + case iso_encryption_type::NONE: // If encryption type was not set for any reason sys_log.warning("init(): Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path); break; } @@ -281,13 +281,13 @@ bool iso_file_decryption::init(const std::string& path) bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std::string& name) { // If it's a non-encrypted type, nothing more to do - if (m_enc_type == iso_encryption_type::ENC_TYPE_NONE) + if (m_enc_type == iso_encryption_type::NONE) { return true; } // If it's a 3k3y iso and 0xF70 data is being requested, we should null it out - if (m_enc_type == iso_encryption_type::ENC_TYPE_3K3Y_DEC || m_enc_type == iso_encryption_type::ENC_TYPE_3K3Y_ENC) + if (m_enc_type == iso_encryption_type::DEC_3K3Y || m_enc_type == iso_encryption_type::ENC_3K3Y) { if (offset + size >= 0xF70ULL && offset <= 0x1070ULL) { @@ -299,7 +299,7 @@ bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std: } // If it's a decrypted iso then return, otherwise go on to the decryption logic - if (m_enc_type == iso_encryption_type::ENC_TYPE_3K3Y_DEC) + if (m_enc_type == iso_encryption_type::DEC_3K3Y) { return true; } @@ -760,7 +760,7 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) const u64 total_size = this->size(); // If it's a non-encrypted type - if (m_dec->get_enc_type() == iso_encryption_type::ENC_TYPE_NONE) + if (m_dec->get_enc_type() == iso_encryption_type::NONE) { u64 total_read = m_file.read_at(archive_first_offset, buffer, max_size); diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index 3aabe36715..725b1af3d5 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -40,10 +40,10 @@ struct iso_region_info // Enum to decide ISO encryption type enum class iso_encryption_type { - ENC_TYPE_NONE, - ENC_TYPE_3K3Y_DEC, - ENC_TYPE_3K3Y_ENC, - ENC_TYPE_REDUMP + NONE, + DEC_3K3Y, + ENC_3K3Y, + REDUMP }; // ISO file decryption class @@ -51,7 +51,7 @@ class iso_file_decryption { private: aes_context m_aes_dec; - iso_encryption_type m_enc_type = iso_encryption_type::ENC_TYPE_NONE; + iso_encryption_type m_enc_type = iso_encryption_type::NONE; std::vector m_region_info; void reset(); From 22049d4f7735e564307b13579b8d8e2ff6f1b34c Mon Sep 17 00:00:00 2001 From: digant73 Date: Sun, 5 Apr 2026 01:29:48 +0200 Subject: [PATCH 12/17] rewrite nested if() --- rpcs3/Loader/ISO.cpp | 91 ++++++++++++++++++-------------------------- rpcs3/Loader/ISO.h | 2 - 2 files changed, 37 insertions(+), 56 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index cb32b941f0..f5300d716b 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -75,7 +75,6 @@ static void decrypt_data(aes_context& aes, unsigned char* data, u32 sector_count if (aes_crypt_cbc(&aes, AES_DECRYPT, ISO_SECTOR_SIZE, iv.data(), &data[ISO_SECTOR_SIZE * i], &data[ISO_SECTOR_SIZE * i]) != 0) { sys_log.error("decrypt_data(): Error decrypting data (decrypt_data() > aes_crypt_cbc())"); - return; } } @@ -87,11 +86,6 @@ void iso_file_decryption::reset() m_region_info.clear(); } -iso_file_decryption::~iso_file_decryption() -{ - reset(); -} - bool iso_file_decryption::init(const std::string& path) { reset(); @@ -107,62 +101,51 @@ bool iso_file_decryption::init(const std::string& path) // Store the ISO region information (needed by both the "Redump" type (only on "decrypt()" method) and "3k3y" type) // - bool region_info_stored = false; + fs::file iso_file(path); - if (fs::file iso_file(path); iso_file) - { - if (iso_file.size() >= sec0_sec1.size()) - { - if (iso_file.read(sec0_sec1.data(), sec0_sec1.size()) == sec0_sec1.size()) - { - // NOTE: - // - // Following checks and assigned values are based on PS3 ISO specification. - // E.g. all even regions (0, 2, 4 etc.) are always unencrypted while the odd ones are encrypted - - size_t region_count = char_arr_BE_to_uint(sec0_sec1.data()); - - // Ensure the region count is a proper value - if (region_count > 0 && region_count < 32) - { - m_region_info.resize(region_count * 2 - 1); - - 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); - m_region_info[i].region_first_addr = (i == 0 ? 0ULL : m_region_info[i - 1].region_last_addr + 1ULL); - m_region_info[i].region_last_addr = (static_cast(char_arr_BE_to_uint(sec0_sec1.data() + 12 + (i * 4))) - - (i % 2 == 1 ? 1ULL : 0ULL)) * ISO_SECTOR_SIZE + ISO_SECTOR_SIZE - 1ULL; - - region_info_stored = true; - } - } - else // It's non-PS3ISO - { - sys_log.error("init(): Failed to read region information: %s", path); - } - } - else - { - sys_log.error("init(): Failed to read file: %s", path); - } - } - else - { - sys_log.error("init(): Found only %ull sector(s) (minimum required is 2): %s", iso_file.size(), path); - } - } - else + if (!iso_file) { sys_log.error("init(): Failed to open file: %s", path); + return false; } - if (!region_info_stored) + if (iso_file.size() < sec0_sec1.size()) { + sys_log.error("init(): Found only %ull sector(s) (minimum required is 2): %s", iso_file.size(), path); return false; } + if (iso_file.read(sec0_sec1.data(), sec0_sec1.size()) != sec0_sec1.size()) + { + sys_log.error("init(): Failed to read file: %s", path); + return false; + } + + // NOTE: + // + // Following checks and assigned values are based on PS3 ISO specification. + // E.g. all even regions (0, 2, 4 etc.) are always unencrypted while the odd ones are encrypted + + size_t region_count = char_arr_BE_to_uint(sec0_sec1.data()); + + // Ensure the region count is a proper value + if (region_count < 1 || region_count > 31) // It's non-PS3ISO + { + sys_log.error("init(): Failed to read region information: %s", path); + return false; + } + + m_region_info.resize(region_count * 2 - 1); + + 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); + m_region_info[i].region_first_addr = (i == 0 ? 0ULL : m_region_info[i - 1].region_last_addr + 1ULL); + m_region_info[i].region_last_addr = (static_cast(char_arr_BE_to_uint(sec0_sec1.data() + 12 + (i * 4))) + - (i % 2 == 1 ? 1ULL : 0ULL)) * ISO_SECTOR_SIZE + ISO_SECTOR_SIZE - 1ULL; + } + // // Check for Redump type // @@ -215,7 +198,7 @@ bool iso_file_decryption::init(const std::string& path) // Check for 3k3y type // - // If encryption type is still set to none (sec0_sec1 will be zeroed out if it didn't succeed above) + // If encryption type is still set to none if (m_enc_type == iso_encryption_type::NONE) { // The 3k3y watermarks located at 0xF70: (D|E)ncrypted 3K BLD diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index 725b1af3d5..f4e28e54cd 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -57,8 +57,6 @@ private: void reset(); public: - ~iso_file_decryption(); - iso_encryption_type get_enc_type() const { return m_enc_type; } bool init(const std::string& path); From 37032282722d2a884f2a0f2420d83942a1ff96d4 Mon Sep 17 00:00:00 2001 From: digant73 Date: Sun, 5 Apr 2026 14:22:33 +0200 Subject: [PATCH 13/17] add explicit support to .key --- rpcs3/Loader/ISO.cpp | 33 +++++++++++++++++++++------------ rpcs3/Loader/ISO.h | 13 +++++++------ 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index f5300d716b..54c8058ed2 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -51,7 +51,7 @@ static u32 char_arr_BE_to_uint(const u8* arr) return arr[0] << 24 | arr[1] << 16 | arr[2] << 8 | arr[3]; } -// Reset the iv to a particular lba +// Reset the iv to a particular LBA static void reset_iv(std::array& iv, u32 lba) { memset(iv.data(), 0, 12); @@ -150,15 +150,24 @@ bool iso_file_decryption::init(const std::string& path) // Check for Redump type // - std::string key_path; const usz ext_pos = path.rfind('.'); + std::string key_path; // If no file extension is provided, set "key_path" appending ".dkey" to "path". // Otherwise, replace the extension (e.g. ".iso") with ".dkey" key_path = ext_pos == umax ? path + ".dkey" : path.substr(0, ext_pos) + ".dkey"; + fs::file key_file(key_path); + + // If no ".dkey" file exists, try with ".key" + if (!key_file) + { + key_path = ext_pos == umax ? path + ".key" : path.substr(0, ext_pos) + ".key"; + key_file = fs::file(key_path); + } + // Check if "key_path" exists and create the "m_aes_dec" context if so - if (fs::file key_file(key_path); key_file) + if (key_file) { char key_str[32]; unsigned char key[16]; @@ -201,7 +210,7 @@ bool iso_file_decryption::init(const std::string& path) // If encryption type is still set to none if (m_enc_type == iso_encryption_type::NONE) { - // The 3k3y watermarks located at 0xF70: (D|E)ncrypted 3K BLD + // The 3k3y watermarks located at offset 0xF70: (D|E)ncrypted 3K BLD static const unsigned char k3k3y_enc_watermark[16] = {0x45, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x33, 0x4B, 0x20, 0x42, 0x4C, 0x44}; static const unsigned char k3k3y_dec_watermark[16] = @@ -269,7 +278,7 @@ bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std: return true; } - // If it's a 3k3y iso and 0xF70 data is being requested, we should null it out + // If it's a 3k3y ISO and data at offset 0xF70 is being requested, we should null it out if (m_enc_type == iso_encryption_type::DEC_3K3Y || m_enc_type == iso_encryption_type::ENC_3K3Y) { if (offset + size >= 0xF70ULL && offset <= 0x1070ULL) @@ -281,7 +290,7 @@ bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std: memset(buf_overlap_start, 0x00, offset + size < 0x1070ULL ? size - (buf_overlap_start - buf) : 0x100ULL - (buf_overlap_start - buf)); } - // If it's a decrypted iso then return, otherwise go on to the decryption logic + // If it's a decrypted ISO then return, otherwise go on to the decryption logic if (m_enc_type == iso_encryption_type::DEC_3K3Y) { return true; @@ -322,7 +331,7 @@ bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std: } } - sys_log.error("decrypt(): %s: LBA request wasn't in the 'm_region_info' for an encrypted iso? - RP: 0x%lx, RC: 0x%lx, LR: (0x%016lx - 0x%016lx)", + sys_log.error("decrypt(): %s: LBA request wasn't in the 'm_region_info' for an encrypted ISO? - RP: 0x%lx, RC: 0x%lx, LR: (0x%016lx - 0x%016lx)", name, offset, static_cast(m_region_info.size()), @@ -528,7 +537,7 @@ iso_archive::iso_archive(const std::string& path) if (!m_dec->init(path)) { - // Not iso... TODO: throw something?? + // Not ISO... TODO: throw something?? return; } @@ -772,7 +781,7 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) // if trying to read the previously not requested bytes (marked with "x" in the picture below) // // ------------------------------------------------------------------- - // file on iso archive: | ' ' | + // file on ISO archive: | ' ' | // ------------------------------------------------------------------- // ' ' // --------------------------------------- @@ -780,7 +789,7 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) // --------------------------------------- // ' ' ' ' // --------------------------------------------------------------------------------------------------------------- - // iso archive: | sec 0 | sec 1 |xxx'#####|#########|#########|#########|#'xxxxxxx| | ... | sec n-1 | sec n | + // ISO archive: | sec 0 | sec 1 |xxx'#####|#########|#########|#########|#'xxxxxxx| | ... | sec n-1 | sec n | // --------------------------------------------------------------------------------------------------------------- // ' ' ' ' // |first sec| |last sec | @@ -1014,7 +1023,7 @@ void iso_dir::rewind() void load_iso(const std::string& path) { - sys_log.notice("Loading iso '%s'", path); + sys_log.notice("Loading ISO '%s'", path); fs::set_virtual_device("iso_overlay_fs_dev", stx::make_shared(path)); @@ -1023,7 +1032,7 @@ void load_iso(const std::string& path) void unload_iso() { - sys_log.notice("Unloading iso"); + sys_log.notice("Unloading ISO"); fs::set_virtual_device("iso_overlay_fs_dev", stx::shared_ptr()); } diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index f4e28e54cd..bd5b11df2b 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -14,22 +14,23 @@ void unload_iso(); /* - Hijacked the "iso_archive::iso_archive" method to test if the ".iso" file is encrypted and sets a flag. - The flag is set according to the first matching encryption type found in the following order: - - Redump type: ".dkey" file, with the same name of the ".iso" file, exists in the same folder of the ".iso" file - - 3k3y type: 3k3y watermark exists at 0xF70 + The flag is set according to the first matching encryption type found following the order below: + - Redump type: ".dkey" or ".key" (as alternative) file, with the same name of the ".iso" file, + exists in the same folder of the ".iso" file + - 3k3y type: 3k3y watermark exists at offset 0xF70 If the flag is set then the "iso_file::read" method will decrypt the data on the fly - Supported ISO encryption type: - Decrypted (.iso) - 3k3y (decrypted / encrypted) (.iso) - - Redump (encrypted) (.iso & .dkey) + - Redump (encrypted) (.iso + .dkey/.key) - Unsupported ISO encryption type: - Encrypted split ISO files */ -// Struct to store ISO region information (storing addrs instead of lba since we need to compare the addr anyway, -// so would have to multiply or divide every read if storing lba) +// Struct to store ISO region information (storing addresses instead of LBA since we need to compare +// the address anyway, so would have to multiply or divide every read if storing LBA) struct iso_region_info { bool encrypted = false; From 84592d86d8877716efc86e01d100e0d6f5245e13 Mon Sep 17 00:00:00 2001 From: digant73 Date: Sun, 5 Apr 2026 15:59:20 +0200 Subject: [PATCH 14/17] minor cleanup --- rpcs3/Loader/ISO.cpp | 2 +- rpcs3/Loader/ISO.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 54c8058ed2..cdd70fde23 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -207,7 +207,7 @@ bool iso_file_decryption::init(const std::string& path) // Check for 3k3y type // - // If encryption type is still set to none + // If encryption type is still set to NONE if (m_enc_type == iso_encryption_type::NONE) { // The 3k3y watermarks located at offset 0xF70: (D|E)ncrypted 3K BLD diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index bd5b11df2b..a0d66b3834 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -15,9 +15,9 @@ void unload_iso(); /* - Hijacked the "iso_archive::iso_archive" method to test if the ".iso" file is encrypted and sets a flag. The flag is set according to the first matching encryption type found following the order below: - - Redump type: ".dkey" or ".key" (as alternative) file, with the same name of the ".iso" file, - exists in the same folder of the ".iso" file - - 3k3y type: 3k3y watermark exists at offset 0xF70 + - Redump: ".dkey" or ".key" (as alternative) file, with the same name of the ".iso" file, + exists in the same folder of the ".iso" file + - 3k3y: 3k3y watermark exists at offset 0xF70 If the flag is set then the "iso_file::read" method will decrypt the data on the fly - Supported ISO encryption type: From c137beb4fcd4624837bde355dbebbaa119dee6bf Mon Sep 17 00:00:00 2001 From: digant73 Date: Tue, 7 Apr 2026 02:31:37 +0200 Subject: [PATCH 15/17] optimize partial sector read --- rpcs3/Loader/ISO.cpp | 248 ++++++++++++++++++++++++++----------------- rpcs3/Loader/ISO.h | 2 +- 2 files changed, 153 insertions(+), 97 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index cdd70fde23..0b1b140e91 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -16,10 +16,13 @@ constexpr u64 ISO_SECTOR_SIZE = 2048; struct iso_sector { - u64 archive_first_addr = 0; - u64 offset = 0; - u64 size = 0; - std::array buf {}; + u64 lba_address; + u64 offset; + u64 size; + u64 address_aligned; + u64 offset_aligned; + u64 size_aligned; + std::array buf; }; bool is_file_iso(const std::string& path) @@ -62,22 +65,85 @@ static void reset_iv(std::array& iv, u32 lba) iv[15] = (lba & 0x000000FF) >> 0; } -// Main function that will decrypt the sector(s) (needs to be a multiple of ISO_SECTOR_SIZE) -static void decrypt_data(aes_context& aes, unsigned char* data, u32 sector_count, u32 start_lba) +// Main function that will decrypt the sector(s) +static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u64 size) { - // Micro optimization, only ever used by "decrypt_data()" - std::array iv; - - for (u32 i = 0; i < sector_count; ++i) + if (size == 0) { - reset_iv(iv, start_lba + i); + return false; + } - if (aes_crypt_cbc(&aes, AES_DECRYPT, ISO_SECTOR_SIZE, iv.data(), &data[ISO_SECTOR_SIZE * i], &data[ISO_SECTOR_SIZE * i]) != 0) + //if ((size % 16) != 0) + //{ + // sys_log.error("decrypt_data(): Requested ciphertext blocks' size must be a multiple of 16 (%ull)", size); + // return; + //} + + u32 cur_sector_lba = static_cast(offset / ISO_SECTOR_SIZE); // First sector's LBA + const u32 sector_count = static_cast((offset + size - 1) / ISO_SECTOR_SIZE) - cur_sector_lba + 1; + const u32 sector_inner_count = sector_count > 2 ? sector_count - 2 : 0; // Remove first and last sector + const u64 sector_offset = offset % ISO_SECTOR_SIZE; + + std::array iv; + u64 dec_size; + u64 cur_offset; + u64 cur_size; + + // If the offset is not at the beginning of a sector, the first 16 bytes in the buffer + // represents the IV for decrypting the next data in the buffer. + // Otherwise, the IV is based on sector's LBA + if (sector_offset != 0) + { + memcpy(iv.data(), buffer, 16); + dec_size = cur_offset = 16; + } + else + { + reset_iv(iv, cur_sector_lba); + dec_size = cur_offset = 0; + } + + cur_size = sector_offset + size <= ISO_SECTOR_SIZE ? size : ISO_SECTOR_SIZE - sector_offset; + 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) + { + sys_log.error("decrypt_data(): Error decrypting data on first sector read"); + return false; + } + + dec_size += cur_size; + cur_offset += cur_size; + + // Inner sector(s), if any + for (u32 i = 0; i < sector_inner_count; i++) + { + 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) { - sys_log.error("decrypt_data(): Error decrypting data (decrypt_data() > aes_crypt_cbc())"); - return; + sys_log.error("decrypt_data(): Error decrypting data on inner sector(s) read"); + return false; + } + + dec_size += ISO_SECTOR_SIZE; + cur_offset += ISO_SECTOR_SIZE; + } + + // Partial (or even full) last sector, if any + if (dec_size < size) + { + reset_iv(iv, ++cur_sector_lba); // Next sector's IV + + if (aes_crypt_cbc(&aes, AES_DECRYPT, size - dec_size, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0) + { + sys_log.error("decrypt_data(): Error decrypting data on last sector read"); + return false; } } + + return true; } void iso_file_decryption::reset() @@ -184,7 +250,7 @@ bool iso_file_decryption::init(const std::string& path) } else { - hex_to_bytes(key, std::string_view(key_str, key_len), key_len); + hex_to_bytes(key, std::string_view(key_str, key_len), static_cast(key_len)); } if (aes_setkey_dec(&m_aes_dec, key, 128) == 0) @@ -309,23 +375,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, reinterpret_cast(buffer), - static_cast(size / ISO_SECTOR_SIZE), static_cast(offset / ISO_SECTOR_SIZE)); - - // TODO: Sanity check. - // We are assuming that reads always start at the beginning of a sector and that all reads will be - // multiples of ISO_SECTOR_SIZE - // - // NOTE: Both are easy fixes, but, code can be more simple + efficient if these two conditions are true - // (which they look to be from initial testing) - - /*if (size % ISO_SECTOR_SIZE != 0 || offset % ISO_SECTOR_SIZE != 0) - { - sys_log.error("decrypt(): %s: Encryption assumptions were not met, code needs to be updated, your game is probably about to crash - offset: 0x%lx, size: 0x%lx", - name, - static_cast(offset), - static_cast(size); - }*/ + decrypt_data(m_aes_dec, offset, reinterpret_cast(buffer), size); return true; } @@ -735,6 +785,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; } @@ -750,11 +801,12 @@ 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) { - u64 total_read = m_file.read_at(archive_first_offset, buffer, max_size); + total_read = m_file.read_at(archive_first_offset, buffer, max_size); if (size > total_read && (offset + total_read) < total_size) { @@ -768,46 +820,75 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) // IMPORTANT NOTE: // - // For performance reasons, current "iso_file_decryption::decrypt()" method requires that reads always - // start at the beginning of a sector and that all reads will be multiples of ISO_SECTOR_SIZE. - // Both are easy fixes, but, code can be more simple + efficient if these two conditions are true - // (which they look to be from initial testing) + // "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 // - // - // TODO: - // - // The local buffers storing the first and last sector of the reads could be replaced by caches. - // That will allow to avoid to read and decrypt again the entire sector on a further call to "read_at()" method - // if trying to read the previously not requested bytes (marked with "x" in the picture below) - // - // ------------------------------------------------------------------- - // file on ISO archive: | ' ' | - // ------------------------------------------------------------------- - // ' ' - // --------------------------------------- - // buffer: | | - // --------------------------------------- - // ' ' ' ' - // --------------------------------------------------------------------------------------------------------------- - // ISO archive: | sec 0 | sec 1 |xxx'#####|#########|#########|#########|#'xxxxxxx| | ... | sec n-1 | sec n | - // --------------------------------------------------------------------------------------------------------------- - // ' ' ' ' - // |first sec| |last sec | + // ---------------------------------------------------------------------- + // 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 archive_last_offset = archive_first_offset + max_size - 1; iso_sector first_sec, last_sec; + u64 offset_first_out; + u64 offset_aligned; + u64 offset_aligned_first_out; - first_sec.archive_first_addr = (archive_first_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; + 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 = archive_last_offset < (first_sec.archive_first_addr + ISO_SECTOR_SIZE) ? max_size : ISO_SECTOR_SIZE - first_sec.offset; - last_sec.archive_first_addr = (archive_last_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; - last_sec.offset = 0; + 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; last_sec.size = (archive_last_offset % ISO_SECTOR_SIZE) + 1; - u64 sec_count = (last_sec.archive_first_addr - first_sec.archive_first_addr) / ISO_SECTOR_SIZE + 1; - u64 sec_inner_size = (sec_count - 2) * ISO_SECTOR_SIZE; + offset_first_out = first_sec.offset + first_sec.size; + offset_aligned = first_sec.offset & ~0xF; + offset_aligned_first_out = (first_sec.offset + first_sec.size) & ~0xF; - if (m_file.read_at(first_sec.archive_first_addr, first_sec.buf.data(), ISO_SECTOR_SIZE) != ISO_SECTOR_SIZE) + 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; + + 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; + + u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1; + u64 sector_inner_size = sector_count > 2 ? (sector_count - 2) * ISO_SECTOR_SIZE : 0; + u64 expected_read; + + expected_read = first_sec.size_aligned; + total_read = m_file.read_at(first_sec.address_aligned, &first_sec.buf.data()[first_sec.offset_aligned], first_sec.size_aligned); + + if (sector_count > 2) // If inner sector(s) are present + { + expected_read += sector_inner_size; + total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast(buffer)[first_sec.size], sector_inner_size); + } + + if (sector_count > 1) // If last sector is present + { + expected_read += last_sec.size_aligned; + total_read += m_file.read_at(last_sec.address_aligned, last_sec.buf.data(), last_sec.size_aligned); + } + + if (total_read != expected_read) { sys_log.error("read_at(): %s: Error reading from file", m_meta.name); @@ -815,45 +896,20 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) return 0; } - // Decrypt read buffer (if needed) and copy to destination buffer - m_dec->decrypt(first_sec.archive_first_addr, first_sec.buf.data(), ISO_SECTOR_SIZE, m_meta.name); - memcpy(buffer, first_sec.buf.data() + first_sec.offset, first_sec.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); - // If the sector was already read, decrypted and copied to destination buffer, nothing more to do - if (sec_count < 2) + if (sector_count > 2) // If inner sector(s) are present { - return max_size; + m_dec->decrypt(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast(buffer)[first_sec.size], sector_inner_size, m_meta.name); } - if (m_file.read_at(last_sec.archive_first_addr, last_sec.buf.data(), ISO_SECTOR_SIZE) != ISO_SECTOR_SIZE) + if (sector_count > 1) // If last sector is present { - sys_log.error("read_at(): %s: Error reading from file", m_meta.name); - - seek(m_pos, fs::seek_set); - return 0; + 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); } - // Decrypt read buffer (if needed) and copy to destination buffer - m_dec->decrypt(last_sec.archive_first_addr, last_sec.buf.data(), ISO_SECTOR_SIZE, m_meta.name); - memcpy(reinterpret_cast(buffer) + max_size - last_sec.size, last_sec.buf.data(), last_sec.size); - - // If the sector was already read, decrypted and copied to destination buffer, nothing more to do - if (sec_count < 3) - { - return max_size; - } - - if (m_file.read_at(first_sec.archive_first_addr + ISO_SECTOR_SIZE, reinterpret_cast(buffer) + first_sec.size, sec_inner_size) != sec_inner_size) - { - sys_log.error("read_at(): %s: Error reading from file", m_meta.name); - - seek(m_pos, fs::seek_set); - return 0; - } - - // Decrypt read buffer (if needed) and copy to destination buffer - m_dec->decrypt(first_sec.archive_first_addr + ISO_SECTOR_SIZE, reinterpret_cast(buffer) + first_sec.size, sec_inner_size, m_meta.name); - return max_size; } diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index a0d66b3834..8a88e876bf 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -23,7 +23,7 @@ void unload_iso(); - Supported ISO encryption type: - Decrypted (.iso) - 3k3y (decrypted / encrypted) (.iso) - - Redump (encrypted) (.iso + .dkey/.key) + - Redump (encrypted) (.iso + .dkey / .key) - Unsupported ISO encryption type: - Encrypted split ISO files From 1a92a0f08e420f0209ec2ea619d56548b82ebbe7 Mon Sep 17 00:00:00 2001 From: digant73 Date: Tue, 7 Apr 2026 21:31:02 +0200 Subject: [PATCH 16/17] minor optimization --- rpcs3/Loader/ISO.cpp | 123 ++++++++++++++++++++++++------------------- 1 file changed, 70 insertions(+), 53 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 0b1b140e91..7ab6fea717 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -68,10 +68,13 @@ 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) { - if (size == 0) - { - return false; - } + // 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 + + //if (size == 0) + //{ + // return false; + //} //if ((size % 16) != 0) //{ @@ -81,11 +84,9 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 u32 cur_sector_lba = static_cast(offset / ISO_SECTOR_SIZE); // First sector's LBA const u32 sector_count = static_cast((offset + size - 1) / ISO_SECTOR_SIZE) - cur_sector_lba + 1; - const u32 sector_inner_count = sector_count > 2 ? sector_count - 2 : 0; // Remove first and last sector const u64 sector_offset = offset % ISO_SECTOR_SIZE; std::array iv; - u64 dec_size; u64 cur_offset; u64 cur_size; @@ -95,12 +96,12 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 if (sector_offset != 0) { memcpy(iv.data(), buffer, 16); - dec_size = cur_offset = 16; + cur_offset = 16; } else { reset_iv(iv, cur_sector_lba); - dec_size = cur_offset = 0; + cur_offset = 0; } cur_size = sector_offset + size <= ISO_SECTOR_SIZE ? size : ISO_SECTOR_SIZE - sector_offset; @@ -113,11 +114,17 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 return false; } - dec_size += cur_size; + if (sector_count < 2) // If no more sector(s) + { + return true; + } + cur_offset += cur_size; + const u32 inner_sector_count = sector_count > 2 ? sector_count - 2 : 0; // Remove first and last sector + // Inner sector(s), if any - for (u32 i = 0; i < sector_inner_count; i++) + for (u32 i = 0; i < inner_sector_count; i++) { reset_iv(iv, ++cur_sector_lba); // Next sector's IV @@ -127,20 +134,16 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 return false; } - dec_size += ISO_SECTOR_SIZE; cur_offset += ISO_SECTOR_SIZE; } - // Partial (or even full) last sector, if any - if (dec_size < size) - { - reset_iv(iv, ++cur_sector_lba); // Next sector's IV + reset_iv(iv, ++cur_sector_lba); // Next sector's IV - if (aes_crypt_cbc(&aes, AES_DECRYPT, size - dec_size, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0) - { - sys_log.error("decrypt_data(): Error decrypting data on last sector read"); - return false; - } + // 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) + { + sys_log.error("decrypt_data(): Error decrypting data on last sector read"); + return false; } return true; @@ -841,7 +844,6 @@ 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; - u64 offset_first_out; u64 offset_aligned; u64 offset_aligned_first_out; @@ -850,10 +852,13 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 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; + //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; - offset_first_out = first_sec.offset + first_sec.size; + // + // First sector + // + offset_aligned = first_sec.offset & ~0xF; offset_aligned_first_out = (first_sec.offset + first_sec.size) & ~0xF; @@ -863,32 +868,58 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) offset_aligned_first_out - first_sec.offset_aligned; first_sec.address_aligned = first_sec.lba_address + first_sec.offset_aligned; - offset_aligned_first_out = last_sec.size & ~0xF; + total_read = m_file.read_at(first_sec.address_aligned, &first_sec.buf.data()[first_sec.offset_aligned], first_sec.size_aligned); - last_sec.size_aligned = offset_aligned_first_out != last_sec.size ? - offset_aligned_first_out + 16 : - offset_aligned_first_out; + 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); u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1; - u64 sector_inner_size = sector_count > 2 ? (sector_count - 2) * ISO_SECTOR_SIZE : 0; - u64 expected_read; - expected_read = first_sec.size_aligned; - total_read = m_file.read_at(first_sec.address_aligned, &first_sec.buf.data()[first_sec.offset_aligned], first_sec.size_aligned); + if (sector_count < 2) // If no more sector(s) + { + if (total_read != first_sec.size_aligned) + { + sys_log.error("read_at(): %s: Error reading from file", m_meta.name); + + seek(m_pos, fs::seek_set); + return 0; + } + + return max_size; + } + + // + // Inner sector(s), if any + // + + u64 expected_inner_sector_read = 0; if (sector_count > 2) // If inner sector(s) are present { - expected_read += sector_inner_size; - total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast(buffer)[first_sec.size], sector_inner_size); + u64 inner_sector_size = expected_inner_sector_read = (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); } - if (sector_count > 1) // If last sector is present - { - expected_read += last_sec.size_aligned; - total_read += m_file.read_at(last_sec.address_aligned, last_sec.buf.data(), last_sec.size_aligned); - } + // + // Last sector + // - if (total_read != expected_read) + 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, 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); + + // + // 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 + expected_inner_sector_read) { sys_log.error("read_at(): %s: Error reading from file", m_meta.name); @@ -896,20 +927,6 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) return 0; } - 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); - - if (sector_count > 2) // If inner sector(s) are present - { - m_dec->decrypt(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast(buffer)[first_sec.size], sector_inner_size, m_meta.name); - } - - if (sector_count > 1) // If last sector is present - { - 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); - } - return max_size; } From 3f0c27f5ff5115038c3721bc9388dfd870b3accd Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:07:33 +0200 Subject: [PATCH 17/17] fix conflict --- rpcs3/Loader/ISO.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 721471834f..558dcc54ab 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -413,13 +413,8 @@ inline T retrieve_endian_int(const u8* buf) return out; } -<<<<<<< add_encrypted_iso // Assumed that directory entry is at file head -static std::optional iso_read_directory_entry(fs::file& file, bool names_in_ucs2 = false) -======= -// assumed that directory_entry is at file head static std::optional iso_read_directory_entry(fs::file& entry, bool names_in_ucs2 = false) ->>>>>>> master { const auto start_pos = entry.pos(); const u8 entry_length = entry.read();