This commit is contained in:
Antonino Di Guardo 2026-04-03 23:09:02 +00:00 committed by GitHub
commit 6c090ad9c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 568 additions and 59 deletions

View File

@ -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<u64>(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<unsigned char*>(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<unsigned char*>(buffer),
static_cast<u32>(size / ISO_SECTOR_SIZE), static_cast<u32>(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<unsigned long int>(offset),
static_cast<unsigned long int>(size);
}*/
return true;
}
}
sys_log.error("decrypt(): %s: LBA request wasn't in the 'm_region_info' for an encrypted iso? - RP: 0x%lx, RC: 0x%lx, LR: (0x%016lx - 0x%016lx)",
name,
offset,
static_cast<unsigned long int>(m_region_count),
static_cast<unsigned long int>(m_region_count > 0 ? m_region_info[m_region_count - 1].region_first_addr : 0),
static_cast<unsigned long int>(m_region_count > 0 ? m_region_info[m_region_count - 1].region_last_addr : 0));
return true;
}
template<typename T>
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_fs_metadata> iso_read_directory_entry(fs::file& file, bool names_in_ucs2 = false)
// Assumed that directory entry is at file head
static std::optional<iso_fs_metadata> iso_read_directory_entry(fs::file& file, bool names_in_ucs2 = false)
{
const auto start_pos = file.pos();
const u8 entry_length = file.read<u8>();
@ -145,49 +478,57 @@ std::optional<iso_fs_metadata> 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<usz> 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<iso_file_decryption>();
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<iso_file>(fs::file(m_path), *archive_file));
if (!archive_file)
{
return psf::registry();
}
const fs::file psf_file(std::make_unique<iso_file>(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_file_decryption> 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<u64, iso_extent_info> 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<u8*>(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<u8*>(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<u8*>(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<u8*>(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<u8*>(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<fs::file_base> iso_device::open(const std::string& path, bs_t<fs
return nullptr;
}
return std::make_unique<iso_file>(fs::file(iso_path, mode), *node);
return std::make_unique<iso_file>(fs::file(m_path, mode), m_archive.get_dec(), *node);
}
std::unique_ptr<fs::dir_base> iso_device::open_dir(const std::string& path)

View File

@ -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<iso_file_decryption> m_dec;
iso_fs_metadata m_meta;
u64 m_pos = 0;
std::pair<u64, iso_extent_info> 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_file_decryption> 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<iso_file_decryption> 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<iso_file_decryption> 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;