diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 0ddc3e39d9..f5300d716b 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,16 @@ LOG_CHANNEL(sys_log, "SYS"); +constexpr u64 ISO_SECTOR_SIZE = 2048; + +struct iso_sector +{ + u64 archive_first_addr = 0; + u64 offset = 0; + u64 size = 0; + std::array buf {}; +}; + bool is_file_iso(const std::string& path) { if (path.empty()) return false; @@ -34,7 +45,292 @@ 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) (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()" + std::array iv; + + 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.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; + } + } +} + +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 + // + + std::string key_path; + 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" + 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]; + + 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), 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 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 0xF70 data 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, 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_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 read_both_endian_int(fs::file& file) @@ -55,8 +351,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 +441,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 +524,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 +546,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 +644,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 +703,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 +715,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 +732,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() == iso_encryption_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.data(), 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.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) + { + return max_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); + + 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.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; } u64 iso_file::write(const void* /*buffer*/, u64 /*size*/) @@ -569,7 +983,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..f4e28e54cd 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,57 @@ 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 = 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 +90,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 +128,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;