From a1a140db91ea6617aa85efe2c9111c6a79c59186 Mon Sep 17 00:00:00 2001 From: oltolm Date: Sun, 5 Apr 2026 19:06:01 +0200 Subject: [PATCH 1/6] CPUThread: fix ASAN use-after-free --- rpcs3/Emu/CPU/CPUThread.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index 4bd5fc9157..8eddff9e0b 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -729,8 +729,14 @@ void cpu_thread::operator()() { if (_this) { - sys_log.warning("CPU Thread '%s' terminated abnormally!", name); cleanup(); + + auto log_thread = named_thread("CPU Thread Cleanup Logger", [name = name]() + { + sys_log.warning("CPU Thread '%s' terminated abnormally!", name); + }); + + log_thread(); } } } cleanup; From 8121bd443ca355ab89b894099c19dc02ac535f9d Mon Sep 17 00:00:00 2001 From: Ani Date: Thu, 9 Apr 2026 22:40:19 +0200 Subject: [PATCH 2/6] ppu: Enable vector NaN fixup by default --- rpcs3/Emu/system_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 9dbaa0c723..7a84455dae 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -69,7 +69,7 @@ struct cfg_root : cfg::node cfg::_bool use_accurate_dfma{ this, "Use Accurate DFMA", true }; // Enable accurate double-precision FMA for CPUs which do not support it natively cfg::_bool ppu_set_sat_bit{ this, "PPU Set Saturation Bit", false }; // Accuracy. If unset, completely disable saturation flag handling. cfg::_bool ppu_use_nj_bit{ this, "PPU Accurate Non-Java Mode", false }; // Accuracy. If set, accurately emulate NJ flag. Implies NJ fixup. - cfg::_bool ppu_fix_vnan{ this, "PPU Fixup Vector NaN Values", false }; // Accuracy. Partial. + cfg::_bool ppu_fix_vnan{ this, "PPU Fixup Vector NaN Values", true }; // Accuracy. Partial. cfg::_bool ppu_set_vnan{ this, "PPU Accurate Vector NaN Values", false }; // Accuracy. Implies ppu_fix_vnan. cfg::_bool ppu_set_fpcc{ this, "PPU Set FPCC Bits", false }; // Accuracy. From 1b1143094eece0fb0c6bc98e821b0f6fcdcda98b Mon Sep 17 00:00:00 2001 From: kd-11 Date: Fri, 10 Apr 2026 02:47:31 +0300 Subject: [PATCH 3/6] rsx: Improve handling of aligned memory --- rpcs3/Emu/RSX/Common/aligned_malloc.hpp | 88 +++++++++++++++++++++++++ rpcs3/Emu/RSX/Common/io_buffer.h | 9 ++- rpcs3/Emu/RSX/Common/simple_array.hpp | 60 +---------------- rpcs3/emucore.vcxproj | 1 + rpcs3/emucore.vcxproj.filters | 3 + 5 files changed, 101 insertions(+), 60 deletions(-) create mode 100644 rpcs3/Emu/RSX/Common/aligned_malloc.hpp diff --git a/rpcs3/Emu/RSX/Common/aligned_malloc.hpp b/rpcs3/Emu/RSX/Common/aligned_malloc.hpp new file mode 100644 index 0000000000..2ca59c3cf2 --- /dev/null +++ b/rpcs3/Emu/RSX/Common/aligned_malloc.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include + +namespace rsx +{ + namespace aligned_allocator + { + template + requires (Align != 0) && ((Align& (Align - 1)) == 0) + size_t align_up(size_t size) + { + return (size + (Align - 1)) & ~(Align - 1); + } + + template + requires (Align != 0) && ((Align& (Align - 1)) == 0) + void* malloc(size_t size) + { +#if defined(_WIN32) + return _aligned_malloc(size, Align); +#elif defined(__APPLE__) + constexpr size_t NativeAlign = std::max(Align, sizeof(void*)); + return std::aligned_alloc(NativeAlign, align_up(size)); +#else + return std::aligned_alloc(Align, align_up(size)); +#endif + } + + template + requires (Align != 0) && ((Align& (Align - 1)) == 0) + void* realloc(void* prev_ptr, [[maybe_unused]] size_t prev_size, size_t new_size) + { + if (align_up(prev_size) >= new_size) + { + return prev_ptr; + } + + ensure(reinterpret_cast(prev_ptr) % Align == 0, "Pointer not aligned to Align"); +#if defined(_WIN32) + return _aligned_realloc(prev_ptr, new_size, Align); +#else +#if defined(__APPLE__) + constexpr size_t NativeAlign = std::max(Align, sizeof(void*)); + void* ret = std::aligned_alloc(NativeAlign, align_up(new_size)); +#else + void* ret = std::aligned_alloc(Align, align_up(new_size)); +#endif + std::memcpy(ret, prev_ptr, std::min(prev_size, new_size)); + std::free(prev_ptr); + return ret; +#endif + } + + static inline void free(void* ptr) + { +#ifdef _WIN32 + _aligned_free(ptr); +#else + std::free(ptr); +#endif + } + } + + template + class aligned_pointer_t + { + public: + aligned_pointer_t(size_t size) + { + m_ptr = aligned_allocator::malloc(size); + } + + virtual ~aligned_pointer_t() + { + aligned_allocator::free(m_ptr); + } + + T* data() const { return m_ptr; } + + T& operator * () const { return *m_ptr; } + + T* operator -> () const { return m_ptr; } + + private: + T* m_ptr; + }; +} diff --git a/rpcs3/Emu/RSX/Common/io_buffer.h b/rpcs3/Emu/RSX/Common/io_buffer.h index 59e8e6a32e..edca80675b 100644 --- a/rpcs3/Emu/RSX/Common/io_buffer.h +++ b/rpcs3/Emu/RSX/Common/io_buffer.h @@ -81,10 +81,17 @@ namespace rsx std::span as_span() const { auto bytes = data(); - ensure((reinterpret_cast(bytes) & (sizeof(T) - 1)) == 0, "IO buffer span cast requires naturally aligned pointers."); + ensure(is_naturally_aligned(), "IO buffer span cast requires naturally aligned pointers."); return { utils::bless(bytes), m_size / sizeof(T) }; } + template + bool is_naturally_aligned() const + { + return ((reinterpret_cast(data()) & (alignof(T) - 1)) == 0) && + (m_size % sizeof(T)) == 0; + } + bool empty() const { return m_size == 0; diff --git a/rpcs3/Emu/RSX/Common/simple_array.hpp b/rpcs3/Emu/RSX/Common/simple_array.hpp index 6852e670fb..a37df9dd54 100644 --- a/rpcs3/Emu/RSX/Common/simple_array.hpp +++ b/rpcs3/Emu/RSX/Common/simple_array.hpp @@ -3,70 +3,12 @@ #include #include #include -#include +#include "aligned_malloc.hpp" #include "reverse_ptr.hpp" namespace rsx { - namespace aligned_allocator - { - template - requires (Align != 0) && ((Align & (Align - 1)) == 0) - size_t align_up(size_t size) - { - return (size + (Align - 1)) & ~(Align - 1); - } - - template - requires (Align != 0) && ((Align & (Align - 1)) == 0) - void* malloc(size_t size) - { -#if defined(_WIN32) - return _aligned_malloc(size, Align); -#elif defined(__APPLE__) - constexpr size_t NativeAlign = std::max(Align, sizeof(void*)); - return std::aligned_alloc(NativeAlign, align_up(size)); -#else - return std::aligned_alloc(Align, align_up(size)); -#endif - } - - template - requires (Align != 0) && ((Align & (Align - 1)) == 0) - void* realloc(void* prev_ptr, [[maybe_unused]] size_t prev_size, size_t new_size) - { - if (align_up(prev_size) >= new_size) - { - return prev_ptr; - } - - ensure(reinterpret_cast(prev_ptr) % Align == 0, "Pointer not aligned to Align"); -#if defined(_WIN32) - return _aligned_realloc(prev_ptr, new_size, Align); -#else -#if defined(__APPLE__) - constexpr size_t NativeAlign = std::max(Align, sizeof(void*)); - void* ret = std::aligned_alloc(NativeAlign, align_up(new_size)); -#else - void* ret = std::aligned_alloc(Align, align_up(new_size)); -#endif - std::memcpy(ret, prev_ptr, std::min(prev_size, new_size)); - std::free(prev_ptr); - return ret; -#endif - } - - static inline void free(void* ptr) - { -#ifdef _WIN32 - _aligned_free(ptr); -#else - std::free(ptr); -#endif - } - } - template concept span_like = requires(C& c) { diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 7253171856..42aab47a04 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -661,6 +661,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index a5a5825f44..6b9c82c959 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -2872,6 +2872,9 @@ Emu\GPU\RSX\Overlays + + Emu\GPU\RSX\Common + From 09554c43baaad391b8679fe6d55a23babbd2dec8 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Fri, 10 Apr 2026 02:50:43 +0300 Subject: [PATCH 4/6] rsx: Allow DXT texture decoding to gracefully fall back to unaligned memory addresses --- rpcs3/Emu/RSX/Common/TextureUtils.cpp | 64 ++++++++++++++++++++------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/rpcs3/Emu/RSX/Common/TextureUtils.cpp b/rpcs3/Emu/RSX/Common/TextureUtils.cpp index 8cf3466bb9..43068bf723 100644 --- a/rpcs3/Emu/RSX/Common/TextureUtils.cpp +++ b/rpcs3/Emu/RSX/Common/TextureUtils.cpp @@ -7,6 +7,12 @@ #include "util/asm.hpp" +// Unaligned u128 alias +union x128 +{ + u8 _u8[16]; +}; + namespace utils { template @@ -520,14 +526,14 @@ struct copy_decoded_bc1_block struct copy_decoded_bc2_block { - static void copy_mipmap_level(std::span dst, std::span src, u16 width_in_block, u32 row_count, u16 depth, u32 dst_pitch_in_block, u32 src_pitch_in_block) + static void copy_mipmap_level(std::span dst, std::span src, u16 width_in_block, u32 row_count, u16 depth, u32 dst_pitch_in_block, u32 src_pitch_in_block) { u32 src_offset = 0, dst_offset = 0, destinationPitch = dst_pitch_in_block * 4; for (u32 row = 0; row < row_count * depth; row++) { for (u32 col = 0; col < width_in_block; col++) { - const u8* compressedBlock = reinterpret_cast(&src[src_offset + col]); + const u8* compressedBlock = src[src_offset + col]._u8; u8* decompressedBlock = reinterpret_cast(&dst[dst_offset + col * 4]); bcdec_bc2(compressedBlock, decompressedBlock, destinationPitch); } @@ -540,14 +546,14 @@ struct copy_decoded_bc2_block struct copy_decoded_bc3_block { - static void copy_mipmap_level(std::span dst, std::span src, u16 width_in_block, u32 row_count, u16 depth, u32 dst_pitch_in_block, u32 src_pitch_in_block) + static void copy_mipmap_level(std::span dst, std::span src, u16 width_in_block, u32 row_count, u16 depth, u32 dst_pitch_in_block, u32 src_pitch_in_block) { u32 src_offset = 0, dst_offset = 0, destinationPitch = dst_pitch_in_block * 4; for (u32 row = 0; row < row_count * depth; row++) { for (u32 col = 0; col < width_in_block; col++) { - const u8* compressedBlock = reinterpret_cast(&src[src_offset + col]); + const u8* compressedBlock = src[src_offset + col]._u8; u8* decompressedBlock = reinterpret_cast(&dst[dst_offset + col * 4]); bcdec_bc3(compressedBlock, decompressedBlock, destinationPitch); } @@ -1039,22 +1045,25 @@ namespace rsx // This is only supported using Nvidia OpenGL. // Remove the VTC tiling to support ATI and Vulkan. copy_unmodified_block_vtc::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), w, h, depth, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); + break; } - else if (is_3d && !is_po2 && caps.supports_vtc_decoding) + + if (is_3d && !is_po2 && caps.supports_vtc_decoding) { // In this case, hardware expects us to feed it a VTC input, but on PS3 we only have a linear one. // We need to compress the 2D-planar DXT input into a VTC output copy_linear_block_to_vtc::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), w, h, depth, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); + break; } - else if (caps.supports_zero_copy) + + if (caps.supports_zero_copy) { result.require_upload = true; result.deferred_cmds = build_transfer_cmds(src_layout.data.data(), 8, w, h, depth, 0, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); + break; } - else - { - copy_unmodified_block::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), 1, w, h, depth, 0, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); - } + + copy_unmodified_block::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), 1, w, h, depth, 0, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); break; } @@ -1062,7 +1071,7 @@ namespace rsx { if (!caps.supports_dxt) { - copy_decoded_bc2_block::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), w, h, depth, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); + copy_decoded_bc2_block::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), w, h, depth, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); break; } [[fallthrough]]; @@ -1071,7 +1080,7 @@ namespace rsx { if (!caps.supports_dxt) { - copy_decoded_bc3_block::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), w, h, depth, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); + copy_decoded_bc3_block::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), w, h, depth, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); break; } @@ -1083,23 +1092,44 @@ namespace rsx // PS3 uses the Nvidia VTC memory layout for compressed 3D textures. // This is only supported using Nvidia OpenGL. // Remove the VTC tiling to support ATI and Vulkan. - copy_unmodified_block_vtc::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), w, h, depth, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); + if (src_layout.data.is_naturally_aligned()) + { + copy_unmodified_block_vtc::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), w, h, depth, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); + break; + } + + copy_unmodified_block_vtc::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), w, h, depth, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); + break; } - else if (is_3d && !is_po2 && caps.supports_vtc_decoding) + + if (is_3d && !is_po2 && caps.supports_vtc_decoding) { // In this case, hardware expects us to feed it a VTC input, but on PS3 we only have a linear one. // We need to compress the 2D-planar DXT input into a VTC output - copy_linear_block_to_vtc::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), w, h, depth, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); + if (src_layout.data.is_naturally_aligned()) + { + copy_linear_block_to_vtc::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), w, h, depth, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); + break; + } + + copy_linear_block_to_vtc::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), w, h, depth, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); + break; } - else if (caps.supports_zero_copy) + + if (caps.supports_zero_copy) { result.require_upload = true; result.deferred_cmds = build_transfer_cmds(src_layout.data.data(), 16, w, h, depth, 0, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); + break; } - else + + if (src_layout.data.is_naturally_aligned()) { copy_unmodified_block::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), 1, w, h, depth, 0, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); + break; } + + copy_unmodified_block::copy_mipmap_level(dst_buffer.as_span(), src_layout.data.as_span(), 1, w, h, depth, 0, get_row_pitch_in_block(w, caps.alignment), src_layout.pitch_in_block); break; } From fb194241d529b5c888780966fbe573e4d2c22aa1 Mon Sep 17 00:00:00 2001 From: oltolm Date: Sun, 5 Apr 2026 16:55:30 +0200 Subject: [PATCH 5/6] fix LLVM assert in use_begin --- rpcs3/Emu/CPU/CPUTranslator.cpp | 71 ++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/rpcs3/Emu/CPU/CPUTranslator.cpp b/rpcs3/Emu/CPU/CPUTranslator.cpp index 6bd7924ea5..66b5c69af0 100644 --- a/rpcs3/Emu/CPU/CPUTranslator.cpp +++ b/rpcs3/Emu/CPU/CPUTranslator.cpp @@ -244,36 +244,48 @@ llvm::Value* cpu_translator::bitcast(llvm::Value* val, llvm::Type* type, std::so } } - for (auto it = source_val->use_begin(); it != source_val->use_end(); ++it) + // Skip use iteration for values that don't have use lists +#if LLVM_VERSION_MAJOR >= 21 + if (source_val->hasUseList()) +#endif { - llvm::Value* it_val = *it; - - if (!it_val) + for (llvm::Value* it_val : source_val->uses()) { - continue; - } - - llvm::CastInst* bci = llvm::dyn_cast_or_null(it_val); - - // Walk through bitcasts - while (bci && bci->getOpcode() == llvm::Instruction::BitCast) - { - if (bci->getParent() != m_ir->GetInsertBlock()) + if (!it_val) { - break; + continue; } - if (bci->getType() == type) - { - return bci; - } + llvm::CastInst* bci = llvm::dyn_cast_or_null(it_val); - if (bci->use_begin() == bci->use_end()) + // Walk through bitcasts + while (bci && bci->getOpcode() == llvm::Instruction::BitCast) { - break; - } + if (bci->getParent() != m_ir->GetInsertBlock()) + { + break; + } - bci = llvm::dyn_cast_or_null(*bci->use_begin()); + if (bci->getType() == type) + { + return bci; + } + + // Check if bci has use list before accessing use_begin() +#if LLVM_VERSION_MAJOR >= 21 + if (!bci->hasUseList()) + { + break; + } +#endif + + if (bci->use_begin() == bci->use_end()) + { + break; + } + + bci = llvm::dyn_cast_or_null(*bci->use_begin()); + } } } @@ -553,14 +565,25 @@ void cpu_translator::erase_stores(llvm::ArrayRef args) { for (auto v : args) { - for (auto it = v->use_begin(); it != v->use_end(); ++it) + // Skip use iteration for values that don't have use lists +#if LLVM_VERSION_MAJOR >= 21 + if (!v->hasUseList()) + continue; +#endif + + for (llvm::Value* i : v->uses()) { - llvm::Value* i = *it; llvm::CastInst* bci = nullptr; // Walk through bitcasts while (i && (bci = llvm::dyn_cast(i)) && bci->getOpcode() == llvm::Instruction::BitCast) { + // Check if bci has use list before accessing use_begin() +#if LLVM_VERSION_MAJOR >= 21 + if (!bci->hasUseList()) + break; +#endif + i = *bci->use_begin(); } From 110c786d802c4c17850de1d6945dc4df08ac393a Mon Sep 17 00:00:00 2001 From: digant73 Date: Fri, 3 Apr 2026 00:05:51 +0200 Subject: [PATCH 6/6] Add support to encrypted ISO fix compile errors and suppress minor warnings strip minor bug fixes and cleanup minor cleanup minor cleanup Fixed crash at boot parsing an ISO with a empty directory entry at file head applied suggested changes added missing break in switch Update rpcs3/Loader/ISO.cpp Co-authored-by: Megamouse use hex_to_bytes() instead of self made functions minor cleanup rewrite nested if() add explicit support to .key minor cleanup optimize partial sector read minor optimization fix conflict --- rpcs3/Loader/ISO.cpp | 611 +++++++++++++++++++++++++++++++++++++++---- rpcs3/Loader/ISO.h | 78 +++++- 2 files changed, 624 insertions(+), 65 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 544b04d379..f3eba4268b 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,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 buf; +}; + bool is_file_iso(const std::string& path) { if (path.empty()) return false; @@ -34,7 +48,351 @@ 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) +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) + { + sys_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) + { + sys_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) + { + 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 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 + // + + 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 + { + 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(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; + } + } + + 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 retrieve_endian_int(const u8* buf) @@ -55,13 +413,16 @@ inline T retrieve_endian_int(const u8* buf) return out; } -// assumed that directory_entry is at file head +// 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; + 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) @@ -94,12 +455,14 @@ static std::optional iso_read_directory_entry(fs::file& entry, 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; @@ -110,6 +473,7 @@ static std::optional iso_read_directory_entry(fs::file& entry, 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) @@ -120,13 +484,14 @@ static std::optional iso_read_directory_entry(fs::file& entry, { file_name = ".."; } - else if (names_in_ucs2) // for strings in joliet descriptor + else if (names_in_ucs2) // For strings in joliet descriptor { - // characters are stored in big endian format. + // 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); - const u16* raw = reinterpret_cast(file_name.data()); for (size_t i = 0; i < utf16.size(); ++i, raw++) { utf16[i] = *reinterpret_cast*>(raw); @@ -145,7 +510,7 @@ static std::optional iso_read_directory_entry(fs::file& entry, file_name.pop_back(); } - // skip the rest of the entry. + // Skip the rest of the entry entry.seek(entry_length + start_pos); return iso_fs_metadata @@ -165,38 +530,43 @@ static std::optional iso_read_directory_entry(fs::file& entry, }; } -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; @@ -204,11 +574,14 @@ void iso_form_hierarchy(fs::file& file, iso_fs_node& node, bool use_ucs2_decodin } } - 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()); } @@ -241,10 +614,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; } @@ -262,16 +636,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); @@ -355,23 +734,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() @@ -410,14 +793,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); @@ -430,29 +805,157 @@ 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(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(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) + { + sys_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) + { + sys_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*/) @@ -590,7 +1093,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) @@ -621,7 +1124,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(path)); @@ -630,7 +1133,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()); } diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index 2665ec29e5..8a88e876bf 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,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 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 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 +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 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;