#include "stdafx.h" #include "ISO.h" #include "Emu/VFS.h" #include "Crypto/utils.h" #include #include #include #include #include LOG_CHANNEL(sys_log, "SYS"); LOG_CHANNEL(iso_log, "ISO"); 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; if (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; 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'; } // 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) { iso_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) { iso_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) { iso_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; } // // 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) { iso_log.error("init: Failed to open file: %s", path); return false; } std::array sec0_sec1 {}; if (iso_file.size() < sec0_sec1.size()) { iso_log.error("init: Found only %ull sector(s) (minimum required is 2): %s", iso_file.size(), path); return false; } if (iso_file.read(sec0_sec1.data(), sec0_sec1.size()) != sec0_sec1.size()) { iso_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 const u32 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 { iso_log.error("init: Failed to read region information: '%s' (region_count=%d)", path, region_count); 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 { iso_log.error("init: Failed to process key file: %s", key_path); } } else { iso_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 { iso_log.error("init: Failed to set encryption type to ENC_3K3Y: %s", path); } } else if (memcmp(&k3k3y_dec_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_dec_watermark)) == 0) { m_enc_type = iso_encryption_type::DEC_3K3Y; // SET ENCRYPTION TYPE: DEC_3K3Y } } switch (m_enc_type) { case iso_encryption_type::REDUMP: iso_log.warning("init: Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_info.size(), path); break; case iso_encryption_type::ENC_3K3Y: iso_log.warning("init: Set 'enc type': ENC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); break; case iso_encryption_type::DEC_3K3Y: iso_log.warning("init: Set 'enc type': DEC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); break; case iso_encryption_type::NONE: // If encryption type was not set for any reason iso_log.warning("init: Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path); 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; } } iso_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) { T out {}; if constexpr (std::endian::little == std::endian::native) { // first half = little-endian copy std::memcpy(&out, buf, sizeof(T)); } else { // second half = big-endian copy std::memcpy(&out, buf + sizeof(T), sizeof(T)); } return out; } // 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(); const u8 entry_length = entry.read(); if (entry_length == 0) { return std::nullopt; } // Batch this set of file reads. This reduces overall time spent in iso_read_directory_entry by ~41% #pragma pack(push, 1) struct iso_entry_header { //u8 entry_length; // Handled separately u8 extended_attribute_length; u8 start_sector[8]; u8 file_size[8]; u8 year; u8 month; u8 day; u8 hour; u8 minute; u8 second; u8 timezone_value; u8 flags; u8 file_unit_size; u8 interleave; u8 volume_sequence_number[4]; u8 file_name_length; //u8 file_name[file_name_length]; // Handled separately }; #pragma pack(pop) static_assert(sizeof(iso_entry_header) == 32); const iso_entry_header header = entry.read(); const u32 start_sector = retrieve_endian_int(header.start_sector); const u32 file_size = retrieve_endian_int(header.file_size); std::tm file_date = {}; file_date.tm_year = header.year; file_date.tm_mon = header.month - 1; file_date.tm_mday = header.day; file_date.tm_hour = header.hour; file_date.tm_min = header.minute; file_date.tm_sec = header.second; const s16 timezone_value = header.timezone_value; const s16 timezone_offset = (timezone_value - 50) * 15 * 60; const std::time_t date_time = std::mktime(&file_date) + timezone_offset; // 2nd flag bit indicates whether a given fs node is a directory const bool is_directory = header.flags & 0b00000010; const bool has_more_extents = header.flags & 0b10000000; std::string file_name; entry.read(file_name, header.file_name_length); if (header.file_name_length == 1 && file_name[0] == 0) { file_name = "."; } else if (file_name == "\1") { file_name = ".."; } 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()); std::u16string utf16; utf16.resize(header.file_name_length / 2); for (usz i = 0; i < utf16.size(); ++i, raw++) { utf16[i] = *reinterpret_cast*>(raw); } file_name = utf16_to_utf8(utf16); } if (file_name.ends_with(";1")) { file_name.erase(file_name.end() - 2, file_name.end()); } if (header.file_name_length > 1 && file_name.ends_with(".")) { file_name.pop_back(); } // Skip the rest of the entry entry.seek(entry_length + start_pos); return iso_fs_metadata { .name = std::move(file_name), .time = date_time, .is_directory = is_directory, .has_multiple_extents = has_more_extents, .extents = { iso_extent_info { .start = start_sector, .size = file_size } } }; } 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; } std::vector multi_extent_node_indices; // 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_SECTOR_SIZE); while (file.pos() < end_pos) { auto entry = iso_read_directory_entry(file, use_ucs2_decoding); if (!entry) { 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 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 selected_node->metadata.extents.push_back(entry->extents[0]); extent_added = true; break; } } if (extent_added) { continue; } if (entry->has_multiple_extents) { // Haven't pushed entry to node.children yet so node.children::size() == entry_index multi_extent_node_indices.push_back(node.children.size()); } node.children.push_back(std::make_unique(iso_fs_node{ .metadata = std::move(*entry) })); } for (auto& child_node : node.children) { if (child_node->metadata.name != "." && child_node->metadata.name != "..") { iso_form_hierarchy(file, *child_node, use_ucs2_decoding, parent_path + "/" + node.metadata.name); } } } u64 iso_fs_metadata::size() const { u64 total_size = 0; for (const auto& extent : extents) { total_size += extent.size; } return total_size; } iso_archive::iso_archive(const std::string& path) { m_path = path; m_file = fs::file(path); m_dec = std::make_shared(); if (!m_dec->init(path)) { // Not ISO... TODO: throw something?? return; } u8 descriptor_type = -2; bool use_ucs2_decoding = false; do { const auto descriptor_start = m_file.pos(); descriptor_type = m_file.read(); // 1 = primary vol descriptor, 2 = joliet SVD if (descriptor_type == 1 || descriptor_type == 2) { use_ucs2_decoding = descriptor_type == 2; // Skip the rest of descriptor's data m_file.seek(155, fs::seek_cur); const auto node = iso_read_directory_entry(m_file, use_ucs2_decoding); if (node) { m_root = iso_fs_node { .metadata = node.value() }; } } m_file.seek(descriptor_start + ISO_SECTOR_SIZE); } while (descriptor_type != 255); iso_form_hierarchy(m_file, m_root, use_ucs2_decoding); } iso_fs_node* iso_archive::retrieve(const std::string& passed_path) { if (passed_path.empty()) return nullptr; const std::string path = std::filesystem::path(passed_path).string(); const std::string_view path_sv = path; usz start = 0; usz end = path_sv.find_first_of(fs::delim); std::stack search_stack; search_stack.push(&m_root); do { if (search_stack.empty()) return nullptr; const auto* top_entry = search_stack.top(); if (end == umax) { end = path.size(); } const std::string_view path_component = path_sv.substr(start, end-start); bool found = false; if (path_component == ".") { found = true; } else if (path_component == "..") { search_stack.pop(); found = true; } else { for (const auto& entry : top_entry->children) { if (entry->metadata.name == path_component) { search_stack.push(entry.get()); found = true; break; } } } 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; return search_stack.top(); } bool iso_archive::exists(const std::string& path) { return retrieve(path) != nullptr; } bool iso_archive::is_file(const std::string& path) { const auto file_node = retrieve(path); 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), 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), m_dec, *archive_file)); return psf::load_object(psf_file, path); } iso_file::iso_file(fs::file&& iso_handle, std::shared_ptr iso_dec, const iso_fs_node& node) : m_file(std::move(iso_handle)), m_dec(iso_dec), m_meta(node.metadata) { m_file.seek(node.metadata.extents[0].start * ISO_SECTOR_SIZE); } fs::stat_t iso_file::get_stat() { return fs::stat_t { .is_directory = false, .is_symlink = false, .is_writable = false, .size = size(), .atime = m_meta.time, .mtime = m_meta.time, .ctime = m_meta.time }; } bool iso_file::trunc(u64 /*length*/) { fs::g_tls_error = fs::error::readonly; return false; } std::pair iso_file::get_extent_pos(u64 pos) const { ensure(!m_meta.extents.empty()); auto it = m_meta.extents.begin(); while (pos >= it->size && it != m_meta.extents.end() - 1) { pos -= it->size; it++; } return {pos, *it}; } u64 iso_file::local_extent_remaining(u64 pos) const { const auto [local_pos, extent] = get_extent_pos(pos); return extent.size - local_pos; } 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 max_size = std::min(size, local_extent_remaining(offset)); if (max_size == 0) { return 0; } const u64 archive_first_offset = file_offset(offset); const u64 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) { iso_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) { iso_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*/) { fs::g_tls_error = fs::error::readonly; return 0; } u64 iso_file::seek(s64 offset, fs::seek_mode whence) { const s64 total_size = size(); const s64 new_pos = whence == fs::seek_set ? offset : whence == fs::seek_cur ? offset + m_pos : whence == fs::seek_end ? offset + total_size : -1; if (new_pos < 0) { fs::g_tls_error = fs::error::inval; return -1; } const u64 result = m_file.seek(file_offset(m_pos)); if (result == umax) return umax; m_pos = new_pos; return m_pos; } u64 iso_file::size() { u64 extent_sizes = 0; for (const auto& extent : m_meta.extents) { extent_sizes += extent.size; } return extent_sizes; } void iso_file::release() { m_file.release(); } bool iso_dir::read(fs::dir_entry& entry) { if (m_pos < m_node.children.size()) { const auto& selected = m_node.children[m_pos].get()->metadata; entry.name = selected.name; entry.atime = selected.time; entry.mtime = selected.time; entry.ctime = selected.time; entry.is_directory = selected.is_directory; entry.is_symlink = false; entry.is_writable = false; entry.size = selected.size(); m_pos++; return true; } return false; } 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; return false; } const auto& meta = node->metadata; info = fs::stat_t { .is_directory = meta.is_directory, .is_symlink = false, .is_writable = false, .size = meta.size(), .atime = meta.time, .mtime = meta.time, .ctime = meta.time }; return true; } 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(); info = fs::device_stat { .block_size = size, .total_size = size, .total_free = 0, .avail_free = 0 }; 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); if (!node) { fs::g_tls_error = fs::error::noent; return nullptr; } if (node->metadata.is_directory) { fs::g_tls_error = fs::error::isdir; return nullptr; } 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; return nullptr; } if (!node->metadata.is_directory) { // fs::dir::open -> ::readdir should return ENOTDIR when path is pointing to a file instead of a folder. fs::g_tls_error = fs::error::notdir; return nullptr; } 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); fs::set_virtual_device("iso_overlay_fs_dev", stx::make_shared(path)); vfs::mount("/dev_bdvd/"sv, iso_device::virtual_device_name + "/"); } void unload_iso() { sys_log.notice("Unloading ISO"); fs::set_virtual_device("iso_overlay_fs_dev", stx::shared_ptr()); }