diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 544b04d379..558dcc54ab 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 @@ -11,6 +12,19 @@ LOG_CHANNEL(sys_log, "SYS"); +constexpr u64 ISO_SECTOR_SIZE = 2048; + +struct iso_sector +{ + 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) { if (path.empty()) return false; @@ -34,7 +48,351 @@ bool is_file_iso(const fs::file& file) && magic[4] == '1'; } -const int ISO_BLOCK_SIZE = 2048; +// Convert 4 bytes in big-endian format to an unsigned integer +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(std::array& iv, u32 lba) +{ + memset(iv.data(), 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) +static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u64 size) +{ + // The following preliminary checks are good to be provided. + // Commented out to gain a bit of performance, just because we know the caller is providing values in the expected range + + //if (size == 0) + //{ + // return false; + //} + + //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 u64 sector_offset = offset % ISO_SECTOR_SIZE; + + std::array iv; + 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); + cur_offset = 16; + } + else + { + reset_iv(iv, cur_sector_lba); + 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; + } + + 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 < inner_sector_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 on inner sector(s) read"); + return false; + } + + cur_offset += ISO_SECTOR_SIZE; + } + + reset_iv(iv, ++cur_sector_lba); // Next sector's IV + + // Partial (or even full) last sector + if (aes_crypt_cbc(&aes, AES_DECRYPT, size - cur_offset, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0) + { + sys_log.error("decrypt_data(): Error decrypting data on last sector read"); + return false; + } + + return true; +} + +void iso_file_decryption::reset() +{ + m_enc_type = iso_encryption_type::NONE; + m_region_info.clear(); +} + +bool iso_file_decryption::init(const std::string& path) +{ + reset(); + + if (!is_file_iso(path)) + { + return false; + } + + std::array sec0_sec1 {}; + + // + // Store the ISO region information (needed by both the "Redump" type (only on "decrypt()" method) and "3k3y" type) + // + + fs::file iso_file(path); + + if (!iso_file) + { + sys_log.error("init(): Failed to open file: %s", path); + return false; + } + + 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 + // + + 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 (key_file) + { + char key_str[32]; + unsigned char key[16]; + + const u64 key_len = key_file.read(key_str, sizeof(key_str)); + + 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 + { + 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) + { + m_enc_type = iso_encryption_type::REDUMP; // SET ENCRYPTION TYPE: REDUMP + } + } + + 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); + } + } + 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 + if (m_enc_type == iso_encryption_type::NONE) + { + // 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] = + {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 = iso_encryption_type::ENC_3K3Y; // SET ENCRYPTION TYPE: ENC_3K3Y + } + } + } + + 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 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::DEC_3K3Y; // SET ENCRYPTION TYPE: DEC_3K3Y + } + } + + switch (m_enc_type) + { + 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_3K3Y: + sys_log.warning("init(): Set 'enc type': ENC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); + break; + case iso_encryption_type::DEC_3K3Y: + sys_log.warning("init(): Set 'enc type': DEC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); + break; + case iso_encryption_type::NONE: // If encryption type was not set for any reason + sys_log.warning("init(): Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path); + break; + } + + 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 == iso_encryption_type::NONE) + { + return true; + } + + // 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) + { + // Zero out the 0xF70 - 0x1070 overlap + unsigned char* buf = reinterpret_cast(buffer); + unsigned char* buf_overlap_start = offset < 0xF70ULL ? buf + 0xF70ULL - offset : 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 + if (m_enc_type == iso_encryption_type::DEC_3K3Y) + { + return true; + } + } + + // If it's an encrypted type, check if the request lies in an encrypted range + for (const iso_region_info& info : m_region_info) + { + if (offset >= info.region_first_addr && offset <= info.region_last_addr) + { + // We found the region, decrypt if needed + if (!info.encrypted) + { + return true; + } + + // Decrypt the region before sending it back + decrypt_data(m_aes_dec, offset, reinterpret_cast(buffer), 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, + 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; +} template inline T retrieve_endian_int(const u8* buf) @@ -55,7 +413,7 @@ inline T retrieve_endian_int(const u8* buf) return out; } -// assumed that directory_entry is at file head +// Assumed that directory entry is at file head static std::optional iso_read_directory_entry(fs::file& entry, bool names_in_ucs2 = false) { const auto start_pos = entry.pos(); @@ -165,38 +523,43 @@ static std::optional iso_read_directory_entry(fs::file& entry, }; } -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.start * ISO_SECTOR_SIZE) + directory_extent.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; @@ -204,11 +567,14 @@ void iso_form_hierarchy(fs::file& file, iso_fs_node& node, bool use_ucs2_decodin } } - 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()); } @@ -241,10 +607,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; } @@ -262,16 +629,21 @@ 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 + 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_BLOCK_SIZE); + m_file.seek(descriptor_start + ISO_SECTOR_SIZE); } while (descriptor_type != 255); @@ -355,23 +727,27 @@ bool iso_archive::is_file(const std::string& path) iso_file iso_archive::open(const std::string& path) { - return iso_file(fs::file(m_path), *ensure(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(node.metadata.extents[0].start * ISO_SECTOR_SIZE); } fs::stat_t iso_file::get_stat() @@ -410,14 +786,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); @@ -430,29 +798,157 @@ 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(); + u64 total_read; + + // If it's a non-encrypted type + if (m_dec->get_enc_type() == iso_encryption_type::NONE) + { + 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: + // + // "iso_file_decryption::decrypt()" method requires that offset and size are multiple of 16 bytes + // (ciphertext block's size) and that a previous ciphertext block (used as IV) is read in case + // offset is not a multiple of ISO_SECTOR_SIZE + // + // ---------------------------------------------------------------------- + // file on ISO archive: | ' ' | + // ---------------------------------------------------------------------- + // ' ' + // --------------------------------------------- + // buffer: | | + // --------------------------------------------- + // ' ' ' ' + // ------------------------------------------------------------------------------------------------------------------------------------- + // ISO archive: | sec 0 | sec 1 |xxxxx######'###########'###########'###########'##xxxxxxxxx| | ... | sec n-1 | sec n | + // ------------------------------------------------------------------------------------------------------------------------------------- + // 16 Bytes x block read: | | | | | | | '#######'###########'###########'###########'###| | | | | | | | | | | | | | | + // ' ' ' ' + // | first sec | inner sec(s) | last sec | + + const u64 archive_last_offset = archive_first_offset + max_size - 1; + iso_sector first_sec, last_sec; + u64 offset_aligned; + u64 offset_aligned_first_out; + + first_sec.lba_address = (archive_first_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; + first_sec.offset = archive_first_offset % ISO_SECTOR_SIZE; + first_sec.size = first_sec.offset + max_size <= ISO_SECTOR_SIZE ? max_size : ISO_SECTOR_SIZE - first_sec.offset; + + last_sec.lba_address = last_sec.address_aligned = (archive_last_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; + //last_sec.offset = last_sec.offset_aligned = 0; // Always 0 so no need to set and use those attributes + last_sec.size = (archive_last_offset % ISO_SECTOR_SIZE) + 1; + + // + // First sector + // + + offset_aligned = first_sec.offset & ~0xF; + offset_aligned_first_out = (first_sec.offset + first_sec.size) & ~0xF; + + first_sec.offset_aligned = offset_aligned != 0 ? offset_aligned - 16 : 0; // Eventually include the previous block (used as IV) + first_sec.size_aligned = offset_aligned_first_out != (first_sec.offset + first_sec.size) ? + offset_aligned_first_out + 16 - first_sec.offset_aligned : + offset_aligned_first_out - first_sec.offset_aligned; + first_sec.address_aligned = first_sec.lba_address + first_sec.offset_aligned; + + total_read = m_file.read_at(first_sec.address_aligned, &first_sec.buf.data()[first_sec.offset_aligned], first_sec.size_aligned); + + 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; + + 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 + { + 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); + } + + // + // Last sector + // + + offset_aligned_first_out = last_sec.size & ~0xF; + last_sec.size_aligned = offset_aligned_first_out != last_sec.size ? offset_aligned_first_out + 16 : offset_aligned_first_out; + + total_read += m_file.read_at(last_sec.address_aligned, 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); + + seek(m_pos, fs::seek_set); + return 0; + } + + return max_size; } u64 iso_file::write(const void* /*buffer*/, u64 /*size*/) @@ -590,7 +1086,7 @@ 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) @@ -621,7 +1117,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)); @@ -630,7 +1126,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 2665ec29e5..8a88e876bf 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -4,6 +4,7 @@ #include "Utilities/File.h" #include "util/types.hpp" +#include "Crypto/aes.h" bool is_file_iso(const std::string& path); bool is_file_iso(const fs::file& path); @@ -11,6 +12,58 @@ 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 found following the order below: + - 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: + - Decrypted (.iso) + - 3k3y (decrypted / encrypted) (.iso) + - Redump (encrypted) (.iso + .dkey / .key) + +- Unsupported ISO encryption type: + - Encrypted split ISO files +*/ + +// 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; + u64 region_first_addr = 0; + u64 region_last_addr = 0; +}; + +// Enum to decide ISO encryption type +enum class iso_encryption_type +{ + NONE, + DEC_3K3Y, + ENC_3K3Y, + REDUMP +}; + +// ISO file decryption class +class iso_file_decryption +{ +private: + aes_context m_aes_dec; + iso_encryption_type m_enc_type = iso_encryption_type::NONE; + std::vector m_region_info; + + void reset(); + +public: + 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 +91,17 @@ class iso_file : public fs::file_base { private: fs::file m_file; - iso_fs_metadata m_meta {}; + 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 +129,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;