mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-04-09 02:51:31 -06:00
Merge c137beb4fc into 34c26eff68
This commit is contained in:
commit
29f4e08ac7
@ -2,6 +2,7 @@
|
||||
|
||||
#include "ISO.h"
|
||||
#include "Emu/VFS.h"
|
||||
#include "Crypto/utils.h"
|
||||
|
||||
#include <codecvt>
|
||||
#include <algorithm>
|
||||
@ -11,6 +12,19 @@
|
||||
|
||||
LOG_CHANNEL(sys_log, "SYS");
|
||||
|
||||
constexpr u64 ISO_SECTOR_SIZE = 2048;
|
||||
|
||||
struct iso_sector
|
||||
{
|
||||
u64 lba_address;
|
||||
u64 offset;
|
||||
u64 size;
|
||||
u64 address_aligned;
|
||||
u64 offset_aligned;
|
||||
u64 size_aligned;
|
||||
std::array<u8, ISO_SECTOR_SIZE> buf;
|
||||
};
|
||||
|
||||
bool is_file_iso(const std::string& path)
|
||||
{
|
||||
if (path.empty()) return false;
|
||||
@ -34,7 +48,348 @@ 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<u8, 16>& 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)
|
||||
{
|
||||
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<u32>(offset / ISO_SECTOR_SIZE); // First sector's LBA
|
||||
const u32 sector_count = static_cast<u32>((offset + size - 1) / ISO_SECTOR_SIZE) - cur_sector_lba + 1;
|
||||
const u32 sector_inner_count = sector_count > 2 ? sector_count - 2 : 0; // Remove first and last sector
|
||||
const u64 sector_offset = offset % ISO_SECTOR_SIZE;
|
||||
|
||||
std::array<u8, 16> iv;
|
||||
u64 dec_size;
|
||||
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);
|
||||
dec_size = cur_offset = 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
reset_iv(iv, cur_sector_lba);
|
||||
dec_size = cur_offset = 0;
|
||||
}
|
||||
|
||||
cur_size = sector_offset + size <= ISO_SECTOR_SIZE ? size : ISO_SECTOR_SIZE - sector_offset;
|
||||
cur_size -= cur_offset;
|
||||
|
||||
// Partial (or even full) first sector
|
||||
if (aes_crypt_cbc(&aes, AES_DECRYPT, cur_size, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0)
|
||||
{
|
||||
sys_log.error("decrypt_data(): Error decrypting data on first sector read");
|
||||
return false;
|
||||
}
|
||||
|
||||
dec_size += cur_size;
|
||||
cur_offset += cur_size;
|
||||
|
||||
// Inner sector(s), if any
|
||||
for (u32 i = 0; i < sector_inner_count; i++)
|
||||
{
|
||||
reset_iv(iv, ++cur_sector_lba); // Next sector's IV
|
||||
|
||||
if (aes_crypt_cbc(&aes, AES_DECRYPT, ISO_SECTOR_SIZE, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0)
|
||||
{
|
||||
sys_log.error("decrypt_data(): Error decrypting data on inner sector(s) read");
|
||||
return false;
|
||||
}
|
||||
|
||||
dec_size += ISO_SECTOR_SIZE;
|
||||
cur_offset += ISO_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
// Partial (or even full) last sector, if any
|
||||
if (dec_size < size)
|
||||
{
|
||||
reset_iv(iv, ++cur_sector_lba); // Next sector's IV
|
||||
|
||||
if (aes_crypt_cbc(&aes, AES_DECRYPT, size - dec_size, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0)
|
||||
{
|
||||
sys_log.error("decrypt_data(): Error decrypting data on last sector read");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void iso_file_decryption::reset()
|
||||
{
|
||||
m_enc_type = iso_encryption_type::NONE;
|
||||
m_region_info.clear();
|
||||
}
|
||||
|
||||
bool iso_file_decryption::init(const std::string& path)
|
||||
{
|
||||
reset();
|
||||
|
||||
if (!is_file_iso(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<u8, ISO_SECTOR_SIZE * 2> 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<u64>(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<unsigned int>(key_len));
|
||||
}
|
||||
|
||||
if (aes_setkey_dec(&m_aes_dec, key, 128) == 0)
|
||||
{
|
||||
m_enc_type = iso_encryption_type::REDUMP; // SET ENCRYPTION TYPE: REDUMP
|
||||
}
|
||||
}
|
||||
|
||||
if (m_enc_type == iso_encryption_type::NONE) // If encryption type was not set to REDUMP for any reason
|
||||
{
|
||||
sys_log.error("init(): Failed to process key file: %s", key_path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sys_log.warning("init(): Failed to open, or missing, key file: %s", key_path);
|
||||
}
|
||||
|
||||
//
|
||||
// Check for 3k3y type
|
||||
//
|
||||
|
||||
// If encryption type is still set to NONE
|
||||
if (m_enc_type == iso_encryption_type::NONE)
|
||||
{
|
||||
// The 3k3y watermarks located at offset 0xF70: (D|E)ncrypted 3K BLD
|
||||
static const unsigned char k3k3y_enc_watermark[16] =
|
||||
{0x45, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x33, 0x4B, 0x20, 0x42, 0x4C, 0x44};
|
||||
static const unsigned char k3k3y_dec_watermark[16] =
|
||||
{0x44, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x33, 0x4B, 0x20, 0x42, 0x4C, 0x44};
|
||||
|
||||
if (memcmp(&k3k3y_enc_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_enc_watermark)) == 0)
|
||||
{
|
||||
// Grab D1 from the 3k3y sector
|
||||
unsigned char key[16];
|
||||
|
||||
memcpy(key, &sec0_sec1[0xF80], 0x10);
|
||||
|
||||
// Convert D1 to KEY and generate the "m_aes_dec" context
|
||||
unsigned char key_d1[] = {0x38, 11, 0xcf, 11, 0x53, 0x45, 0x5b, 60, 120, 0x17, 0xab, 0x4f, 0xa3, 0xba, 0x90, 0xed};
|
||||
unsigned char iv_d1[] = {0x69, 0x47, 0x47, 0x72, 0xaf, 0x6f, 0xda, 0xb3, 0x42, 0x74, 0x3a, 0xef, 170, 0x18, 0x62, 0x87};
|
||||
|
||||
aes_context aes_d1;
|
||||
|
||||
if (aes_setkey_enc(&aes_d1, key_d1, 128) == 0)
|
||||
{
|
||||
if (aes_crypt_cbc(&aes_d1, AES_ENCRYPT, 16, &iv_d1[0], key, key) == 0)
|
||||
{
|
||||
if (aes_setkey_dec(&m_aes_dec, key, 128) == 0)
|
||||
{
|
||||
m_enc_type = iso_encryption_type::ENC_3K3Y; // SET ENCRYPTION TYPE: ENC_3K3Y
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_enc_type == iso_encryption_type::NONE) // If encryption type was not set to ENC_3K3Y for any reason
|
||||
{
|
||||
sys_log.error("init(): Failed to set encryption type to ENC_3K3Y: %s", path);
|
||||
}
|
||||
}
|
||||
else if (memcmp(&k3k3y_dec_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_dec_watermark)) == 0)
|
||||
{
|
||||
m_enc_type = iso_encryption_type::DEC_3K3Y; // SET ENCRYPTION TYPE: DEC_3K3Y
|
||||
}
|
||||
}
|
||||
|
||||
switch (m_enc_type)
|
||||
{
|
||||
case iso_encryption_type::REDUMP:
|
||||
sys_log.warning("init(): Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_info.size(), path);
|
||||
break;
|
||||
case iso_encryption_type::ENC_3K3Y:
|
||||
sys_log.warning("init(): Set 'enc type': ENC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path);
|
||||
break;
|
||||
case iso_encryption_type::DEC_3K3Y:
|
||||
sys_log.warning("init(): Set 'enc type': DEC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path);
|
||||
break;
|
||||
case iso_encryption_type::NONE: // If encryption type was not set for any reason
|
||||
sys_log.warning("init(): Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std::string& name)
|
||||
{
|
||||
// If it's a non-encrypted type, nothing more to do
|
||||
if (m_enc_type == iso_encryption_type::NONE)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If it's a 3k3y ISO and data at offset 0xF70 is being requested, we should null it out
|
||||
if (m_enc_type == iso_encryption_type::DEC_3K3Y || m_enc_type == iso_encryption_type::ENC_3K3Y)
|
||||
{
|
||||
if (offset + size >= 0xF70ULL && offset <= 0x1070ULL)
|
||||
{
|
||||
// Zero out the 0xF70 - 0x1070 overlap
|
||||
unsigned char* buf = reinterpret_cast<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 == 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<unsigned char*>(buffer), size);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
sys_log.error("decrypt(): %s: LBA request wasn't in the 'm_region_info' for an encrypted ISO? - RP: 0x%lx, RC: 0x%lx, LR: (0x%016lx - 0x%016lx)",
|
||||
name,
|
||||
offset,
|
||||
static_cast<unsigned long int>(m_region_info.size()),
|
||||
static_cast<unsigned long int>(!m_region_info.empty() ? m_region_info.back().region_first_addr : 0),
|
||||
static_cast<unsigned long int>(!m_region_info.empty() ? m_region_info.back().region_last_addr : 0));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline T read_both_endian_int(fs::file& file)
|
||||
@ -55,8 +410,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 +500,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 +583,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 +605,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 +703,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 +762,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,29 +774,143 @@ u64 iso_file::local_extent_size(u64 pos) const
|
||||
return get_extent_pos(pos).second.size;
|
||||
}
|
||||
|
||||
// Assumed valid and in bounds
|
||||
u64 iso_file::file_offset(u64 pos) const
|
||||
{
|
||||
const auto [local_pos, extent] = get_extent_pos(pos);
|
||||
|
||||
return (extent.start * ISO_SECTOR_SIZE) + local_pos;
|
||||
}
|
||||
|
||||
u64 iso_file::read(void* buffer, u64 size)
|
||||
{
|
||||
const auto r = read_at(m_pos, buffer, size);
|
||||
|
||||
m_pos += r;
|
||||
return r;
|
||||
}
|
||||
|
||||
u64 iso_file::read_at(u64 offset, void* buffer, u64 size)
|
||||
{
|
||||
const u64 local_remaining = local_extent_remaining(offset);
|
||||
const u64 max_size = std::min(size, local_extent_remaining(offset));
|
||||
|
||||
const u64 total_read = m_file.read_at(file_offset(offset), buffer, std::min(size, local_remaining));
|
||||
|
||||
const auto total_size = this->size();
|
||||
|
||||
if (size > total_read && (offset + total_read) < total_size)
|
||||
if (max_size == 0)
|
||||
{
|
||||
const u64 second_total_read = read_at(offset + total_read, reinterpret_cast<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();
|
||||
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<u8*>(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_first_out;
|
||||
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;
|
||||
last_sec.size = (archive_last_offset % ISO_SECTOR_SIZE) + 1;
|
||||
|
||||
offset_first_out = first_sec.offset + first_sec.size;
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1;
|
||||
u64 sector_inner_size = sector_count > 2 ? (sector_count - 2) * ISO_SECTOR_SIZE : 0;
|
||||
u64 expected_read;
|
||||
|
||||
expected_read = first_sec.size_aligned;
|
||||
total_read = m_file.read_at(first_sec.address_aligned, &first_sec.buf.data()[first_sec.offset_aligned], first_sec.size_aligned);
|
||||
|
||||
if (sector_count > 2) // If inner sector(s) are present
|
||||
{
|
||||
expected_read += sector_inner_size;
|
||||
total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast<u8*>(buffer)[first_sec.size], sector_inner_size);
|
||||
}
|
||||
|
||||
if (sector_count > 1) // If last sector is present
|
||||
{
|
||||
expected_read += last_sec.size_aligned;
|
||||
total_read += m_file.read_at(last_sec.address_aligned, last_sec.buf.data(), last_sec.size_aligned);
|
||||
}
|
||||
|
||||
if (total_read != expected_read)
|
||||
{
|
||||
sys_log.error("read_at(): %s: Error reading from file", m_meta.name);
|
||||
|
||||
seek(m_pos, fs::seek_set);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (sector_count > 2) // If inner sector(s) are present
|
||||
{
|
||||
m_dec->decrypt(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast<u8*>(buffer)[first_sec.size], sector_inner_size, m_meta.name);
|
||||
}
|
||||
|
||||
if (sector_count > 1) // If last sector is present
|
||||
{
|
||||
m_dec->decrypt(last_sec.address_aligned, last_sec.buf.data(), last_sec.size_aligned, m_meta.name);
|
||||
memcpy(&reinterpret_cast<u8*>(buffer)[max_size - last_sec.size], last_sec.buf.data(), last_sec.size);
|
||||
}
|
||||
|
||||
return max_size;
|
||||
}
|
||||
|
||||
u64 iso_file::write(const void* /*buffer*/, u64 /*size*/)
|
||||
@ -569,7 +1048,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)
|
||||
@ -600,7 +1079,7 @@ void iso_dir::rewind()
|
||||
|
||||
void load_iso(const std::string& path)
|
||||
{
|
||||
sys_log.notice("Loading iso '%s'", path);
|
||||
sys_log.notice("Loading ISO '%s'", path);
|
||||
|
||||
fs::set_virtual_device("iso_overlay_fs_dev", stx::make_shared<iso_device>(path));
|
||||
|
||||
@ -609,7 +1088,7 @@ void load_iso(const std::string& path)
|
||||
|
||||
void unload_iso()
|
||||
{
|
||||
sys_log.notice("Unloading iso");
|
||||
sys_log.notice("Unloading ISO");
|
||||
|
||||
fs::set_virtual_device("iso_overlay_fs_dev", stx::shared_ptr<iso_device>());
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
#include "Utilities/File.h"
|
||||
#include "util/types.hpp"
|
||||
#include "Crypto/aes.h"
|
||||
|
||||
bool is_file_iso(const std::string& path);
|
||||
bool is_file_iso(const fs::file& path);
|
||||
@ -11,6 +12,58 @@ bool is_file_iso(const fs::file& path);
|
||||
void load_iso(const std::string& path);
|
||||
void unload_iso();
|
||||
|
||||
/*
|
||||
- Hijacked the "iso_archive::iso_archive" method to test if the ".iso" file is encrypted and sets a flag.
|
||||
The flag is set according to the first matching encryption type found following the order below:
|
||||
- Redump: ".dkey" or ".key" (as alternative) file, with the same name of the ".iso" file,
|
||||
exists in the same folder of the ".iso" file
|
||||
- 3k3y: 3k3y watermark exists at offset 0xF70
|
||||
If the flag is set then the "iso_file::read" method will decrypt the data on the fly
|
||||
|
||||
- Supported ISO encryption type:
|
||||
- Decrypted (.iso)
|
||||
- 3k3y (decrypted / encrypted) (.iso)
|
||||
- Redump (encrypted) (.iso + .dkey / .key)
|
||||
|
||||
- Unsupported ISO encryption type:
|
||||
- Encrypted split ISO files
|
||||
*/
|
||||
|
||||
// Struct to store ISO region information (storing addresses instead of LBA since we need to compare
|
||||
// the address anyway, so would have to multiply or divide every read if storing LBA)
|
||||
struct iso_region_info
|
||||
{
|
||||
bool encrypted = false;
|
||||
u64 region_first_addr = 0;
|
||||
u64 region_last_addr = 0;
|
||||
};
|
||||
|
||||
// Enum to decide ISO encryption type
|
||||
enum class iso_encryption_type
|
||||
{
|
||||
NONE,
|
||||
DEC_3K3Y,
|
||||
ENC_3K3Y,
|
||||
REDUMP
|
||||
};
|
||||
|
||||
// ISO file decryption class
|
||||
class iso_file_decryption
|
||||
{
|
||||
private:
|
||||
aes_context m_aes_dec;
|
||||
iso_encryption_type m_enc_type = iso_encryption_type::NONE;
|
||||
std::vector<iso_region_info> m_region_info;
|
||||
|
||||
void reset();
|
||||
|
||||
public:
|
||||
iso_encryption_type get_enc_type() const { return m_enc_type; }
|
||||
|
||||
bool init(const std::string& path);
|
||||
bool decrypt(u64 offset, void* buffer, u64 size, const std::string& name);
|
||||
};
|
||||
|
||||
struct iso_extent_info
|
||||
{
|
||||
u64 start = 0;
|
||||
@ -38,16 +91,17 @@ class iso_file : public fs::file_base
|
||||
{
|
||||
private:
|
||||
fs::file m_file;
|
||||
iso_fs_metadata m_meta {};
|
||||
std::shared_ptr<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 +129,47 @@ public:
|
||||
void rewind() override;
|
||||
};
|
||||
|
||||
// represents the .iso file itself.
|
||||
// Represents the .iso file itself
|
||||
class iso_archive
|
||||
{
|
||||
private:
|
||||
std::string m_path;
|
||||
iso_fs_node m_root {};
|
||||
fs::file m_file;
|
||||
std::shared_ptr<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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user