diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 0ddc3e39d9..26a05cb4f4 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -11,6 +11,16 @@ LOG_CHANNEL(sys_log, "SYS"); +const u64 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()) return false; @@ -34,7 +44,330 @@ bool is_file_iso(const fs::file& file) && magic[4] == '1'; } -const int ISO_BLOCK_SIZE = 2048; +// 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') + 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 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()" + 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() >= 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 ? 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; + } + } + 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 >= 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 == 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, + static_cast(offset), + static_cast(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_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; +} template inline T read_both_endian_int(fs::file& file) @@ -55,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(); @@ -145,49 +478,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.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; } } - 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()); } @@ -220,10 +561,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,16 +583,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); @@ -334,23 +681,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() @@ -389,14 +740,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,6 +752,14 @@ 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); @@ -418,20 +769,120 @@ u64 iso_file::read(void* buffer, u64 size) 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: + // + // 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: + // + // 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 | + + 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*/) @@ -569,7 +1020,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) diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index 2665ec29e5..605f138428 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,60 @@ 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 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 + +- 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 +}; + +// 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 +93,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 +131,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;