diff options
75 files changed, 8040 insertions, 1028 deletions
diff --git a/src/common/alignment.h b/src/common/alignment.h index fa715d497..0057052af 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <bit> | ||
| 6 | #include <cstddef> | 7 | #include <cstddef> |
| 7 | #include <new> | 8 | #include <new> |
| 8 | #include <type_traits> | 9 | #include <type_traits> |
| @@ -10,7 +11,7 @@ | |||
| 10 | namespace Common { | 11 | namespace Common { |
| 11 | 12 | ||
| 12 | template <typename T> | 13 | template <typename T> |
| 13 | requires std::is_unsigned_v<T> | 14 | requires std::is_integral_v<T> |
| 14 | [[nodiscard]] constexpr T AlignUp(T value, size_t size) { | 15 | [[nodiscard]] constexpr T AlignUp(T value, size_t size) { |
| 15 | auto mod{static_cast<T>(value % size)}; | 16 | auto mod{static_cast<T>(value % size)}; |
| 16 | value -= mod; | 17 | value -= mod; |
| @@ -24,7 +25,7 @@ template <typename T> | |||
| 24 | } | 25 | } |
| 25 | 26 | ||
| 26 | template <typename T> | 27 | template <typename T> |
| 27 | requires std::is_unsigned_v<T> | 28 | requires std::is_integral_v<T> |
| 28 | [[nodiscard]] constexpr T AlignDown(T value, size_t size) { | 29 | [[nodiscard]] constexpr T AlignDown(T value, size_t size) { |
| 29 | return static_cast<T>(value - value % size); | 30 | return static_cast<T>(value - value % size); |
| 30 | } | 31 | } |
| @@ -55,6 +56,30 @@ template <typename T, typename U> | |||
| 55 | return (x + (y - 1)) / y; | 56 | return (x + (y - 1)) / y; |
| 56 | } | 57 | } |
| 57 | 58 | ||
| 59 | template <typename T> | ||
| 60 | requires std::is_integral_v<T> | ||
| 61 | [[nodiscard]] constexpr T LeastSignificantOneBit(T x) { | ||
| 62 | return x & ~(x - 1); | ||
| 63 | } | ||
| 64 | |||
| 65 | template <typename T> | ||
| 66 | requires std::is_integral_v<T> | ||
| 67 | [[nodiscard]] constexpr T ResetLeastSignificantOneBit(T x) { | ||
| 68 | return x & (x - 1); | ||
| 69 | } | ||
| 70 | |||
| 71 | template <typename T> | ||
| 72 | requires std::is_integral_v<T> | ||
| 73 | [[nodiscard]] constexpr bool IsPowerOfTwo(T x) { | ||
| 74 | return x > 0 && ResetLeastSignificantOneBit(x) == 0; | ||
| 75 | } | ||
| 76 | |||
| 77 | template <typename T> | ||
| 78 | requires std::is_integral_v<T> | ||
| 79 | [[nodiscard]] constexpr T FloorPowerOfTwo(T x) { | ||
| 80 | return T{1} << (sizeof(T) * 8 - std::countl_zero(x) - 1); | ||
| 81 | } | ||
| 82 | |||
| 58 | template <typename T, size_t Align = 16> | 83 | template <typename T, size_t Align = 16> |
| 59 | class AlignmentAllocator { | 84 | class AlignmentAllocator { |
| 60 | public: | 85 | public: |
diff --git a/src/common/lz4_compression.cpp b/src/common/lz4_compression.cpp index ffb32fecf..6867c03c4 100644 --- a/src/common/lz4_compression.cpp +++ b/src/common/lz4_compression.cpp | |||
| @@ -71,4 +71,10 @@ std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, std::size_t un | |||
| 71 | return uncompressed; | 71 | return uncompressed; |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | int DecompressLZ4(void* dst, size_t dst_size, const void* src, size_t src_size) { | ||
| 75 | // This is just a thin wrapper around LZ4. | ||
| 76 | return LZ4_decompress_safe(reinterpret_cast<const char*>(src), reinterpret_cast<char*>(dst), | ||
| 77 | static_cast<int>(src_size), static_cast<int>(dst_size)); | ||
| 78 | } | ||
| 79 | |||
| 74 | } // namespace Common::Compression | 80 | } // namespace Common::Compression |
diff --git a/src/common/lz4_compression.h b/src/common/lz4_compression.h index 7fd53a960..7200e0f22 100644 --- a/src/common/lz4_compression.h +++ b/src/common/lz4_compression.h | |||
| @@ -56,4 +56,6 @@ namespace Common::Compression { | |||
| 56 | [[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, | 56 | [[nodiscard]] std::vector<u8> DecompressDataLZ4(std::span<const u8> compressed, |
| 57 | std::size_t uncompressed_size); | 57 | std::size_t uncompressed_size); |
| 58 | 58 | ||
| 59 | int DecompressLZ4(void* dst, size_t dst_size, const void* src, size_t src_size); | ||
| 60 | |||
| 59 | } // namespace Common::Compression | 61 | } // namespace Common::Compression |
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4b7395be8..012648d69 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -37,6 +37,49 @@ add_library(core STATIC | |||
| 37 | debugger/gdbstub.h | 37 | debugger/gdbstub.h |
| 38 | device_memory.cpp | 38 | device_memory.cpp |
| 39 | device_memory.h | 39 | device_memory.h |
| 40 | file_sys/fssystem/fs_i_storage.h | ||
| 41 | file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp | ||
| 42 | file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h | ||
| 43 | file_sys/fssystem/fssystem_aes_ctr_storage.cpp | ||
| 44 | file_sys/fssystem/fssystem_aes_ctr_storage.h | ||
| 45 | file_sys/fssystem/fssystem_aes_xts_storage.cpp | ||
| 46 | file_sys/fssystem/fssystem_aes_xts_storage.h | ||
| 47 | file_sys/fssystem/fssystem_alignment_matching_storage.h | ||
| 48 | file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp | ||
| 49 | file_sys/fssystem/fssystem_alignment_matching_storage_impl.h | ||
| 50 | file_sys/fssystem/fssystem_bucket_tree.cpp | ||
| 51 | file_sys/fssystem/fssystem_bucket_tree.h | ||
| 52 | file_sys/fssystem/fssystem_bucket_tree_utils.h | ||
| 53 | file_sys/fssystem/fssystem_compressed_storage.h | ||
| 54 | file_sys/fssystem/fssystem_compression_common.h | ||
| 55 | file_sys/fssystem/fssystem_compression_configuration.cpp | ||
| 56 | file_sys/fssystem/fssystem_compression_configuration.h | ||
| 57 | file_sys/fssystem/fssystem_crypto_configuration.cpp | ||
| 58 | file_sys/fssystem/fssystem_crypto_configuration.h | ||
| 59 | file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp | ||
| 60 | file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h | ||
| 61 | file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp | ||
| 62 | file_sys/fssystem/fssystem_hierarchical_sha256_storage.h | ||
| 63 | file_sys/fssystem/fssystem_indirect_storage.cpp | ||
| 64 | file_sys/fssystem/fssystem_indirect_storage.h | ||
| 65 | file_sys/fssystem/fssystem_integrity_romfs_storage.cpp | ||
| 66 | file_sys/fssystem/fssystem_integrity_romfs_storage.h | ||
| 67 | file_sys/fssystem/fssystem_integrity_verification_storage.cpp | ||
| 68 | file_sys/fssystem/fssystem_integrity_verification_storage.h | ||
| 69 | file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h | ||
| 70 | file_sys/fssystem/fssystem_nca_file_system_driver.cpp | ||
| 71 | file_sys/fssystem/fssystem_nca_file_system_driver.h | ||
| 72 | file_sys/fssystem/fssystem_nca_header.cpp | ||
| 73 | file_sys/fssystem/fssystem_nca_header.h | ||
| 74 | file_sys/fssystem/fssystem_nca_reader.cpp | ||
| 75 | file_sys/fssystem/fssystem_pooled_buffer.cpp | ||
| 76 | file_sys/fssystem/fssystem_pooled_buffer.h | ||
| 77 | file_sys/fssystem/fssystem_sparse_storage.cpp | ||
| 78 | file_sys/fssystem/fssystem_sparse_storage.h | ||
| 79 | file_sys/fssystem/fssystem_switch_storage.h | ||
| 80 | file_sys/fssystem/fssystem_utility.cpp | ||
| 81 | file_sys/fssystem/fssystem_utility.h | ||
| 82 | file_sys/fssystem/fs_types.h | ||
| 40 | file_sys/bis_factory.cpp | 83 | file_sys/bis_factory.cpp |
| 41 | file_sys/bis_factory.h | 84 | file_sys/bis_factory.h |
| 42 | file_sys/card_image.cpp | 85 | file_sys/card_image.cpp |
| @@ -57,8 +100,6 @@ add_library(core STATIC | |||
| 57 | file_sys/mode.h | 100 | file_sys/mode.h |
| 58 | file_sys/nca_metadata.cpp | 101 | file_sys/nca_metadata.cpp |
| 59 | file_sys/nca_metadata.h | 102 | file_sys/nca_metadata.h |
| 60 | file_sys/nca_patch.cpp | ||
| 61 | file_sys/nca_patch.h | ||
| 62 | file_sys/partition_filesystem.cpp | 103 | file_sys/partition_filesystem.cpp |
| 63 | file_sys/partition_filesystem.h | 104 | file_sys/partition_filesystem.h |
| 64 | file_sys/patch_manager.cpp | 105 | file_sys/patch_manager.cpp |
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 5d02865f4..54b53d020 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp | |||
| @@ -29,8 +29,8 @@ constexpr std::array partition_names{ | |||
| 29 | 29 | ||
| 30 | XCI::XCI(VirtualFile file_, u64 program_id, size_t program_index) | 30 | XCI::XCI(VirtualFile file_, u64 program_id, size_t program_index) |
| 31 | : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, | 31 | : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, |
| 32 | partitions(partition_names.size()), | 32 | partitions(partition_names.size()), partitions_raw(partition_names.size()), |
| 33 | partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} { | 33 | keys{Core::Crypto::KeyManager::Instance()} { |
| 34 | if (file->ReadObject(&header) != sizeof(GamecardHeader)) { | 34 | if (file->ReadObject(&header) != sizeof(GamecardHeader)) { |
| 35 | status = Loader::ResultStatus::ErrorBadXCIHeader; | 35 | status = Loader::ResultStatus::ErrorBadXCIHeader; |
| 36 | return; | 36 | return; |
| @@ -183,7 +183,7 @@ u32 XCI::GetSystemUpdateVersion() { | |||
| 183 | } | 183 | } |
| 184 | 184 | ||
| 185 | for (const auto& update_file : update->GetFiles()) { | 185 | for (const auto& update_file : update->GetFiles()) { |
| 186 | NCA nca{update_file, nullptr, 0}; | 186 | NCA nca{update_file}; |
| 187 | 187 | ||
| 188 | if (nca.GetStatus() != Loader::ResultStatus::Success) { | 188 | if (nca.GetStatus() != Loader::ResultStatus::Success) { |
| 189 | continue; | 189 | continue; |
| @@ -296,7 +296,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { | |||
| 296 | continue; | 296 | continue; |
| 297 | } | 297 | } |
| 298 | 298 | ||
| 299 | auto nca = std::make_shared<NCA>(partition_file, nullptr, 0); | 299 | auto nca = std::make_shared<NCA>(partition_file); |
| 300 | if (nca->IsUpdate()) { | 300 | if (nca->IsUpdate()) { |
| 301 | continue; | 301 | continue; |
| 302 | } | 302 | } |
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 06efab46d..2361b169e 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp | |||
| @@ -12,545 +12,110 @@ | |||
| 12 | #include "core/crypto/ctr_encryption_layer.h" | 12 | #include "core/crypto/ctr_encryption_layer.h" |
| 13 | #include "core/crypto/key_manager.h" | 13 | #include "core/crypto/key_manager.h" |
| 14 | #include "core/file_sys/content_archive.h" | 14 | #include "core/file_sys/content_archive.h" |
| 15 | #include "core/file_sys/nca_patch.h" | ||
| 16 | #include "core/file_sys/partition_filesystem.h" | 15 | #include "core/file_sys/partition_filesystem.h" |
| 17 | #include "core/file_sys/vfs_offset.h" | 16 | #include "core/file_sys/vfs_offset.h" |
| 18 | #include "core/loader/loader.h" | 17 | #include "core/loader/loader.h" |
| 19 | 18 | ||
| 20 | namespace FileSys { | 19 | #include "core/file_sys/fssystem/fssystem_compression_configuration.h" |
| 20 | #include "core/file_sys/fssystem/fssystem_crypto_configuration.h" | ||
| 21 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 21 | 22 | ||
| 22 | // Media offsets in headers are stored divided by 512. Mult. by this to get real offset. | 23 | namespace FileSys { |
| 23 | constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200; | ||
| 24 | |||
| 25 | constexpr u64 SECTION_HEADER_SIZE = 0x200; | ||
| 26 | constexpr u64 SECTION_HEADER_OFFSET = 0x400; | ||
| 27 | |||
| 28 | constexpr u32 IVFC_MAX_LEVEL = 6; | ||
| 29 | |||
| 30 | enum class NCASectionFilesystemType : u8 { | ||
| 31 | PFS0 = 0x2, | ||
| 32 | ROMFS = 0x3, | ||
| 33 | }; | ||
| 34 | |||
| 35 | struct IVFCLevel { | ||
| 36 | u64_le offset; | ||
| 37 | u64_le size; | ||
| 38 | u32_le block_size; | ||
| 39 | u32_le reserved; | ||
| 40 | }; | ||
| 41 | static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size."); | ||
| 42 | |||
| 43 | struct IVFCHeader { | ||
| 44 | u32_le magic; | ||
| 45 | u32_le magic_number; | ||
| 46 | INSERT_PADDING_BYTES_NOINIT(8); | ||
| 47 | std::array<IVFCLevel, 6> levels; | ||
| 48 | INSERT_PADDING_BYTES_NOINIT(64); | ||
| 49 | }; | ||
| 50 | static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); | ||
| 51 | |||
| 52 | struct NCASectionHeaderBlock { | ||
| 53 | INSERT_PADDING_BYTES_NOINIT(3); | ||
| 54 | NCASectionFilesystemType filesystem_type; | ||
| 55 | NCASectionCryptoType crypto_type; | ||
| 56 | INSERT_PADDING_BYTES_NOINIT(3); | ||
| 57 | }; | ||
| 58 | static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); | ||
| 59 | |||
| 60 | struct NCABucketInfo { | ||
| 61 | u64 table_offset; | ||
| 62 | u64 table_size; | ||
| 63 | std::array<u8, 0x10> table_header; | ||
| 64 | }; | ||
| 65 | static_assert(sizeof(NCABucketInfo) == 0x20, "NCABucketInfo has incorrect size."); | ||
| 66 | |||
| 67 | struct NCASparseInfo { | ||
| 68 | NCABucketInfo bucket; | ||
| 69 | u64 physical_offset; | ||
| 70 | u16 generation; | ||
| 71 | INSERT_PADDING_BYTES_NOINIT(0x6); | ||
| 72 | }; | ||
| 73 | static_assert(sizeof(NCASparseInfo) == 0x30, "NCASparseInfo has incorrect size."); | ||
| 74 | |||
| 75 | struct NCACompressionInfo { | ||
| 76 | NCABucketInfo bucket; | ||
| 77 | INSERT_PADDING_BYTES_NOINIT(0x8); | ||
| 78 | }; | ||
| 79 | static_assert(sizeof(NCACompressionInfo) == 0x28, "NCACompressionInfo has incorrect size."); | ||
| 80 | |||
| 81 | struct NCASectionRaw { | ||
| 82 | NCASectionHeaderBlock header; | ||
| 83 | std::array<u8, 0x138> block_data; | ||
| 84 | std::array<u8, 0x8> section_ctr; | ||
| 85 | NCASparseInfo sparse_info; | ||
| 86 | NCACompressionInfo compression_info; | ||
| 87 | INSERT_PADDING_BYTES_NOINIT(0x60); | ||
| 88 | }; | ||
| 89 | static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size."); | ||
| 90 | |||
| 91 | struct PFS0Superblock { | ||
| 92 | NCASectionHeaderBlock header_block; | ||
| 93 | std::array<u8, 0x20> hash; | ||
| 94 | u32_le size; | ||
| 95 | INSERT_PADDING_BYTES_NOINIT(4); | ||
| 96 | u64_le hash_table_offset; | ||
| 97 | u64_le hash_table_size; | ||
| 98 | u64_le pfs0_header_offset; | ||
| 99 | u64_le pfs0_size; | ||
| 100 | INSERT_PADDING_BYTES_NOINIT(0x1B0); | ||
| 101 | }; | ||
| 102 | static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); | ||
| 103 | |||
| 104 | struct RomFSSuperblock { | ||
| 105 | NCASectionHeaderBlock header_block; | ||
| 106 | IVFCHeader ivfc; | ||
| 107 | INSERT_PADDING_BYTES_NOINIT(0x118); | ||
| 108 | }; | ||
| 109 | static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); | ||
| 110 | |||
| 111 | struct BKTRHeader { | ||
| 112 | u64_le offset; | ||
| 113 | u64_le size; | ||
| 114 | u32_le magic; | ||
| 115 | INSERT_PADDING_BYTES_NOINIT(0x4); | ||
| 116 | u32_le number_entries; | ||
| 117 | INSERT_PADDING_BYTES_NOINIT(0x4); | ||
| 118 | }; | ||
| 119 | static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size."); | ||
| 120 | |||
| 121 | struct BKTRSuperblock { | ||
| 122 | NCASectionHeaderBlock header_block; | ||
| 123 | IVFCHeader ivfc; | ||
| 124 | INSERT_PADDING_BYTES_NOINIT(0x18); | ||
| 125 | BKTRHeader relocation; | ||
| 126 | BKTRHeader subsection; | ||
| 127 | INSERT_PADDING_BYTES_NOINIT(0xC0); | ||
| 128 | }; | ||
| 129 | static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size."); | ||
| 130 | |||
| 131 | union NCASectionHeader { | ||
| 132 | NCASectionRaw raw{}; | ||
| 133 | PFS0Superblock pfs0; | ||
| 134 | RomFSSuperblock romfs; | ||
| 135 | BKTRSuperblock bktr; | ||
| 136 | }; | ||
| 137 | static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size."); | ||
| 138 | |||
| 139 | static bool IsValidNCA(const NCAHeader& header) { | ||
| 140 | // TODO(DarkLordZach): Add NCA2/NCA0 support. | ||
| 141 | return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); | ||
| 142 | } | ||
| 143 | 24 | ||
| 144 | NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset) | 25 | NCA::NCA(VirtualFile file_, const NCA* base_nca) |
| 145 | : file(std::move(file_)), | 26 | : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { |
| 146 | bktr_base_romfs(std::move(bktr_base_romfs_)), keys{Core::Crypto::KeyManager::Instance()} { | ||
| 147 | if (file == nullptr) { | 27 | if (file == nullptr) { |
| 148 | status = Loader::ResultStatus::ErrorNullFile; | 28 | status = Loader::ResultStatus::ErrorNullFile; |
| 149 | return; | 29 | return; |
| 150 | } | 30 | } |
| 151 | 31 | ||
| 152 | if (sizeof(NCAHeader) != file->ReadObject(&header)) { | 32 | reader = std::make_shared<NcaReader>(); |
| 153 | LOG_ERROR(Loader, "File reader errored out during header read."); | 33 | if (Result rc = |
| 34 | reader->Initialize(file, GetCryptoConfiguration(), *GetNcaCompressionConfiguration()); | ||
| 35 | R_FAILED(rc)) { | ||
| 36 | if (rc != ResultInvalidNcaSignature) { | ||
| 37 | LOG_ERROR(Loader, "File reader errored out during header read: {:#x}", | ||
| 38 | rc.GetInnerValue()); | ||
| 39 | } | ||
| 154 | status = Loader::ResultStatus::ErrorBadNCAHeader; | 40 | status = Loader::ResultStatus::ErrorBadNCAHeader; |
| 155 | return; | 41 | return; |
| 156 | } | 42 | } |
| 157 | 43 | ||
| 158 | if (!HandlePotentialHeaderDecryption()) { | 44 | RightsId rights_id{}; |
| 159 | return; | 45 | reader->GetRightsId(rights_id.data(), rights_id.size()); |
| 160 | } | 46 | if (rights_id != RightsId{}) { |
| 161 | 47 | // External decryption key required; provide it here. | |
| 162 | has_rights_id = std::ranges::any_of(header.rights_id, [](char c) { return c != '\0'; }); | 48 | const auto key_generation = std::max<s32>(reader->GetKeyGeneration(), 1) - 1; |
| 163 | |||
| 164 | const std::vector<NCASectionHeader> sections = ReadSectionHeaders(); | ||
| 165 | is_update = std::ranges::any_of(sections, [](const NCASectionHeader& nca_header) { | ||
| 166 | return nca_header.raw.header.crypto_type == NCASectionCryptoType::BKTR; | ||
| 167 | }); | ||
| 168 | |||
| 169 | if (!ReadSections(sections, bktr_base_ivfc_offset)) { | ||
| 170 | return; | ||
| 171 | } | ||
| 172 | |||
| 173 | status = Loader::ResultStatus::Success; | ||
| 174 | } | ||
| 175 | |||
| 176 | NCA::~NCA() = default; | ||
| 177 | |||
| 178 | bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) { | ||
| 179 | if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { | ||
| 180 | status = Loader::ResultStatus::ErrorNCA2; | ||
| 181 | return false; | ||
| 182 | } | ||
| 183 | |||
| 184 | if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) { | ||
| 185 | status = Loader::ResultStatus::ErrorNCA0; | ||
| 186 | return false; | ||
| 187 | } | ||
| 188 | |||
| 189 | return true; | ||
| 190 | } | ||
| 191 | |||
| 192 | bool NCA::HandlePotentialHeaderDecryption() { | ||
| 193 | if (IsValidNCA(header)) { | ||
| 194 | return true; | ||
| 195 | } | ||
| 196 | |||
| 197 | if (!CheckSupportedNCA(header)) { | ||
| 198 | return false; | ||
| 199 | } | ||
| 200 | |||
| 201 | NCAHeader dec_header{}; | ||
| 202 | Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( | ||
| 203 | keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); | ||
| 204 | cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, | ||
| 205 | Core::Crypto::Op::Decrypt); | ||
| 206 | if (IsValidNCA(dec_header)) { | ||
| 207 | header = dec_header; | ||
| 208 | encrypted = true; | ||
| 209 | } else { | ||
| 210 | if (!CheckSupportedNCA(dec_header)) { | ||
| 211 | return false; | ||
| 212 | } | ||
| 213 | |||
| 214 | if (keys.HasKey(Core::Crypto::S256KeyType::Header)) { | ||
| 215 | status = Loader::ResultStatus::ErrorIncorrectHeaderKey; | ||
| 216 | } else { | ||
| 217 | status = Loader::ResultStatus::ErrorMissingHeaderKey; | ||
| 218 | } | ||
| 219 | return false; | ||
| 220 | } | ||
| 221 | |||
| 222 | return true; | ||
| 223 | } | ||
| 224 | |||
| 225 | std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const { | ||
| 226 | const std::ptrdiff_t number_sections = | ||
| 227 | std::ranges::count_if(header.section_tables, [](const NCASectionTableEntry& entry) { | ||
| 228 | return entry.media_offset > 0; | ||
| 229 | }); | ||
| 230 | |||
| 231 | std::vector<NCASectionHeader> sections(number_sections); | ||
| 232 | const auto length_sections = SECTION_HEADER_SIZE * number_sections; | ||
| 233 | |||
| 234 | if (encrypted) { | ||
| 235 | auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET); | ||
| 236 | Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( | ||
| 237 | keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); | ||
| 238 | cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, | ||
| 239 | Core::Crypto::Op::Decrypt); | ||
| 240 | } else { | ||
| 241 | file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); | ||
| 242 | } | ||
| 243 | 49 | ||
| 244 | return sections; | 50 | u128 rights_id_u128; |
| 245 | } | 51 | std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id)); |
| 246 | |||
| 247 | bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) { | ||
| 248 | for (std::size_t i = 0; i < sections.size(); ++i) { | ||
| 249 | const auto& section = sections[i]; | ||
| 250 | |||
| 251 | if (section.raw.sparse_info.bucket.table_offset != 0 && | ||
| 252 | section.raw.sparse_info.bucket.table_size != 0) { | ||
| 253 | LOG_ERROR(Loader, "Sparse NCAs are not supported."); | ||
| 254 | status = Loader::ResultStatus::ErrorSparseNCA; | ||
| 255 | return false; | ||
| 256 | } | ||
| 257 | 52 | ||
| 258 | if (section.raw.compression_info.bucket.table_offset != 0 && | 53 | auto titlekey = |
| 259 | section.raw.compression_info.bucket.table_size != 0) { | 54 | keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id_u128[1], rights_id_u128[0]); |
| 260 | LOG_ERROR(Loader, "Compressed NCAs are not supported."); | 55 | if (titlekey == Core::Crypto::Key128{}) { |
| 261 | status = Loader::ResultStatus::ErrorCompressedNCA; | 56 | status = Loader::ResultStatus::ErrorMissingTitlekey; |
| 262 | return false; | 57 | return; |
| 263 | } | 58 | } |
| 264 | 59 | ||
| 265 | if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { | 60 | if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, key_generation)) { |
| 266 | if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) { | 61 | status = Loader::ResultStatus::ErrorMissingTitlekek; |
| 267 | return false; | 62 | return; |
| 268 | } | ||
| 269 | } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { | ||
| 270 | if (!ReadPFS0Section(section, header.section_tables[i])) { | ||
| 271 | return false; | ||
| 272 | } | ||
| 273 | } | 63 | } |
| 274 | } | ||
| 275 | 64 | ||
| 276 | return true; | 65 | auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, key_generation); |
| 277 | } | 66 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB); |
| 67 | cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), | ||
| 68 | Core::Crypto::Op::Decrypt); | ||
| 278 | 69 | ||
| 279 | bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry, | 70 | reader->SetExternalDecryptionKey(titlekey.data(), titlekey.size()); |
| 280 | u64 bktr_base_ivfc_offset) { | ||
| 281 | const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER; | ||
| 282 | ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; | ||
| 283 | const std::size_t romfs_offset = base_offset + ivfc_offset; | ||
| 284 | const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; | ||
| 285 | auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset); | ||
| 286 | auto dec = Decrypt(section, raw, romfs_offset); | ||
| 287 | |||
| 288 | if (dec == nullptr) { | ||
| 289 | if (status != Loader::ResultStatus::Success) | ||
| 290 | return false; | ||
| 291 | if (has_rights_id) | ||
| 292 | status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; | ||
| 293 | else | ||
| 294 | status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; | ||
| 295 | return false; | ||
| 296 | } | 71 | } |
| 297 | 72 | ||
| 298 | if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) { | 73 | const s32 fs_count = reader->GetFsCount(); |
| 299 | if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') || | 74 | NcaFileSystemDriver fs(base_nca ? base_nca->reader : nullptr, reader); |
| 300 | section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) { | 75 | std::vector<VirtualFile> filesystems(fs_count); |
| 301 | status = Loader::ResultStatus::ErrorBadBKTRHeader; | 76 | for (s32 i = 0; i < fs_count; i++) { |
| 302 | return false; | 77 | NcaFsHeaderReader header_reader; |
| 303 | } | 78 | const Result rc = fs.OpenStorage(&filesystems[i], &header_reader, i); |
| 304 | 79 | if (R_FAILED(rc)) { | |
| 305 | if (section.bktr.relocation.offset + section.bktr.relocation.size != | 80 | LOG_ERROR(Loader, "File reader errored out during read of section {}: {:#x}", i, |
| 306 | section.bktr.subsection.offset) { | 81 | rc.GetInnerValue()); |
| 307 | status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation; | 82 | status = Loader::ResultStatus::ErrorBadNCAHeader; |
| 308 | return false; | 83 | return; |
| 309 | } | 84 | } |
| 310 | 85 | ||
| 311 | const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); | 86 | if (header_reader.GetFsType() == NcaFsHeader::FsType::RomFs) { |
| 312 | if (section.bktr.subsection.offset + section.bktr.subsection.size != size) { | 87 | files.push_back(filesystems[i]); |
| 313 | status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd; | 88 | romfs = files.back(); |
| 314 | return false; | ||
| 315 | } | 89 | } |
| 316 | 90 | ||
| 317 | const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; | 91 | if (header_reader.GetFsType() == NcaFsHeader::FsType::PartitionFs) { |
| 318 | RelocationBlock relocation_block{}; | 92 | auto npfs = std::make_shared<PartitionFilesystem>(filesystems[i]); |
| 319 | if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) != | 93 | if (npfs->GetStatus() == Loader::ResultStatus::Success) { |
| 320 | sizeof(RelocationBlock)) { | 94 | dirs.push_back(npfs); |
| 321 | status = Loader::ResultStatus::ErrorBadRelocationBlock; | 95 | if (IsDirectoryExeFS(npfs)) { |
| 322 | return false; | 96 | exefs = dirs.back(); |
| 323 | } | 97 | } else if (IsDirectoryLogoPartition(npfs)) { |
| 324 | SubsectionBlock subsection_block{}; | 98 | logo = dirs.back(); |
| 325 | if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) != | 99 | } else { |
| 326 | sizeof(RelocationBlock)) { | 100 | continue; |
| 327 | status = Loader::ResultStatus::ErrorBadSubsectionBlock; | ||
| 328 | return false; | ||
| 329 | } | ||
| 330 | |||
| 331 | std::vector<RelocationBucketRaw> relocation_buckets_raw( | ||
| 332 | (section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw)); | ||
| 333 | if (dec->ReadBytes(relocation_buckets_raw.data(), | ||
| 334 | section.bktr.relocation.size - sizeof(RelocationBlock), | ||
| 335 | section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) != | ||
| 336 | section.bktr.relocation.size - sizeof(RelocationBlock)) { | ||
| 337 | status = Loader::ResultStatus::ErrorBadRelocationBuckets; | ||
| 338 | return false; | ||
| 339 | } | ||
| 340 | |||
| 341 | std::vector<SubsectionBucketRaw> subsection_buckets_raw( | ||
| 342 | (section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw)); | ||
| 343 | if (dec->ReadBytes(subsection_buckets_raw.data(), | ||
| 344 | section.bktr.subsection.size - sizeof(SubsectionBlock), | ||
| 345 | section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) != | ||
| 346 | section.bktr.subsection.size - sizeof(SubsectionBlock)) { | ||
| 347 | status = Loader::ResultStatus::ErrorBadSubsectionBuckets; | ||
| 348 | return false; | ||
| 349 | } | ||
| 350 | |||
| 351 | std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size()); | ||
| 352 | std::ranges::transform(relocation_buckets_raw, relocation_buckets.begin(), | ||
| 353 | &ConvertRelocationBucketRaw); | ||
| 354 | std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size()); | ||
| 355 | std::ranges::transform(subsection_buckets_raw, subsection_buckets.begin(), | ||
| 356 | &ConvertSubsectionBucketRaw); | ||
| 357 | |||
| 358 | u32 ctr_low; | ||
| 359 | std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low)); | ||
| 360 | subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low}); | ||
| 361 | subsection_buckets.back().entries.push_back({size, {0}, 0}); | ||
| 362 | |||
| 363 | std::optional<Core::Crypto::Key128> key; | ||
| 364 | if (encrypted) { | ||
| 365 | if (has_rights_id) { | ||
| 366 | status = Loader::ResultStatus::Success; | ||
| 367 | key = GetTitlekey(); | ||
| 368 | if (!key) { | ||
| 369 | status = Loader::ResultStatus::ErrorMissingTitlekey; | ||
| 370 | return false; | ||
| 371 | } | ||
| 372 | } else { | ||
| 373 | key = GetKeyAreaKey(NCASectionCryptoType::BKTR); | ||
| 374 | if (!key) { | ||
| 375 | status = Loader::ResultStatus::ErrorMissingKeyAreaKey; | ||
| 376 | return false; | ||
| 377 | } | 101 | } |
| 378 | } | 102 | } |
| 379 | } | 103 | } |
| 380 | 104 | ||
| 381 | if (bktr_base_romfs == nullptr) { | 105 | // TODO: Is this correct?? |
| 382 | status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; | 106 | if (header_reader.GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx) { |
| 383 | return false; | 107 | is_update = true; |
| 384 | } | ||
| 385 | |||
| 386 | auto bktr = std::make_shared<BKTR>( | ||
| 387 | bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset), | ||
| 388 | relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted, | ||
| 389 | encrypted ? *key : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset, | ||
| 390 | section.raw.section_ctr); | ||
| 391 | |||
| 392 | // BKTR applies to entire IVFC, so make an offset version to level 6 | ||
| 393 | files.push_back(std::make_shared<OffsetVfsFile>( | ||
| 394 | bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset)); | ||
| 395 | } else { | ||
| 396 | files.push_back(std::move(dec)); | ||
| 397 | } | ||
| 398 | |||
| 399 | romfs = files.back(); | ||
| 400 | return true; | ||
| 401 | } | ||
| 402 | |||
| 403 | bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) { | ||
| 404 | const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) + | ||
| 405 | section.pfs0.pfs0_header_offset; | ||
| 406 | const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); | ||
| 407 | |||
| 408 | auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset); | ||
| 409 | if (dec != nullptr) { | ||
| 410 | auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec)); | ||
| 411 | |||
| 412 | if (npfs->GetStatus() == Loader::ResultStatus::Success) { | ||
| 413 | dirs.push_back(std::move(npfs)); | ||
| 414 | if (IsDirectoryExeFS(dirs.back())) | ||
| 415 | exefs = dirs.back(); | ||
| 416 | else if (IsDirectoryLogoPartition(dirs.back())) | ||
| 417 | logo = dirs.back(); | ||
| 418 | } else { | ||
| 419 | if (has_rights_id) | ||
| 420 | status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; | ||
| 421 | else | ||
| 422 | status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; | ||
| 423 | return false; | ||
| 424 | } | 108 | } |
| 425 | } else { | ||
| 426 | if (status != Loader::ResultStatus::Success) | ||
| 427 | return false; | ||
| 428 | if (has_rights_id) | ||
| 429 | status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; | ||
| 430 | else | ||
| 431 | status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; | ||
| 432 | return false; | ||
| 433 | } | ||
| 434 | |||
| 435 | return true; | ||
| 436 | } | ||
| 437 | |||
| 438 | u8 NCA::GetCryptoRevision() const { | ||
| 439 | u8 master_key_id = header.crypto_type; | ||
| 440 | if (header.crypto_type_2 > master_key_id) | ||
| 441 | master_key_id = header.crypto_type_2; | ||
| 442 | if (master_key_id > 0) | ||
| 443 | --master_key_id; | ||
| 444 | return master_key_id; | ||
| 445 | } | ||
| 446 | |||
| 447 | std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const { | ||
| 448 | const auto master_key_id = GetCryptoRevision(); | ||
| 449 | |||
| 450 | if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) { | ||
| 451 | return std::nullopt; | ||
| 452 | } | 109 | } |
| 453 | 110 | ||
| 454 | std::vector<u8> key_area(header.key_area.begin(), header.key_area.end()); | 111 | if (is_update && base_nca == nullptr) { |
| 455 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | 112 | status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; |
| 456 | keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index), | ||
| 457 | Core::Crypto::Mode::ECB); | ||
| 458 | cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt); | ||
| 459 | |||
| 460 | Core::Crypto::Key128 out{}; | ||
| 461 | if (type == NCASectionCryptoType::XTS) { | ||
| 462 | std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); | ||
| 463 | } else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) { | ||
| 464 | std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin()); | ||
| 465 | } else { | 113 | } else { |
| 466 | LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", | 114 | status = Loader::ResultStatus::Success; |
| 467 | type); | ||
| 468 | } | 115 | } |
| 469 | |||
| 470 | u128 out_128{}; | ||
| 471 | std::memcpy(out_128.data(), out.data(), sizeof(u128)); | ||
| 472 | LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", | ||
| 473 | master_key_id, header.key_index, out_128[1], out_128[0]); | ||
| 474 | |||
| 475 | return out; | ||
| 476 | } | 116 | } |
| 477 | 117 | ||
| 478 | std::optional<Core::Crypto::Key128> NCA::GetTitlekey() { | 118 | NCA::~NCA() = default; |
| 479 | const auto master_key_id = GetCryptoRevision(); | ||
| 480 | |||
| 481 | u128 rights_id{}; | ||
| 482 | memcpy(rights_id.data(), header.rights_id.data(), 16); | ||
| 483 | if (rights_id == u128{}) { | ||
| 484 | status = Loader::ResultStatus::ErrorInvalidRightsID; | ||
| 485 | return std::nullopt; | ||
| 486 | } | ||
| 487 | |||
| 488 | auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); | ||
| 489 | if (titlekey == Core::Crypto::Key128{}) { | ||
| 490 | status = Loader::ResultStatus::ErrorMissingTitlekey; | ||
| 491 | return std::nullopt; | ||
| 492 | } | ||
| 493 | |||
| 494 | if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { | ||
| 495 | status = Loader::ResultStatus::ErrorMissingTitlekek; | ||
| 496 | return std::nullopt; | ||
| 497 | } | ||
| 498 | |||
| 499 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | ||
| 500 | keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB); | ||
| 501 | cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt); | ||
| 502 | |||
| 503 | return titlekey; | ||
| 504 | } | ||
| 505 | |||
| 506 | VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) { | ||
| 507 | if (!encrypted) | ||
| 508 | return in; | ||
| 509 | |||
| 510 | switch (s_header.raw.header.crypto_type) { | ||
| 511 | case NCASectionCryptoType::NONE: | ||
| 512 | LOG_TRACE(Crypto, "called with mode=NONE"); | ||
| 513 | return in; | ||
| 514 | case NCASectionCryptoType::CTR: | ||
| 515 | // During normal BKTR decryption, this entire function is skipped. This is for the metadata, | ||
| 516 | // which uses the same CTR as usual. | ||
| 517 | case NCASectionCryptoType::BKTR: | ||
| 518 | LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); | ||
| 519 | { | ||
| 520 | std::optional<Core::Crypto::Key128> key; | ||
| 521 | if (has_rights_id) { | ||
| 522 | status = Loader::ResultStatus::Success; | ||
| 523 | key = GetTitlekey(); | ||
| 524 | if (!key) { | ||
| 525 | if (status == Loader::ResultStatus::Success) | ||
| 526 | status = Loader::ResultStatus::ErrorMissingTitlekey; | ||
| 527 | return nullptr; | ||
| 528 | } | ||
| 529 | } else { | ||
| 530 | key = GetKeyAreaKey(NCASectionCryptoType::CTR); | ||
| 531 | if (!key) { | ||
| 532 | status = Loader::ResultStatus::ErrorMissingKeyAreaKey; | ||
| 533 | return nullptr; | ||
| 534 | } | ||
| 535 | } | ||
| 536 | |||
| 537 | auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key, | ||
| 538 | starting_offset); | ||
| 539 | Core::Crypto::CTREncryptionLayer::IVData iv{}; | ||
| 540 | for (std::size_t i = 0; i < 8; ++i) { | ||
| 541 | iv[i] = s_header.raw.section_ctr[8 - i - 1]; | ||
| 542 | } | ||
| 543 | out->SetIV(iv); | ||
| 544 | return std::static_pointer_cast<VfsFile>(out); | ||
| 545 | } | ||
| 546 | case NCASectionCryptoType::XTS: | ||
| 547 | // TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs | ||
| 548 | default: | ||
| 549 | LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", | ||
| 550 | s_header.raw.header.crypto_type); | ||
| 551 | return nullptr; | ||
| 552 | } | ||
| 553 | } | ||
| 554 | 119 | ||
| 555 | Loader::ResultStatus NCA::GetStatus() const { | 120 | Loader::ResultStatus NCA::GetStatus() const { |
| 556 | return status; | 121 | return status; |
| @@ -579,21 +144,26 @@ VirtualDir NCA::GetParentDirectory() const { | |||
| 579 | } | 144 | } |
| 580 | 145 | ||
| 581 | NCAContentType NCA::GetType() const { | 146 | NCAContentType NCA::GetType() const { |
| 582 | return header.content_type; | 147 | u8 type = static_cast<u8>(reader->GetContentType()); |
| 148 | return static_cast<NCAContentType>(type); | ||
| 583 | } | 149 | } |
| 584 | 150 | ||
| 585 | u64 NCA::GetTitleId() const { | 151 | u64 NCA::GetTitleId() const { |
| 586 | if (is_update || status == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) | 152 | if (is_update) { |
| 587 | return header.title_id | 0x800; | 153 | return reader->GetProgramId() | 0x800; |
| 588 | return header.title_id; | 154 | } else { |
| 155 | return reader->GetProgramId(); | ||
| 156 | } | ||
| 589 | } | 157 | } |
| 590 | 158 | ||
| 591 | std::array<u8, 16> NCA::GetRightsId() const { | 159 | RightsId NCA::GetRightsId() const { |
| 592 | return header.rights_id; | 160 | RightsId result; |
| 161 | reader->GetRightsId(result.data(), result.size()); | ||
| 162 | return result; | ||
| 593 | } | 163 | } |
| 594 | 164 | ||
| 595 | u32 NCA::GetSDKVersion() const { | 165 | u32 NCA::GetSDKVersion() const { |
| 596 | return header.sdk_version; | 166 | return reader->GetSdkAddonVersion(); |
| 597 | } | 167 | } |
| 598 | 168 | ||
| 599 | bool NCA::IsUpdate() const { | 169 | bool NCA::IsUpdate() const { |
| @@ -612,10 +182,6 @@ VirtualFile NCA::GetBaseFile() const { | |||
| 612 | return file; | 182 | return file; |
| 613 | } | 183 | } |
| 614 | 184 | ||
| 615 | u64 NCA::GetBaseIVFCOffset() const { | ||
| 616 | return ivfc_offset; | ||
| 617 | } | ||
| 618 | |||
| 619 | VirtualDir NCA::GetLogoPartition() const { | 185 | VirtualDir NCA::GetLogoPartition() const { |
| 620 | return logo; | 186 | return logo; |
| 621 | } | 187 | } |
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 20f524f80..af521d453 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h | |||
| @@ -21,7 +21,7 @@ enum class ResultStatus : u16; | |||
| 21 | 21 | ||
| 22 | namespace FileSys { | 22 | namespace FileSys { |
| 23 | 23 | ||
| 24 | union NCASectionHeader; | 24 | class NcaReader; |
| 25 | 25 | ||
| 26 | /// Describes the type of content within an NCA archive. | 26 | /// Describes the type of content within an NCA archive. |
| 27 | enum class NCAContentType : u8 { | 27 | enum class NCAContentType : u8 { |
| @@ -45,41 +45,7 @@ enum class NCAContentType : u8 { | |||
| 45 | PublicData = 5, | 45 | PublicData = 5, |
| 46 | }; | 46 | }; |
| 47 | 47 | ||
| 48 | enum class NCASectionCryptoType : u8 { | 48 | using RightsId = std::array<u8, 0x10>; |
| 49 | NONE = 1, | ||
| 50 | XTS = 2, | ||
| 51 | CTR = 3, | ||
| 52 | BKTR = 4, | ||
| 53 | }; | ||
| 54 | |||
| 55 | struct NCASectionTableEntry { | ||
| 56 | u32_le media_offset; | ||
| 57 | u32_le media_end_offset; | ||
| 58 | INSERT_PADDING_BYTES(0x8); | ||
| 59 | }; | ||
| 60 | static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size."); | ||
| 61 | |||
| 62 | struct NCAHeader { | ||
| 63 | std::array<u8, 0x100> rsa_signature_1; | ||
| 64 | std::array<u8, 0x100> rsa_signature_2; | ||
| 65 | u32_le magic; | ||
| 66 | u8 is_system; | ||
| 67 | NCAContentType content_type; | ||
| 68 | u8 crypto_type; | ||
| 69 | u8 key_index; | ||
| 70 | u64_le size; | ||
| 71 | u64_le title_id; | ||
| 72 | INSERT_PADDING_BYTES(0x4); | ||
| 73 | u32_le sdk_version; | ||
| 74 | u8 crypto_type_2; | ||
| 75 | INSERT_PADDING_BYTES(15); | ||
| 76 | std::array<u8, 0x10> rights_id; | ||
| 77 | std::array<NCASectionTableEntry, 0x4> section_tables; | ||
| 78 | std::array<std::array<u8, 0x20>, 0x4> hash_tables; | ||
| 79 | std::array<u8, 0x40> key_area; | ||
| 80 | INSERT_PADDING_BYTES(0xC0); | ||
| 81 | }; | ||
| 82 | static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); | ||
| 83 | 49 | ||
| 84 | inline bool IsDirectoryExeFS(const VirtualDir& pfs) { | 50 | inline bool IsDirectoryExeFS(const VirtualDir& pfs) { |
| 85 | // According to switchbrew, an exefs must only contain these two files: | 51 | // According to switchbrew, an exefs must only contain these two files: |
| @@ -97,8 +63,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) { | |||
| 97 | // After construction, use GetStatus to determine if the file is valid and ready to be used. | 63 | // After construction, use GetStatus to determine if the file is valid and ready to be used. |
| 98 | class NCA : public ReadOnlyVfsDirectory { | 64 | class NCA : public ReadOnlyVfsDirectory { |
| 99 | public: | 65 | public: |
| 100 | explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr, | 66 | explicit NCA(VirtualFile file, const NCA* base_nca = nullptr); |
| 101 | u64 bktr_base_ivfc_offset = 0); | ||
| 102 | ~NCA() override; | 67 | ~NCA() override; |
| 103 | 68 | ||
| 104 | Loader::ResultStatus GetStatus() const; | 69 | Loader::ResultStatus GetStatus() const; |
| @@ -110,7 +75,7 @@ public: | |||
| 110 | 75 | ||
| 111 | NCAContentType GetType() const; | 76 | NCAContentType GetType() const; |
| 112 | u64 GetTitleId() const; | 77 | u64 GetTitleId() const; |
| 113 | std::array<u8, 0x10> GetRightsId() const; | 78 | RightsId GetRightsId() const; |
| 114 | u32 GetSDKVersion() const; | 79 | u32 GetSDKVersion() const; |
| 115 | bool IsUpdate() const; | 80 | bool IsUpdate() const; |
| 116 | 81 | ||
| @@ -119,26 +84,9 @@ public: | |||
| 119 | 84 | ||
| 120 | VirtualFile GetBaseFile() const; | 85 | VirtualFile GetBaseFile() const; |
| 121 | 86 | ||
| 122 | // Returns the base ivfc offset used in BKTR patching. | ||
| 123 | u64 GetBaseIVFCOffset() const; | ||
| 124 | |||
| 125 | VirtualDir GetLogoPartition() const; | 87 | VirtualDir GetLogoPartition() const; |
| 126 | 88 | ||
| 127 | private: | 89 | private: |
| 128 | bool CheckSupportedNCA(const NCAHeader& header); | ||
| 129 | bool HandlePotentialHeaderDecryption(); | ||
| 130 | |||
| 131 | std::vector<NCASectionHeader> ReadSectionHeaders() const; | ||
| 132 | bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset); | ||
| 133 | bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry, | ||
| 134 | u64 bktr_base_ivfc_offset); | ||
| 135 | bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry); | ||
| 136 | |||
| 137 | u8 GetCryptoRevision() const; | ||
| 138 | std::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const; | ||
| 139 | std::optional<Core::Crypto::Key128> GetTitlekey(); | ||
| 140 | VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset); | ||
| 141 | |||
| 142 | std::vector<VirtualDir> dirs; | 90 | std::vector<VirtualDir> dirs; |
| 143 | std::vector<VirtualFile> files; | 91 | std::vector<VirtualFile> files; |
| 144 | 92 | ||
| @@ -146,11 +94,6 @@ private: | |||
| 146 | VirtualDir exefs = nullptr; | 94 | VirtualDir exefs = nullptr; |
| 147 | VirtualDir logo = nullptr; | 95 | VirtualDir logo = nullptr; |
| 148 | VirtualFile file; | 96 | VirtualFile file; |
| 149 | VirtualFile bktr_base_romfs; | ||
| 150 | u64 ivfc_offset = 0; | ||
| 151 | |||
| 152 | NCAHeader header{}; | ||
| 153 | bool has_rights_id{}; | ||
| 154 | 97 | ||
| 155 | Loader::ResultStatus status{}; | 98 | Loader::ResultStatus status{}; |
| 156 | 99 | ||
| @@ -158,6 +101,7 @@ private: | |||
| 158 | bool is_update = false; | 101 | bool is_update = false; |
| 159 | 102 | ||
| 160 | Core::Crypto::KeyManager& keys; | 103 | Core::Crypto::KeyManager& keys; |
| 104 | std::shared_ptr<NcaReader> reader; | ||
| 161 | }; | 105 | }; |
| 162 | 106 | ||
| 163 | } // namespace FileSys | 107 | } // namespace FileSys |
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index 7cee0c7df..2f5045a67 100644 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h | |||
| @@ -17,4 +17,74 @@ constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001}; | |||
| 17 | constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; | 17 | constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; |
| 18 | constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; | 18 | constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; |
| 19 | 19 | ||
| 20 | constexpr Result ResultUnsupportedSdkVersion{ErrorModule::FS, 50}; | ||
| 21 | constexpr Result ResultPartitionNotFound{ErrorModule::FS, 1001}; | ||
| 22 | constexpr Result ResultUnsupportedVersion{ErrorModule::FS, 3002}; | ||
| 23 | constexpr Result ResultOutOfRange{ErrorModule::FS, 3005}; | ||
| 24 | constexpr Result ResultAllocationMemoryFailedInFileSystemBuddyHeapA{ErrorModule::FS, 3294}; | ||
| 25 | constexpr Result ResultAllocationMemoryFailedInNcaFileSystemDriverI{ErrorModule::FS, 3341}; | ||
| 26 | constexpr Result ResultAllocationMemoryFailedInNcaReaderA{ErrorModule::FS, 3363}; | ||
| 27 | constexpr Result ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA{ErrorModule::FS, 3399}; | ||
| 28 | constexpr Result ResultAllocationMemoryFailedInIntegrityRomFsStorageA{ErrorModule::FS, 3412}; | ||
| 29 | constexpr Result ResultAllocationMemoryFailedMakeUnique{ErrorModule::FS, 3422}; | ||
| 30 | constexpr Result ResultAllocationMemoryFailedAllocateShared{ErrorModule::FS, 3423}; | ||
| 31 | constexpr Result ResultInvalidAesCtrCounterExtendedEntryOffset{ErrorModule::FS, 4012}; | ||
| 32 | constexpr Result ResultIndirectStorageCorrupted{ErrorModule::FS, 4021}; | ||
| 33 | constexpr Result ResultInvalidIndirectEntryOffset{ErrorModule::FS, 4022}; | ||
| 34 | constexpr Result ResultInvalidIndirectEntryStorageIndex{ErrorModule::FS, 4023}; | ||
| 35 | constexpr Result ResultInvalidIndirectStorageSize{ErrorModule::FS, 4024}; | ||
| 36 | constexpr Result ResultInvalidBucketTreeSignature{ErrorModule::FS, 4032}; | ||
| 37 | constexpr Result ResultInvalidBucketTreeEntryCount{ErrorModule::FS, 4033}; | ||
| 38 | constexpr Result ResultInvalidBucketTreeNodeEntryCount{ErrorModule::FS, 4034}; | ||
| 39 | constexpr Result ResultInvalidBucketTreeNodeOffset{ErrorModule::FS, 4035}; | ||
| 40 | constexpr Result ResultInvalidBucketTreeEntryOffset{ErrorModule::FS, 4036}; | ||
| 41 | constexpr Result ResultInvalidBucketTreeEntrySetOffset{ErrorModule::FS, 4037}; | ||
| 42 | constexpr Result ResultInvalidBucketTreeNodeIndex{ErrorModule::FS, 4038}; | ||
| 43 | constexpr Result ResultInvalidBucketTreeVirtualOffset{ErrorModule::FS, 4039}; | ||
| 44 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashType{ErrorModule::FS, 4084}; | ||
| 45 | constexpr Result ResultRomNcaInvalidIntegrityLayerInfoOffset{ErrorModule::FS, 4085}; | ||
| 46 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataSize{ErrorModule::FS, 4086}; | ||
| 47 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataOffset{ErrorModule::FS, 4087}; | ||
| 48 | constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataHash{ErrorModule::FS, 4088}; | ||
| 49 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashType{ErrorModule::FS, 4089}; | ||
| 50 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataSize{ErrorModule::FS, 4090}; | ||
| 51 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataOffset{ErrorModule::FS, 4091}; | ||
| 52 | constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataHash{ErrorModule::FS, 4091}; | ||
| 53 | constexpr Result ResultNcaBaseStorageOutOfRangeB{ErrorModule::FS, 4509}; | ||
| 54 | constexpr Result ResultNcaBaseStorageOutOfRangeC{ErrorModule::FS, 4510}; | ||
| 55 | constexpr Result ResultNcaBaseStorageOutOfRangeD{ErrorModule::FS, 4511}; | ||
| 56 | constexpr Result ResultInvalidNcaSignature{ErrorModule::FS, 4517}; | ||
| 57 | constexpr Result ResultNcaFsHeaderHashVerificationFailed{ErrorModule::FS, 4520}; | ||
| 58 | constexpr Result ResultInvalidNcaKeyIndex{ErrorModule::FS, 4521}; | ||
| 59 | constexpr Result ResultInvalidNcaFsHeaderHashType{ErrorModule::FS, 4522}; | ||
| 60 | constexpr Result ResultInvalidNcaFsHeaderEncryptionType{ErrorModule::FS, 4523}; | ||
| 61 | constexpr Result ResultInvalidNcaPatchInfoIndirectSize{ErrorModule::FS, 4524}; | ||
| 62 | constexpr Result ResultInvalidNcaPatchInfoAesCtrExSize{ErrorModule::FS, 4525}; | ||
| 63 | constexpr Result ResultInvalidNcaPatchInfoAesCtrExOffset{ErrorModule::FS, 4526}; | ||
| 64 | constexpr Result ResultInvalidNcaHeader{ErrorModule::FS, 4528}; | ||
| 65 | constexpr Result ResultInvalidNcaFsHeader{ErrorModule::FS, 4529}; | ||
| 66 | constexpr Result ResultNcaBaseStorageOutOfRangeE{ErrorModule::FS, 4530}; | ||
| 67 | constexpr Result ResultInvalidHierarchicalSha256BlockSize{ErrorModule::FS, 4532}; | ||
| 68 | constexpr Result ResultInvalidHierarchicalSha256LayerCount{ErrorModule::FS, 4533}; | ||
| 69 | constexpr Result ResultHierarchicalSha256BaseStorageTooLarge{ErrorModule::FS, 4534}; | ||
| 70 | constexpr Result ResultHierarchicalSha256HashVerificationFailed{ErrorModule::FS, 4535}; | ||
| 71 | constexpr Result ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount{ErrorModule::FS, 4541}; | ||
| 72 | constexpr Result ResultInvalidNcaIndirectStorageOutOfRange{ErrorModule::FS, 4542}; | ||
| 73 | constexpr Result ResultInvalidNcaHeader1SignatureKeyGeneration{ErrorModule::FS, 4543}; | ||
| 74 | constexpr Result ResultInvalidCompressedStorageSize{ErrorModule::FS, 4547}; | ||
| 75 | constexpr Result ResultInvalidNcaMetaDataHashDataSize{ErrorModule::FS, 4548}; | ||
| 76 | constexpr Result ResultInvalidNcaMetaDataHashDataHash{ErrorModule::FS, 4549}; | ||
| 77 | constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324}; | ||
| 78 | constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325}; | ||
| 79 | constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326}; | ||
| 80 | constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327}; | ||
| 81 | constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001}; | ||
| 82 | constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061}; | ||
| 83 | constexpr Result ResultInvalidSize{ErrorModule::FS, 6062}; | ||
| 84 | constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063}; | ||
| 85 | constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325}; | ||
| 86 | constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387}; | ||
| 87 | constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388}; | ||
| 88 | constexpr Result ResultBufferAllocationFailed{ErrorModule::FS, 6705}; | ||
| 89 | |||
| 20 | } // namespace FileSys | 90 | } // namespace FileSys |
diff --git a/src/core/file_sys/fssystem/fs_i_storage.h b/src/core/file_sys/fssystem/fs_i_storage.h new file mode 100644 index 000000000..416dd57b8 --- /dev/null +++ b/src/core/file_sys/fssystem/fs_i_storage.h | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/overflow.h" | ||
| 7 | #include "core/file_sys/errors.h" | ||
| 8 | #include "core/file_sys/vfs.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | class IStorage : public VfsFile { | ||
| 13 | public: | ||
| 14 | virtual std::string GetName() const override { | ||
| 15 | return {}; | ||
| 16 | } | ||
| 17 | |||
| 18 | virtual VirtualDir GetContainingDirectory() const override { | ||
| 19 | return {}; | ||
| 20 | } | ||
| 21 | |||
| 22 | virtual bool IsWritable() const override { | ||
| 23 | return true; | ||
| 24 | } | ||
| 25 | |||
| 26 | virtual bool IsReadable() const override { | ||
| 27 | return true; | ||
| 28 | } | ||
| 29 | |||
| 30 | virtual bool Resize(size_t size) override { | ||
| 31 | return false; | ||
| 32 | } | ||
| 33 | |||
| 34 | virtual bool Rename(std::string_view name) override { | ||
| 35 | return false; | ||
| 36 | } | ||
| 37 | |||
| 38 | static inline Result CheckAccessRange(s64 offset, s64 size, s64 total_size) { | ||
| 39 | R_UNLESS(offset >= 0, ResultInvalidOffset); | ||
| 40 | R_UNLESS(size >= 0, ResultInvalidSize); | ||
| 41 | R_UNLESS(Common::WrappingAdd(offset, size) >= offset, ResultOutOfRange); | ||
| 42 | R_UNLESS(offset + size <= total_size, ResultOutOfRange); | ||
| 43 | R_SUCCEED(); | ||
| 44 | } | ||
| 45 | }; | ||
| 46 | |||
| 47 | class IReadOnlyStorage : public IStorage { | ||
| 48 | public: | ||
| 49 | virtual bool IsWritable() const override { | ||
| 50 | return false; | ||
| 51 | } | ||
| 52 | |||
| 53 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 54 | return 0; | ||
| 55 | } | ||
| 56 | }; | ||
| 57 | |||
| 58 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fs_types.h b/src/core/file_sys/fssystem/fs_types.h new file mode 100644 index 000000000..43aeaf447 --- /dev/null +++ b/src/core/file_sys/fssystem/fs_types.h | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | struct Int64 { | ||
| 11 | u32 low; | ||
| 12 | u32 high; | ||
| 13 | |||
| 14 | constexpr void Set(s64 v) { | ||
| 15 | this->low = static_cast<u32>((v & static_cast<u64>(0x00000000FFFFFFFFULL)) >> 0); | ||
| 16 | this->high = static_cast<u32>((v & static_cast<u64>(0xFFFFFFFF00000000ULL)) >> 32); | ||
| 17 | } | ||
| 18 | |||
| 19 | constexpr s64 Get() const { | ||
| 20 | return (static_cast<s64>(this->high) << 32) | (static_cast<s64>(this->low)); | ||
| 21 | } | ||
| 22 | |||
| 23 | constexpr Int64& operator=(s64 v) { | ||
| 24 | this->Set(v); | ||
| 25 | return *this; | ||
| 26 | } | ||
| 27 | |||
| 28 | constexpr operator s64() const { | ||
| 29 | return this->Get(); | ||
| 30 | } | ||
| 31 | }; | ||
| 32 | |||
| 33 | struct HashSalt { | ||
| 34 | static constexpr size_t Size = 32; | ||
| 35 | |||
| 36 | std::array<u8, Size> value; | ||
| 37 | }; | ||
| 38 | static_assert(std::is_trivial_v<HashSalt>); | ||
| 39 | static_assert(sizeof(HashSalt) == HashSalt::Size); | ||
| 40 | |||
| 41 | constexpr inline size_t IntegrityMinLayerCount = 2; | ||
| 42 | constexpr inline size_t IntegrityMaxLayerCount = 7; | ||
| 43 | constexpr inline size_t IntegrityLayerCountSave = 5; | ||
| 44 | constexpr inline size_t IntegrityLayerCountSaveDataMeta = 4; | ||
| 45 | |||
| 46 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp new file mode 100644 index 000000000..bf189c606 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp | |||
| @@ -0,0 +1,252 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 7 | #include "core/file_sys/vfs_offset.h" | ||
| 8 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | namespace { | ||
| 12 | |||
| 13 | class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor { | ||
| 14 | public: | ||
| 15 | virtual void Decrypt( | ||
| 16 | u8* buf, size_t buf_size, const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key, | ||
| 17 | const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) override final; | ||
| 18 | }; | ||
| 19 | |||
| 20 | } // namespace | ||
| 21 | |||
| 22 | Result AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out) { | ||
| 23 | std::unique_ptr<IDecryptor> decryptor = std::make_unique<SoftwareDecryptor>(); | ||
| 24 | R_UNLESS(decryptor != nullptr, ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA); | ||
| 25 | *out = std::move(decryptor); | ||
| 26 | R_SUCCEED(); | ||
| 27 | } | ||
| 28 | |||
| 29 | Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value, | ||
| 30 | VirtualFile data_storage, | ||
| 31 | VirtualFile table_storage) { | ||
| 32 | // Read and verify the bucket tree header. | ||
| 33 | BucketTree::Header header; | ||
| 34 | table_storage->ReadObject(std::addressof(header), 0); | ||
| 35 | R_TRY(header.Verify()); | ||
| 36 | |||
| 37 | // Determine extents. | ||
| 38 | const auto node_storage_size = QueryNodeStorageSize(header.entry_count); | ||
| 39 | const auto entry_storage_size = QueryEntryStorageSize(header.entry_count); | ||
| 40 | const auto node_storage_offset = QueryHeaderStorageSize(); | ||
| 41 | const auto entry_storage_offset = node_storage_offset + node_storage_size; | ||
| 42 | |||
| 43 | // Create a software decryptor. | ||
| 44 | std::unique_ptr<IDecryptor> sw_decryptor; | ||
| 45 | R_TRY(CreateSoftwareDecryptor(std::addressof(sw_decryptor))); | ||
| 46 | |||
| 47 | // Initialize. | ||
| 48 | R_RETURN(this->Initialize( | ||
| 49 | key, key_size, secure_value, 0, data_storage, | ||
| 50 | std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset), | ||
| 51 | std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset), | ||
| 52 | header.entry_count, std::move(sw_decryptor))); | ||
| 53 | } | ||
| 54 | |||
| 55 | Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value, | ||
| 56 | s64 counter_offset, VirtualFile data_storage, | ||
| 57 | VirtualFile node_storage, VirtualFile entry_storage, | ||
| 58 | s32 entry_count, | ||
| 59 | std::unique_ptr<IDecryptor>&& decryptor) { | ||
| 60 | // Validate preconditions. | ||
| 61 | ASSERT(key != nullptr); | ||
| 62 | ASSERT(key_size == KeySize); | ||
| 63 | ASSERT(counter_offset >= 0); | ||
| 64 | ASSERT(decryptor != nullptr); | ||
| 65 | |||
| 66 | // Initialize the bucket tree table. | ||
| 67 | if (entry_count > 0) { | ||
| 68 | R_TRY( | ||
| 69 | m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count)); | ||
| 70 | } else { | ||
| 71 | m_table.Initialize(NodeSize, 0); | ||
| 72 | } | ||
| 73 | |||
| 74 | // Set members. | ||
| 75 | m_data_storage = data_storage; | ||
| 76 | std::memcpy(m_key.data(), key, key_size); | ||
| 77 | m_secure_value = secure_value; | ||
| 78 | m_counter_offset = counter_offset; | ||
| 79 | m_decryptor = std::move(decryptor); | ||
| 80 | |||
| 81 | R_SUCCEED(); | ||
| 82 | } | ||
| 83 | |||
| 84 | void AesCtrCounterExtendedStorage::Finalize() { | ||
| 85 | if (this->IsInitialized()) { | ||
| 86 | m_table.Finalize(); | ||
| 87 | m_data_storage = VirtualFile(); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | Result AesCtrCounterExtendedStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, | ||
| 92 | s32 entry_count, s64 offset, s64 size) { | ||
| 93 | // Validate pre-conditions. | ||
| 94 | ASSERT(offset >= 0); | ||
| 95 | ASSERT(size >= 0); | ||
| 96 | ASSERT(this->IsInitialized()); | ||
| 97 | |||
| 98 | // Clear the out count. | ||
| 99 | R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument); | ||
| 100 | *out_entry_count = 0; | ||
| 101 | |||
| 102 | // Succeed if there's no range. | ||
| 103 | R_SUCCEED_IF(size == 0); | ||
| 104 | |||
| 105 | // If we have an output array, we need it to be non-null. | ||
| 106 | R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument); | ||
| 107 | |||
| 108 | // Check that our range is valid. | ||
| 109 | BucketTree::Offsets table_offsets; | ||
| 110 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 111 | |||
| 112 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 113 | |||
| 114 | // Find the offset in our tree. | ||
| 115 | BucketTree::Visitor visitor; | ||
| 116 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 117 | { | ||
| 118 | const auto entry_offset = visitor.Get<Entry>()->GetOffset(); | ||
| 119 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 120 | ResultInvalidAesCtrCounterExtendedEntryOffset); | ||
| 121 | } | ||
| 122 | |||
| 123 | // Prepare to loop over entries. | ||
| 124 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 125 | s32 count = 0; | ||
| 126 | |||
| 127 | auto cur_entry = *visitor.Get<Entry>(); | ||
| 128 | while (cur_entry.GetOffset() < end_offset) { | ||
| 129 | // Try to write the entry to the out list | ||
| 130 | if (entry_count != 0) { | ||
| 131 | if (count >= entry_count) { | ||
| 132 | break; | ||
| 133 | } | ||
| 134 | std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry)); | ||
| 135 | } | ||
| 136 | |||
| 137 | count++; | ||
| 138 | |||
| 139 | // Advance. | ||
| 140 | if (visitor.CanMoveNext()) { | ||
| 141 | R_TRY(visitor.MoveNext()); | ||
| 142 | cur_entry = *visitor.Get<Entry>(); | ||
| 143 | } else { | ||
| 144 | break; | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | // Write the output count. | ||
| 149 | *out_entry_count = count; | ||
| 150 | R_SUCCEED(); | ||
| 151 | } | ||
| 152 | |||
| 153 | size_t AesCtrCounterExtendedStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 154 | // Validate preconditions. | ||
| 155 | ASSERT(offset >= 0); | ||
| 156 | ASSERT(this->IsInitialized()); | ||
| 157 | |||
| 158 | // Allow zero size. | ||
| 159 | if (size == 0) { | ||
| 160 | return size; | ||
| 161 | } | ||
| 162 | |||
| 163 | // Validate arguments. | ||
| 164 | ASSERT(buffer != nullptr); | ||
| 165 | ASSERT(Common::IsAligned(offset, BlockSize)); | ||
| 166 | ASSERT(Common::IsAligned(size, BlockSize)); | ||
| 167 | |||
| 168 | BucketTree::Offsets table_offsets; | ||
| 169 | ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(table_offsets)))); | ||
| 170 | |||
| 171 | ASSERT(table_offsets.IsInclude(offset, size)); | ||
| 172 | |||
| 173 | // Read the data. | ||
| 174 | m_data_storage->Read(buffer, size, offset); | ||
| 175 | |||
| 176 | // Find the offset in our tree. | ||
| 177 | BucketTree::Visitor visitor; | ||
| 178 | ASSERT(R_SUCCEEDED(m_table.Find(std::addressof(visitor), offset))); | ||
| 179 | { | ||
| 180 | const auto entry_offset = visitor.Get<Entry>()->GetOffset(); | ||
| 181 | ASSERT(Common::IsAligned(entry_offset, BlockSize)); | ||
| 182 | ASSERT(0 <= entry_offset && table_offsets.IsInclude(entry_offset)); | ||
| 183 | } | ||
| 184 | |||
| 185 | // Prepare to read in chunks. | ||
| 186 | u8* cur_data = static_cast<u8*>(buffer); | ||
| 187 | auto cur_offset = offset; | ||
| 188 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 189 | |||
| 190 | while (cur_offset < end_offset) { | ||
| 191 | // Get the current entry. | ||
| 192 | const auto cur_entry = *visitor.Get<Entry>(); | ||
| 193 | |||
| 194 | // Get and validate the entry's offset. | ||
| 195 | const auto cur_entry_offset = cur_entry.GetOffset(); | ||
| 196 | ASSERT(static_cast<size_t>(cur_entry_offset) <= cur_offset); | ||
| 197 | |||
| 198 | // Get and validate the next entry offset. | ||
| 199 | s64 next_entry_offset; | ||
| 200 | if (visitor.CanMoveNext()) { | ||
| 201 | ASSERT(R_SUCCEEDED(visitor.MoveNext())); | ||
| 202 | next_entry_offset = visitor.Get<Entry>()->GetOffset(); | ||
| 203 | ASSERT(table_offsets.IsInclude(next_entry_offset)); | ||
| 204 | } else { | ||
| 205 | next_entry_offset = table_offsets.end_offset; | ||
| 206 | } | ||
| 207 | ASSERT(Common::IsAligned(next_entry_offset, BlockSize)); | ||
| 208 | ASSERT(cur_offset < static_cast<size_t>(next_entry_offset)); | ||
| 209 | |||
| 210 | // Get the offset of the entry in the data we read. | ||
| 211 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 212 | const auto data_size = (next_entry_offset - cur_entry_offset) - data_offset; | ||
| 213 | ASSERT(data_size > 0); | ||
| 214 | |||
| 215 | // Determine how much is left. | ||
| 216 | const auto remaining_size = end_offset - cur_offset; | ||
| 217 | const auto cur_size = static_cast<size_t>(std::min(remaining_size, data_size)); | ||
| 218 | ASSERT(cur_size <= size); | ||
| 219 | |||
| 220 | // If necessary, perform decryption. | ||
| 221 | if (cur_entry.encryption_value == Entry::Encryption::Encrypted) { | ||
| 222 | // Make the CTR for the data we're decrypting. | ||
| 223 | const auto counter_offset = m_counter_offset + cur_entry_offset + data_offset; | ||
| 224 | NcaAesCtrUpperIv upper_iv = { | ||
| 225 | .part = {.generation = static_cast<u32>(cur_entry.generation), | ||
| 226 | .secure_value = m_secure_value}}; | ||
| 227 | |||
| 228 | std::array<u8, IvSize> iv; | ||
| 229 | AesCtrStorage::MakeIv(iv.data(), IvSize, upper_iv.value, counter_offset); | ||
| 230 | |||
| 231 | // Decrypt. | ||
| 232 | m_decryptor->Decrypt(cur_data, cur_size, m_key, iv); | ||
| 233 | } | ||
| 234 | |||
| 235 | // Advance. | ||
| 236 | cur_data += cur_size; | ||
| 237 | cur_offset += cur_size; | ||
| 238 | } | ||
| 239 | |||
| 240 | return size; | ||
| 241 | } | ||
| 242 | |||
| 243 | void SoftwareDecryptor::Decrypt(u8* buf, size_t buf_size, | ||
| 244 | const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key, | ||
| 245 | const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) { | ||
| 246 | Core::Crypto::AESCipher<Core::Crypto::Key128, AesCtrCounterExtendedStorage::KeySize> cipher( | ||
| 247 | key, Core::Crypto::Mode::CTR); | ||
| 248 | cipher.SetIV(iv); | ||
| 249 | cipher.Transcode(buf, buf_size, buf, Core::Crypto::Op::Decrypt); | ||
| 250 | } | ||
| 251 | |||
| 252 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h new file mode 100644 index 000000000..a79904fad --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h | |||
| @@ -0,0 +1,114 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "common/literals.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | using namespace Common::Literals; | ||
| 15 | |||
| 16 | class AesCtrCounterExtendedStorage : public IReadOnlyStorage { | ||
| 17 | YUZU_NON_COPYABLE(AesCtrCounterExtendedStorage); | ||
| 18 | YUZU_NON_MOVEABLE(AesCtrCounterExtendedStorage); | ||
| 19 | |||
| 20 | public: | ||
| 21 | static constexpr size_t BlockSize = 0x10; | ||
| 22 | static constexpr size_t KeySize = 0x10; | ||
| 23 | static constexpr size_t IvSize = 0x10; | ||
| 24 | static constexpr size_t NodeSize = 16_KiB; | ||
| 25 | |||
| 26 | class IDecryptor { | ||
| 27 | public: | ||
| 28 | virtual ~IDecryptor() {} | ||
| 29 | virtual void Decrypt(u8* buf, size_t buf_size, const std::array<u8, KeySize>& key, | ||
| 30 | const std::array<u8, IvSize>& iv) = 0; | ||
| 31 | }; | ||
| 32 | |||
| 33 | struct Entry { | ||
| 34 | enum class Encryption : u8 { | ||
| 35 | Encrypted = 0, | ||
| 36 | NotEncrypted = 1, | ||
| 37 | }; | ||
| 38 | |||
| 39 | std::array<u8, sizeof(s64)> offset; | ||
| 40 | Encryption encryption_value; | ||
| 41 | std::array<u8, 3> reserved; | ||
| 42 | s32 generation; | ||
| 43 | |||
| 44 | void SetOffset(s64 value) { | ||
| 45 | std::memcpy(this->offset.data(), std::addressof(value), sizeof(s64)); | ||
| 46 | } | ||
| 47 | |||
| 48 | s64 GetOffset() const { | ||
| 49 | s64 value; | ||
| 50 | std::memcpy(std::addressof(value), this->offset.data(), sizeof(s64)); | ||
| 51 | return value; | ||
| 52 | } | ||
| 53 | }; | ||
| 54 | static_assert(sizeof(Entry) == 0x10); | ||
| 55 | static_assert(alignof(Entry) == 4); | ||
| 56 | static_assert(std::is_trivial_v<Entry>); | ||
| 57 | |||
| 58 | public: | ||
| 59 | static constexpr s64 QueryHeaderStorageSize() { | ||
| 60 | return BucketTree::QueryHeaderStorageSize(); | ||
| 61 | } | ||
| 62 | |||
| 63 | static constexpr s64 QueryNodeStorageSize(s32 entry_count) { | ||
| 64 | return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 65 | } | ||
| 66 | |||
| 67 | static constexpr s64 QueryEntryStorageSize(s32 entry_count) { | ||
| 68 | return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 69 | } | ||
| 70 | |||
| 71 | static Result CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out); | ||
| 72 | |||
| 73 | private: | ||
| 74 | mutable BucketTree m_table; | ||
| 75 | VirtualFile m_data_storage; | ||
| 76 | std::array<u8, KeySize> m_key; | ||
| 77 | u32 m_secure_value; | ||
| 78 | s64 m_counter_offset; | ||
| 79 | std::unique_ptr<IDecryptor> m_decryptor; | ||
| 80 | |||
| 81 | public: | ||
| 82 | AesCtrCounterExtendedStorage() | ||
| 83 | : m_table(), m_data_storage(), m_secure_value(), m_counter_offset(), m_decryptor() {} | ||
| 84 | virtual ~AesCtrCounterExtendedStorage() { | ||
| 85 | this->Finalize(); | ||
| 86 | } | ||
| 87 | |||
| 88 | Result Initialize(const void* key, size_t key_size, u32 secure_value, s64 counter_offset, | ||
| 89 | VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage, | ||
| 90 | s32 entry_count, std::unique_ptr<IDecryptor>&& decryptor); | ||
| 91 | void Finalize(); | ||
| 92 | |||
| 93 | bool IsInitialized() const { | ||
| 94 | return m_table.IsInitialized(); | ||
| 95 | } | ||
| 96 | |||
| 97 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 98 | |||
| 99 | virtual size_t GetSize() const override { | ||
| 100 | BucketTree::Offsets offsets; | ||
| 101 | ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(offsets)))); | ||
| 102 | |||
| 103 | return offsets.end_offset; | ||
| 104 | } | ||
| 105 | |||
| 106 | Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset, | ||
| 107 | s64 size); | ||
| 108 | |||
| 109 | private: | ||
| 110 | Result Initialize(const void* key, size_t key_size, u32 secure_value, VirtualFile data_storage, | ||
| 111 | VirtualFile table_storage); | ||
| 112 | }; | ||
| 113 | |||
| 114 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp new file mode 100644 index 000000000..b65aca18d --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp | |||
| @@ -0,0 +1,129 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/swap.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_utility.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | void AesCtrStorage::MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset) { | ||
| 13 | ASSERT(dst != nullptr); | ||
| 14 | ASSERT(dst_size == IvSize); | ||
| 15 | ASSERT(offset >= 0); | ||
| 16 | |||
| 17 | const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst); | ||
| 18 | |||
| 19 | *reinterpret_cast<u64_be*>(out_addr + 0) = upper; | ||
| 20 | *reinterpret_cast<s64_be*>(out_addr + sizeof(u64)) = static_cast<s64>(offset / BlockSize); | ||
| 21 | } | ||
| 22 | |||
| 23 | AesCtrStorage::AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv, | ||
| 24 | size_t iv_size) | ||
| 25 | : m_base_storage(std::move(base)) { | ||
| 26 | ASSERT(m_base_storage != nullptr); | ||
| 27 | ASSERT(key != nullptr); | ||
| 28 | ASSERT(iv != nullptr); | ||
| 29 | ASSERT(key_size == KeySize); | ||
| 30 | ASSERT(iv_size == IvSize); | ||
| 31 | |||
| 32 | std::memcpy(m_key.data(), key, KeySize); | ||
| 33 | std::memcpy(m_iv.data(), iv, IvSize); | ||
| 34 | |||
| 35 | m_cipher.emplace(m_key, Core::Crypto::Mode::CTR); | ||
| 36 | } | ||
| 37 | |||
| 38 | size_t AesCtrStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 39 | // Allow zero-size reads. | ||
| 40 | if (size == 0) { | ||
| 41 | return size; | ||
| 42 | } | ||
| 43 | |||
| 44 | // Ensure buffer is valid. | ||
| 45 | ASSERT(buffer != nullptr); | ||
| 46 | |||
| 47 | // We can only read at block aligned offsets. | ||
| 48 | ASSERT(Common::IsAligned(offset, BlockSize)); | ||
| 49 | ASSERT(Common::IsAligned(size, BlockSize)); | ||
| 50 | |||
| 51 | // Read the data. | ||
| 52 | m_base_storage->Read(buffer, size, offset); | ||
| 53 | |||
| 54 | // Setup the counter. | ||
| 55 | std::array<u8, IvSize> ctr; | ||
| 56 | std::memcpy(ctr.data(), m_iv.data(), IvSize); | ||
| 57 | AddCounter(ctr.data(), IvSize, offset / BlockSize); | ||
| 58 | |||
| 59 | // Decrypt. | ||
| 60 | m_cipher->SetIV(ctr); | ||
| 61 | m_cipher->Transcode(buffer, size, buffer, Core::Crypto::Op::Decrypt); | ||
| 62 | |||
| 63 | return size; | ||
| 64 | } | ||
| 65 | |||
| 66 | size_t AesCtrStorage::Write(const u8* buffer, size_t size, size_t offset) { | ||
| 67 | // Allow zero-size writes. | ||
| 68 | if (size == 0) { | ||
| 69 | return size; | ||
| 70 | } | ||
| 71 | |||
| 72 | // Ensure buffer is valid. | ||
| 73 | ASSERT(buffer != nullptr); | ||
| 74 | |||
| 75 | // We can only write at block aligned offsets. | ||
| 76 | ASSERT(Common::IsAligned(offset, BlockSize)); | ||
| 77 | ASSERT(Common::IsAligned(size, BlockSize)); | ||
| 78 | |||
| 79 | // Get a pooled buffer. | ||
| 80 | PooledBuffer pooled_buffer; | ||
| 81 | const bool use_work_buffer = true; | ||
| 82 | if (use_work_buffer) { | ||
| 83 | pooled_buffer.Allocate(size, BlockSize); | ||
| 84 | } | ||
| 85 | |||
| 86 | // Setup the counter. | ||
| 87 | std::array<u8, IvSize> ctr; | ||
| 88 | std::memcpy(ctr.data(), m_iv.data(), IvSize); | ||
| 89 | AddCounter(ctr.data(), IvSize, offset / BlockSize); | ||
| 90 | |||
| 91 | // Loop until all data is written. | ||
| 92 | size_t remaining = size; | ||
| 93 | s64 cur_offset = 0; | ||
| 94 | while (remaining > 0) { | ||
| 95 | // Determine data we're writing and where. | ||
| 96 | const size_t write_size = | ||
| 97 | use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining; | ||
| 98 | |||
| 99 | void* write_buf; | ||
| 100 | if (use_work_buffer) { | ||
| 101 | write_buf = pooled_buffer.GetBuffer(); | ||
| 102 | } else { | ||
| 103 | write_buf = const_cast<u8*>(buffer); | ||
| 104 | } | ||
| 105 | |||
| 106 | // Encrypt the data. | ||
| 107 | m_cipher->SetIV(ctr); | ||
| 108 | m_cipher->Transcode(buffer, write_size, reinterpret_cast<u8*>(write_buf), | ||
| 109 | Core::Crypto::Op::Encrypt); | ||
| 110 | |||
| 111 | // Write the encrypted data. | ||
| 112 | m_base_storage->Write(reinterpret_cast<u8*>(write_buf), write_size, offset + cur_offset); | ||
| 113 | |||
| 114 | // Advance. | ||
| 115 | cur_offset += write_size; | ||
| 116 | remaining -= write_size; | ||
| 117 | if (remaining > 0) { | ||
| 118 | AddCounter(ctr.data(), IvSize, write_size / BlockSize); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | return size; | ||
| 123 | } | ||
| 124 | |||
| 125 | size_t AesCtrStorage::GetSize() const { | ||
| 126 | return m_base_storage->GetSize(); | ||
| 127 | } | ||
| 128 | |||
| 129 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h new file mode 100644 index 000000000..bceb1f9ad --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "core/crypto/aes_util.h" | ||
| 9 | #include "core/crypto/key_manager.h" | ||
| 10 | #include "core/file_sys/errors.h" | ||
| 11 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 12 | #include "core/file_sys/vfs.h" | ||
| 13 | |||
| 14 | namespace FileSys { | ||
| 15 | |||
| 16 | class AesCtrStorage : public IStorage { | ||
| 17 | YUZU_NON_COPYABLE(AesCtrStorage); | ||
| 18 | YUZU_NON_MOVEABLE(AesCtrStorage); | ||
| 19 | |||
| 20 | public: | ||
| 21 | static constexpr size_t BlockSize = 0x10; | ||
| 22 | static constexpr size_t KeySize = 0x10; | ||
| 23 | static constexpr size_t IvSize = 0x10; | ||
| 24 | |||
| 25 | private: | ||
| 26 | VirtualFile m_base_storage; | ||
| 27 | std::array<u8, KeySize> m_key; | ||
| 28 | std::array<u8, IvSize> m_iv; | ||
| 29 | mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key128>> m_cipher; | ||
| 30 | |||
| 31 | public: | ||
| 32 | static void MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset); | ||
| 33 | |||
| 34 | public: | ||
| 35 | AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv, | ||
| 36 | size_t iv_size); | ||
| 37 | |||
| 38 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 39 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override; | ||
| 40 | virtual size_t GetSize() const override; | ||
| 41 | }; | ||
| 42 | |||
| 43 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp new file mode 100644 index 000000000..022424229 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp | |||
| @@ -0,0 +1,112 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/swap.h" | ||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_utility.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | void AesXtsStorage::MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size) { | ||
| 14 | ASSERT(dst != nullptr); | ||
| 15 | ASSERT(dst_size == IvSize); | ||
| 16 | ASSERT(offset >= 0); | ||
| 17 | |||
| 18 | const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst); | ||
| 19 | |||
| 20 | *reinterpret_cast<s64_be*>(out_addr + sizeof(s64)) = offset / block_size; | ||
| 21 | } | ||
| 22 | |||
| 23 | AesXtsStorage::AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size, | ||
| 24 | const void* iv, size_t iv_size, size_t block_size) | ||
| 25 | : m_base_storage(std::move(base)), m_block_size(block_size), m_mutex() { | ||
| 26 | ASSERT(m_base_storage != nullptr); | ||
| 27 | ASSERT(key1 != nullptr); | ||
| 28 | ASSERT(key2 != nullptr); | ||
| 29 | ASSERT(iv != nullptr); | ||
| 30 | ASSERT(key_size == KeySize); | ||
| 31 | ASSERT(iv_size == IvSize); | ||
| 32 | ASSERT(Common::IsAligned(m_block_size, AesBlockSize)); | ||
| 33 | |||
| 34 | std::memcpy(m_key.data() + 0, key1, KeySize); | ||
| 35 | std::memcpy(m_key.data() + 0x10, key2, KeySize); | ||
| 36 | std::memcpy(m_iv.data(), iv, IvSize); | ||
| 37 | |||
| 38 | m_cipher.emplace(m_key, Core::Crypto::Mode::XTS); | ||
| 39 | } | ||
| 40 | |||
| 41 | size_t AesXtsStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 42 | // Allow zero-size reads. | ||
| 43 | if (size == 0) { | ||
| 44 | return size; | ||
| 45 | } | ||
| 46 | |||
| 47 | // Ensure buffer is valid. | ||
| 48 | ASSERT(buffer != nullptr); | ||
| 49 | |||
| 50 | // We can only read at block aligned offsets. | ||
| 51 | ASSERT(Common::IsAligned(offset, AesBlockSize)); | ||
| 52 | ASSERT(Common::IsAligned(size, AesBlockSize)); | ||
| 53 | |||
| 54 | // Read the data. | ||
| 55 | m_base_storage->Read(buffer, size, offset); | ||
| 56 | |||
| 57 | // Setup the counter. | ||
| 58 | std::array<u8, IvSize> ctr; | ||
| 59 | std::memcpy(ctr.data(), m_iv.data(), IvSize); | ||
| 60 | AddCounter(ctr.data(), IvSize, offset / m_block_size); | ||
| 61 | |||
| 62 | // Handle any unaligned data before the start. | ||
| 63 | size_t processed_size = 0; | ||
| 64 | if ((offset % m_block_size) != 0) { | ||
| 65 | // Determine the size of the pre-data read. | ||
| 66 | const size_t skip_size = | ||
| 67 | static_cast<size_t>(offset - Common::AlignDown(offset, m_block_size)); | ||
| 68 | const size_t data_size = std::min(size, m_block_size - skip_size); | ||
| 69 | |||
| 70 | // Decrypt into a pooled buffer. | ||
| 71 | { | ||
| 72 | PooledBuffer tmp_buf(m_block_size, m_block_size); | ||
| 73 | ASSERT(tmp_buf.GetSize() >= m_block_size); | ||
| 74 | |||
| 75 | std::memset(tmp_buf.GetBuffer(), 0, skip_size); | ||
| 76 | std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size); | ||
| 77 | |||
| 78 | m_cipher->SetIV(ctr); | ||
| 79 | m_cipher->Transcode(tmp_buf.GetBuffer(), m_block_size, tmp_buf.GetBuffer(), | ||
| 80 | Core::Crypto::Op::Decrypt); | ||
| 81 | |||
| 82 | std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size); | ||
| 83 | } | ||
| 84 | |||
| 85 | AddCounter(ctr.data(), IvSize, 1); | ||
| 86 | processed_size += data_size; | ||
| 87 | ASSERT(processed_size == std::min(size, m_block_size - skip_size)); | ||
| 88 | } | ||
| 89 | |||
| 90 | // Decrypt aligned chunks. | ||
| 91 | char* cur = reinterpret_cast<char*>(buffer) + processed_size; | ||
| 92 | size_t remaining = size - processed_size; | ||
| 93 | while (remaining > 0) { | ||
| 94 | const size_t cur_size = std::min(m_block_size, remaining); | ||
| 95 | |||
| 96 | m_cipher->SetIV(ctr); | ||
| 97 | m_cipher->Transcode(cur, cur_size, cur, Core::Crypto::Op::Decrypt); | ||
| 98 | |||
| 99 | remaining -= cur_size; | ||
| 100 | cur += cur_size; | ||
| 101 | |||
| 102 | AddCounter(ctr.data(), IvSize, 1); | ||
| 103 | } | ||
| 104 | |||
| 105 | return size; | ||
| 106 | } | ||
| 107 | |||
| 108 | size_t AesXtsStorage::GetSize() const { | ||
| 109 | return m_base_storage->GetSize(); | ||
| 110 | } | ||
| 111 | |||
| 112 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h new file mode 100644 index 000000000..2307a2659 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "core/crypto/aes_util.h" | ||
| 9 | #include "core/crypto/key_manager.h" | ||
| 10 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | class AesXtsStorage : public IReadOnlyStorage { | ||
| 15 | YUZU_NON_COPYABLE(AesXtsStorage); | ||
| 16 | YUZU_NON_MOVEABLE(AesXtsStorage); | ||
| 17 | |||
| 18 | public: | ||
| 19 | static constexpr size_t AesBlockSize = 0x10; | ||
| 20 | static constexpr size_t KeySize = 0x20; | ||
| 21 | static constexpr size_t IvSize = 0x10; | ||
| 22 | |||
| 23 | private: | ||
| 24 | VirtualFile m_base_storage; | ||
| 25 | std::array<u8, KeySize> m_key; | ||
| 26 | std::array<u8, IvSize> m_iv; | ||
| 27 | const size_t m_block_size; | ||
| 28 | std::mutex m_mutex; | ||
| 29 | mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key256>> m_cipher; | ||
| 30 | |||
| 31 | public: | ||
| 32 | static void MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size); | ||
| 33 | |||
| 34 | public: | ||
| 35 | AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size, | ||
| 36 | const void* iv, size_t iv_size, size_t block_size); | ||
| 37 | |||
| 38 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 39 | virtual size_t GetSize() const override; | ||
| 40 | }; | ||
| 41 | |||
| 42 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h new file mode 100644 index 000000000..27d34fd17 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h | |||
| @@ -0,0 +1,146 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/alignment.h" | ||
| 7 | #include "core/file_sys/errors.h" | ||
| 8 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | template <size_t DataAlign_, size_t BufferAlign_> | ||
| 15 | class AlignmentMatchingStorage : public IStorage { | ||
| 16 | YUZU_NON_COPYABLE(AlignmentMatchingStorage); | ||
| 17 | YUZU_NON_MOVEABLE(AlignmentMatchingStorage); | ||
| 18 | |||
| 19 | public: | ||
| 20 | static constexpr size_t DataAlign = DataAlign_; | ||
| 21 | static constexpr size_t BufferAlign = BufferAlign_; | ||
| 22 | |||
| 23 | static constexpr size_t DataAlignMax = 0x200; | ||
| 24 | static_assert(DataAlign <= DataAlignMax); | ||
| 25 | static_assert(Common::IsPowerOfTwo(DataAlign)); | ||
| 26 | static_assert(Common::IsPowerOfTwo(BufferAlign)); | ||
| 27 | |||
| 28 | private: | ||
| 29 | VirtualFile m_base_storage; | ||
| 30 | s64 m_base_storage_size; | ||
| 31 | |||
| 32 | public: | ||
| 33 | explicit AlignmentMatchingStorage(VirtualFile bs) : m_base_storage(std::move(bs)) {} | ||
| 34 | |||
| 35 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 36 | // Allocate a work buffer on stack. | ||
| 37 | alignas(DataAlignMax) char work_buf[DataAlign]; | ||
| 38 | |||
| 39 | // Succeed if zero size. | ||
| 40 | if (size == 0) { | ||
| 41 | return size; | ||
| 42 | } | ||
| 43 | |||
| 44 | // Validate arguments. | ||
| 45 | ASSERT(buffer != nullptr); | ||
| 46 | |||
| 47 | s64 bs_size = this->GetSize(); | ||
| 48 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 49 | |||
| 50 | return AlignmentMatchingStorageImpl::Read(m_base_storage, work_buf, sizeof(work_buf), | ||
| 51 | DataAlign, BufferAlign, offset, buffer, size); | ||
| 52 | } | ||
| 53 | |||
| 54 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 55 | // Allocate a work buffer on stack. | ||
| 56 | alignas(DataAlignMax) char work_buf[DataAlign]; | ||
| 57 | |||
| 58 | // Succeed if zero size. | ||
| 59 | if (size == 0) { | ||
| 60 | return size; | ||
| 61 | } | ||
| 62 | |||
| 63 | // Validate arguments. | ||
| 64 | ASSERT(buffer != nullptr); | ||
| 65 | |||
| 66 | s64 bs_size = this->GetSize(); | ||
| 67 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 68 | |||
| 69 | return AlignmentMatchingStorageImpl::Write(m_base_storage, work_buf, sizeof(work_buf), | ||
| 70 | DataAlign, BufferAlign, offset, buffer, size); | ||
| 71 | } | ||
| 72 | |||
| 73 | virtual size_t GetSize() const override { | ||
| 74 | return m_base_storage->GetSize(); | ||
| 75 | } | ||
| 76 | }; | ||
| 77 | |||
| 78 | template <size_t BufferAlign_> | ||
| 79 | class AlignmentMatchingStoragePooledBuffer : public IStorage { | ||
| 80 | YUZU_NON_COPYABLE(AlignmentMatchingStoragePooledBuffer); | ||
| 81 | YUZU_NON_MOVEABLE(AlignmentMatchingStoragePooledBuffer); | ||
| 82 | |||
| 83 | public: | ||
| 84 | static constexpr size_t BufferAlign = BufferAlign_; | ||
| 85 | |||
| 86 | static_assert(Common::IsPowerOfTwo(BufferAlign)); | ||
| 87 | |||
| 88 | private: | ||
| 89 | VirtualFile m_base_storage; | ||
| 90 | s64 m_base_storage_size; | ||
| 91 | size_t m_data_align; | ||
| 92 | |||
| 93 | public: | ||
| 94 | explicit AlignmentMatchingStoragePooledBuffer(VirtualFile bs, size_t da) | ||
| 95 | : m_base_storage(std::move(bs)), m_data_align(da) { | ||
| 96 | ASSERT(Common::IsPowerOfTwo(da)); | ||
| 97 | } | ||
| 98 | |||
| 99 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 100 | // Succeed if zero size. | ||
| 101 | if (size == 0) { | ||
| 102 | return size; | ||
| 103 | } | ||
| 104 | |||
| 105 | // Validate arguments. | ||
| 106 | ASSERT(buffer != nullptr); | ||
| 107 | |||
| 108 | s64 bs_size = this->GetSize(); | ||
| 109 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 110 | |||
| 111 | // Allocate a pooled buffer. | ||
| 112 | PooledBuffer pooled_buffer; | ||
| 113 | pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align); | ||
| 114 | |||
| 115 | return AlignmentMatchingStorageImpl::Read(m_base_storage, pooled_buffer.GetBuffer(), | ||
| 116 | pooled_buffer.GetSize(), m_data_align, | ||
| 117 | BufferAlign, offset, buffer, size); | ||
| 118 | } | ||
| 119 | |||
| 120 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 121 | // Succeed if zero size. | ||
| 122 | if (size == 0) { | ||
| 123 | return size; | ||
| 124 | } | ||
| 125 | |||
| 126 | // Validate arguments. | ||
| 127 | ASSERT(buffer != nullptr); | ||
| 128 | |||
| 129 | s64 bs_size = this->GetSize(); | ||
| 130 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); | ||
| 131 | |||
| 132 | // Allocate a pooled buffer. | ||
| 133 | PooledBuffer pooled_buffer; | ||
| 134 | pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align); | ||
| 135 | |||
| 136 | return AlignmentMatchingStorageImpl::Write(m_base_storage, pooled_buffer.GetBuffer(), | ||
| 137 | pooled_buffer.GetSize(), m_data_align, | ||
| 138 | BufferAlign, offset, buffer, size); | ||
| 139 | } | ||
| 140 | |||
| 141 | virtual size_t GetSize() const override { | ||
| 142 | return m_base_storage->GetSize(); | ||
| 143 | } | ||
| 144 | }; | ||
| 145 | |||
| 146 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp new file mode 100644 index 000000000..641c888ae --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp | |||
| @@ -0,0 +1,204 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | |||
| 11 | template <typename T> | ||
| 12 | constexpr size_t GetRoundDownDifference(T x, size_t align) { | ||
| 13 | return static_cast<size_t>(x - Common::AlignDown(x, align)); | ||
| 14 | } | ||
| 15 | |||
| 16 | template <typename T> | ||
| 17 | constexpr size_t GetRoundUpDifference(T x, size_t align) { | ||
| 18 | return static_cast<size_t>(Common::AlignUp(x, align) - x); | ||
| 19 | } | ||
| 20 | |||
| 21 | template <typename T> | ||
| 22 | size_t GetRoundUpDifference(T* x, size_t align) { | ||
| 23 | return GetRoundUpDifference(reinterpret_cast<uintptr_t>(x), align); | ||
| 24 | } | ||
| 25 | |||
| 26 | } // namespace | ||
| 27 | |||
| 28 | size_t AlignmentMatchingStorageImpl::Read(VirtualFile base_storage, char* work_buf, | ||
| 29 | size_t work_buf_size, size_t data_alignment, | ||
| 30 | size_t buffer_alignment, s64 offset, u8* buffer, | ||
| 31 | size_t size) { | ||
| 32 | // Check preconditions. | ||
| 33 | ASSERT(work_buf_size >= data_alignment); | ||
| 34 | |||
| 35 | // Succeed if zero size. | ||
| 36 | if (size == 0) { | ||
| 37 | return size; | ||
| 38 | } | ||
| 39 | |||
| 40 | // Validate arguments. | ||
| 41 | ASSERT(buffer != nullptr); | ||
| 42 | |||
| 43 | // Determine extents. | ||
| 44 | u8* aligned_core_buffer; | ||
| 45 | s64 core_offset; | ||
| 46 | size_t core_size; | ||
| 47 | size_t buffer_gap; | ||
| 48 | size_t offset_gap; | ||
| 49 | s64 covered_offset; | ||
| 50 | |||
| 51 | const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment); | ||
| 52 | if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference, | ||
| 53 | buffer_alignment)) { | ||
| 54 | aligned_core_buffer = buffer + offset_round_up_difference; | ||
| 55 | |||
| 56 | core_offset = Common::AlignUp(offset, data_alignment); | ||
| 57 | core_size = (size < offset_round_up_difference) | ||
| 58 | ? 0 | ||
| 59 | : Common::AlignDown(size - offset_round_up_difference, data_alignment); | ||
| 60 | buffer_gap = 0; | ||
| 61 | offset_gap = 0; | ||
| 62 | |||
| 63 | covered_offset = core_size > 0 ? core_offset : offset; | ||
| 64 | } else { | ||
| 65 | const size_t buffer_round_up_difference = GetRoundUpDifference(buffer, buffer_alignment); | ||
| 66 | |||
| 67 | aligned_core_buffer = buffer + buffer_round_up_difference; | ||
| 68 | |||
| 69 | core_offset = Common::AlignDown(offset, data_alignment); | ||
| 70 | core_size = (size < buffer_round_up_difference) | ||
| 71 | ? 0 | ||
| 72 | : Common::AlignDown(size - buffer_round_up_difference, data_alignment); | ||
| 73 | buffer_gap = buffer_round_up_difference; | ||
| 74 | offset_gap = GetRoundDownDifference(offset, data_alignment); | ||
| 75 | |||
| 76 | covered_offset = offset; | ||
| 77 | } | ||
| 78 | |||
| 79 | // Read the core portion. | ||
| 80 | if (core_size > 0) { | ||
| 81 | base_storage->Read(aligned_core_buffer, core_size, core_offset); | ||
| 82 | |||
| 83 | if (offset_gap != 0 || buffer_gap != 0) { | ||
| 84 | std::memmove(aligned_core_buffer - buffer_gap, aligned_core_buffer + offset_gap, | ||
| 85 | core_size - offset_gap); | ||
| 86 | core_size -= offset_gap; | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | // Handle the head portion. | ||
| 91 | if (offset < covered_offset) { | ||
| 92 | const s64 head_offset = Common::AlignDown(offset, data_alignment); | ||
| 93 | const size_t head_size = static_cast<size_t>(covered_offset - offset); | ||
| 94 | |||
| 95 | ASSERT(GetRoundDownDifference(offset, data_alignment) + head_size <= work_buf_size); | ||
| 96 | |||
| 97 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); | ||
| 98 | std::memcpy(buffer, work_buf + GetRoundDownDifference(offset, data_alignment), head_size); | ||
| 99 | } | ||
| 100 | |||
| 101 | // Handle the tail portion. | ||
| 102 | s64 tail_offset = covered_offset + core_size; | ||
| 103 | size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset); | ||
| 104 | while (remaining_tail_size > 0) { | ||
| 105 | const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment); | ||
| 106 | const auto cur_size = | ||
| 107 | std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset), | ||
| 108 | remaining_tail_size); | ||
| 109 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); | ||
| 110 | |||
| 111 | ASSERT((tail_offset - offset) + cur_size <= size); | ||
| 112 | ASSERT((tail_offset - aligned_tail_offset) + cur_size <= data_alignment); | ||
| 113 | std::memcpy(reinterpret_cast<char*>(buffer) + (tail_offset - offset), | ||
| 114 | work_buf + (tail_offset - aligned_tail_offset), cur_size); | ||
| 115 | |||
| 116 | remaining_tail_size -= cur_size; | ||
| 117 | tail_offset += cur_size; | ||
| 118 | } | ||
| 119 | |||
| 120 | return size; | ||
| 121 | } | ||
| 122 | |||
| 123 | size_t AlignmentMatchingStorageImpl::Write(VirtualFile base_storage, char* work_buf, | ||
| 124 | size_t work_buf_size, size_t data_alignment, | ||
| 125 | size_t buffer_alignment, s64 offset, const u8* buffer, | ||
| 126 | size_t size) { | ||
| 127 | // Check preconditions. | ||
| 128 | ASSERT(work_buf_size >= data_alignment); | ||
| 129 | |||
| 130 | // Succeed if zero size. | ||
| 131 | if (size == 0) { | ||
| 132 | return size; | ||
| 133 | } | ||
| 134 | |||
| 135 | // Validate arguments. | ||
| 136 | ASSERT(buffer != nullptr); | ||
| 137 | |||
| 138 | // Determine extents. | ||
| 139 | const u8* aligned_core_buffer; | ||
| 140 | s64 core_offset; | ||
| 141 | size_t core_size; | ||
| 142 | s64 covered_offset; | ||
| 143 | |||
| 144 | const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment); | ||
| 145 | if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference, | ||
| 146 | buffer_alignment)) { | ||
| 147 | aligned_core_buffer = buffer + offset_round_up_difference; | ||
| 148 | |||
| 149 | core_offset = Common::AlignUp(offset, data_alignment); | ||
| 150 | core_size = (size < offset_round_up_difference) | ||
| 151 | ? 0 | ||
| 152 | : Common::AlignDown(size - offset_round_up_difference, data_alignment); | ||
| 153 | |||
| 154 | covered_offset = core_size > 0 ? core_offset : offset; | ||
| 155 | } else { | ||
| 156 | aligned_core_buffer = nullptr; | ||
| 157 | |||
| 158 | core_offset = Common::AlignDown(offset, data_alignment); | ||
| 159 | core_size = 0; | ||
| 160 | |||
| 161 | covered_offset = offset; | ||
| 162 | } | ||
| 163 | |||
| 164 | // Write the core portion. | ||
| 165 | if (core_size > 0) { | ||
| 166 | base_storage->Write(aligned_core_buffer, core_size, core_offset); | ||
| 167 | } | ||
| 168 | |||
| 169 | // Handle the head portion. | ||
| 170 | if (offset < covered_offset) { | ||
| 171 | const s64 head_offset = Common::AlignDown(offset, data_alignment); | ||
| 172 | const size_t head_size = static_cast<size_t>(covered_offset - offset); | ||
| 173 | |||
| 174 | ASSERT((offset - head_offset) + head_size <= data_alignment); | ||
| 175 | |||
| 176 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); | ||
| 177 | std::memcpy(work_buf + (offset - head_offset), buffer, head_size); | ||
| 178 | base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); | ||
| 179 | } | ||
| 180 | |||
| 181 | // Handle the tail portion. | ||
| 182 | s64 tail_offset = covered_offset + core_size; | ||
| 183 | size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset); | ||
| 184 | while (remaining_tail_size > 0) { | ||
| 185 | ASSERT(static_cast<size_t>(tail_offset - offset) < size); | ||
| 186 | |||
| 187 | const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment); | ||
| 188 | const auto cur_size = | ||
| 189 | std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset), | ||
| 190 | remaining_tail_size); | ||
| 191 | |||
| 192 | base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); | ||
| 193 | std::memcpy(work_buf + GetRoundDownDifference(tail_offset, data_alignment), | ||
| 194 | buffer + (tail_offset - offset), cur_size); | ||
| 195 | base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); | ||
| 196 | |||
| 197 | remaining_tail_size -= cur_size; | ||
| 198 | tail_offset += cur_size; | ||
| 199 | } | ||
| 200 | |||
| 201 | return size; | ||
| 202 | } | ||
| 203 | |||
| 204 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h new file mode 100644 index 000000000..4a05b0e88 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 8 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | class AlignmentMatchingStorageImpl { | ||
| 12 | public: | ||
| 13 | static size_t Read(VirtualFile base_storage, char* work_buf, size_t work_buf_size, | ||
| 14 | size_t data_alignment, size_t buffer_alignment, s64 offset, u8* buffer, | ||
| 15 | size_t size); | ||
| 16 | static size_t Write(VirtualFile base_storage, char* work_buf, size_t work_buf_size, | ||
| 17 | size_t data_alignment, size_t buffer_alignment, s64 offset, | ||
| 18 | const u8* buffer, size_t size); | ||
| 19 | }; | ||
| 20 | |||
| 21 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp new file mode 100644 index 000000000..699a366f1 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp | |||
| @@ -0,0 +1,598 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/errors.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 8 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | namespace { | ||
| 12 | |||
| 13 | using Node = impl::BucketTreeNode<const s64*>; | ||
| 14 | static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader)); | ||
| 15 | static_assert(std::is_trivial_v<Node>); | ||
| 16 | |||
| 17 | constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader); | ||
| 18 | |||
| 19 | class StorageNode { | ||
| 20 | private: | ||
| 21 | class Offset { | ||
| 22 | public: | ||
| 23 | using difference_type = s64; | ||
| 24 | |||
| 25 | private: | ||
| 26 | s64 m_offset; | ||
| 27 | s32 m_stride; | ||
| 28 | |||
| 29 | public: | ||
| 30 | constexpr Offset(s64 offset, s32 stride) : m_offset(offset), m_stride(stride) {} | ||
| 31 | |||
| 32 | constexpr Offset& operator++() { | ||
| 33 | m_offset += m_stride; | ||
| 34 | return *this; | ||
| 35 | } | ||
| 36 | constexpr Offset operator++(int) { | ||
| 37 | Offset ret(*this); | ||
| 38 | m_offset += m_stride; | ||
| 39 | return ret; | ||
| 40 | } | ||
| 41 | |||
| 42 | constexpr Offset& operator--() { | ||
| 43 | m_offset -= m_stride; | ||
| 44 | return *this; | ||
| 45 | } | ||
| 46 | constexpr Offset operator--(int) { | ||
| 47 | Offset ret(*this); | ||
| 48 | m_offset -= m_stride; | ||
| 49 | return ret; | ||
| 50 | } | ||
| 51 | |||
| 52 | constexpr difference_type operator-(const Offset& rhs) const { | ||
| 53 | return (m_offset - rhs.m_offset) / m_stride; | ||
| 54 | } | ||
| 55 | |||
| 56 | constexpr Offset operator+(difference_type ofs) const { | ||
| 57 | return Offset(m_offset + ofs * m_stride, m_stride); | ||
| 58 | } | ||
| 59 | constexpr Offset operator-(difference_type ofs) const { | ||
| 60 | return Offset(m_offset - ofs * m_stride, m_stride); | ||
| 61 | } | ||
| 62 | |||
| 63 | constexpr Offset& operator+=(difference_type ofs) { | ||
| 64 | m_offset += ofs * m_stride; | ||
| 65 | return *this; | ||
| 66 | } | ||
| 67 | constexpr Offset& operator-=(difference_type ofs) { | ||
| 68 | m_offset -= ofs * m_stride; | ||
| 69 | return *this; | ||
| 70 | } | ||
| 71 | |||
| 72 | constexpr bool operator==(const Offset& rhs) const { | ||
| 73 | return m_offset == rhs.m_offset; | ||
| 74 | } | ||
| 75 | constexpr bool operator!=(const Offset& rhs) const { | ||
| 76 | return m_offset != rhs.m_offset; | ||
| 77 | } | ||
| 78 | |||
| 79 | constexpr s64 Get() const { | ||
| 80 | return m_offset; | ||
| 81 | } | ||
| 82 | }; | ||
| 83 | |||
| 84 | private: | ||
| 85 | const Offset m_start; | ||
| 86 | const s32 m_count; | ||
| 87 | s32 m_index; | ||
| 88 | |||
| 89 | public: | ||
| 90 | StorageNode(size_t size, s32 count) | ||
| 91 | : m_start(NodeHeaderSize, static_cast<s32>(size)), m_count(count), m_index(-1) {} | ||
| 92 | StorageNode(s64 ofs, size_t size, s32 count) | ||
| 93 | : m_start(NodeHeaderSize + ofs, static_cast<s32>(size)), m_count(count), m_index(-1) {} | ||
| 94 | |||
| 95 | s32 GetIndex() const { | ||
| 96 | return m_index; | ||
| 97 | } | ||
| 98 | |||
| 99 | void Find(const char* buffer, s64 virtual_address) { | ||
| 100 | s32 end = m_count; | ||
| 101 | auto pos = m_start; | ||
| 102 | |||
| 103 | while (end > 0) { | ||
| 104 | auto half = end / 2; | ||
| 105 | auto mid = pos + half; | ||
| 106 | |||
| 107 | s64 offset = 0; | ||
| 108 | std::memcpy(std::addressof(offset), buffer + mid.Get(), sizeof(s64)); | ||
| 109 | |||
| 110 | if (offset <= virtual_address) { | ||
| 111 | pos = mid + 1; | ||
| 112 | end -= half + 1; | ||
| 113 | } else { | ||
| 114 | end = half; | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | m_index = static_cast<s32>(pos - m_start) - 1; | ||
| 119 | } | ||
| 120 | |||
| 121 | Result Find(VirtualFile storage, s64 virtual_address) { | ||
| 122 | s32 end = m_count; | ||
| 123 | auto pos = m_start; | ||
| 124 | |||
| 125 | while (end > 0) { | ||
| 126 | auto half = end / 2; | ||
| 127 | auto mid = pos + half; | ||
| 128 | |||
| 129 | s64 offset = 0; | ||
| 130 | storage->ReadObject(std::addressof(offset), mid.Get()); | ||
| 131 | |||
| 132 | if (offset <= virtual_address) { | ||
| 133 | pos = mid + 1; | ||
| 134 | end -= half + 1; | ||
| 135 | } else { | ||
| 136 | end = half; | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | m_index = static_cast<s32>(pos - m_start) - 1; | ||
| 141 | R_SUCCEED(); | ||
| 142 | } | ||
| 143 | }; | ||
| 144 | |||
| 145 | } // namespace | ||
| 146 | |||
| 147 | void BucketTree::Header::Format(s32 entry_count_) { | ||
| 148 | ASSERT(entry_count_ >= 0); | ||
| 149 | |||
| 150 | this->magic = Magic; | ||
| 151 | this->version = Version; | ||
| 152 | this->entry_count = entry_count_; | ||
| 153 | this->reserved = 0; | ||
| 154 | } | ||
| 155 | |||
| 156 | Result BucketTree::Header::Verify() const { | ||
| 157 | R_UNLESS(this->magic == Magic, ResultInvalidBucketTreeSignature); | ||
| 158 | R_UNLESS(this->entry_count >= 0, ResultInvalidBucketTreeEntryCount); | ||
| 159 | R_UNLESS(this->version <= Version, ResultUnsupportedVersion); | ||
| 160 | R_SUCCEED(); | ||
| 161 | } | ||
| 162 | |||
| 163 | Result BucketTree::NodeHeader::Verify(s32 node_index, size_t node_size, size_t entry_size) const { | ||
| 164 | R_UNLESS(this->index == node_index, ResultInvalidBucketTreeNodeIndex); | ||
| 165 | R_UNLESS(entry_size != 0 && node_size >= entry_size + NodeHeaderSize, ResultInvalidSize); | ||
| 166 | |||
| 167 | const size_t max_entry_count = (node_size - NodeHeaderSize) / entry_size; | ||
| 168 | R_UNLESS(this->count > 0 && static_cast<size_t>(this->count) <= max_entry_count, | ||
| 169 | ResultInvalidBucketTreeNodeEntryCount); | ||
| 170 | R_UNLESS(this->offset >= 0, ResultInvalidBucketTreeNodeOffset); | ||
| 171 | |||
| 172 | R_SUCCEED(); | ||
| 173 | } | ||
| 174 | |||
| 175 | Result BucketTree::Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size, | ||
| 176 | size_t entry_size, s32 entry_count) { | ||
| 177 | // Validate preconditions. | ||
| 178 | ASSERT(entry_size >= sizeof(s64)); | ||
| 179 | ASSERT(node_size >= entry_size + sizeof(NodeHeader)); | ||
| 180 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 181 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 182 | ASSERT(!this->IsInitialized()); | ||
| 183 | |||
| 184 | // Ensure valid entry count. | ||
| 185 | R_UNLESS(entry_count > 0, ResultInvalidArgument); | ||
| 186 | |||
| 187 | // Allocate node. | ||
| 188 | R_UNLESS(m_node_l1.Allocate(node_size), ResultBufferAllocationFailed); | ||
| 189 | ON_RESULT_FAILURE { | ||
| 190 | m_node_l1.Free(node_size); | ||
| 191 | }; | ||
| 192 | |||
| 193 | // Read node. | ||
| 194 | node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), node_size); | ||
| 195 | |||
| 196 | // Verify node. | ||
| 197 | R_TRY(m_node_l1->Verify(0, node_size, sizeof(s64))); | ||
| 198 | |||
| 199 | // Validate offsets. | ||
| 200 | const auto offset_count = GetOffsetCount(node_size); | ||
| 201 | const auto entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count); | ||
| 202 | const auto* const node = m_node_l1.Get<Node>(); | ||
| 203 | |||
| 204 | s64 start_offset; | ||
| 205 | if (offset_count < entry_set_count && node->GetCount() < offset_count) { | ||
| 206 | start_offset = *node->GetEnd(); | ||
| 207 | } else { | ||
| 208 | start_offset = *node->GetBegin(); | ||
| 209 | } | ||
| 210 | const auto end_offset = node->GetEndOffset(); | ||
| 211 | |||
| 212 | R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), | ||
| 213 | ResultInvalidBucketTreeEntryOffset); | ||
| 214 | R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset); | ||
| 215 | |||
| 216 | // Set member variables. | ||
| 217 | m_node_storage = node_storage; | ||
| 218 | m_entry_storage = entry_storage; | ||
| 219 | m_node_size = node_size; | ||
| 220 | m_entry_size = entry_size; | ||
| 221 | m_entry_count = entry_count; | ||
| 222 | m_offset_count = offset_count; | ||
| 223 | m_entry_set_count = entry_set_count; | ||
| 224 | |||
| 225 | m_offset_cache.offsets.start_offset = start_offset; | ||
| 226 | m_offset_cache.offsets.end_offset = end_offset; | ||
| 227 | m_offset_cache.is_initialized = true; | ||
| 228 | |||
| 229 | // Cancel guard. | ||
| 230 | R_SUCCEED(); | ||
| 231 | } | ||
| 232 | |||
| 233 | void BucketTree::Initialize(size_t node_size, s64 end_offset) { | ||
| 234 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 235 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 236 | ASSERT(end_offset > 0); | ||
| 237 | ASSERT(!this->IsInitialized()); | ||
| 238 | |||
| 239 | m_node_size = node_size; | ||
| 240 | |||
| 241 | m_offset_cache.offsets.start_offset = 0; | ||
| 242 | m_offset_cache.offsets.end_offset = end_offset; | ||
| 243 | m_offset_cache.is_initialized = true; | ||
| 244 | } | ||
| 245 | |||
| 246 | void BucketTree::Finalize() { | ||
| 247 | if (this->IsInitialized()) { | ||
| 248 | m_node_storage = VirtualFile(); | ||
| 249 | m_entry_storage = VirtualFile(); | ||
| 250 | m_node_l1.Free(m_node_size); | ||
| 251 | m_node_size = 0; | ||
| 252 | m_entry_size = 0; | ||
| 253 | m_entry_count = 0; | ||
| 254 | m_offset_count = 0; | ||
| 255 | m_entry_set_count = 0; | ||
| 256 | |||
| 257 | m_offset_cache.offsets.start_offset = 0; | ||
| 258 | m_offset_cache.offsets.end_offset = 0; | ||
| 259 | m_offset_cache.is_initialized = false; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | Result BucketTree::Find(Visitor* visitor, s64 virtual_address) { | ||
| 264 | ASSERT(visitor != nullptr); | ||
| 265 | ASSERT(this->IsInitialized()); | ||
| 266 | |||
| 267 | R_UNLESS(virtual_address >= 0, ResultInvalidOffset); | ||
| 268 | R_UNLESS(!this->IsEmpty(), ResultOutOfRange); | ||
| 269 | |||
| 270 | BucketTree::Offsets offsets; | ||
| 271 | R_TRY(this->GetOffsets(std::addressof(offsets))); | ||
| 272 | |||
| 273 | R_TRY(visitor->Initialize(this, offsets)); | ||
| 274 | |||
| 275 | R_RETURN(visitor->Find(virtual_address)); | ||
| 276 | } | ||
| 277 | |||
| 278 | Result BucketTree::InvalidateCache() { | ||
| 279 | // Reset our offsets. | ||
| 280 | m_offset_cache.is_initialized = false; | ||
| 281 | |||
| 282 | R_SUCCEED(); | ||
| 283 | } | ||
| 284 | |||
| 285 | Result BucketTree::EnsureOffsetCache() { | ||
| 286 | // If we already have an offset cache, we're good. | ||
| 287 | R_SUCCEED_IF(m_offset_cache.is_initialized); | ||
| 288 | |||
| 289 | // Acquire exclusive right to edit the offset cache. | ||
| 290 | std::scoped_lock lk(m_offset_cache.mutex); | ||
| 291 | |||
| 292 | // Check again, to be sure. | ||
| 293 | R_SUCCEED_IF(m_offset_cache.is_initialized); | ||
| 294 | |||
| 295 | // Read/verify L1. | ||
| 296 | m_node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), m_node_size); | ||
| 297 | R_TRY(m_node_l1->Verify(0, m_node_size, sizeof(s64))); | ||
| 298 | |||
| 299 | // Get the node. | ||
| 300 | auto* const node = m_node_l1.Get<Node>(); | ||
| 301 | |||
| 302 | s64 start_offset; | ||
| 303 | if (m_offset_count < m_entry_set_count && node->GetCount() < m_offset_count) { | ||
| 304 | start_offset = *node->GetEnd(); | ||
| 305 | } else { | ||
| 306 | start_offset = *node->GetBegin(); | ||
| 307 | } | ||
| 308 | const auto end_offset = node->GetEndOffset(); | ||
| 309 | |||
| 310 | R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), | ||
| 311 | ResultInvalidBucketTreeEntryOffset); | ||
| 312 | R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset); | ||
| 313 | |||
| 314 | m_offset_cache.offsets.start_offset = start_offset; | ||
| 315 | m_offset_cache.offsets.end_offset = end_offset; | ||
| 316 | m_offset_cache.is_initialized = true; | ||
| 317 | |||
| 318 | R_SUCCEED(); | ||
| 319 | } | ||
| 320 | |||
| 321 | Result BucketTree::Visitor::Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets) { | ||
| 322 | ASSERT(tree != nullptr); | ||
| 323 | ASSERT(m_tree == nullptr || m_tree == tree); | ||
| 324 | |||
| 325 | if (m_entry == nullptr) { | ||
| 326 | m_entry = ::operator new(tree->m_entry_size); | ||
| 327 | R_UNLESS(m_entry != nullptr, ResultBufferAllocationFailed); | ||
| 328 | |||
| 329 | m_tree = tree; | ||
| 330 | m_offsets = offsets; | ||
| 331 | } | ||
| 332 | |||
| 333 | R_SUCCEED(); | ||
| 334 | } | ||
| 335 | |||
| 336 | Result BucketTree::Visitor::MoveNext() { | ||
| 337 | R_UNLESS(this->IsValid(), ResultOutOfRange); | ||
| 338 | |||
| 339 | // Invalidate our index, and read the header for the next index. | ||
| 340 | auto entry_index = m_entry_index + 1; | ||
| 341 | if (entry_index == m_entry_set.info.count) { | ||
| 342 | const auto entry_set_index = m_entry_set.info.index + 1; | ||
| 343 | R_UNLESS(entry_set_index < m_entry_set_count, ResultOutOfRange); | ||
| 344 | |||
| 345 | m_entry_index = -1; | ||
| 346 | |||
| 347 | const auto end = m_entry_set.info.end; | ||
| 348 | |||
| 349 | const auto entry_set_size = m_tree->m_node_size; | ||
| 350 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 351 | |||
| 352 | m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset); | ||
| 353 | R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size)); | ||
| 354 | |||
| 355 | R_UNLESS(m_entry_set.info.start == end && m_entry_set.info.start < m_entry_set.info.end, | ||
| 356 | ResultInvalidBucketTreeEntrySetOffset); | ||
| 357 | |||
| 358 | entry_index = 0; | ||
| 359 | } else { | ||
| 360 | m_entry_index = 1; | ||
| 361 | } | ||
| 362 | |||
| 363 | // Read the new entry. | ||
| 364 | const auto entry_size = m_tree->m_entry_size; | ||
| 365 | const auto entry_offset = impl::GetBucketTreeEntryOffset( | ||
| 366 | m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index); | ||
| 367 | m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); | ||
| 368 | |||
| 369 | // Note that we changed index. | ||
| 370 | m_entry_index = entry_index; | ||
| 371 | R_SUCCEED(); | ||
| 372 | } | ||
| 373 | |||
| 374 | Result BucketTree::Visitor::MovePrevious() { | ||
| 375 | R_UNLESS(this->IsValid(), ResultOutOfRange); | ||
| 376 | |||
| 377 | // Invalidate our index, and read the header for the previous index. | ||
| 378 | auto entry_index = m_entry_index; | ||
| 379 | if (entry_index == 0) { | ||
| 380 | R_UNLESS(m_entry_set.info.index > 0, ResultOutOfRange); | ||
| 381 | |||
| 382 | m_entry_index = -1; | ||
| 383 | |||
| 384 | const auto start = m_entry_set.info.start; | ||
| 385 | |||
| 386 | const auto entry_set_size = m_tree->m_node_size; | ||
| 387 | const auto entry_set_index = m_entry_set.info.index - 1; | ||
| 388 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 389 | |||
| 390 | m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset); | ||
| 391 | R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size)); | ||
| 392 | |||
| 393 | R_UNLESS(m_entry_set.info.end == start && m_entry_set.info.start < m_entry_set.info.end, | ||
| 394 | ResultInvalidBucketTreeEntrySetOffset); | ||
| 395 | |||
| 396 | entry_index = m_entry_set.info.count; | ||
| 397 | } else { | ||
| 398 | m_entry_index = -1; | ||
| 399 | } | ||
| 400 | |||
| 401 | --entry_index; | ||
| 402 | |||
| 403 | // Read the new entry. | ||
| 404 | const auto entry_size = m_tree->m_entry_size; | ||
| 405 | const auto entry_offset = impl::GetBucketTreeEntryOffset( | ||
| 406 | m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index); | ||
| 407 | m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); | ||
| 408 | |||
| 409 | // Note that we changed index. | ||
| 410 | m_entry_index = entry_index; | ||
| 411 | R_SUCCEED(); | ||
| 412 | } | ||
| 413 | |||
| 414 | Result BucketTree::Visitor::Find(s64 virtual_address) { | ||
| 415 | ASSERT(m_tree != nullptr); | ||
| 416 | |||
| 417 | // Get the node. | ||
| 418 | const auto* const node = m_tree->m_node_l1.Get<Node>(); | ||
| 419 | R_UNLESS(virtual_address < node->GetEndOffset(), ResultOutOfRange); | ||
| 420 | |||
| 421 | // Get the entry set index. | ||
| 422 | s32 entry_set_index = -1; | ||
| 423 | if (m_tree->IsExistOffsetL2OnL1() && virtual_address < node->GetBeginOffset()) { | ||
| 424 | const auto start = node->GetEnd(); | ||
| 425 | const auto end = node->GetBegin() + m_tree->m_offset_count; | ||
| 426 | |||
| 427 | auto pos = std::upper_bound(start, end, virtual_address); | ||
| 428 | R_UNLESS(start < pos, ResultOutOfRange); | ||
| 429 | --pos; | ||
| 430 | |||
| 431 | entry_set_index = static_cast<s32>(pos - start); | ||
| 432 | } else { | ||
| 433 | const auto start = node->GetBegin(); | ||
| 434 | const auto end = node->GetEnd(); | ||
| 435 | |||
| 436 | auto pos = std::upper_bound(start, end, virtual_address); | ||
| 437 | R_UNLESS(start < pos, ResultOutOfRange); | ||
| 438 | --pos; | ||
| 439 | |||
| 440 | if (m_tree->IsExistL2()) { | ||
| 441 | const auto node_index = static_cast<s32>(pos - start); | ||
| 442 | R_UNLESS(0 <= node_index && node_index < m_tree->m_offset_count, | ||
| 443 | ResultInvalidBucketTreeNodeOffset); | ||
| 444 | |||
| 445 | R_TRY(this->FindEntrySet(std::addressof(entry_set_index), virtual_address, node_index)); | ||
| 446 | } else { | ||
| 447 | entry_set_index = static_cast<s32>(pos - start); | ||
| 448 | } | ||
| 449 | } | ||
| 450 | |||
| 451 | // Validate the entry set index. | ||
| 452 | R_UNLESS(0 <= entry_set_index && entry_set_index < m_tree->m_entry_set_count, | ||
| 453 | ResultInvalidBucketTreeNodeOffset); | ||
| 454 | |||
| 455 | // Find the entry. | ||
| 456 | R_TRY(this->FindEntry(virtual_address, entry_set_index)); | ||
| 457 | |||
| 458 | // Set count. | ||
| 459 | m_entry_set_count = m_tree->m_entry_set_count; | ||
| 460 | R_SUCCEED(); | ||
| 461 | } | ||
| 462 | |||
| 463 | Result BucketTree::Visitor::FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index) { | ||
| 464 | const auto node_size = m_tree->m_node_size; | ||
| 465 | |||
| 466 | PooledBuffer pool(node_size, 1); | ||
| 467 | if (node_size <= pool.GetSize()) { | ||
| 468 | R_RETURN( | ||
| 469 | this->FindEntrySetWithBuffer(out_index, virtual_address, node_index, pool.GetBuffer())); | ||
| 470 | } else { | ||
| 471 | pool.Deallocate(); | ||
| 472 | R_RETURN(this->FindEntrySetWithoutBuffer(out_index, virtual_address, node_index)); | ||
| 473 | } | ||
| 474 | } | ||
| 475 | |||
| 476 | Result BucketTree::Visitor::FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, | ||
| 477 | s32 node_index, char* buffer) { | ||
| 478 | // Calculate node extents. | ||
| 479 | const auto node_size = m_tree->m_node_size; | ||
| 480 | const auto node_offset = (node_index + 1) * static_cast<s64>(node_size); | ||
| 481 | VirtualFile storage = m_tree->m_node_storage; | ||
| 482 | |||
| 483 | // Read the node. | ||
| 484 | storage->Read(reinterpret_cast<u8*>(buffer), node_size, node_offset); | ||
| 485 | |||
| 486 | // Validate the header. | ||
| 487 | NodeHeader header; | ||
| 488 | std::memcpy(std::addressof(header), buffer, NodeHeaderSize); | ||
| 489 | R_TRY(header.Verify(node_index, node_size, sizeof(s64))); | ||
| 490 | |||
| 491 | // Create the node, and find. | ||
| 492 | StorageNode node(sizeof(s64), header.count); | ||
| 493 | node.Find(buffer, virtual_address); | ||
| 494 | R_UNLESS(node.GetIndex() >= 0, ResultInvalidBucketTreeVirtualOffset); | ||
| 495 | |||
| 496 | // Return the index. | ||
| 497 | *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex())); | ||
| 498 | R_SUCCEED(); | ||
| 499 | } | ||
| 500 | |||
| 501 | Result BucketTree::Visitor::FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, | ||
| 502 | s32 node_index) { | ||
| 503 | // Calculate node extents. | ||
| 504 | const auto node_size = m_tree->m_node_size; | ||
| 505 | const auto node_offset = (node_index + 1) * static_cast<s64>(node_size); | ||
| 506 | VirtualFile storage = m_tree->m_node_storage; | ||
| 507 | |||
| 508 | // Read and validate the header. | ||
| 509 | NodeHeader header; | ||
| 510 | storage->ReadObject(std::addressof(header), node_offset); | ||
| 511 | R_TRY(header.Verify(node_index, node_size, sizeof(s64))); | ||
| 512 | |||
| 513 | // Create the node, and find. | ||
| 514 | StorageNode node(node_offset, sizeof(s64), header.count); | ||
| 515 | R_TRY(node.Find(storage, virtual_address)); | ||
| 516 | R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); | ||
| 517 | |||
| 518 | // Return the index. | ||
| 519 | *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex())); | ||
| 520 | R_SUCCEED(); | ||
| 521 | } | ||
| 522 | |||
| 523 | Result BucketTree::Visitor::FindEntry(s64 virtual_address, s32 entry_set_index) { | ||
| 524 | const auto entry_set_size = m_tree->m_node_size; | ||
| 525 | |||
| 526 | PooledBuffer pool(entry_set_size, 1); | ||
| 527 | if (entry_set_size <= pool.GetSize()) { | ||
| 528 | R_RETURN(this->FindEntryWithBuffer(virtual_address, entry_set_index, pool.GetBuffer())); | ||
| 529 | } else { | ||
| 530 | pool.Deallocate(); | ||
| 531 | R_RETURN(this->FindEntryWithoutBuffer(virtual_address, entry_set_index)); | ||
| 532 | } | ||
| 533 | } | ||
| 534 | |||
| 535 | Result BucketTree::Visitor::FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, | ||
| 536 | char* buffer) { | ||
| 537 | // Calculate entry set extents. | ||
| 538 | const auto entry_size = m_tree->m_entry_size; | ||
| 539 | const auto entry_set_size = m_tree->m_node_size; | ||
| 540 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 541 | VirtualFile storage = m_tree->m_entry_storage; | ||
| 542 | |||
| 543 | // Read the entry set. | ||
| 544 | storage->Read(reinterpret_cast<u8*>(buffer), entry_set_size, entry_set_offset); | ||
| 545 | |||
| 546 | // Validate the entry_set. | ||
| 547 | EntrySetHeader entry_set; | ||
| 548 | std::memcpy(std::addressof(entry_set), buffer, sizeof(EntrySetHeader)); | ||
| 549 | R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size)); | ||
| 550 | |||
| 551 | // Create the node, and find. | ||
| 552 | StorageNode node(entry_size, entry_set.info.count); | ||
| 553 | node.Find(buffer, virtual_address); | ||
| 554 | R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); | ||
| 555 | |||
| 556 | // Copy the data into entry. | ||
| 557 | const auto entry_index = node.GetIndex(); | ||
| 558 | const auto entry_offset = impl::GetBucketTreeEntryOffset(0, entry_size, entry_index); | ||
| 559 | std::memcpy(m_entry, buffer + entry_offset, entry_size); | ||
| 560 | |||
| 561 | // Set our entry set/index. | ||
| 562 | m_entry_set = entry_set; | ||
| 563 | m_entry_index = entry_index; | ||
| 564 | |||
| 565 | R_SUCCEED(); | ||
| 566 | } | ||
| 567 | |||
| 568 | Result BucketTree::Visitor::FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index) { | ||
| 569 | // Calculate entry set extents. | ||
| 570 | const auto entry_size = m_tree->m_entry_size; | ||
| 571 | const auto entry_set_size = m_tree->m_node_size; | ||
| 572 | const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); | ||
| 573 | VirtualFile storage = m_tree->m_entry_storage; | ||
| 574 | |||
| 575 | // Read and validate the entry_set. | ||
| 576 | EntrySetHeader entry_set; | ||
| 577 | storage->ReadObject(std::addressof(entry_set), entry_set_offset); | ||
| 578 | R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size)); | ||
| 579 | |||
| 580 | // Create the node, and find. | ||
| 581 | StorageNode node(entry_set_offset, entry_size, entry_set.info.count); | ||
| 582 | R_TRY(node.Find(storage, virtual_address)); | ||
| 583 | R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); | ||
| 584 | |||
| 585 | // Copy the data into entry. | ||
| 586 | const auto entry_index = node.GetIndex(); | ||
| 587 | const auto entry_offset = | ||
| 588 | impl::GetBucketTreeEntryOffset(entry_set_offset, entry_size, entry_index); | ||
| 589 | storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); | ||
| 590 | |||
| 591 | // Set our entry set/index. | ||
| 592 | m_entry_set = entry_set; | ||
| 593 | m_entry_index = entry_index; | ||
| 594 | |||
| 595 | R_SUCCEED(); | ||
| 596 | } | ||
| 597 | |||
| 598 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.h b/src/core/file_sys/fssystem/fssystem_bucket_tree.h new file mode 100644 index 000000000..74a2f7583 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.h | |||
| @@ -0,0 +1,417 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <mutex> | ||
| 7 | |||
| 8 | #include "common/alignment.h" | ||
| 9 | #include "common/common_funcs.h" | ||
| 10 | #include "common/common_types.h" | ||
| 11 | #include "common/literals.h" | ||
| 12 | |||
| 13 | #include "core/file_sys/vfs.h" | ||
| 14 | #include "core/hle/result.h" | ||
| 15 | |||
| 16 | namespace FileSys { | ||
| 17 | |||
| 18 | using namespace Common::Literals; | ||
| 19 | |||
| 20 | class BucketTree { | ||
| 21 | YUZU_NON_COPYABLE(BucketTree); | ||
| 22 | YUZU_NON_MOVEABLE(BucketTree); | ||
| 23 | |||
| 24 | public: | ||
| 25 | static constexpr u32 Magic = Common::MakeMagic('B', 'K', 'T', 'R'); | ||
| 26 | static constexpr u32 Version = 1; | ||
| 27 | |||
| 28 | static constexpr size_t NodeSizeMin = 1_KiB; | ||
| 29 | static constexpr size_t NodeSizeMax = 512_KiB; | ||
| 30 | |||
| 31 | public: | ||
| 32 | class Visitor; | ||
| 33 | |||
| 34 | struct Header { | ||
| 35 | u32 magic; | ||
| 36 | u32 version; | ||
| 37 | s32 entry_count; | ||
| 38 | s32 reserved; | ||
| 39 | |||
| 40 | void Format(s32 entry_count); | ||
| 41 | Result Verify() const; | ||
| 42 | }; | ||
| 43 | static_assert(std::is_trivial_v<Header>); | ||
| 44 | static_assert(sizeof(Header) == 0x10); | ||
| 45 | |||
| 46 | struct NodeHeader { | ||
| 47 | s32 index; | ||
| 48 | s32 count; | ||
| 49 | s64 offset; | ||
| 50 | |||
| 51 | Result Verify(s32 node_index, size_t node_size, size_t entry_size) const; | ||
| 52 | }; | ||
| 53 | static_assert(std::is_trivial_v<NodeHeader>); | ||
| 54 | static_assert(sizeof(NodeHeader) == 0x10); | ||
| 55 | |||
| 56 | struct Offsets { | ||
| 57 | s64 start_offset; | ||
| 58 | s64 end_offset; | ||
| 59 | |||
| 60 | constexpr bool IsInclude(s64 offset) const { | ||
| 61 | return this->start_offset <= offset && offset < this->end_offset; | ||
| 62 | } | ||
| 63 | |||
| 64 | constexpr bool IsInclude(s64 offset, s64 size) const { | ||
| 65 | return size > 0 && this->start_offset <= offset && size <= (this->end_offset - offset); | ||
| 66 | } | ||
| 67 | }; | ||
| 68 | static_assert(std::is_trivial_v<Offsets>); | ||
| 69 | static_assert(sizeof(Offsets) == 0x10); | ||
| 70 | |||
| 71 | struct OffsetCache { | ||
| 72 | Offsets offsets; | ||
| 73 | std::mutex mutex; | ||
| 74 | bool is_initialized; | ||
| 75 | |||
| 76 | OffsetCache() : offsets{-1, -1}, mutex(), is_initialized(false) {} | ||
| 77 | }; | ||
| 78 | |||
| 79 | class ContinuousReadingInfo { | ||
| 80 | private: | ||
| 81 | size_t m_read_size; | ||
| 82 | s32 m_skip_count; | ||
| 83 | bool m_done; | ||
| 84 | |||
| 85 | public: | ||
| 86 | constexpr ContinuousReadingInfo() : m_read_size(), m_skip_count(), m_done() {} | ||
| 87 | |||
| 88 | constexpr void Reset() { | ||
| 89 | m_read_size = 0; | ||
| 90 | m_skip_count = 0; | ||
| 91 | m_done = false; | ||
| 92 | } | ||
| 93 | |||
| 94 | constexpr void SetSkipCount(s32 count) { | ||
| 95 | ASSERT(count >= 0); | ||
| 96 | m_skip_count = count; | ||
| 97 | } | ||
| 98 | constexpr s32 GetSkipCount() const { | ||
| 99 | return m_skip_count; | ||
| 100 | } | ||
| 101 | constexpr bool CheckNeedScan() { | ||
| 102 | return (--m_skip_count) <= 0; | ||
| 103 | } | ||
| 104 | |||
| 105 | constexpr void Done() { | ||
| 106 | m_read_size = 0; | ||
| 107 | m_done = true; | ||
| 108 | } | ||
| 109 | constexpr bool IsDone() const { | ||
| 110 | return m_done; | ||
| 111 | } | ||
| 112 | |||
| 113 | constexpr void SetReadSize(size_t size) { | ||
| 114 | m_read_size = size; | ||
| 115 | } | ||
| 116 | constexpr size_t GetReadSize() const { | ||
| 117 | return m_read_size; | ||
| 118 | } | ||
| 119 | constexpr bool CanDo() const { | ||
| 120 | return m_read_size > 0; | ||
| 121 | } | ||
| 122 | }; | ||
| 123 | |||
| 124 | private: | ||
| 125 | class NodeBuffer { | ||
| 126 | YUZU_NON_COPYABLE(NodeBuffer); | ||
| 127 | |||
| 128 | private: | ||
| 129 | void* m_header; | ||
| 130 | |||
| 131 | public: | ||
| 132 | NodeBuffer() : m_header() {} | ||
| 133 | |||
| 134 | ~NodeBuffer() { | ||
| 135 | ASSERT(m_header == nullptr); | ||
| 136 | } | ||
| 137 | |||
| 138 | NodeBuffer(NodeBuffer&& rhs) : m_header(rhs.m_header) { | ||
| 139 | rhs.m_header = nullptr; | ||
| 140 | } | ||
| 141 | |||
| 142 | NodeBuffer& operator=(NodeBuffer&& rhs) { | ||
| 143 | if (this != std::addressof(rhs)) { | ||
| 144 | ASSERT(m_header == nullptr); | ||
| 145 | |||
| 146 | m_header = rhs.m_header; | ||
| 147 | |||
| 148 | rhs.m_header = nullptr; | ||
| 149 | } | ||
| 150 | return *this; | ||
| 151 | } | ||
| 152 | |||
| 153 | bool Allocate(size_t node_size) { | ||
| 154 | ASSERT(m_header == nullptr); | ||
| 155 | |||
| 156 | m_header = ::operator new(node_size, std::align_val_t{sizeof(s64)}); | ||
| 157 | |||
| 158 | // ASSERT(Common::IsAligned(m_header, sizeof(s64))); | ||
| 159 | |||
| 160 | return m_header != nullptr; | ||
| 161 | } | ||
| 162 | |||
| 163 | void Free(size_t node_size) { | ||
| 164 | if (m_header) { | ||
| 165 | ::operator delete(m_header, std::align_val_t{sizeof(s64)}); | ||
| 166 | m_header = nullptr; | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | void FillZero(size_t node_size) const { | ||
| 171 | if (m_header) { | ||
| 172 | std::memset(m_header, 0, node_size); | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | NodeHeader* Get() const { | ||
| 177 | return reinterpret_cast<NodeHeader*>(m_header); | ||
| 178 | } | ||
| 179 | |||
| 180 | NodeHeader* operator->() const { | ||
| 181 | return this->Get(); | ||
| 182 | } | ||
| 183 | |||
| 184 | template <typename T> | ||
| 185 | T* Get() const { | ||
| 186 | static_assert(std::is_trivial_v<T>); | ||
| 187 | static_assert(sizeof(T) == sizeof(NodeHeader)); | ||
| 188 | return reinterpret_cast<T*>(m_header); | ||
| 189 | } | ||
| 190 | }; | ||
| 191 | |||
| 192 | private: | ||
| 193 | static constexpr s32 GetEntryCount(size_t node_size, size_t entry_size) { | ||
| 194 | return static_cast<s32>((node_size - sizeof(NodeHeader)) / entry_size); | ||
| 195 | } | ||
| 196 | |||
| 197 | static constexpr s32 GetOffsetCount(size_t node_size) { | ||
| 198 | return static_cast<s32>((node_size - sizeof(NodeHeader)) / sizeof(s64)); | ||
| 199 | } | ||
| 200 | |||
| 201 | static constexpr s32 GetEntrySetCount(size_t node_size, size_t entry_size, s32 entry_count) { | ||
| 202 | const s32 entry_count_per_node = GetEntryCount(node_size, entry_size); | ||
| 203 | return Common::DivideUp(entry_count, entry_count_per_node); | ||
| 204 | } | ||
| 205 | |||
| 206 | static constexpr s32 GetNodeL2Count(size_t node_size, size_t entry_size, s32 entry_count) { | ||
| 207 | const s32 offset_count_per_node = GetOffsetCount(node_size); | ||
| 208 | const s32 entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count); | ||
| 209 | |||
| 210 | if (entry_set_count <= offset_count_per_node) { | ||
| 211 | return 0; | ||
| 212 | } | ||
| 213 | |||
| 214 | const s32 node_l2_count = Common::DivideUp(entry_set_count, offset_count_per_node); | ||
| 215 | ASSERT(node_l2_count <= offset_count_per_node); | ||
| 216 | |||
| 217 | return Common::DivideUp(entry_set_count - (offset_count_per_node - (node_l2_count - 1)), | ||
| 218 | offset_count_per_node); | ||
| 219 | } | ||
| 220 | |||
| 221 | public: | ||
| 222 | static constexpr s64 QueryHeaderStorageSize() { | ||
| 223 | return sizeof(Header); | ||
| 224 | } | ||
| 225 | |||
| 226 | static constexpr s64 QueryNodeStorageSize(size_t node_size, size_t entry_size, | ||
| 227 | s32 entry_count) { | ||
| 228 | ASSERT(entry_size >= sizeof(s64)); | ||
| 229 | ASSERT(node_size >= entry_size + sizeof(NodeHeader)); | ||
| 230 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 231 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 232 | ASSERT(entry_count >= 0); | ||
| 233 | |||
| 234 | if (entry_count <= 0) { | ||
| 235 | return 0; | ||
| 236 | } | ||
| 237 | return (1 + GetNodeL2Count(node_size, entry_size, entry_count)) * | ||
| 238 | static_cast<s64>(node_size); | ||
| 239 | } | ||
| 240 | |||
| 241 | static constexpr s64 QueryEntryStorageSize(size_t node_size, size_t entry_size, | ||
| 242 | s32 entry_count) { | ||
| 243 | ASSERT(entry_size >= sizeof(s64)); | ||
| 244 | ASSERT(node_size >= entry_size + sizeof(NodeHeader)); | ||
| 245 | ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); | ||
| 246 | ASSERT(Common::IsPowerOfTwo(node_size)); | ||
| 247 | ASSERT(entry_count >= 0); | ||
| 248 | |||
| 249 | if (entry_count <= 0) { | ||
| 250 | return 0; | ||
| 251 | } | ||
| 252 | return GetEntrySetCount(node_size, entry_size, entry_count) * static_cast<s64>(node_size); | ||
| 253 | } | ||
| 254 | |||
| 255 | private: | ||
| 256 | mutable VirtualFile m_node_storage; | ||
| 257 | mutable VirtualFile m_entry_storage; | ||
| 258 | NodeBuffer m_node_l1; | ||
| 259 | size_t m_node_size; | ||
| 260 | size_t m_entry_size; | ||
| 261 | s32 m_entry_count; | ||
| 262 | s32 m_offset_count; | ||
| 263 | s32 m_entry_set_count; | ||
| 264 | OffsetCache m_offset_cache; | ||
| 265 | |||
| 266 | public: | ||
| 267 | BucketTree() | ||
| 268 | : m_node_storage(), m_entry_storage(), m_node_l1(), m_node_size(), m_entry_size(), | ||
| 269 | m_entry_count(), m_offset_count(), m_entry_set_count(), m_offset_cache() {} | ||
| 270 | ~BucketTree() { | ||
| 271 | this->Finalize(); | ||
| 272 | } | ||
| 273 | |||
| 274 | Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size, | ||
| 275 | size_t entry_size, s32 entry_count); | ||
| 276 | void Initialize(size_t node_size, s64 end_offset); | ||
| 277 | void Finalize(); | ||
| 278 | |||
| 279 | bool IsInitialized() const { | ||
| 280 | return m_node_size > 0; | ||
| 281 | } | ||
| 282 | bool IsEmpty() const { | ||
| 283 | return m_entry_size == 0; | ||
| 284 | } | ||
| 285 | |||
| 286 | Result Find(Visitor* visitor, s64 virtual_address); | ||
| 287 | Result InvalidateCache(); | ||
| 288 | |||
| 289 | s32 GetEntryCount() const { | ||
| 290 | return m_entry_count; | ||
| 291 | } | ||
| 292 | |||
| 293 | Result GetOffsets(Offsets* out) { | ||
| 294 | // Ensure we have an offset cache. | ||
| 295 | R_TRY(this->EnsureOffsetCache()); | ||
| 296 | |||
| 297 | // Set the output. | ||
| 298 | *out = m_offset_cache.offsets; | ||
| 299 | R_SUCCEED(); | ||
| 300 | } | ||
| 301 | |||
| 302 | private: | ||
| 303 | template <typename EntryType> | ||
| 304 | struct ContinuousReadingParam { | ||
| 305 | s64 offset; | ||
| 306 | size_t size; | ||
| 307 | NodeHeader entry_set; | ||
| 308 | s32 entry_index; | ||
| 309 | Offsets offsets; | ||
| 310 | EntryType entry; | ||
| 311 | }; | ||
| 312 | |||
| 313 | private: | ||
| 314 | template <typename EntryType> | ||
| 315 | Result ScanContinuousReading(ContinuousReadingInfo* out_info, | ||
| 316 | const ContinuousReadingParam<EntryType>& param) const; | ||
| 317 | |||
| 318 | bool IsExistL2() const { | ||
| 319 | return m_offset_count < m_entry_set_count; | ||
| 320 | } | ||
| 321 | bool IsExistOffsetL2OnL1() const { | ||
| 322 | return this->IsExistL2() && m_node_l1->count < m_offset_count; | ||
| 323 | } | ||
| 324 | |||
| 325 | s64 GetEntrySetIndex(s32 node_index, s32 offset_index) const { | ||
| 326 | return (m_offset_count - m_node_l1->count) + (m_offset_count * node_index) + offset_index; | ||
| 327 | } | ||
| 328 | |||
| 329 | Result EnsureOffsetCache(); | ||
| 330 | }; | ||
| 331 | |||
| 332 | class BucketTree::Visitor { | ||
| 333 | YUZU_NON_COPYABLE(Visitor); | ||
| 334 | YUZU_NON_MOVEABLE(Visitor); | ||
| 335 | |||
| 336 | private: | ||
| 337 | friend class BucketTree; | ||
| 338 | |||
| 339 | union EntrySetHeader { | ||
| 340 | NodeHeader header; | ||
| 341 | struct Info { | ||
| 342 | s32 index; | ||
| 343 | s32 count; | ||
| 344 | s64 end; | ||
| 345 | s64 start; | ||
| 346 | } info; | ||
| 347 | static_assert(std::is_trivial_v<Info>); | ||
| 348 | }; | ||
| 349 | static_assert(std::is_trivial_v<EntrySetHeader>); | ||
| 350 | |||
| 351 | private: | ||
| 352 | const BucketTree* m_tree; | ||
| 353 | BucketTree::Offsets m_offsets; | ||
| 354 | void* m_entry; | ||
| 355 | s32 m_entry_index; | ||
| 356 | s32 m_entry_set_count; | ||
| 357 | EntrySetHeader m_entry_set; | ||
| 358 | |||
| 359 | public: | ||
| 360 | constexpr Visitor() | ||
| 361 | : m_tree(), m_entry(), m_entry_index(-1), m_entry_set_count(), m_entry_set{} {} | ||
| 362 | ~Visitor() { | ||
| 363 | if (m_entry != nullptr) { | ||
| 364 | ::operator delete(m_entry, m_tree->m_entry_size); | ||
| 365 | m_tree = nullptr; | ||
| 366 | m_entry = nullptr; | ||
| 367 | } | ||
| 368 | } | ||
| 369 | |||
| 370 | bool IsValid() const { | ||
| 371 | return m_entry_index >= 0; | ||
| 372 | } | ||
| 373 | bool CanMoveNext() const { | ||
| 374 | return this->IsValid() && (m_entry_index + 1 < m_entry_set.info.count || | ||
| 375 | m_entry_set.info.index + 1 < m_entry_set_count); | ||
| 376 | } | ||
| 377 | bool CanMovePrevious() const { | ||
| 378 | return this->IsValid() && (m_entry_index > 0 || m_entry_set.info.index > 0); | ||
| 379 | } | ||
| 380 | |||
| 381 | Result MoveNext(); | ||
| 382 | Result MovePrevious(); | ||
| 383 | |||
| 384 | template <typename EntryType> | ||
| 385 | Result ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, size_t size) const; | ||
| 386 | |||
| 387 | const void* Get() const { | ||
| 388 | ASSERT(this->IsValid()); | ||
| 389 | return m_entry; | ||
| 390 | } | ||
| 391 | |||
| 392 | template <typename T> | ||
| 393 | const T* Get() const { | ||
| 394 | ASSERT(this->IsValid()); | ||
| 395 | return reinterpret_cast<const T*>(m_entry); | ||
| 396 | } | ||
| 397 | |||
| 398 | const BucketTree* GetTree() const { | ||
| 399 | return m_tree; | ||
| 400 | } | ||
| 401 | |||
| 402 | private: | ||
| 403 | Result Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets); | ||
| 404 | |||
| 405 | Result Find(s64 virtual_address); | ||
| 406 | |||
| 407 | Result FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index); | ||
| 408 | Result FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, s32 node_index, | ||
| 409 | char* buffer); | ||
| 410 | Result FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, s32 node_index); | ||
| 411 | |||
| 412 | Result FindEntry(s64 virtual_address, s32 entry_set_index); | ||
| 413 | Result FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char* buffer); | ||
| 414 | Result FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index); | ||
| 415 | }; | ||
| 416 | |||
| 417 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h new file mode 100644 index 000000000..030b2916b --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h | |||
| @@ -0,0 +1,170 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | template <typename EntryType> | ||
| 14 | Result BucketTree::ScanContinuousReading(ContinuousReadingInfo* out_info, | ||
| 15 | const ContinuousReadingParam<EntryType>& param) const { | ||
| 16 | static_assert(std::is_trivial_v<ContinuousReadingParam<EntryType>>); | ||
| 17 | |||
| 18 | // Validate our preconditions. | ||
| 19 | ASSERT(this->IsInitialized()); | ||
| 20 | ASSERT(out_info != nullptr); | ||
| 21 | ASSERT(m_entry_size == sizeof(EntryType)); | ||
| 22 | |||
| 23 | // Reset the output. | ||
| 24 | out_info->Reset(); | ||
| 25 | |||
| 26 | // If there's nothing to read, we're done. | ||
| 27 | R_SUCCEED_IF(param.size == 0); | ||
| 28 | |||
| 29 | // If we're reading a fragment, we're done. | ||
| 30 | R_SUCCEED_IF(param.entry.IsFragment()); | ||
| 31 | |||
| 32 | // Validate the first entry. | ||
| 33 | auto entry = param.entry; | ||
| 34 | auto cur_offset = param.offset; | ||
| 35 | R_UNLESS(entry.GetVirtualOffset() <= cur_offset, ResultOutOfRange); | ||
| 36 | |||
| 37 | // Create a pooled buffer for our scan. | ||
| 38 | PooledBuffer pool(m_node_size, 1); | ||
| 39 | char* buffer = nullptr; | ||
| 40 | |||
| 41 | s64 entry_storage_size = m_entry_storage->GetSize(); | ||
| 42 | |||
| 43 | // Read the node. | ||
| 44 | if (m_node_size <= pool.GetSize()) { | ||
| 45 | buffer = pool.GetBuffer(); | ||
| 46 | const auto ofs = param.entry_set.index * static_cast<s64>(m_node_size); | ||
| 47 | R_UNLESS(m_node_size + ofs <= static_cast<size_t>(entry_storage_size), | ||
| 48 | ResultInvalidBucketTreeNodeEntryCount); | ||
| 49 | |||
| 50 | m_entry_storage->Read(reinterpret_cast<u8*>(buffer), m_node_size, ofs); | ||
| 51 | } | ||
| 52 | |||
| 53 | // Calculate extents. | ||
| 54 | const auto end_offset = cur_offset + static_cast<s64>(param.size); | ||
| 55 | s64 phys_offset = entry.GetPhysicalOffset(); | ||
| 56 | |||
| 57 | // Start merge tracking. | ||
| 58 | s64 merge_size = 0; | ||
| 59 | s64 readable_size = 0; | ||
| 60 | bool merged = false; | ||
| 61 | |||
| 62 | // Iterate. | ||
| 63 | auto entry_index = param.entry_index; | ||
| 64 | for (const auto entry_count = param.entry_set.count; entry_index < entry_count; ++entry_index) { | ||
| 65 | // If we're past the end, we're done. | ||
| 66 | if (end_offset <= cur_offset) { | ||
| 67 | break; | ||
| 68 | } | ||
| 69 | |||
| 70 | // Validate the entry offset. | ||
| 71 | const auto entry_offset = entry.GetVirtualOffset(); | ||
| 72 | R_UNLESS(entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset); | ||
| 73 | |||
| 74 | // Get the next entry. | ||
| 75 | EntryType next_entry = {}; | ||
| 76 | s64 next_entry_offset; | ||
| 77 | |||
| 78 | if (entry_index + 1 < entry_count) { | ||
| 79 | if (buffer != nullptr) { | ||
| 80 | const auto ofs = impl::GetBucketTreeEntryOffset(0, m_entry_size, entry_index + 1); | ||
| 81 | std::memcpy(std::addressof(next_entry), buffer + ofs, m_entry_size); | ||
| 82 | } else { | ||
| 83 | const auto ofs = impl::GetBucketTreeEntryOffset(param.entry_set.index, m_node_size, | ||
| 84 | m_entry_size, entry_index + 1); | ||
| 85 | m_entry_storage->ReadObject(std::addressof(next_entry), ofs); | ||
| 86 | } | ||
| 87 | |||
| 88 | next_entry_offset = next_entry.GetVirtualOffset(); | ||
| 89 | R_UNLESS(param.offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset); | ||
| 90 | } else { | ||
| 91 | next_entry_offset = param.entry_set.offset; | ||
| 92 | } | ||
| 93 | |||
| 94 | // Validate the next entry offset. | ||
| 95 | R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset); | ||
| 96 | |||
| 97 | // Determine the much data there is. | ||
| 98 | const auto data_size = next_entry_offset - cur_offset; | ||
| 99 | ASSERT(data_size > 0); | ||
| 100 | |||
| 101 | // Determine how much data we should read. | ||
| 102 | const auto remaining_size = end_offset - cur_offset; | ||
| 103 | const size_t read_size = static_cast<size_t>(std::min(data_size, remaining_size)); | ||
| 104 | ASSERT(read_size <= param.size); | ||
| 105 | |||
| 106 | // Update our merge tracking. | ||
| 107 | if (entry.IsFragment()) { | ||
| 108 | // If we can't merge, stop looping. | ||
| 109 | if (EntryType::FragmentSizeMax <= read_size || remaining_size <= data_size) { | ||
| 110 | break; | ||
| 111 | } | ||
| 112 | |||
| 113 | // Otherwise, add the current size to the merge size. | ||
| 114 | merge_size += read_size; | ||
| 115 | } else { | ||
| 116 | // If we can't merge, stop looping. | ||
| 117 | if (phys_offset != entry.GetPhysicalOffset()) { | ||
| 118 | break; | ||
| 119 | } | ||
| 120 | |||
| 121 | // Add the size to the readable amount. | ||
| 122 | readable_size += merge_size + read_size; | ||
| 123 | ASSERT(readable_size <= static_cast<s64>(param.size)); | ||
| 124 | |||
| 125 | // Update whether we've merged. | ||
| 126 | merged |= merge_size > 0; | ||
| 127 | merge_size = 0; | ||
| 128 | } | ||
| 129 | |||
| 130 | // Advance. | ||
| 131 | cur_offset += read_size; | ||
| 132 | ASSERT(cur_offset <= end_offset); | ||
| 133 | |||
| 134 | phys_offset += next_entry_offset - entry_offset; | ||
| 135 | entry = next_entry; | ||
| 136 | } | ||
| 137 | |||
| 138 | // If we merged, set our readable size. | ||
| 139 | if (merged) { | ||
| 140 | out_info->SetReadSize(static_cast<size_t>(readable_size)); | ||
| 141 | } | ||
| 142 | out_info->SetSkipCount(entry_index - param.entry_index); | ||
| 143 | |||
| 144 | R_SUCCEED(); | ||
| 145 | } | ||
| 146 | |||
| 147 | template <typename EntryType> | ||
| 148 | Result BucketTree::Visitor::ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, | ||
| 149 | size_t size) const { | ||
| 150 | static_assert(std::is_trivial_v<EntryType>); | ||
| 151 | ASSERT(this->IsValid()); | ||
| 152 | |||
| 153 | // Create our parameters. | ||
| 154 | ContinuousReadingParam<EntryType> param = { | ||
| 155 | .offset = offset, | ||
| 156 | .size = size, | ||
| 157 | .entry_set = m_entry_set.header, | ||
| 158 | .entry_index = m_entry_index, | ||
| 159 | .offsets{}, | ||
| 160 | .entry{}, | ||
| 161 | }; | ||
| 162 | std::memcpy(std::addressof(param.offsets), std::addressof(m_offsets), | ||
| 163 | sizeof(BucketTree::Offsets)); | ||
| 164 | std::memcpy(std::addressof(param.entry), m_entry, sizeof(EntryType)); | ||
| 165 | |||
| 166 | // Scan. | ||
| 167 | R_RETURN(m_tree->ScanContinuousReading<EntryType>(out_info, param)); | ||
| 168 | } | ||
| 169 | |||
| 170 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h new file mode 100644 index 000000000..5503613fc --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h | |||
| @@ -0,0 +1,110 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 7 | |||
| 8 | namespace FileSys::impl { | ||
| 9 | |||
| 10 | class SafeValue { | ||
| 11 | public: | ||
| 12 | static s64 GetInt64(const void* ptr) { | ||
| 13 | s64 value; | ||
| 14 | std::memcpy(std::addressof(value), ptr, sizeof(s64)); | ||
| 15 | return value; | ||
| 16 | } | ||
| 17 | |||
| 18 | static s64 GetInt64(const s64* ptr) { | ||
| 19 | return GetInt64(static_cast<const void*>(ptr)); | ||
| 20 | } | ||
| 21 | |||
| 22 | static s64 GetInt64(const s64& v) { | ||
| 23 | return GetInt64(std::addressof(v)); | ||
| 24 | } | ||
| 25 | |||
| 26 | static void SetInt64(void* dst, const void* src) { | ||
| 27 | std::memcpy(dst, src, sizeof(s64)); | ||
| 28 | } | ||
| 29 | |||
| 30 | static void SetInt64(void* dst, const s64* src) { | ||
| 31 | return SetInt64(dst, static_cast<const void*>(src)); | ||
| 32 | } | ||
| 33 | |||
| 34 | static void SetInt64(void* dst, const s64& v) { | ||
| 35 | return SetInt64(dst, std::addressof(v)); | ||
| 36 | } | ||
| 37 | }; | ||
| 38 | |||
| 39 | template <typename IteratorType> | ||
| 40 | struct BucketTreeNode { | ||
| 41 | using Header = BucketTree::NodeHeader; | ||
| 42 | |||
| 43 | Header header; | ||
| 44 | |||
| 45 | s32 GetCount() const { | ||
| 46 | return this->header.count; | ||
| 47 | } | ||
| 48 | |||
| 49 | void* GetArray() { | ||
| 50 | return std::addressof(this->header) + 1; | ||
| 51 | } | ||
| 52 | template <typename T> | ||
| 53 | T* GetArray() { | ||
| 54 | return reinterpret_cast<T*>(this->GetArray()); | ||
| 55 | } | ||
| 56 | const void* GetArray() const { | ||
| 57 | return std::addressof(this->header) + 1; | ||
| 58 | } | ||
| 59 | template <typename T> | ||
| 60 | const T* GetArray() const { | ||
| 61 | return reinterpret_cast<const T*>(this->GetArray()); | ||
| 62 | } | ||
| 63 | |||
| 64 | s64 GetBeginOffset() const { | ||
| 65 | return *this->GetArray<s64>(); | ||
| 66 | } | ||
| 67 | s64 GetEndOffset() const { | ||
| 68 | return this->header.offset; | ||
| 69 | } | ||
| 70 | |||
| 71 | IteratorType GetBegin() { | ||
| 72 | return IteratorType(this->GetArray<s64>()); | ||
| 73 | } | ||
| 74 | IteratorType GetEnd() { | ||
| 75 | return IteratorType(this->GetArray<s64>()) + this->header.count; | ||
| 76 | } | ||
| 77 | IteratorType GetBegin() const { | ||
| 78 | return IteratorType(this->GetArray<s64>()); | ||
| 79 | } | ||
| 80 | IteratorType GetEnd() const { | ||
| 81 | return IteratorType(this->GetArray<s64>()) + this->header.count; | ||
| 82 | } | ||
| 83 | |||
| 84 | IteratorType GetBegin(size_t entry_size) { | ||
| 85 | return IteratorType(this->GetArray(), entry_size); | ||
| 86 | } | ||
| 87 | IteratorType GetEnd(size_t entry_size) { | ||
| 88 | return IteratorType(this->GetArray(), entry_size) + this->header.count; | ||
| 89 | } | ||
| 90 | IteratorType GetBegin(size_t entry_size) const { | ||
| 91 | return IteratorType(this->GetArray(), entry_size); | ||
| 92 | } | ||
| 93 | IteratorType GetEnd(size_t entry_size) const { | ||
| 94 | return IteratorType(this->GetArray(), entry_size) + this->header.count; | ||
| 95 | } | ||
| 96 | }; | ||
| 97 | |||
| 98 | constexpr inline s64 GetBucketTreeEntryOffset(s64 entry_set_offset, size_t entry_size, | ||
| 99 | s32 entry_index) { | ||
| 100 | return entry_set_offset + sizeof(BucketTree::NodeHeader) + | ||
| 101 | entry_index * static_cast<s64>(entry_size); | ||
| 102 | } | ||
| 103 | |||
| 104 | constexpr inline s64 GetBucketTreeEntryOffset(s32 entry_set_index, size_t node_size, | ||
| 105 | size_t entry_size, s32 entry_index) { | ||
| 106 | return GetBucketTreeEntryOffset(entry_set_index * static_cast<s64>(node_size), entry_size, | ||
| 107 | entry_index); | ||
| 108 | } | ||
| 109 | |||
| 110 | } // namespace FileSys::impl | ||
diff --git a/src/core/file_sys/fssystem/fssystem_compressed_storage.h b/src/core/file_sys/fssystem/fssystem_compressed_storage.h new file mode 100644 index 000000000..e407add1b --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compressed_storage.h | |||
| @@ -0,0 +1,960 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/literals.h" | ||
| 7 | |||
| 8 | #include "core/file_sys/errors.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 11 | #include "core/file_sys/fssystem/fssystem_compression_common.h" | ||
| 12 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 13 | #include "core/file_sys/vfs.h" | ||
| 14 | |||
| 15 | namespace FileSys { | ||
| 16 | |||
| 17 | using namespace Common::Literals; | ||
| 18 | |||
| 19 | class CompressedStorage : public IReadOnlyStorage { | ||
| 20 | YUZU_NON_COPYABLE(CompressedStorage); | ||
| 21 | YUZU_NON_MOVEABLE(CompressedStorage); | ||
| 22 | |||
| 23 | public: | ||
| 24 | static constexpr size_t NodeSize = 16_KiB; | ||
| 25 | |||
| 26 | struct Entry { | ||
| 27 | s64 virt_offset; | ||
| 28 | s64 phys_offset; | ||
| 29 | CompressionType compression_type; | ||
| 30 | s32 phys_size; | ||
| 31 | |||
| 32 | s64 GetPhysicalSize() const { | ||
| 33 | return this->phys_size; | ||
| 34 | } | ||
| 35 | }; | ||
| 36 | static_assert(std::is_trivial_v<Entry>); | ||
| 37 | static_assert(sizeof(Entry) == 0x18); | ||
| 38 | |||
| 39 | public: | ||
| 40 | static constexpr s64 QueryNodeStorageSize(s32 entry_count) { | ||
| 41 | return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 42 | } | ||
| 43 | |||
| 44 | static constexpr s64 QueryEntryStorageSize(s32 entry_count) { | ||
| 45 | return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 46 | } | ||
| 47 | |||
| 48 | private: | ||
| 49 | class CompressedStorageCore { | ||
| 50 | YUZU_NON_COPYABLE(CompressedStorageCore); | ||
| 51 | YUZU_NON_MOVEABLE(CompressedStorageCore); | ||
| 52 | |||
| 53 | private: | ||
| 54 | size_t m_block_size_max; | ||
| 55 | size_t m_continuous_reading_size_max; | ||
| 56 | BucketTree m_table; | ||
| 57 | VirtualFile m_data_storage; | ||
| 58 | GetDecompressorFunction m_get_decompressor_function; | ||
| 59 | |||
| 60 | public: | ||
| 61 | CompressedStorageCore() : m_table(), m_data_storage() {} | ||
| 62 | |||
| 63 | ~CompressedStorageCore() { | ||
| 64 | this->Finalize(); | ||
| 65 | } | ||
| 66 | |||
| 67 | public: | ||
| 68 | Result Initialize(VirtualFile data_storage, VirtualFile node_storage, | ||
| 69 | VirtualFile entry_storage, s32 bktr_entry_count, size_t block_size_max, | ||
| 70 | size_t continuous_reading_size_max, | ||
| 71 | GetDecompressorFunction get_decompressor) { | ||
| 72 | // Check pre-conditions. | ||
| 73 | ASSERT(0 < block_size_max); | ||
| 74 | ASSERT(block_size_max <= continuous_reading_size_max); | ||
| 75 | ASSERT(get_decompressor != nullptr); | ||
| 76 | |||
| 77 | // Initialize our entry table. | ||
| 78 | R_TRY(m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), | ||
| 79 | bktr_entry_count)); | ||
| 80 | |||
| 81 | // Set our other fields. | ||
| 82 | m_block_size_max = block_size_max; | ||
| 83 | m_continuous_reading_size_max = continuous_reading_size_max; | ||
| 84 | m_data_storage = data_storage; | ||
| 85 | m_get_decompressor_function = get_decompressor; | ||
| 86 | |||
| 87 | R_SUCCEED(); | ||
| 88 | } | ||
| 89 | |||
| 90 | void Finalize() { | ||
| 91 | if (this->IsInitialized()) { | ||
| 92 | m_table.Finalize(); | ||
| 93 | m_data_storage = VirtualFile(); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | VirtualFile GetDataStorage() { | ||
| 98 | return m_data_storage; | ||
| 99 | } | ||
| 100 | |||
| 101 | Result GetDataStorageSize(s64* out) { | ||
| 102 | // Check pre-conditions. | ||
| 103 | ASSERT(out != nullptr); | ||
| 104 | |||
| 105 | // Get size. | ||
| 106 | *out = m_data_storage->GetSize(); | ||
| 107 | |||
| 108 | R_SUCCEED(); | ||
| 109 | } | ||
| 110 | |||
| 111 | BucketTree& GetEntryTable() { | ||
| 112 | return m_table; | ||
| 113 | } | ||
| 114 | |||
| 115 | Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, | ||
| 116 | s64 offset, s64 size) { | ||
| 117 | // Check pre-conditions. | ||
| 118 | ASSERT(offset >= 0); | ||
| 119 | ASSERT(size >= 0); | ||
| 120 | ASSERT(this->IsInitialized()); | ||
| 121 | |||
| 122 | // Check that we can output the count. | ||
| 123 | R_UNLESS(out_read_count != nullptr, ResultNullptrArgument); | ||
| 124 | |||
| 125 | // Check that we have anything to read at all. | ||
| 126 | R_SUCCEED_IF(size == 0); | ||
| 127 | |||
| 128 | // Check that either we have a buffer, or this is to determine how many we need. | ||
| 129 | if (max_entry_count != 0) { | ||
| 130 | R_UNLESS(out_entries != nullptr, ResultNullptrArgument); | ||
| 131 | } | ||
| 132 | |||
| 133 | // Get the table offsets. | ||
| 134 | BucketTree::Offsets table_offsets; | ||
| 135 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 136 | |||
| 137 | // Validate arguments. | ||
| 138 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 139 | |||
| 140 | // Find the offset in our tree. | ||
| 141 | BucketTree::Visitor visitor; | ||
| 142 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 143 | { | ||
| 144 | const auto entry_offset = visitor.Get<Entry>()->virt_offset; | ||
| 145 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 146 | ResultUnexpectedInCompressedStorageA); | ||
| 147 | } | ||
| 148 | |||
| 149 | // Get the entries. | ||
| 150 | const auto end_offset = offset + size; | ||
| 151 | s32 read_count = 0; | ||
| 152 | while (visitor.Get<Entry>()->virt_offset < end_offset) { | ||
| 153 | // If we should be setting the output, do so. | ||
| 154 | if (max_entry_count != 0) { | ||
| 155 | // Ensure we only read as many entries as we can. | ||
| 156 | if (read_count >= max_entry_count) { | ||
| 157 | break; | ||
| 158 | } | ||
| 159 | |||
| 160 | // Set the current output entry. | ||
| 161 | out_entries[read_count] = *visitor.Get<Entry>(); | ||
| 162 | } | ||
| 163 | |||
| 164 | // Increase the read count. | ||
| 165 | ++read_count; | ||
| 166 | |||
| 167 | // If we're at the end, we're done. | ||
| 168 | if (!visitor.CanMoveNext()) { | ||
| 169 | break; | ||
| 170 | } | ||
| 171 | |||
| 172 | // Move to the next entry. | ||
| 173 | R_TRY(visitor.MoveNext()); | ||
| 174 | } | ||
| 175 | |||
| 176 | // Set the output read count. | ||
| 177 | *out_read_count = read_count; | ||
| 178 | R_SUCCEED(); | ||
| 179 | } | ||
| 180 | |||
| 181 | Result GetSize(s64* out) { | ||
| 182 | // Check pre-conditions. | ||
| 183 | ASSERT(out != nullptr); | ||
| 184 | |||
| 185 | // Get our table offsets. | ||
| 186 | BucketTree::Offsets offsets; | ||
| 187 | R_TRY(m_table.GetOffsets(std::addressof(offsets))); | ||
| 188 | |||
| 189 | // Set the output. | ||
| 190 | *out = offsets.end_offset; | ||
| 191 | R_SUCCEED(); | ||
| 192 | } | ||
| 193 | |||
| 194 | Result OperatePerEntry(s64 offset, s64 size, auto f) { | ||
| 195 | // Check pre-conditions. | ||
| 196 | ASSERT(offset >= 0); | ||
| 197 | ASSERT(size >= 0); | ||
| 198 | ASSERT(this->IsInitialized()); | ||
| 199 | |||
| 200 | // Succeed if there's nothing to operate on. | ||
| 201 | R_SUCCEED_IF(size == 0); | ||
| 202 | |||
| 203 | // Get the table offsets. | ||
| 204 | BucketTree::Offsets table_offsets; | ||
| 205 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 206 | |||
| 207 | // Validate arguments. | ||
| 208 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 209 | |||
| 210 | // Find the offset in our tree. | ||
| 211 | BucketTree::Visitor visitor; | ||
| 212 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 213 | { | ||
| 214 | const auto entry_offset = visitor.Get<Entry>()->virt_offset; | ||
| 215 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 216 | ResultUnexpectedInCompressedStorageA); | ||
| 217 | } | ||
| 218 | |||
| 219 | // Prepare to operate in chunks. | ||
| 220 | auto cur_offset = offset; | ||
| 221 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 222 | |||
| 223 | while (cur_offset < end_offset) { | ||
| 224 | // Get the current entry. | ||
| 225 | const auto cur_entry = *visitor.Get<Entry>(); | ||
| 226 | |||
| 227 | // Get and validate the entry's offset. | ||
| 228 | const auto cur_entry_offset = cur_entry.virt_offset; | ||
| 229 | R_UNLESS(cur_entry_offset <= cur_offset, ResultUnexpectedInCompressedStorageA); | ||
| 230 | |||
| 231 | // Get and validate the next entry offset. | ||
| 232 | s64 next_entry_offset; | ||
| 233 | if (visitor.CanMoveNext()) { | ||
| 234 | R_TRY(visitor.MoveNext()); | ||
| 235 | next_entry_offset = visitor.Get<Entry>()->virt_offset; | ||
| 236 | R_UNLESS(table_offsets.IsInclude(next_entry_offset), | ||
| 237 | ResultUnexpectedInCompressedStorageA); | ||
| 238 | } else { | ||
| 239 | next_entry_offset = table_offsets.end_offset; | ||
| 240 | } | ||
| 241 | R_UNLESS(cur_offset < next_entry_offset, ResultUnexpectedInCompressedStorageA); | ||
| 242 | |||
| 243 | // Get the offset of the entry in the data we read. | ||
| 244 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 245 | const auto data_size = (next_entry_offset - cur_entry_offset); | ||
| 246 | ASSERT(data_size > 0); | ||
| 247 | |||
| 248 | // Determine how much is left. | ||
| 249 | const auto remaining_size = end_offset - cur_offset; | ||
| 250 | const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset); | ||
| 251 | ASSERT(cur_size <= size); | ||
| 252 | |||
| 253 | // Get the data storage size. | ||
| 254 | s64 storage_size = m_data_storage->GetSize(); | ||
| 255 | |||
| 256 | // Check that our read remains naively physically in bounds. | ||
| 257 | R_UNLESS(0 <= cur_entry.phys_offset && cur_entry.phys_offset <= storage_size, | ||
| 258 | ResultUnexpectedInCompressedStorageC); | ||
| 259 | |||
| 260 | // If we have any compression, verify that we remain physically in bounds. | ||
| 261 | if (cur_entry.compression_type != CompressionType::None) { | ||
| 262 | R_UNLESS(cur_entry.phys_offset + cur_entry.GetPhysicalSize() <= storage_size, | ||
| 263 | ResultUnexpectedInCompressedStorageC); | ||
| 264 | } | ||
| 265 | |||
| 266 | // Check that block alignment requirements are met. | ||
| 267 | if (CompressionTypeUtility::IsBlockAlignmentRequired(cur_entry.compression_type)) { | ||
| 268 | R_UNLESS(Common::IsAligned(cur_entry.phys_offset, CompressionBlockAlignment), | ||
| 269 | ResultUnexpectedInCompressedStorageA); | ||
| 270 | } | ||
| 271 | |||
| 272 | // Invoke the operator. | ||
| 273 | bool is_continuous = true; | ||
| 274 | R_TRY( | ||
| 275 | f(std::addressof(is_continuous), cur_entry, data_size, data_offset, cur_size)); | ||
| 276 | |||
| 277 | // If not continuous, we're done. | ||
| 278 | if (!is_continuous) { | ||
| 279 | break; | ||
| 280 | } | ||
| 281 | |||
| 282 | // Advance. | ||
| 283 | cur_offset += cur_size; | ||
| 284 | } | ||
| 285 | |||
| 286 | R_SUCCEED(); | ||
| 287 | } | ||
| 288 | |||
| 289 | public: | ||
| 290 | using ReadImplFunction = std::function<Result(void*, size_t)>; | ||
| 291 | using ReadFunction = std::function<Result(size_t, const ReadImplFunction&)>; | ||
| 292 | |||
| 293 | public: | ||
| 294 | Result Read(s64 offset, s64 size, const ReadFunction& read_func) { | ||
| 295 | // Check pre-conditions. | ||
| 296 | ASSERT(offset >= 0); | ||
| 297 | ASSERT(this->IsInitialized()); | ||
| 298 | |||
| 299 | // Succeed immediately, if we hvae nothing to read. | ||
| 300 | R_SUCCEED_IF(size == 0); | ||
| 301 | |||
| 302 | // Declare read lambda. | ||
| 303 | constexpr int EntriesCountMax = 0x80; | ||
| 304 | struct Entries { | ||
| 305 | CompressionType compression_type; | ||
| 306 | u32 gap_from_prev; | ||
| 307 | u32 physical_size; | ||
| 308 | u32 virtual_size; | ||
| 309 | }; | ||
| 310 | Entries entries[EntriesCountMax]; | ||
| 311 | s32 entry_count = 0; | ||
| 312 | Entry prev_entry = { | ||
| 313 | .virt_offset = -1, | ||
| 314 | }; | ||
| 315 | bool will_allocate_pooled_buffer = false; | ||
| 316 | s64 required_access_physical_offset = 0; | ||
| 317 | s64 required_access_physical_size = 0; | ||
| 318 | |||
| 319 | auto PerformRequiredRead = [&]() -> Result { | ||
| 320 | // If there are no entries, we have nothing to do. | ||
| 321 | R_SUCCEED_IF(entry_count == 0); | ||
| 322 | |||
| 323 | // Get the remaining size in a convenient form. | ||
| 324 | const size_t total_required_size = | ||
| 325 | static_cast<size_t>(required_access_physical_size); | ||
| 326 | |||
| 327 | // Perform the read based on whether we need to allocate a buffer. | ||
| 328 | if (will_allocate_pooled_buffer) { | ||
| 329 | // Allocate a pooled buffer. | ||
| 330 | PooledBuffer pooled_buffer; | ||
| 331 | if (pooled_buffer.GetAllocatableSizeMax() >= total_required_size) { | ||
| 332 | pooled_buffer.Allocate(total_required_size, m_block_size_max); | ||
| 333 | } else { | ||
| 334 | pooled_buffer.AllocateParticularlyLarge( | ||
| 335 | std::min<size_t>( | ||
| 336 | total_required_size, | ||
| 337 | PooledBuffer::GetAllocatableParticularlyLargeSizeMax()), | ||
| 338 | m_block_size_max); | ||
| 339 | } | ||
| 340 | |||
| 341 | // Read each of the entries. | ||
| 342 | for (s32 entry_idx = 0; entry_idx < entry_count; ++entry_idx) { | ||
| 343 | // Determine the current read size. | ||
| 344 | bool will_use_pooled_buffer = false; | ||
| 345 | const size_t cur_read_size = [&]() -> size_t { | ||
| 346 | if (const size_t target_entry_size = | ||
| 347 | static_cast<size_t>(entries[entry_idx].physical_size) + | ||
| 348 | static_cast<size_t>(entries[entry_idx].gap_from_prev); | ||
| 349 | target_entry_size <= pooled_buffer.GetSize()) { | ||
| 350 | // We'll be using the pooled buffer. | ||
| 351 | will_use_pooled_buffer = true; | ||
| 352 | |||
| 353 | // Determine how much we can read. | ||
| 354 | const size_t max_size = std::min<size_t>( | ||
| 355 | required_access_physical_size, pooled_buffer.GetSize()); | ||
| 356 | |||
| 357 | size_t read_size = 0; | ||
| 358 | for (auto n = entry_idx; n < entry_count; ++n) { | ||
| 359 | const size_t cur_entry_size = | ||
| 360 | static_cast<size_t>(entries[n].physical_size) + | ||
| 361 | static_cast<size_t>(entries[n].gap_from_prev); | ||
| 362 | if (read_size + cur_entry_size > max_size) { | ||
| 363 | break; | ||
| 364 | } | ||
| 365 | |||
| 366 | read_size += cur_entry_size; | ||
| 367 | } | ||
| 368 | |||
| 369 | return read_size; | ||
| 370 | } else { | ||
| 371 | // If we don't fit, we must be uncompressed. | ||
| 372 | ASSERT(entries[entry_idx].compression_type == | ||
| 373 | CompressionType::None); | ||
| 374 | |||
| 375 | // We can perform the whole of an uncompressed read directly. | ||
| 376 | return entries[entry_idx].virtual_size; | ||
| 377 | } | ||
| 378 | }(); | ||
| 379 | |||
| 380 | // Perform the read based on whether or not we'll use the pooled buffer. | ||
| 381 | if (will_use_pooled_buffer) { | ||
| 382 | // Read the compressed data into the pooled buffer. | ||
| 383 | auto* const buffer = pooled_buffer.GetBuffer(); | ||
| 384 | m_data_storage->Read(reinterpret_cast<u8*>(buffer), cur_read_size, | ||
| 385 | required_access_physical_offset); | ||
| 386 | |||
| 387 | // Decompress the data. | ||
| 388 | size_t buffer_offset; | ||
| 389 | for (buffer_offset = 0; | ||
| 390 | entry_idx < entry_count && | ||
| 391 | ((static_cast<size_t>(entries[entry_idx].physical_size) + | ||
| 392 | static_cast<size_t>(entries[entry_idx].gap_from_prev)) == 0 || | ||
| 393 | buffer_offset < cur_read_size); | ||
| 394 | buffer_offset += entries[entry_idx++].physical_size) { | ||
| 395 | // Advance by the relevant gap. | ||
| 396 | buffer_offset += entries[entry_idx].gap_from_prev; | ||
| 397 | |||
| 398 | const auto compression_type = entries[entry_idx].compression_type; | ||
| 399 | switch (compression_type) { | ||
| 400 | case CompressionType::None: { | ||
| 401 | // Check that we can remain within bounds. | ||
| 402 | ASSERT(buffer_offset + entries[entry_idx].virtual_size <= | ||
| 403 | cur_read_size); | ||
| 404 | |||
| 405 | // Perform no decompression. | ||
| 406 | R_TRY(read_func( | ||
| 407 | entries[entry_idx].virtual_size, | ||
| 408 | [&](void* dst, size_t dst_size) -> Result { | ||
| 409 | // Check that the size is valid. | ||
| 410 | ASSERT(dst_size == entries[entry_idx].virtual_size); | ||
| 411 | |||
| 412 | // We have no compression, so just copy the data | ||
| 413 | // out. | ||
| 414 | std::memcpy(dst, buffer + buffer_offset, | ||
| 415 | entries[entry_idx].virtual_size); | ||
| 416 | R_SUCCEED(); | ||
| 417 | })); | ||
| 418 | |||
| 419 | break; | ||
| 420 | } | ||
| 421 | case CompressionType::Zeros: { | ||
| 422 | // Check that we can remain within bounds. | ||
| 423 | ASSERT(buffer_offset <= cur_read_size); | ||
| 424 | |||
| 425 | // Zero the memory. | ||
| 426 | R_TRY(read_func( | ||
| 427 | entries[entry_idx].virtual_size, | ||
| 428 | [&](void* dst, size_t dst_size) -> Result { | ||
| 429 | // Check that the size is valid. | ||
| 430 | ASSERT(dst_size == entries[entry_idx].virtual_size); | ||
| 431 | |||
| 432 | // The data is zeroes, so zero the buffer. | ||
| 433 | std::memset(dst, 0, entries[entry_idx].virtual_size); | ||
| 434 | R_SUCCEED(); | ||
| 435 | })); | ||
| 436 | |||
| 437 | break; | ||
| 438 | } | ||
| 439 | default: { | ||
| 440 | // Check that we can remain within bounds. | ||
| 441 | ASSERT(buffer_offset + entries[entry_idx].physical_size <= | ||
| 442 | cur_read_size); | ||
| 443 | |||
| 444 | // Get the decompressor. | ||
| 445 | const auto decompressor = | ||
| 446 | this->GetDecompressor(compression_type); | ||
| 447 | R_UNLESS(decompressor != nullptr, | ||
| 448 | ResultUnexpectedInCompressedStorageB); | ||
| 449 | |||
| 450 | // Decompress the data. | ||
| 451 | R_TRY(read_func(entries[entry_idx].virtual_size, | ||
| 452 | [&](void* dst, size_t dst_size) -> Result { | ||
| 453 | // Check that the size is valid. | ||
| 454 | ASSERT(dst_size == | ||
| 455 | entries[entry_idx].virtual_size); | ||
| 456 | |||
| 457 | // Perform the decompression. | ||
| 458 | R_RETURN(decompressor( | ||
| 459 | dst, entries[entry_idx].virtual_size, | ||
| 460 | buffer + buffer_offset, | ||
| 461 | entries[entry_idx].physical_size)); | ||
| 462 | })); | ||
| 463 | |||
| 464 | break; | ||
| 465 | } | ||
| 466 | } | ||
| 467 | } | ||
| 468 | |||
| 469 | // Check that we processed the correct amount of data. | ||
| 470 | ASSERT(buffer_offset == cur_read_size); | ||
| 471 | } else { | ||
| 472 | // Account for the gap from the previous entry. | ||
| 473 | required_access_physical_offset += entries[entry_idx].gap_from_prev; | ||
| 474 | required_access_physical_size -= entries[entry_idx].gap_from_prev; | ||
| 475 | |||
| 476 | // We don't need the buffer (as the data is uncompressed), so just | ||
| 477 | // execute the read. | ||
| 478 | R_TRY( | ||
| 479 | read_func(cur_read_size, [&](void* dst, size_t dst_size) -> Result { | ||
| 480 | // Check that the size is valid. | ||
| 481 | ASSERT(dst_size == cur_read_size); | ||
| 482 | |||
| 483 | // Perform the read. | ||
| 484 | m_data_storage->Read(reinterpret_cast<u8*>(dst), cur_read_size, | ||
| 485 | required_access_physical_offset); | ||
| 486 | |||
| 487 | R_SUCCEED(); | ||
| 488 | })); | ||
| 489 | } | ||
| 490 | |||
| 491 | // Advance on. | ||
| 492 | required_access_physical_offset += cur_read_size; | ||
| 493 | required_access_physical_size -= cur_read_size; | ||
| 494 | } | ||
| 495 | |||
| 496 | // Verify that we have nothing remaining to read. | ||
| 497 | ASSERT(required_access_physical_size == 0); | ||
| 498 | |||
| 499 | R_SUCCEED(); | ||
| 500 | } else { | ||
| 501 | // We don't need a buffer, so just execute the read. | ||
| 502 | R_TRY(read_func(total_required_size, [&](void* dst, size_t dst_size) -> Result { | ||
| 503 | // Check that the size is valid. | ||
| 504 | ASSERT(dst_size == total_required_size); | ||
| 505 | |||
| 506 | // Perform the read. | ||
| 507 | m_data_storage->Read(reinterpret_cast<u8*>(dst), total_required_size, | ||
| 508 | required_access_physical_offset); | ||
| 509 | |||
| 510 | R_SUCCEED(); | ||
| 511 | })); | ||
| 512 | } | ||
| 513 | |||
| 514 | R_SUCCEED(); | ||
| 515 | }; | ||
| 516 | |||
| 517 | R_TRY(this->OperatePerEntry( | ||
| 518 | offset, size, | ||
| 519 | [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, | ||
| 520 | s64 data_offset, s64 read_size) -> Result { | ||
| 521 | // Determine the physical extents. | ||
| 522 | s64 physical_offset, physical_size; | ||
| 523 | if (CompressionTypeUtility::IsRandomAccessible(entry.compression_type)) { | ||
| 524 | physical_offset = entry.phys_offset + data_offset; | ||
| 525 | physical_size = read_size; | ||
| 526 | } else { | ||
| 527 | physical_offset = entry.phys_offset; | ||
| 528 | physical_size = entry.GetPhysicalSize(); | ||
| 529 | } | ||
| 530 | |||
| 531 | // If we have a pending data storage operation, perform it if we have to. | ||
| 532 | const s64 required_access_physical_end = | ||
| 533 | required_access_physical_offset + required_access_physical_size; | ||
| 534 | if (required_access_physical_size > 0) { | ||
| 535 | const bool required_by_gap = | ||
| 536 | !(required_access_physical_end <= physical_offset && | ||
| 537 | physical_offset <= Common::AlignUp(required_access_physical_end, | ||
| 538 | CompressionBlockAlignment)); | ||
| 539 | const bool required_by_continuous_size = | ||
| 540 | ((physical_size + physical_offset) - required_access_physical_end) + | ||
| 541 | required_access_physical_size > | ||
| 542 | static_cast<s64>(m_continuous_reading_size_max); | ||
| 543 | const bool required_by_entry_count = entry_count == EntriesCountMax; | ||
| 544 | if (required_by_gap || required_by_continuous_size || | ||
| 545 | required_by_entry_count) { | ||
| 546 | // Check that our planned access is sane. | ||
| 547 | ASSERT(!will_allocate_pooled_buffer || | ||
| 548 | required_access_physical_size <= | ||
| 549 | static_cast<s64>(m_continuous_reading_size_max)); | ||
| 550 | |||
| 551 | // Perform the required read. | ||
| 552 | const Result rc = PerformRequiredRead(); | ||
| 553 | if (R_FAILED(rc)) { | ||
| 554 | R_THROW(rc); | ||
| 555 | } | ||
| 556 | |||
| 557 | // Reset our requirements. | ||
| 558 | prev_entry.virt_offset = -1; | ||
| 559 | required_access_physical_size = 0; | ||
| 560 | entry_count = 0; | ||
| 561 | will_allocate_pooled_buffer = false; | ||
| 562 | } | ||
| 563 | } | ||
| 564 | |||
| 565 | // Sanity check that we're within bounds on entries. | ||
| 566 | ASSERT(entry_count < EntriesCountMax); | ||
| 567 | |||
| 568 | // Determine if a buffer allocation is needed. | ||
| 569 | if (entry.compression_type != CompressionType::None || | ||
| 570 | (prev_entry.virt_offset >= 0 && | ||
| 571 | entry.virt_offset - prev_entry.virt_offset != | ||
| 572 | entry.phys_offset - prev_entry.phys_offset)) { | ||
| 573 | will_allocate_pooled_buffer = true; | ||
| 574 | } | ||
| 575 | |||
| 576 | // If we need to access the data storage, update our required access parameters. | ||
| 577 | if (CompressionTypeUtility::IsDataStorageAccessRequired( | ||
| 578 | entry.compression_type)) { | ||
| 579 | // If the data is compressed, ensure the access is sane. | ||
| 580 | if (entry.compression_type != CompressionType::None) { | ||
| 581 | R_UNLESS(data_offset == 0, ResultInvalidOffset); | ||
| 582 | R_UNLESS(virtual_data_size == read_size, ResultInvalidSize); | ||
| 583 | R_UNLESS(entry.GetPhysicalSize() <= static_cast<s64>(m_block_size_max), | ||
| 584 | ResultUnexpectedInCompressedStorageD); | ||
| 585 | } | ||
| 586 | |||
| 587 | // Update the required access parameters. | ||
| 588 | s64 gap_from_prev; | ||
| 589 | if (required_access_physical_size > 0) { | ||
| 590 | gap_from_prev = physical_offset - required_access_physical_end; | ||
| 591 | } else { | ||
| 592 | gap_from_prev = 0; | ||
| 593 | required_access_physical_offset = physical_offset; | ||
| 594 | } | ||
| 595 | required_access_physical_size += physical_size + gap_from_prev; | ||
| 596 | |||
| 597 | // Create an entry. to access the data storage. | ||
| 598 | entries[entry_count++] = { | ||
| 599 | .compression_type = entry.compression_type, | ||
| 600 | .gap_from_prev = static_cast<u32>(gap_from_prev), | ||
| 601 | .physical_size = static_cast<u32>(physical_size), | ||
| 602 | .virtual_size = static_cast<u32>(read_size), | ||
| 603 | }; | ||
| 604 | } else { | ||
| 605 | // Verify that we're allowed to be operating on the non-data-storage-access | ||
| 606 | // type. | ||
| 607 | R_UNLESS(entry.compression_type == CompressionType::Zeros, | ||
| 608 | ResultUnexpectedInCompressedStorageB); | ||
| 609 | |||
| 610 | // If we have entries, create a fake entry for the zero region. | ||
| 611 | if (entry_count != 0) { | ||
| 612 | // We need to have a physical size. | ||
| 613 | R_UNLESS(entry.GetPhysicalSize() != 0, | ||
| 614 | ResultUnexpectedInCompressedStorageD); | ||
| 615 | |||
| 616 | // Create a fake entry. | ||
| 617 | entries[entry_count++] = { | ||
| 618 | .compression_type = CompressionType::Zeros, | ||
| 619 | .gap_from_prev = 0, | ||
| 620 | .physical_size = 0, | ||
| 621 | .virtual_size = static_cast<u32>(read_size), | ||
| 622 | }; | ||
| 623 | } else { | ||
| 624 | // We have no entries, we we can just perform the read. | ||
| 625 | const Result rc = | ||
| 626 | read_func(static_cast<size_t>(read_size), | ||
| 627 | [&](void* dst, size_t dst_size) -> Result { | ||
| 628 | // Check the space we should zero is correct. | ||
| 629 | ASSERT(dst_size == static_cast<size_t>(read_size)); | ||
| 630 | |||
| 631 | // Zero the memory. | ||
| 632 | std::memset(dst, 0, read_size); | ||
| 633 | R_SUCCEED(); | ||
| 634 | }); | ||
| 635 | if (R_FAILED(rc)) { | ||
| 636 | R_THROW(rc); | ||
| 637 | } | ||
| 638 | } | ||
| 639 | } | ||
| 640 | |||
| 641 | // Set the previous entry. | ||
| 642 | prev_entry = entry; | ||
| 643 | |||
| 644 | // We're continuous. | ||
| 645 | *out_continuous = true; | ||
| 646 | R_SUCCEED(); | ||
| 647 | })); | ||
| 648 | |||
| 649 | // If we still have a pending access, perform it. | ||
| 650 | if (required_access_physical_size != 0) { | ||
| 651 | R_TRY(PerformRequiredRead()); | ||
| 652 | } | ||
| 653 | |||
| 654 | R_SUCCEED(); | ||
| 655 | } | ||
| 656 | |||
| 657 | private: | ||
| 658 | DecompressorFunction GetDecompressor(CompressionType type) const { | ||
| 659 | // Check that we can get a decompressor for the type. | ||
| 660 | if (CompressionTypeUtility::IsUnknownType(type)) { | ||
| 661 | return nullptr; | ||
| 662 | } | ||
| 663 | |||
| 664 | // Get the decompressor. | ||
| 665 | return m_get_decompressor_function(type); | ||
| 666 | } | ||
| 667 | |||
| 668 | bool IsInitialized() const { | ||
| 669 | return m_table.IsInitialized(); | ||
| 670 | } | ||
| 671 | }; | ||
| 672 | |||
| 673 | class CacheManager { | ||
| 674 | YUZU_NON_COPYABLE(CacheManager); | ||
| 675 | YUZU_NON_MOVEABLE(CacheManager); | ||
| 676 | |||
| 677 | private: | ||
| 678 | struct AccessRange { | ||
| 679 | s64 virtual_offset; | ||
| 680 | s64 virtual_size; | ||
| 681 | u32 physical_size; | ||
| 682 | bool is_block_alignment_required; | ||
| 683 | |||
| 684 | s64 GetEndVirtualOffset() const { | ||
| 685 | return this->virtual_offset + this->virtual_size; | ||
| 686 | } | ||
| 687 | }; | ||
| 688 | static_assert(std::is_trivial_v<AccessRange>); | ||
| 689 | |||
| 690 | private: | ||
| 691 | s64 m_storage_size = 0; | ||
| 692 | |||
| 693 | public: | ||
| 694 | CacheManager() = default; | ||
| 695 | |||
| 696 | public: | ||
| 697 | Result Initialize(s64 storage_size, size_t cache_size_0, size_t cache_size_1, | ||
| 698 | size_t max_cache_entries) { | ||
| 699 | // Set our fields. | ||
| 700 | m_storage_size = storage_size; | ||
| 701 | |||
| 702 | R_SUCCEED(); | ||
| 703 | } | ||
| 704 | |||
| 705 | Result Read(CompressedStorageCore& core, s64 offset, void* buffer, size_t size) { | ||
| 706 | // If we have nothing to read, succeed. | ||
| 707 | R_SUCCEED_IF(size == 0); | ||
| 708 | |||
| 709 | // Check that we have a buffer to read into. | ||
| 710 | R_UNLESS(buffer != nullptr, ResultNullptrArgument); | ||
| 711 | |||
| 712 | // Check that the read is in bounds. | ||
| 713 | R_UNLESS(offset <= m_storage_size, ResultInvalidOffset); | ||
| 714 | |||
| 715 | // Determine how much we can read. | ||
| 716 | const size_t read_size = std::min<size_t>(size, m_storage_size - offset); | ||
| 717 | |||
| 718 | // Create head/tail ranges. | ||
| 719 | AccessRange head_range = {}; | ||
| 720 | AccessRange tail_range = {}; | ||
| 721 | bool is_tail_set = false; | ||
| 722 | |||
| 723 | // Operate to determine the head range. | ||
| 724 | R_TRY(core.OperatePerEntry( | ||
| 725 | offset, 1, | ||
| 726 | [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, | ||
| 727 | s64 data_offset, s64 data_read_size) -> Result { | ||
| 728 | // Set the head range. | ||
| 729 | head_range = { | ||
| 730 | .virtual_offset = entry.virt_offset, | ||
| 731 | .virtual_size = virtual_data_size, | ||
| 732 | .physical_size = static_cast<u32>(entry.phys_size), | ||
| 733 | .is_block_alignment_required = | ||
| 734 | CompressionTypeUtility::IsBlockAlignmentRequired( | ||
| 735 | entry.compression_type), | ||
| 736 | }; | ||
| 737 | |||
| 738 | // If required, set the tail range. | ||
| 739 | if (static_cast<s64>(offset + read_size) <= | ||
| 740 | entry.virt_offset + virtual_data_size) { | ||
| 741 | tail_range = { | ||
| 742 | .virtual_offset = entry.virt_offset, | ||
| 743 | .virtual_size = virtual_data_size, | ||
| 744 | .physical_size = static_cast<u32>(entry.phys_size), | ||
| 745 | .is_block_alignment_required = | ||
| 746 | CompressionTypeUtility::IsBlockAlignmentRequired( | ||
| 747 | entry.compression_type), | ||
| 748 | }; | ||
| 749 | is_tail_set = true; | ||
| 750 | } | ||
| 751 | |||
| 752 | // We only want to determine the head range, so we're not continuous. | ||
| 753 | *out_continuous = false; | ||
| 754 | R_SUCCEED(); | ||
| 755 | })); | ||
| 756 | |||
| 757 | // If necessary, determine the tail range. | ||
| 758 | if (!is_tail_set) { | ||
| 759 | R_TRY(core.OperatePerEntry( | ||
| 760 | offset + read_size - 1, 1, | ||
| 761 | [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, | ||
| 762 | s64 data_offset, s64 data_read_size) -> Result { | ||
| 763 | // Set the tail range. | ||
| 764 | tail_range = { | ||
| 765 | .virtual_offset = entry.virt_offset, | ||
| 766 | .virtual_size = virtual_data_size, | ||
| 767 | .physical_size = static_cast<u32>(entry.phys_size), | ||
| 768 | .is_block_alignment_required = | ||
| 769 | CompressionTypeUtility::IsBlockAlignmentRequired( | ||
| 770 | entry.compression_type), | ||
| 771 | }; | ||
| 772 | |||
| 773 | // We only want to determine the tail range, so we're not continuous. | ||
| 774 | *out_continuous = false; | ||
| 775 | R_SUCCEED(); | ||
| 776 | })); | ||
| 777 | } | ||
| 778 | |||
| 779 | // Begin performing the accesses. | ||
| 780 | s64 cur_offset = offset; | ||
| 781 | size_t cur_size = read_size; | ||
| 782 | char* cur_dst = static_cast<char*>(buffer); | ||
| 783 | |||
| 784 | // Determine our alignment. | ||
| 785 | const bool head_unaligned = head_range.is_block_alignment_required && | ||
| 786 | (cur_offset != head_range.virtual_offset || | ||
| 787 | static_cast<s64>(cur_size) < head_range.virtual_size); | ||
| 788 | const bool tail_unaligned = [&]() -> bool { | ||
| 789 | if (tail_range.is_block_alignment_required) { | ||
| 790 | if (static_cast<s64>(cur_size + cur_offset) == | ||
| 791 | tail_range.GetEndVirtualOffset()) { | ||
| 792 | return false; | ||
| 793 | } else if (!head_unaligned) { | ||
| 794 | return true; | ||
| 795 | } else { | ||
| 796 | return head_range.GetEndVirtualOffset() < | ||
| 797 | static_cast<s64>(cur_size + cur_offset); | ||
| 798 | } | ||
| 799 | } else { | ||
| 800 | return false; | ||
| 801 | } | ||
| 802 | }(); | ||
| 803 | |||
| 804 | // Determine start/end offsets. | ||
| 805 | const s64 start_offset = | ||
| 806 | head_range.is_block_alignment_required ? head_range.virtual_offset : cur_offset; | ||
| 807 | const s64 end_offset = tail_range.is_block_alignment_required | ||
| 808 | ? tail_range.GetEndVirtualOffset() | ||
| 809 | : cur_offset + cur_size; | ||
| 810 | |||
| 811 | // Perform the read. | ||
| 812 | bool is_burst_reading = false; | ||
| 813 | R_TRY(core.Read( | ||
| 814 | start_offset, end_offset - start_offset, | ||
| 815 | [&](size_t size_buffer_required, | ||
| 816 | const CompressedStorageCore::ReadImplFunction& read_impl) -> Result { | ||
| 817 | // Determine whether we're burst reading. | ||
| 818 | const AccessRange* unaligned_range = nullptr; | ||
| 819 | if (!is_burst_reading) { | ||
| 820 | // Check whether we're using head, tail, or none as unaligned. | ||
| 821 | if (head_unaligned && head_range.virtual_offset <= cur_offset && | ||
| 822 | cur_offset < head_range.GetEndVirtualOffset()) { | ||
| 823 | unaligned_range = std::addressof(head_range); | ||
| 824 | } else if (tail_unaligned && tail_range.virtual_offset <= cur_offset && | ||
| 825 | cur_offset < tail_range.GetEndVirtualOffset()) { | ||
| 826 | unaligned_range = std::addressof(tail_range); | ||
| 827 | } else { | ||
| 828 | is_burst_reading = true; | ||
| 829 | } | ||
| 830 | } | ||
| 831 | ASSERT((is_burst_reading ^ (unaligned_range != nullptr))); | ||
| 832 | |||
| 833 | // Perform reading by burst, or not. | ||
| 834 | if (is_burst_reading) { | ||
| 835 | // Check that the access is valid for burst reading. | ||
| 836 | ASSERT(size_buffer_required <= cur_size); | ||
| 837 | |||
| 838 | // Perform the read. | ||
| 839 | Result rc = read_impl(cur_dst, size_buffer_required); | ||
| 840 | if (R_FAILED(rc)) { | ||
| 841 | R_THROW(rc); | ||
| 842 | } | ||
| 843 | |||
| 844 | // Advance. | ||
| 845 | cur_dst += size_buffer_required; | ||
| 846 | cur_offset += size_buffer_required; | ||
| 847 | cur_size -= size_buffer_required; | ||
| 848 | |||
| 849 | // Determine whether we're going to continue burst reading. | ||
| 850 | const s64 offset_aligned = | ||
| 851 | tail_unaligned ? tail_range.virtual_offset : end_offset; | ||
| 852 | ASSERT(cur_offset <= offset_aligned); | ||
| 853 | |||
| 854 | if (offset_aligned <= cur_offset) { | ||
| 855 | is_burst_reading = false; | ||
| 856 | } | ||
| 857 | } else { | ||
| 858 | // We're not burst reading, so we have some unaligned range. | ||
| 859 | ASSERT(unaligned_range != nullptr); | ||
| 860 | |||
| 861 | // Check that the size is correct. | ||
| 862 | ASSERT(size_buffer_required == | ||
| 863 | static_cast<size_t>(unaligned_range->virtual_size)); | ||
| 864 | |||
| 865 | // Get a pooled buffer for our read. | ||
| 866 | PooledBuffer pooled_buffer; | ||
| 867 | pooled_buffer.Allocate(size_buffer_required, size_buffer_required); | ||
| 868 | |||
| 869 | // Perform read. | ||
| 870 | Result rc = read_impl(pooled_buffer.GetBuffer(), size_buffer_required); | ||
| 871 | if (R_FAILED(rc)) { | ||
| 872 | R_THROW(rc); | ||
| 873 | } | ||
| 874 | |||
| 875 | // Copy the data we read to the destination. | ||
| 876 | const size_t skip_size = cur_offset - unaligned_range->virtual_offset; | ||
| 877 | const size_t copy_size = std::min<size_t>( | ||
| 878 | cur_size, unaligned_range->GetEndVirtualOffset() - cur_offset); | ||
| 879 | |||
| 880 | std::memcpy(cur_dst, pooled_buffer.GetBuffer() + skip_size, copy_size); | ||
| 881 | |||
| 882 | // Advance. | ||
| 883 | cur_dst += copy_size; | ||
| 884 | cur_offset += copy_size; | ||
| 885 | cur_size -= copy_size; | ||
| 886 | } | ||
| 887 | |||
| 888 | R_SUCCEED(); | ||
| 889 | })); | ||
| 890 | |||
| 891 | R_SUCCEED(); | ||
| 892 | } | ||
| 893 | }; | ||
| 894 | |||
| 895 | private: | ||
| 896 | mutable CompressedStorageCore m_core; | ||
| 897 | mutable CacheManager m_cache_manager; | ||
| 898 | |||
| 899 | public: | ||
| 900 | CompressedStorage() = default; | ||
| 901 | virtual ~CompressedStorage() { | ||
| 902 | this->Finalize(); | ||
| 903 | } | ||
| 904 | |||
| 905 | Result Initialize(VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage, | ||
| 906 | s32 bktr_entry_count, size_t block_size_max, | ||
| 907 | size_t continuous_reading_size_max, GetDecompressorFunction get_decompressor, | ||
| 908 | size_t cache_size_0, size_t cache_size_1, s32 max_cache_entries) { | ||
| 909 | // Initialize our core. | ||
| 910 | R_TRY(m_core.Initialize(data_storage, node_storage, entry_storage, bktr_entry_count, | ||
| 911 | block_size_max, continuous_reading_size_max, get_decompressor)); | ||
| 912 | |||
| 913 | // Get our core size. | ||
| 914 | s64 core_size = 0; | ||
| 915 | R_TRY(m_core.GetSize(std::addressof(core_size))); | ||
| 916 | |||
| 917 | // Initialize our cache manager. | ||
| 918 | R_TRY(m_cache_manager.Initialize(core_size, cache_size_0, cache_size_1, max_cache_entries)); | ||
| 919 | |||
| 920 | R_SUCCEED(); | ||
| 921 | } | ||
| 922 | |||
| 923 | void Finalize() { | ||
| 924 | m_core.Finalize(); | ||
| 925 | } | ||
| 926 | |||
| 927 | VirtualFile GetDataStorage() { | ||
| 928 | return m_core.GetDataStorage(); | ||
| 929 | } | ||
| 930 | |||
| 931 | Result GetDataStorageSize(s64* out) { | ||
| 932 | R_RETURN(m_core.GetDataStorageSize(out)); | ||
| 933 | } | ||
| 934 | |||
| 935 | Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, s64 offset, | ||
| 936 | s64 size) { | ||
| 937 | R_RETURN(m_core.GetEntryList(out_entries, out_read_count, max_entry_count, offset, size)); | ||
| 938 | } | ||
| 939 | |||
| 940 | BucketTree& GetEntryTable() { | ||
| 941 | return m_core.GetEntryTable(); | ||
| 942 | } | ||
| 943 | |||
| 944 | public: | ||
| 945 | virtual size_t GetSize() const override { | ||
| 946 | s64 ret{}; | ||
| 947 | m_core.GetSize(&ret); | ||
| 948 | return ret; | ||
| 949 | } | ||
| 950 | |||
| 951 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 952 | if (R_SUCCEEDED(m_cache_manager.Read(m_core, offset, buffer, size))) { | ||
| 953 | return size; | ||
| 954 | } else { | ||
| 955 | return 0; | ||
| 956 | } | ||
| 957 | } | ||
| 958 | }; | ||
| 959 | |||
| 960 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_compression_common.h b/src/core/file_sys/fssystem/fssystem_compression_common.h new file mode 100644 index 000000000..266e0a7e5 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_common.h | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/hle/result.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | enum class CompressionType : u8 { | ||
| 11 | None = 0, | ||
| 12 | Zeros = 1, | ||
| 13 | Two = 2, | ||
| 14 | Lz4 = 3, | ||
| 15 | Unknown = 4, | ||
| 16 | }; | ||
| 17 | |||
| 18 | using DecompressorFunction = Result (*)(void*, size_t, const void*, size_t); | ||
| 19 | using GetDecompressorFunction = DecompressorFunction (*)(CompressionType); | ||
| 20 | |||
| 21 | constexpr s64 CompressionBlockAlignment = 0x10; | ||
| 22 | |||
| 23 | namespace CompressionTypeUtility { | ||
| 24 | |||
| 25 | constexpr bool IsBlockAlignmentRequired(CompressionType type) { | ||
| 26 | return type != CompressionType::None && type != CompressionType::Zeros; | ||
| 27 | } | ||
| 28 | |||
| 29 | constexpr bool IsDataStorageAccessRequired(CompressionType type) { | ||
| 30 | return type != CompressionType::Zeros; | ||
| 31 | } | ||
| 32 | |||
| 33 | constexpr bool IsRandomAccessible(CompressionType type) { | ||
| 34 | return type == CompressionType::None; | ||
| 35 | } | ||
| 36 | |||
| 37 | constexpr bool IsUnknownType(CompressionType type) { | ||
| 38 | return type >= CompressionType::Unknown; | ||
| 39 | } | ||
| 40 | |||
| 41 | } // namespace CompressionTypeUtility | ||
| 42 | |||
| 43 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp b/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp new file mode 100644 index 000000000..8734f84ca --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/lz4_compression.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_compression_configuration.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | |||
| 11 | Result DecompressLz4(void* dst, size_t dst_size, const void* src, size_t src_size) { | ||
| 12 | auto result = Common::Compression::DecompressLZ4(dst, dst_size, src, src_size); | ||
| 13 | R_UNLESS(static_cast<size_t>(result) == dst_size, ResultUnexpectedInCompressedStorageC); | ||
| 14 | R_SUCCEED(); | ||
| 15 | } | ||
| 16 | |||
| 17 | constexpr DecompressorFunction GetNcaDecompressorFunction(CompressionType type) { | ||
| 18 | switch (type) { | ||
| 19 | case CompressionType::Lz4: | ||
| 20 | return DecompressLz4; | ||
| 21 | default: | ||
| 22 | return nullptr; | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | constexpr NcaCompressionConfiguration g_nca_compression_configuration{ | ||
| 27 | .get_decompressor = GetNcaDecompressorFunction, | ||
| 28 | }; | ||
| 29 | |||
| 30 | } // namespace | ||
| 31 | |||
| 32 | const NcaCompressionConfiguration* GetNcaCompressionConfiguration() { | ||
| 33 | return std::addressof(g_nca_compression_configuration); | ||
| 34 | } | ||
| 35 | |||
| 36 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_compression_configuration.h b/src/core/file_sys/fssystem/fssystem_compression_configuration.h new file mode 100644 index 000000000..b4ec4f203 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_configuration.h | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | const NcaCompressionConfiguration* GetNcaCompressionConfiguration(); | ||
| 11 | |||
| 12 | } | ||
diff --git a/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp b/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp new file mode 100644 index 000000000..7b89d4512 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/crypto/aes_util.h" | ||
| 5 | #include "core/crypto/key_manager.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_crypto_configuration.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | namespace { | ||
| 11 | |||
| 12 | void GenerateKey(void* dst_key, size_t dst_key_size, const void* src_key, size_t src_key_size, | ||
| 13 | s32 key_type) { | ||
| 14 | if (key_type == static_cast<s32>(KeyType::ZeroKey)) { | ||
| 15 | std::memset(dst_key, 0, dst_key_size); | ||
| 16 | return; | ||
| 17 | } | ||
| 18 | |||
| 19 | if (key_type == static_cast<s32>(KeyType::InvalidKey) || | ||
| 20 | key_type < static_cast<s32>(KeyType::ZeroKey) || | ||
| 21 | key_type >= static_cast<s32>(KeyType::NcaExternalKey)) { | ||
| 22 | std::memset(dst_key, 0xFF, dst_key_size); | ||
| 23 | return; | ||
| 24 | } | ||
| 25 | |||
| 26 | const auto& instance = Core::Crypto::KeyManager::Instance(); | ||
| 27 | |||
| 28 | if (key_type == static_cast<s32>(KeyType::NcaHeaderKey1) || | ||
| 29 | key_type == static_cast<s32>(KeyType::NcaHeaderKey2)) { | ||
| 30 | const s32 key_index = static_cast<s32>(KeyType::NcaHeaderKey2) == key_type; | ||
| 31 | const auto key = instance.GetKey(Core::Crypto::S256KeyType::Header); | ||
| 32 | std::memcpy(dst_key, key.data() + key_index * 0x10, std::min(dst_key_size, key.size() / 2)); | ||
| 33 | return; | ||
| 34 | } | ||
| 35 | |||
| 36 | const s32 key_generation = | ||
| 37 | std::max(key_type / NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount, 1) - 1; | ||
| 38 | const s32 key_index = key_type % NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount; | ||
| 39 | |||
| 40 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | ||
| 41 | instance.GetKey(Core::Crypto::S128KeyType::KeyArea, key_generation, key_index), | ||
| 42 | Core::Crypto::Mode::ECB); | ||
| 43 | cipher.Transcode(reinterpret_cast<const u8*>(src_key), src_key_size, | ||
| 44 | reinterpret_cast<u8*>(dst_key), Core::Crypto::Op::Decrypt); | ||
| 45 | } | ||
| 46 | |||
| 47 | } // namespace | ||
| 48 | |||
| 49 | const NcaCryptoConfiguration& GetCryptoConfiguration() { | ||
| 50 | static const NcaCryptoConfiguration configuration = { | ||
| 51 | .generate_key = GenerateKey, | ||
| 52 | }; | ||
| 53 | |||
| 54 | return configuration; | ||
| 55 | } | ||
| 56 | |||
| 57 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_crypto_configuration.h b/src/core/file_sys/fssystem/fssystem_crypto_configuration.h new file mode 100644 index 000000000..7fd9c5a8d --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_crypto_configuration.h | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | const NcaCryptoConfiguration& GetCryptoConfiguration(); | ||
| 11 | |||
| 12 | } | ||
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp new file mode 100644 index 000000000..b2e031d5f --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp | |||
| @@ -0,0 +1,132 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" | ||
| 5 | #include "core/file_sys/vfs_offset.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | HierarchicalIntegrityVerificationStorage::HierarchicalIntegrityVerificationStorage() | ||
| 10 | : m_data_size(-1) { | ||
| 11 | for (size_t i = 0; i < MaxLayers - 1; i++) { | ||
| 12 | m_verify_storages[i] = std::make_shared<IntegrityVerificationStorage>(); | ||
| 13 | } | ||
| 14 | } | ||
| 15 | |||
| 16 | Result HierarchicalIntegrityVerificationStorage::Initialize( | ||
| 17 | const HierarchicalIntegrityVerificationInformation& info, | ||
| 18 | HierarchicalStorageInformation storage, int max_data_cache_entries, int max_hash_cache_entries, | ||
| 19 | s8 buffer_level) { | ||
| 20 | using AlignedStorage = AlignmentMatchingStoragePooledBuffer<1>; | ||
| 21 | |||
| 22 | // Validate preconditions. | ||
| 23 | ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount); | ||
| 24 | |||
| 25 | // Set member variables. | ||
| 26 | m_max_layers = info.max_layers; | ||
| 27 | |||
| 28 | // Initialize the top level verification storage. | ||
| 29 | m_verify_storages[0]->Initialize(storage[HierarchicalStorageInformation::MasterStorage], | ||
| 30 | storage[HierarchicalStorageInformation::Layer1Storage], | ||
| 31 | static_cast<s64>(1) << info.info[0].block_order, HashSize, | ||
| 32 | false); | ||
| 33 | |||
| 34 | // Ensure we don't leak state if further initialization goes wrong. | ||
| 35 | ON_RESULT_FAILURE { | ||
| 36 | m_verify_storages[0]->Finalize(); | ||
| 37 | m_data_size = -1; | ||
| 38 | }; | ||
| 39 | |||
| 40 | // Initialize the top level buffer storage. | ||
| 41 | m_buffer_storages[0] = std::make_shared<AlignedStorage>( | ||
| 42 | m_verify_storages[0], static_cast<s64>(1) << info.info[0].block_order); | ||
| 43 | R_UNLESS(m_buffer_storages[0] != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 44 | |||
| 45 | // Prepare to initialize the level storages. | ||
| 46 | s32 level = 0; | ||
| 47 | |||
| 48 | // Ensure we don't leak state if further initialization goes wrong. | ||
| 49 | ON_RESULT_FAILURE_2 { | ||
| 50 | m_verify_storages[level + 1]->Finalize(); | ||
| 51 | for (; level > 0; --level) { | ||
| 52 | m_buffer_storages[level].reset(); | ||
| 53 | m_verify_storages[level]->Finalize(); | ||
| 54 | } | ||
| 55 | }; | ||
| 56 | |||
| 57 | // Initialize the level storages. | ||
| 58 | for (; level < m_max_layers - 3; ++level) { | ||
| 59 | // Initialize the verification storage. | ||
| 60 | auto buffer_storage = | ||
| 61 | std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0); | ||
| 62 | m_verify_storages[level + 1]->Initialize( | ||
| 63 | std::move(buffer_storage), storage[level + 2], | ||
| 64 | static_cast<s64>(1) << info.info[level + 1].block_order, | ||
| 65 | static_cast<s64>(1) << info.info[level].block_order, false); | ||
| 66 | |||
| 67 | // Initialize the buffer storage. | ||
| 68 | m_buffer_storages[level + 1] = std::make_shared<AlignedStorage>( | ||
| 69 | m_verify_storages[level + 1], static_cast<s64>(1) << info.info[level + 1].block_order); | ||
| 70 | R_UNLESS(m_buffer_storages[level + 1] != nullptr, | ||
| 71 | ResultAllocationMemoryFailedAllocateShared); | ||
| 72 | } | ||
| 73 | |||
| 74 | // Initialize the final level storage. | ||
| 75 | { | ||
| 76 | // Initialize the verification storage. | ||
| 77 | auto buffer_storage = | ||
| 78 | std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0); | ||
| 79 | m_verify_storages[level + 1]->Initialize( | ||
| 80 | std::move(buffer_storage), storage[level + 2], | ||
| 81 | static_cast<s64>(1) << info.info[level + 1].block_order, | ||
| 82 | static_cast<s64>(1) << info.info[level].block_order, true); | ||
| 83 | |||
| 84 | // Initialize the buffer storage. | ||
| 85 | m_buffer_storages[level + 1] = std::make_shared<AlignedStorage>( | ||
| 86 | m_verify_storages[level + 1], static_cast<s64>(1) << info.info[level + 1].block_order); | ||
| 87 | R_UNLESS(m_buffer_storages[level + 1] != nullptr, | ||
| 88 | ResultAllocationMemoryFailedAllocateShared); | ||
| 89 | } | ||
| 90 | |||
| 91 | // Set the data size. | ||
| 92 | m_data_size = info.info[level + 1].size; | ||
| 93 | |||
| 94 | // We succeeded. | ||
| 95 | R_SUCCEED(); | ||
| 96 | } | ||
| 97 | |||
| 98 | void HierarchicalIntegrityVerificationStorage::Finalize() { | ||
| 99 | if (m_data_size >= 0) { | ||
| 100 | m_data_size = 0; | ||
| 101 | |||
| 102 | for (s32 level = m_max_layers - 2; level >= 0; --level) { | ||
| 103 | m_buffer_storages[level].reset(); | ||
| 104 | m_verify_storages[level]->Finalize(); | ||
| 105 | } | ||
| 106 | |||
| 107 | m_data_size = -1; | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | size_t HierarchicalIntegrityVerificationStorage::Read(u8* buffer, size_t size, | ||
| 112 | size_t offset) const { | ||
| 113 | // Validate preconditions. | ||
| 114 | ASSERT(m_data_size >= 0); | ||
| 115 | |||
| 116 | // Succeed if zero-size. | ||
| 117 | if (size == 0) { | ||
| 118 | return size; | ||
| 119 | } | ||
| 120 | |||
| 121 | // Validate arguments. | ||
| 122 | ASSERT(buffer != nullptr); | ||
| 123 | |||
| 124 | // Read the data. | ||
| 125 | return m_buffer_storages[m_max_layers - 2]->Read(buffer, size, offset); | ||
| 126 | } | ||
| 127 | |||
| 128 | size_t HierarchicalIntegrityVerificationStorage::GetSize() const { | ||
| 129 | return m_data_size; | ||
| 130 | } | ||
| 131 | |||
| 132 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h new file mode 100644 index 000000000..5e0a1d143 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h | |||
| @@ -0,0 +1,164 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/alignment.h" | ||
| 7 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fs_types.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h" | ||
| 11 | #include "core/file_sys/vfs_offset.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | struct HierarchicalIntegrityVerificationLevelInformation { | ||
| 16 | Int64 offset; | ||
| 17 | Int64 size; | ||
| 18 | s32 block_order; | ||
| 19 | std::array<u8, 4> reserved; | ||
| 20 | }; | ||
| 21 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationLevelInformation>); | ||
| 22 | static_assert(sizeof(HierarchicalIntegrityVerificationLevelInformation) == 0x18); | ||
| 23 | static_assert(alignof(HierarchicalIntegrityVerificationLevelInformation) == 0x4); | ||
| 24 | |||
| 25 | struct HierarchicalIntegrityVerificationInformation { | ||
| 26 | u32 max_layers; | ||
| 27 | HierarchicalIntegrityVerificationLevelInformation info[IntegrityMaxLayerCount - 1]; | ||
| 28 | HashSalt seed; | ||
| 29 | |||
| 30 | s64 GetLayeredHashSize() const { | ||
| 31 | return this->info[this->max_layers - 2].offset; | ||
| 32 | } | ||
| 33 | |||
| 34 | s64 GetDataOffset() const { | ||
| 35 | return this->info[this->max_layers - 2].offset; | ||
| 36 | } | ||
| 37 | |||
| 38 | s64 GetDataSize() const { | ||
| 39 | return this->info[this->max_layers - 2].size; | ||
| 40 | } | ||
| 41 | }; | ||
| 42 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationInformation>); | ||
| 43 | |||
| 44 | struct HierarchicalIntegrityVerificationMetaInformation { | ||
| 45 | u32 magic; | ||
| 46 | u32 version; | ||
| 47 | u32 master_hash_size; | ||
| 48 | HierarchicalIntegrityVerificationInformation level_hash_info; | ||
| 49 | }; | ||
| 50 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationMetaInformation>); | ||
| 51 | |||
| 52 | struct HierarchicalIntegrityVerificationSizeSet { | ||
| 53 | s64 control_size; | ||
| 54 | s64 master_hash_size; | ||
| 55 | s64 layered_hash_sizes[IntegrityMaxLayerCount - 2]; | ||
| 56 | }; | ||
| 57 | static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationSizeSet>); | ||
| 58 | |||
| 59 | class HierarchicalIntegrityVerificationStorage : public IReadOnlyStorage { | ||
| 60 | YUZU_NON_COPYABLE(HierarchicalIntegrityVerificationStorage); | ||
| 61 | YUZU_NON_MOVEABLE(HierarchicalIntegrityVerificationStorage); | ||
| 62 | |||
| 63 | private: | ||
| 64 | friend struct HierarchicalIntegrityVerificationMetaInformation; | ||
| 65 | |||
| 66 | protected: | ||
| 67 | static constexpr s64 HashSize = 256 / 8; | ||
| 68 | static constexpr size_t MaxLayers = IntegrityMaxLayerCount; | ||
| 69 | |||
| 70 | public: | ||
| 71 | using GenerateRandomFunction = void (*)(void* dst, size_t size); | ||
| 72 | |||
| 73 | class HierarchicalStorageInformation { | ||
| 74 | public: | ||
| 75 | enum { | ||
| 76 | MasterStorage = 0, | ||
| 77 | Layer1Storage = 1, | ||
| 78 | Layer2Storage = 2, | ||
| 79 | Layer3Storage = 3, | ||
| 80 | Layer4Storage = 4, | ||
| 81 | Layer5Storage = 5, | ||
| 82 | DataStorage = 6, | ||
| 83 | }; | ||
| 84 | |||
| 85 | private: | ||
| 86 | VirtualFile m_storages[DataStorage + 1]; | ||
| 87 | |||
| 88 | public: | ||
| 89 | void SetMasterHashStorage(VirtualFile s) { | ||
| 90 | m_storages[MasterStorage] = s; | ||
| 91 | } | ||
| 92 | void SetLayer1HashStorage(VirtualFile s) { | ||
| 93 | m_storages[Layer1Storage] = s; | ||
| 94 | } | ||
| 95 | void SetLayer2HashStorage(VirtualFile s) { | ||
| 96 | m_storages[Layer2Storage] = s; | ||
| 97 | } | ||
| 98 | void SetLayer3HashStorage(VirtualFile s) { | ||
| 99 | m_storages[Layer3Storage] = s; | ||
| 100 | } | ||
| 101 | void SetLayer4HashStorage(VirtualFile s) { | ||
| 102 | m_storages[Layer4Storage] = s; | ||
| 103 | } | ||
| 104 | void SetLayer5HashStorage(VirtualFile s) { | ||
| 105 | m_storages[Layer5Storage] = s; | ||
| 106 | } | ||
| 107 | void SetDataStorage(VirtualFile s) { | ||
| 108 | m_storages[DataStorage] = s; | ||
| 109 | } | ||
| 110 | |||
| 111 | VirtualFile& operator[](s32 index) { | ||
| 112 | ASSERT(MasterStorage <= index && index <= DataStorage); | ||
| 113 | return m_storages[index]; | ||
| 114 | } | ||
| 115 | }; | ||
| 116 | |||
| 117 | private: | ||
| 118 | static GenerateRandomFunction s_generate_random; | ||
| 119 | |||
| 120 | static void SetGenerateRandomFunction(GenerateRandomFunction func) { | ||
| 121 | s_generate_random = func; | ||
| 122 | } | ||
| 123 | |||
| 124 | private: | ||
| 125 | std::shared_ptr<IntegrityVerificationStorage> m_verify_storages[MaxLayers - 1]; | ||
| 126 | std::shared_ptr<AlignmentMatchingStoragePooledBuffer<1>> m_buffer_storages[MaxLayers - 1]; | ||
| 127 | s64 m_data_size; | ||
| 128 | s32 m_max_layers; | ||
| 129 | |||
| 130 | public: | ||
| 131 | HierarchicalIntegrityVerificationStorage(); | ||
| 132 | virtual ~HierarchicalIntegrityVerificationStorage() override { | ||
| 133 | this->Finalize(); | ||
| 134 | } | ||
| 135 | |||
| 136 | Result Initialize(const HierarchicalIntegrityVerificationInformation& info, | ||
| 137 | HierarchicalStorageInformation storage, int max_data_cache_entries, | ||
| 138 | int max_hash_cache_entries, s8 buffer_level); | ||
| 139 | void Finalize(); | ||
| 140 | |||
| 141 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 142 | virtual size_t GetSize() const override; | ||
| 143 | |||
| 144 | bool IsInitialized() const { | ||
| 145 | return m_data_size >= 0; | ||
| 146 | } | ||
| 147 | |||
| 148 | s64 GetL1HashVerificationBlockSize() const { | ||
| 149 | return m_verify_storages[m_max_layers - 2]->GetBlockSize(); | ||
| 150 | } | ||
| 151 | |||
| 152 | VirtualFile GetL1HashStorage() { | ||
| 153 | return std::make_shared<OffsetVfsFile>( | ||
| 154 | m_buffer_storages[m_max_layers - 3], | ||
| 155 | Common::DivideUp(m_data_size, this->GetL1HashVerificationBlockSize()), 0); | ||
| 156 | } | ||
| 157 | |||
| 158 | public: | ||
| 159 | static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) { | ||
| 160 | return static_cast<s8>(16 + max_layers - 2); | ||
| 161 | } | ||
| 162 | }; | ||
| 163 | |||
| 164 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp new file mode 100644 index 000000000..357fa7741 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp | |||
| @@ -0,0 +1,103 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/scope_exit.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | namespace { | ||
| 11 | |||
| 12 | s32 Log2(s32 value) { | ||
| 13 | ASSERT(value > 0); | ||
| 14 | ASSERT(Common::IsPowerOfTwo(value)); | ||
| 15 | |||
| 16 | s32 log = 0; | ||
| 17 | while ((value >>= 1) > 0) { | ||
| 18 | ++log; | ||
| 19 | } | ||
| 20 | return log; | ||
| 21 | } | ||
| 22 | |||
| 23 | } // namespace | ||
| 24 | |||
| 25 | Result HierarchicalSha256Storage::Initialize(VirtualFile* base_storages, s32 layer_count, | ||
| 26 | size_t htbs, void* hash_buf, size_t hash_buf_size) { | ||
| 27 | // Validate preconditions. | ||
| 28 | ASSERT(layer_count == LayerCount); | ||
| 29 | ASSERT(Common::IsPowerOfTwo(htbs)); | ||
| 30 | ASSERT(hash_buf != nullptr); | ||
| 31 | |||
| 32 | // Set size tracking members. | ||
| 33 | m_hash_target_block_size = static_cast<s32>(htbs); | ||
| 34 | m_log_size_ratio = Log2(m_hash_target_block_size / HashSize); | ||
| 35 | |||
| 36 | // Get the base storage size. | ||
| 37 | m_base_storage_size = base_storages[2]->GetSize(); | ||
| 38 | { | ||
| 39 | auto size_guard = SCOPE_GUARD({ m_base_storage_size = 0; }); | ||
| 40 | R_UNLESS(m_base_storage_size <= static_cast<s64>(HashSize) | ||
| 41 | << m_log_size_ratio << m_log_size_ratio, | ||
| 42 | ResultHierarchicalSha256BaseStorageTooLarge); | ||
| 43 | size_guard.Cancel(); | ||
| 44 | } | ||
| 45 | |||
| 46 | // Set hash buffer tracking members. | ||
| 47 | m_base_storage = base_storages[2]; | ||
| 48 | m_hash_buffer = static_cast<char*>(hash_buf); | ||
| 49 | m_hash_buffer_size = hash_buf_size; | ||
| 50 | |||
| 51 | // Read the master hash. | ||
| 52 | std::array<u8, HashSize> master_hash{}; | ||
| 53 | base_storages[0]->ReadObject(std::addressof(master_hash)); | ||
| 54 | |||
| 55 | // Read and validate the data being hashed. | ||
| 56 | s64 hash_storage_size = base_storages[1]->GetSize(); | ||
| 57 | ASSERT(Common::IsAligned(hash_storage_size, HashSize)); | ||
| 58 | ASSERT(hash_storage_size <= m_hash_target_block_size); | ||
| 59 | ASSERT(hash_storage_size <= static_cast<s64>(m_hash_buffer_size)); | ||
| 60 | |||
| 61 | base_storages[1]->Read(reinterpret_cast<u8*>(m_hash_buffer), | ||
| 62 | static_cast<size_t>(hash_storage_size), 0); | ||
| 63 | |||
| 64 | R_SUCCEED(); | ||
| 65 | } | ||
| 66 | |||
| 67 | size_t HierarchicalSha256Storage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 68 | // Succeed if zero-size. | ||
| 69 | if (size == 0) { | ||
| 70 | return size; | ||
| 71 | } | ||
| 72 | |||
| 73 | // Validate that we have a buffer to read into. | ||
| 74 | ASSERT(buffer != nullptr); | ||
| 75 | |||
| 76 | // Validate preconditions. | ||
| 77 | ASSERT(Common::IsAligned(offset, m_hash_target_block_size)); | ||
| 78 | ASSERT(Common::IsAligned(size, m_hash_target_block_size)); | ||
| 79 | |||
| 80 | // Read the data. | ||
| 81 | const size_t reduced_size = static_cast<size_t>( | ||
| 82 | std::min<s64>(m_base_storage_size, | ||
| 83 | Common::AlignUp(offset + size, m_hash_target_block_size)) - | ||
| 84 | offset); | ||
| 85 | m_base_storage->Read(buffer, reduced_size, offset); | ||
| 86 | |||
| 87 | // Setup tracking variables. | ||
| 88 | auto cur_offset = offset; | ||
| 89 | auto remaining_size = reduced_size; | ||
| 90 | while (remaining_size > 0) { | ||
| 91 | const auto cur_size = | ||
| 92 | static_cast<size_t>(std::min<s64>(m_hash_target_block_size, remaining_size)); | ||
| 93 | ASSERT(static_cast<size_t>(cur_offset >> m_log_size_ratio) < m_hash_buffer_size); | ||
| 94 | |||
| 95 | // Advance. | ||
| 96 | cur_offset += cur_size; | ||
| 97 | remaining_size -= cur_size; | ||
| 98 | } | ||
| 99 | |||
| 100 | return size; | ||
| 101 | } | ||
| 102 | |||
| 103 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h new file mode 100644 index 000000000..717ba9748 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <mutex> | ||
| 7 | |||
| 8 | #include "core/file_sys/errors.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 10 | #include "core/file_sys/vfs.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | class HierarchicalSha256Storage : public IReadOnlyStorage { | ||
| 15 | YUZU_NON_COPYABLE(HierarchicalSha256Storage); | ||
| 16 | YUZU_NON_MOVEABLE(HierarchicalSha256Storage); | ||
| 17 | |||
| 18 | public: | ||
| 19 | static constexpr s32 LayerCount = 3; | ||
| 20 | static constexpr size_t HashSize = 256 / 8; | ||
| 21 | |||
| 22 | private: | ||
| 23 | VirtualFile m_base_storage; | ||
| 24 | s64 m_base_storage_size; | ||
| 25 | char* m_hash_buffer; | ||
| 26 | size_t m_hash_buffer_size; | ||
| 27 | s32 m_hash_target_block_size; | ||
| 28 | s32 m_log_size_ratio; | ||
| 29 | std::mutex m_mutex; | ||
| 30 | |||
| 31 | public: | ||
| 32 | HierarchicalSha256Storage() : m_mutex() {} | ||
| 33 | |||
| 34 | Result Initialize(VirtualFile* base_storages, s32 layer_count, size_t htbs, void* hash_buf, | ||
| 35 | size_t hash_buf_size); | ||
| 36 | |||
| 37 | virtual size_t GetSize() const override { | ||
| 38 | return m_base_storage->GetSize(); | ||
| 39 | } | ||
| 40 | |||
| 41 | virtual size_t Read(u8* buffer, size_t length, size_t offset) const override; | ||
| 42 | }; | ||
| 43 | |||
| 44 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp new file mode 100644 index 000000000..45aa08d30 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp | |||
| @@ -0,0 +1,120 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/errors.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_indirect_storage.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | Result IndirectStorage::Initialize(VirtualFile table_storage) { | ||
| 10 | // Read and verify the bucket tree header. | ||
| 11 | BucketTree::Header header; | ||
| 12 | table_storage->ReadObject(std::addressof(header)); | ||
| 13 | R_TRY(header.Verify()); | ||
| 14 | |||
| 15 | // Determine extents. | ||
| 16 | const auto node_storage_size = QueryNodeStorageSize(header.entry_count); | ||
| 17 | const auto entry_storage_size = QueryEntryStorageSize(header.entry_count); | ||
| 18 | const auto node_storage_offset = QueryHeaderStorageSize(); | ||
| 19 | const auto entry_storage_offset = node_storage_offset + node_storage_size; | ||
| 20 | |||
| 21 | // Initialize. | ||
| 22 | R_RETURN(this->Initialize( | ||
| 23 | std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset), | ||
| 24 | std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset), | ||
| 25 | header.entry_count)); | ||
| 26 | } | ||
| 27 | |||
| 28 | void IndirectStorage::Finalize() { | ||
| 29 | if (this->IsInitialized()) { | ||
| 30 | m_table.Finalize(); | ||
| 31 | for (auto i = 0; i < StorageCount; i++) { | ||
| 32 | m_data_storage[i] = VirtualFile(); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, | ||
| 38 | s64 offset, s64 size) { | ||
| 39 | // Validate pre-conditions. | ||
| 40 | ASSERT(offset >= 0); | ||
| 41 | ASSERT(size >= 0); | ||
| 42 | ASSERT(this->IsInitialized()); | ||
| 43 | |||
| 44 | // Clear the out count. | ||
| 45 | R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument); | ||
| 46 | *out_entry_count = 0; | ||
| 47 | |||
| 48 | // Succeed if there's no range. | ||
| 49 | R_SUCCEED_IF(size == 0); | ||
| 50 | |||
| 51 | // If we have an output array, we need it to be non-null. | ||
| 52 | R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument); | ||
| 53 | |||
| 54 | // Check that our range is valid. | ||
| 55 | BucketTree::Offsets table_offsets; | ||
| 56 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 57 | |||
| 58 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 59 | |||
| 60 | // Find the offset in our tree. | ||
| 61 | BucketTree::Visitor visitor; | ||
| 62 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 63 | { | ||
| 64 | const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); | ||
| 65 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 66 | ResultInvalidIndirectEntryOffset); | ||
| 67 | } | ||
| 68 | |||
| 69 | // Prepare to loop over entries. | ||
| 70 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 71 | s32 count = 0; | ||
| 72 | |||
| 73 | auto cur_entry = *visitor.Get<Entry>(); | ||
| 74 | while (cur_entry.GetVirtualOffset() < end_offset) { | ||
| 75 | // Try to write the entry to the out list | ||
| 76 | if (entry_count != 0) { | ||
| 77 | if (count >= entry_count) { | ||
| 78 | break; | ||
| 79 | } | ||
| 80 | std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry)); | ||
| 81 | } | ||
| 82 | |||
| 83 | count++; | ||
| 84 | |||
| 85 | // Advance. | ||
| 86 | if (visitor.CanMoveNext()) { | ||
| 87 | R_TRY(visitor.MoveNext()); | ||
| 88 | cur_entry = *visitor.Get<Entry>(); | ||
| 89 | } else { | ||
| 90 | break; | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | // Write the output count. | ||
| 95 | *out_entry_count = count; | ||
| 96 | R_SUCCEED(); | ||
| 97 | } | ||
| 98 | |||
| 99 | size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 100 | // Validate pre-conditions. | ||
| 101 | ASSERT(offset >= 0); | ||
| 102 | ASSERT(this->IsInitialized()); | ||
| 103 | ASSERT(buffer != nullptr); | ||
| 104 | |||
| 105 | // Succeed if there's nothing to read. | ||
| 106 | if (size == 0) { | ||
| 107 | return 0; | ||
| 108 | } | ||
| 109 | |||
| 110 | const_cast<IndirectStorage*>(this)->OperatePerEntry<true, true>( | ||
| 111 | offset, size, | ||
| 112 | [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { | ||
| 113 | storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset), | ||
| 114 | static_cast<size_t>(cur_size), data_offset); | ||
| 115 | R_SUCCEED(); | ||
| 116 | }); | ||
| 117 | |||
| 118 | return size; | ||
| 119 | } | ||
| 120 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.h b/src/core/file_sys/fssystem/fssystem_indirect_storage.h new file mode 100644 index 000000000..39293667b --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.h | |||
| @@ -0,0 +1,294 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/errors.h" | ||
| 7 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_bucket_tree.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h" | ||
| 10 | #include "core/file_sys/vfs.h" | ||
| 11 | #include "core/file_sys/vfs_offset.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | class IndirectStorage : public IReadOnlyStorage { | ||
| 16 | YUZU_NON_COPYABLE(IndirectStorage); | ||
| 17 | YUZU_NON_MOVEABLE(IndirectStorage); | ||
| 18 | |||
| 19 | public: | ||
| 20 | static constexpr s32 StorageCount = 2; | ||
| 21 | static constexpr size_t NodeSize = 16_KiB; | ||
| 22 | |||
| 23 | struct Entry { | ||
| 24 | u8 virt_offset[sizeof(s64)]; | ||
| 25 | u8 phys_offset[sizeof(s64)]; | ||
| 26 | s32 storage_index; | ||
| 27 | |||
| 28 | void SetVirtualOffset(const s64& ofs) { | ||
| 29 | std::memcpy(this->virt_offset, std::addressof(ofs), sizeof(s64)); | ||
| 30 | } | ||
| 31 | |||
| 32 | s64 GetVirtualOffset() const { | ||
| 33 | s64 offset; | ||
| 34 | std::memcpy(std::addressof(offset), this->virt_offset, sizeof(s64)); | ||
| 35 | return offset; | ||
| 36 | } | ||
| 37 | |||
| 38 | void SetPhysicalOffset(const s64& ofs) { | ||
| 39 | std::memcpy(this->phys_offset, std::addressof(ofs), sizeof(s64)); | ||
| 40 | } | ||
| 41 | |||
| 42 | s64 GetPhysicalOffset() const { | ||
| 43 | s64 offset; | ||
| 44 | std::memcpy(std::addressof(offset), this->phys_offset, sizeof(s64)); | ||
| 45 | return offset; | ||
| 46 | } | ||
| 47 | }; | ||
| 48 | static_assert(std::is_trivial_v<Entry>); | ||
| 49 | static_assert(sizeof(Entry) == 0x14); | ||
| 50 | |||
| 51 | struct EntryData { | ||
| 52 | s64 virt_offset; | ||
| 53 | s64 phys_offset; | ||
| 54 | s32 storage_index; | ||
| 55 | |||
| 56 | void Set(const Entry& entry) { | ||
| 57 | this->virt_offset = entry.GetVirtualOffset(); | ||
| 58 | this->phys_offset = entry.GetPhysicalOffset(); | ||
| 59 | this->storage_index = entry.storage_index; | ||
| 60 | } | ||
| 61 | }; | ||
| 62 | static_assert(std::is_trivial_v<EntryData>); | ||
| 63 | |||
| 64 | private: | ||
| 65 | struct ContinuousReadingEntry { | ||
| 66 | static constexpr size_t FragmentSizeMax = 4_KiB; | ||
| 67 | |||
| 68 | IndirectStorage::Entry entry; | ||
| 69 | |||
| 70 | s64 GetVirtualOffset() const { | ||
| 71 | return this->entry.GetVirtualOffset(); | ||
| 72 | } | ||
| 73 | |||
| 74 | s64 GetPhysicalOffset() const { | ||
| 75 | return this->entry.GetPhysicalOffset(); | ||
| 76 | } | ||
| 77 | |||
| 78 | bool IsFragment() const { | ||
| 79 | return this->entry.storage_index != 0; | ||
| 80 | } | ||
| 81 | }; | ||
| 82 | static_assert(std::is_trivial_v<ContinuousReadingEntry>); | ||
| 83 | |||
| 84 | public: | ||
| 85 | static constexpr s64 QueryHeaderStorageSize() { | ||
| 86 | return BucketTree::QueryHeaderStorageSize(); | ||
| 87 | } | ||
| 88 | |||
| 89 | static constexpr s64 QueryNodeStorageSize(s32 entry_count) { | ||
| 90 | return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 91 | } | ||
| 92 | |||
| 93 | static constexpr s64 QueryEntryStorageSize(s32 entry_count) { | ||
| 94 | return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); | ||
| 95 | } | ||
| 96 | |||
| 97 | private: | ||
| 98 | mutable BucketTree m_table; | ||
| 99 | std::array<VirtualFile, StorageCount> m_data_storage; | ||
| 100 | |||
| 101 | public: | ||
| 102 | IndirectStorage() : m_table(), m_data_storage() {} | ||
| 103 | virtual ~IndirectStorage() { | ||
| 104 | this->Finalize(); | ||
| 105 | } | ||
| 106 | |||
| 107 | Result Initialize(VirtualFile table_storage); | ||
| 108 | void Finalize(); | ||
| 109 | |||
| 110 | bool IsInitialized() const { | ||
| 111 | return m_table.IsInitialized(); | ||
| 112 | } | ||
| 113 | |||
| 114 | Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, s32 entry_count) { | ||
| 115 | R_RETURN( | ||
| 116 | m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count)); | ||
| 117 | } | ||
| 118 | |||
| 119 | void SetStorage(s32 idx, VirtualFile storage) { | ||
| 120 | ASSERT(0 <= idx && idx < StorageCount); | ||
| 121 | m_data_storage[idx] = storage; | ||
| 122 | } | ||
| 123 | |||
| 124 | template <typename T> | ||
| 125 | void SetStorage(s32 idx, T storage, s64 offset, s64 size) { | ||
| 126 | ASSERT(0 <= idx && idx < StorageCount); | ||
| 127 | m_data_storage[idx] = std::make_shared<OffsetVfsFile>(storage, size, offset); | ||
| 128 | } | ||
| 129 | |||
| 130 | Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset, | ||
| 131 | s64 size); | ||
| 132 | |||
| 133 | virtual size_t GetSize() const override { | ||
| 134 | BucketTree::Offsets offsets; | ||
| 135 | m_table.GetOffsets(std::addressof(offsets)); | ||
| 136 | |||
| 137 | return offsets.end_offset; | ||
| 138 | } | ||
| 139 | |||
| 140 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 141 | |||
| 142 | protected: | ||
| 143 | BucketTree& GetEntryTable() { | ||
| 144 | return m_table; | ||
| 145 | } | ||
| 146 | |||
| 147 | VirtualFile& GetDataStorage(s32 index) { | ||
| 148 | ASSERT(0 <= index && index < StorageCount); | ||
| 149 | return m_data_storage[index]; | ||
| 150 | } | ||
| 151 | |||
| 152 | template <bool ContinuousCheck, bool RangeCheck, typename F> | ||
| 153 | Result OperatePerEntry(s64 offset, s64 size, F func); | ||
| 154 | }; | ||
| 155 | |||
| 156 | template <bool ContinuousCheck, bool RangeCheck, typename F> | ||
| 157 | Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) { | ||
| 158 | // Validate preconditions. | ||
| 159 | ASSERT(offset >= 0); | ||
| 160 | ASSERT(size >= 0); | ||
| 161 | ASSERT(this->IsInitialized()); | ||
| 162 | |||
| 163 | // Succeed if there's nothing to operate on. | ||
| 164 | R_SUCCEED_IF(size == 0); | ||
| 165 | |||
| 166 | // Get the table offsets. | ||
| 167 | BucketTree::Offsets table_offsets; | ||
| 168 | R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); | ||
| 169 | |||
| 170 | // Validate arguments. | ||
| 171 | R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); | ||
| 172 | |||
| 173 | // Find the offset in our tree. | ||
| 174 | BucketTree::Visitor visitor; | ||
| 175 | R_TRY(m_table.Find(std::addressof(visitor), offset)); | ||
| 176 | { | ||
| 177 | const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); | ||
| 178 | R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), | ||
| 179 | ResultInvalidIndirectEntryOffset); | ||
| 180 | } | ||
| 181 | |||
| 182 | // Prepare to operate in chunks. | ||
| 183 | auto cur_offset = offset; | ||
| 184 | const auto end_offset = offset + static_cast<s64>(size); | ||
| 185 | BucketTree::ContinuousReadingInfo cr_info; | ||
| 186 | |||
| 187 | while (cur_offset < end_offset) { | ||
| 188 | // Get the current entry. | ||
| 189 | const auto cur_entry = *visitor.Get<Entry>(); | ||
| 190 | |||
| 191 | // Get and validate the entry's offset. | ||
| 192 | const auto cur_entry_offset = cur_entry.GetVirtualOffset(); | ||
| 193 | R_UNLESS(cur_entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset); | ||
| 194 | |||
| 195 | // Validate the storage index. | ||
| 196 | R_UNLESS(0 <= cur_entry.storage_index && cur_entry.storage_index < StorageCount, | ||
| 197 | ResultInvalidIndirectEntryStorageIndex); | ||
| 198 | |||
| 199 | // If we need to check the continuous info, do so. | ||
| 200 | if constexpr (ContinuousCheck) { | ||
| 201 | // Scan, if we need to. | ||
| 202 | if (cr_info.CheckNeedScan()) { | ||
| 203 | R_TRY(visitor.ScanContinuousReading<ContinuousReadingEntry>( | ||
| 204 | std::addressof(cr_info), cur_offset, | ||
| 205 | static_cast<size_t>(end_offset - cur_offset))); | ||
| 206 | } | ||
| 207 | |||
| 208 | // Process a base storage entry. | ||
| 209 | if (cr_info.CanDo()) { | ||
| 210 | // Ensure that we can process. | ||
| 211 | R_UNLESS(cur_entry.storage_index == 0, ResultInvalidIndirectEntryStorageIndex); | ||
| 212 | |||
| 213 | // Ensure that we remain within range. | ||
| 214 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 215 | const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); | ||
| 216 | const auto cur_size = static_cast<s64>(cr_info.GetReadSize()); | ||
| 217 | |||
| 218 | // If we should, verify the range. | ||
| 219 | if constexpr (RangeCheck) { | ||
| 220 | // Get the current data storage's size. | ||
| 221 | s64 cur_data_storage_size = m_data_storage[0]->GetSize(); | ||
| 222 | |||
| 223 | R_UNLESS(0 <= cur_entry_phys_offset && | ||
| 224 | cur_entry_phys_offset <= cur_data_storage_size, | ||
| 225 | ResultInvalidIndirectEntryOffset); | ||
| 226 | R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= | ||
| 227 | cur_data_storage_size, | ||
| 228 | ResultInvalidIndirectStorageSize); | ||
| 229 | } | ||
| 230 | |||
| 231 | // Operate. | ||
| 232 | R_TRY(func(m_data_storage[0], cur_entry_phys_offset + data_offset, cur_offset, | ||
| 233 | cur_size)); | ||
| 234 | |||
| 235 | // Mark as done. | ||
| 236 | cr_info.Done(); | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | // Get and validate the next entry offset. | ||
| 241 | s64 next_entry_offset; | ||
| 242 | if (visitor.CanMoveNext()) { | ||
| 243 | R_TRY(visitor.MoveNext()); | ||
| 244 | next_entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); | ||
| 245 | R_UNLESS(table_offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset); | ||
| 246 | } else { | ||
| 247 | next_entry_offset = table_offsets.end_offset; | ||
| 248 | } | ||
| 249 | R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset); | ||
| 250 | |||
| 251 | // Get the offset of the entry in the data we read. | ||
| 252 | const auto data_offset = cur_offset - cur_entry_offset; | ||
| 253 | const auto data_size = (next_entry_offset - cur_entry_offset); | ||
| 254 | ASSERT(data_size > 0); | ||
| 255 | |||
| 256 | // Determine how much is left. | ||
| 257 | const auto remaining_size = end_offset - cur_offset; | ||
| 258 | const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset); | ||
| 259 | ASSERT(cur_size <= size); | ||
| 260 | |||
| 261 | // Operate, if we need to. | ||
| 262 | bool needs_operate; | ||
| 263 | if constexpr (!ContinuousCheck) { | ||
| 264 | needs_operate = true; | ||
| 265 | } else { | ||
| 266 | needs_operate = !cr_info.IsDone() || cur_entry.storage_index != 0; | ||
| 267 | } | ||
| 268 | |||
| 269 | if (needs_operate) { | ||
| 270 | const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); | ||
| 271 | |||
| 272 | if constexpr (RangeCheck) { | ||
| 273 | // Get the current data storage's size. | ||
| 274 | s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize(); | ||
| 275 | |||
| 276 | // Ensure that we remain within range. | ||
| 277 | R_UNLESS(0 <= cur_entry_phys_offset && | ||
| 278 | cur_entry_phys_offset <= cur_data_storage_size, | ||
| 279 | ResultIndirectStorageCorrupted); | ||
| 280 | R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= cur_data_storage_size, | ||
| 281 | ResultIndirectStorageCorrupted); | ||
| 282 | } | ||
| 283 | |||
| 284 | R_TRY(func(m_data_storage[cur_entry.storage_index], cur_entry_phys_offset + data_offset, | ||
| 285 | cur_offset, cur_size)); | ||
| 286 | } | ||
| 287 | |||
| 288 | cur_offset += cur_size; | ||
| 289 | } | ||
| 290 | |||
| 291 | R_SUCCEED(); | ||
| 292 | } | ||
| 293 | |||
| 294 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp new file mode 100644 index 000000000..2c3da230c --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | Result IntegrityRomFsStorage::Initialize( | ||
| 9 | HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, | ||
| 10 | HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, | ||
| 11 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) { | ||
| 12 | // Set master hash. | ||
| 13 | m_master_hash = master_hash; | ||
| 14 | m_master_hash_storage = std::make_shared<ArrayVfsFile<sizeof(Hash)>>(m_master_hash.value); | ||
| 15 | R_UNLESS(m_master_hash_storage != nullptr, | ||
| 16 | ResultAllocationMemoryFailedInIntegrityRomFsStorageA); | ||
| 17 | |||
| 18 | // Set the master hash storage. | ||
| 19 | storage_info[0] = m_master_hash_storage; | ||
| 20 | |||
| 21 | // Initialize our integrity storage. | ||
| 22 | R_RETURN(m_integrity_storage.Initialize(level_hash_info, storage_info, max_data_cache_entries, | ||
| 23 | max_hash_cache_entries, buffer_level)); | ||
| 24 | } | ||
| 25 | |||
| 26 | void IntegrityRomFsStorage::Finalize() { | ||
| 27 | m_integrity_storage.Finalize(); | ||
| 28 | } | ||
| 29 | |||
| 30 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h new file mode 100644 index 000000000..b80e9a302 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 8 | #include "core/file_sys/vfs_vector.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | constexpr inline size_t IntegrityLayerCountRomFs = 7; | ||
| 13 | constexpr inline size_t IntegrityHashLayerBlockSize = 16_KiB; | ||
| 14 | |||
| 15 | class IntegrityRomFsStorage : public IReadOnlyStorage { | ||
| 16 | private: | ||
| 17 | HierarchicalIntegrityVerificationStorage m_integrity_storage; | ||
| 18 | Hash m_master_hash; | ||
| 19 | std::shared_ptr<ArrayVfsFile<sizeof(Hash)>> m_master_hash_storage; | ||
| 20 | |||
| 21 | public: | ||
| 22 | IntegrityRomFsStorage() {} | ||
| 23 | virtual ~IntegrityRomFsStorage() override { | ||
| 24 | this->Finalize(); | ||
| 25 | } | ||
| 26 | |||
| 27 | Result Initialize( | ||
| 28 | HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, | ||
| 29 | HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, | ||
| 30 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level); | ||
| 31 | void Finalize(); | ||
| 32 | |||
| 33 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 34 | return m_integrity_storage.Read(buffer, size, offset); | ||
| 35 | } | ||
| 36 | |||
| 37 | virtual size_t GetSize() const override { | ||
| 38 | return m_integrity_storage.GetSize(); | ||
| 39 | } | ||
| 40 | }; | ||
| 41 | |||
| 42 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp new file mode 100644 index 000000000..ef36b755e --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp | |||
| @@ -0,0 +1,95 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | constexpr inline u32 ILog2(u32 val) { | ||
| 10 | ASSERT(val > 0); | ||
| 11 | return ((sizeof(u32) * 8) - 1 - std::countl_zero<u32>(val)); | ||
| 12 | } | ||
| 13 | |||
| 14 | void IntegrityVerificationStorage::Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size, | ||
| 15 | s64 upper_layer_verif_block_size, bool is_real_data) { | ||
| 16 | // Validate preconditions. | ||
| 17 | ASSERT(verif_block_size >= HashSize); | ||
| 18 | |||
| 19 | // Set storages. | ||
| 20 | m_hash_storage = hs; | ||
| 21 | m_data_storage = ds; | ||
| 22 | |||
| 23 | // Set verification block sizes. | ||
| 24 | m_verification_block_size = verif_block_size; | ||
| 25 | m_verification_block_order = ILog2(static_cast<u32>(verif_block_size)); | ||
| 26 | ASSERT(m_verification_block_size == 1ll << m_verification_block_order); | ||
| 27 | |||
| 28 | // Set upper layer block sizes. | ||
| 29 | upper_layer_verif_block_size = std::max(upper_layer_verif_block_size, HashSize); | ||
| 30 | m_upper_layer_verification_block_size = upper_layer_verif_block_size; | ||
| 31 | m_upper_layer_verification_block_order = ILog2(static_cast<u32>(upper_layer_verif_block_size)); | ||
| 32 | ASSERT(m_upper_layer_verification_block_size == 1ll << m_upper_layer_verification_block_order); | ||
| 33 | |||
| 34 | // Validate sizes. | ||
| 35 | { | ||
| 36 | s64 hash_size = m_hash_storage->GetSize(); | ||
| 37 | s64 data_size = m_data_storage->GetSize(); | ||
| 38 | ASSERT(((hash_size / HashSize) * m_verification_block_size) >= data_size); | ||
| 39 | } | ||
| 40 | |||
| 41 | // Set data. | ||
| 42 | m_is_real_data = is_real_data; | ||
| 43 | } | ||
| 44 | |||
| 45 | void IntegrityVerificationStorage::Finalize() { | ||
| 46 | m_hash_storage = VirtualFile(); | ||
| 47 | m_data_storage = VirtualFile(); | ||
| 48 | } | ||
| 49 | |||
| 50 | size_t IntegrityVerificationStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 51 | // Validate preconditions. | ||
| 52 | ASSERT(Common::IsAligned(offset, static_cast<size_t>(m_verification_block_size))); | ||
| 53 | ASSERT(Common::IsAligned(size, static_cast<size_t>(m_verification_block_size))); | ||
| 54 | |||
| 55 | // Succeed if zero size. | ||
| 56 | if (size == 0) { | ||
| 57 | return size; | ||
| 58 | } | ||
| 59 | |||
| 60 | // Validate arguments. | ||
| 61 | ASSERT(buffer != nullptr); | ||
| 62 | |||
| 63 | // Validate the offset. | ||
| 64 | s64 data_size = m_data_storage->GetSize(); | ||
| 65 | ASSERT(offset <= static_cast<size_t>(data_size)); | ||
| 66 | |||
| 67 | // Validate the access range. | ||
| 68 | ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange( | ||
| 69 | offset, size, Common::AlignUp(data_size, static_cast<size_t>(m_verification_block_size))))); | ||
| 70 | |||
| 71 | // Determine the read extents. | ||
| 72 | size_t read_size = size; | ||
| 73 | if (static_cast<s64>(offset + read_size) > data_size) { | ||
| 74 | // Determine the padding sizes. | ||
| 75 | s64 padding_offset = data_size - offset; | ||
| 76 | size_t padding_size = static_cast<size_t>( | ||
| 77 | m_verification_block_size - (padding_offset & (m_verification_block_size - 1))); | ||
| 78 | ASSERT(static_cast<s64>(padding_size) < m_verification_block_size); | ||
| 79 | |||
| 80 | // Clear the padding. | ||
| 81 | std::memset(static_cast<u8*>(buffer) + padding_offset, 0, padding_size); | ||
| 82 | |||
| 83 | // Set the new in-bounds size. | ||
| 84 | read_size = static_cast<size_t>(data_size - offset); | ||
| 85 | } | ||
| 86 | |||
| 87 | // Perform the read. | ||
| 88 | return m_data_storage->Read(buffer, read_size, offset); | ||
| 89 | } | ||
| 90 | |||
| 91 | size_t IntegrityVerificationStorage::GetSize() const { | ||
| 92 | return m_data_storage->GetSize(); | ||
| 93 | } | ||
| 94 | |||
| 95 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h new file mode 100644 index 000000000..08515a268 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <optional> | ||
| 7 | |||
| 8 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 9 | #include "core/file_sys/fssystem/fs_types.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | class IntegrityVerificationStorage : public IReadOnlyStorage { | ||
| 14 | YUZU_NON_COPYABLE(IntegrityVerificationStorage); | ||
| 15 | YUZU_NON_MOVEABLE(IntegrityVerificationStorage); | ||
| 16 | |||
| 17 | public: | ||
| 18 | static constexpr s64 HashSize = 256 / 8; | ||
| 19 | |||
| 20 | struct BlockHash { | ||
| 21 | u8 hash[HashSize]; | ||
| 22 | }; | ||
| 23 | static_assert(std::is_trivial_v<BlockHash>); | ||
| 24 | |||
| 25 | private: | ||
| 26 | VirtualFile m_hash_storage; | ||
| 27 | VirtualFile m_data_storage; | ||
| 28 | s64 m_verification_block_size; | ||
| 29 | s64 m_verification_block_order; | ||
| 30 | s64 m_upper_layer_verification_block_size; | ||
| 31 | s64 m_upper_layer_verification_block_order; | ||
| 32 | bool m_is_real_data; | ||
| 33 | |||
| 34 | public: | ||
| 35 | IntegrityVerificationStorage() | ||
| 36 | : m_verification_block_size(0), m_verification_block_order(0), | ||
| 37 | m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0) {} | ||
| 38 | virtual ~IntegrityVerificationStorage() override { | ||
| 39 | this->Finalize(); | ||
| 40 | } | ||
| 41 | |||
| 42 | void Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size, | ||
| 43 | s64 upper_layer_verif_block_size, bool is_real_data); | ||
| 44 | void Finalize(); | ||
| 45 | |||
| 46 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 47 | virtual size_t GetSize() const override; | ||
| 48 | |||
| 49 | s64 GetBlockSize() const { | ||
| 50 | return m_verification_block_size; | ||
| 51 | } | ||
| 52 | |||
| 53 | private: | ||
| 54 | static void SetValidationBit(BlockHash* hash) { | ||
| 55 | ASSERT(hash != nullptr); | ||
| 56 | hash->hash[HashSize - 1] |= 0x80; | ||
| 57 | } | ||
| 58 | |||
| 59 | static bool IsValidationBit(const BlockHash* hash) { | ||
| 60 | ASSERT(hash != nullptr); | ||
| 61 | return (hash->hash[HashSize - 1] & 0x80) != 0; | ||
| 62 | } | ||
| 63 | }; | ||
| 64 | |||
| 65 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h b/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h new file mode 100644 index 000000000..7637272d5 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 4 | |||
| 5 | namespace FileSys { | ||
| 6 | |||
| 7 | class MemoryResourceBufferHoldStorage : public IStorage { | ||
| 8 | YUZU_NON_COPYABLE(MemoryResourceBufferHoldStorage); | ||
| 9 | YUZU_NON_MOVEABLE(MemoryResourceBufferHoldStorage); | ||
| 10 | |||
| 11 | private: | ||
| 12 | VirtualFile m_storage; | ||
| 13 | void* m_buffer; | ||
| 14 | size_t m_buffer_size; | ||
| 15 | |||
| 16 | public: | ||
| 17 | MemoryResourceBufferHoldStorage(VirtualFile storage, size_t buffer_size) | ||
| 18 | : m_storage(std::move(storage)), m_buffer(::operator new(buffer_size)), | ||
| 19 | m_buffer_size(buffer_size) {} | ||
| 20 | |||
| 21 | virtual ~MemoryResourceBufferHoldStorage() { | ||
| 22 | // If we have a buffer, deallocate it. | ||
| 23 | if (m_buffer != nullptr) { | ||
| 24 | ::operator delete(m_buffer); | ||
| 25 | } | ||
| 26 | } | ||
| 27 | |||
| 28 | bool IsValid() const { | ||
| 29 | return m_buffer != nullptr; | ||
| 30 | } | ||
| 31 | void* GetBuffer() const { | ||
| 32 | return m_buffer; | ||
| 33 | } | ||
| 34 | |||
| 35 | public: | ||
| 36 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 37 | // Check pre-conditions. | ||
| 38 | ASSERT(m_storage != nullptr); | ||
| 39 | |||
| 40 | return m_storage->Read(buffer, size, offset); | ||
| 41 | } | ||
| 42 | |||
| 43 | virtual size_t GetSize() const override { | ||
| 44 | // Check pre-conditions. | ||
| 45 | ASSERT(m_storage != nullptr); | ||
| 46 | |||
| 47 | return m_storage->GetSize(); | ||
| 48 | } | ||
| 49 | |||
| 50 | virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { | ||
| 51 | // Check pre-conditions. | ||
| 52 | ASSERT(m_storage != nullptr); | ||
| 53 | |||
| 54 | return m_storage->Write(buffer, size, offset); | ||
| 55 | } | ||
| 56 | }; | ||
| 57 | |||
| 58 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp new file mode 100644 index 000000000..b1b5fb156 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp | |||
| @@ -0,0 +1,1345 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" | ||
| 6 | #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h" | ||
| 8 | #include "core/file_sys/fssystem/fssystem_compressed_storage.h" | ||
| 9 | #include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" | ||
| 10 | #include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" | ||
| 11 | #include "core/file_sys/fssystem/fssystem_indirect_storage.h" | ||
| 12 | #include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h" | ||
| 13 | #include "core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h" | ||
| 14 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 15 | #include "core/file_sys/fssystem/fssystem_sparse_storage.h" | ||
| 16 | #include "core/file_sys/fssystem/fssystem_switch_storage.h" | ||
| 17 | #include "core/file_sys/vfs_offset.h" | ||
| 18 | #include "core/file_sys/vfs_vector.h" | ||
| 19 | |||
| 20 | namespace FileSys { | ||
| 21 | |||
| 22 | namespace { | ||
| 23 | |||
| 24 | constexpr inline s32 IntegrityDataCacheCount = 24; | ||
| 25 | constexpr inline s32 IntegrityHashCacheCount = 8; | ||
| 26 | |||
| 27 | constexpr inline s32 IntegrityDataCacheCountForMeta = 16; | ||
| 28 | constexpr inline s32 IntegrityHashCacheCountForMeta = 2; | ||
| 29 | |||
| 30 | class SharedNcaBodyStorage : public IReadOnlyStorage { | ||
| 31 | YUZU_NON_COPYABLE(SharedNcaBodyStorage); | ||
| 32 | YUZU_NON_MOVEABLE(SharedNcaBodyStorage); | ||
| 33 | |||
| 34 | private: | ||
| 35 | VirtualFile m_storage; | ||
| 36 | std::shared_ptr<NcaReader> m_nca_reader; | ||
| 37 | |||
| 38 | public: | ||
| 39 | SharedNcaBodyStorage(VirtualFile s, std::shared_ptr<NcaReader> r) | ||
| 40 | : m_storage(std::move(s)), m_nca_reader(std::move(r)) {} | ||
| 41 | |||
| 42 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 43 | // Validate pre-conditions. | ||
| 44 | ASSERT(m_storage != nullptr); | ||
| 45 | |||
| 46 | // Read from the base storage. | ||
| 47 | return m_storage->Read(buffer, size, offset); | ||
| 48 | } | ||
| 49 | |||
| 50 | virtual size_t GetSize() const override { | ||
| 51 | // Validate pre-conditions. | ||
| 52 | ASSERT(m_storage != nullptr); | ||
| 53 | |||
| 54 | return m_storage->GetSize(); | ||
| 55 | } | ||
| 56 | }; | ||
| 57 | |||
| 58 | inline s64 GetFsOffset(const NcaReader& reader, s32 fs_index) { | ||
| 59 | return static_cast<s64>(reader.GetFsOffset(fs_index)); | ||
| 60 | } | ||
| 61 | |||
| 62 | inline s64 GetFsEndOffset(const NcaReader& reader, s32 fs_index) { | ||
| 63 | return static_cast<s64>(reader.GetFsEndOffset(fs_index)); | ||
| 64 | } | ||
| 65 | |||
| 66 | using Sha256DataRegion = NcaFsHeader::Region; | ||
| 67 | using IntegrityLevelInfo = NcaFsHeader::HashData::IntegrityMetaInfo::LevelHashInfo; | ||
| 68 | using IntegrityDataInfo = IntegrityLevelInfo::HierarchicalIntegrityVerificationLevelInformation; | ||
| 69 | |||
| 70 | } // namespace | ||
| 71 | |||
| 72 | Result NcaFileSystemDriver::OpenStorageWithContext(VirtualFile* out, | ||
| 73 | NcaFsHeaderReader* out_header_reader, | ||
| 74 | s32 fs_index, StorageContext* ctx) { | ||
| 75 | // Open storage. | ||
| 76 | R_RETURN(this->OpenStorageImpl(out, out_header_reader, fs_index, ctx)); | ||
| 77 | } | ||
| 78 | |||
| 79 | Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, | ||
| 80 | s32 fs_index, StorageContext* ctx) { | ||
| 81 | // Validate preconditions. | ||
| 82 | ASSERT(out != nullptr); | ||
| 83 | ASSERT(out_header_reader != nullptr); | ||
| 84 | ASSERT(0 <= fs_index && fs_index < NcaHeader::FsCountMax); | ||
| 85 | |||
| 86 | // Validate the fs index. | ||
| 87 | R_UNLESS(m_reader->HasFsInfo(fs_index), ResultPartitionNotFound); | ||
| 88 | |||
| 89 | // Initialize our header reader for the fs index. | ||
| 90 | R_TRY(out_header_reader->Initialize(*m_reader, fs_index)); | ||
| 91 | |||
| 92 | // Declare the storage we're opening. | ||
| 93 | VirtualFile storage; | ||
| 94 | |||
| 95 | // Process sparse layer. | ||
| 96 | s64 fs_data_offset = 0; | ||
| 97 | if (out_header_reader->ExistsSparseLayer()) { | ||
| 98 | // Get the sparse info. | ||
| 99 | const auto& sparse_info = out_header_reader->GetSparseInfo(); | ||
| 100 | |||
| 101 | // Create based on whether we have a meta hash layer. | ||
| 102 | if (out_header_reader->ExistsSparseMetaHashLayer()) { | ||
| 103 | // Create the sparse storage with verification. | ||
| 104 | R_TRY(this->CreateSparseStorageWithVerification( | ||
| 105 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 106 | ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, | ||
| 107 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 108 | ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, | ||
| 109 | out_header_reader->GetAesCtrUpperIv(), sparse_info, | ||
| 110 | out_header_reader->GetSparseMetaDataHashDataInfo(), | ||
| 111 | out_header_reader->GetSparseMetaHashType())); | ||
| 112 | } else { | ||
| 113 | // Create the sparse storage. | ||
| 114 | R_TRY(this->CreateSparseStorage( | ||
| 115 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 116 | ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, | ||
| 117 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 118 | fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info)); | ||
| 119 | } | ||
| 120 | } else { | ||
| 121 | // Get the data offsets. | ||
| 122 | fs_data_offset = GetFsOffset(*m_reader, fs_index); | ||
| 123 | const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); | ||
| 124 | |||
| 125 | // Validate that we're within range. | ||
| 126 | const auto data_size = fs_end_offset - fs_data_offset; | ||
| 127 | R_UNLESS(data_size > 0, ResultInvalidNcaHeader); | ||
| 128 | |||
| 129 | // Create the body substorage. | ||
| 130 | R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); | ||
| 131 | |||
| 132 | // Potentially save the body substorage to our context. | ||
| 133 | if (ctx != nullptr) { | ||
| 134 | ctx->body_substorage = storage; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | // Process patch layer. | ||
| 139 | const auto& patch_info = out_header_reader->GetPatchInfo(); | ||
| 140 | VirtualFile patch_meta_aes_ctr_ex_meta_storage; | ||
| 141 | VirtualFile patch_meta_indirect_meta_storage; | ||
| 142 | if (out_header_reader->ExistsPatchMetaHashLayer()) { | ||
| 143 | // Check the meta hash type. | ||
| 144 | R_UNLESS(out_header_reader->GetPatchMetaHashType() == | ||
| 145 | NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, | ||
| 146 | ResultRomNcaInvalidPatchMetaDataHashType); | ||
| 147 | |||
| 148 | // Create the patch meta storage. | ||
| 149 | R_TRY(this->CreatePatchMetaStorage( | ||
| 150 | std::addressof(patch_meta_aes_ctr_ex_meta_storage), | ||
| 151 | std::addressof(patch_meta_indirect_meta_storage), | ||
| 152 | ctx != nullptr ? std::addressof(ctx->patch_layer_info_storage) : nullptr, storage, | ||
| 153 | fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info, | ||
| 154 | out_header_reader->GetPatchMetaDataHashDataInfo())); | ||
| 155 | } | ||
| 156 | |||
| 157 | if (patch_info.HasAesCtrExTable()) { | ||
| 158 | // Check the encryption type. | ||
| 159 | ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None || | ||
| 160 | out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx || | ||
| 161 | out_header_reader->GetEncryptionType() == | ||
| 162 | NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); | ||
| 163 | |||
| 164 | // Create the ex meta storage. | ||
| 165 | VirtualFile aes_ctr_ex_storage_meta_storage = patch_meta_aes_ctr_ex_meta_storage; | ||
| 166 | if (aes_ctr_ex_storage_meta_storage == nullptr) { | ||
| 167 | // If we don't have a meta storage, we must not have a patch meta hash layer. | ||
| 168 | ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); | ||
| 169 | |||
| 170 | R_TRY(this->CreateAesCtrExStorageMetaStorage( | ||
| 171 | std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset, | ||
| 172 | out_header_reader->GetEncryptionType(), out_header_reader->GetAesCtrUpperIv(), | ||
| 173 | patch_info)); | ||
| 174 | } | ||
| 175 | |||
| 176 | // Create the ex storage. | ||
| 177 | VirtualFile aes_ctr_ex_storage; | ||
| 178 | R_TRY(this->CreateAesCtrExStorage( | ||
| 179 | std::addressof(aes_ctr_ex_storage), | ||
| 180 | ctx != nullptr ? std::addressof(ctx->aes_ctr_ex_storage) : nullptr, std::move(storage), | ||
| 181 | aes_ctr_ex_storage_meta_storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), | ||
| 182 | patch_info)); | ||
| 183 | |||
| 184 | // Set the base storage as the ex storage. | ||
| 185 | storage = std::move(aes_ctr_ex_storage); | ||
| 186 | |||
| 187 | // Potentially save storages to our context. | ||
| 188 | if (ctx != nullptr) { | ||
| 189 | ctx->aes_ctr_ex_storage_meta_storage = aes_ctr_ex_storage_meta_storage; | ||
| 190 | ctx->aes_ctr_ex_storage_data_storage = storage; | ||
| 191 | ctx->fs_data_storage = storage; | ||
| 192 | } | ||
| 193 | } else { | ||
| 194 | // Create the appropriate storage for the encryption type. | ||
| 195 | switch (out_header_reader->GetEncryptionType()) { | ||
| 196 | case NcaFsHeader::EncryptionType::None: | ||
| 197 | // If there's no encryption, use the base storage we made previously. | ||
| 198 | break; | ||
| 199 | case NcaFsHeader::EncryptionType::AesXts: | ||
| 200 | R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), | ||
| 201 | fs_data_offset)); | ||
| 202 | break; | ||
| 203 | case NcaFsHeader::EncryptionType::AesCtr: | ||
| 204 | R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), | ||
| 205 | fs_data_offset, out_header_reader->GetAesCtrUpperIv(), | ||
| 206 | AlignmentStorageRequirement::None)); | ||
| 207 | break; | ||
| 208 | case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: { | ||
| 209 | // Create the aes ctr storage. | ||
| 210 | VirtualFile aes_ctr_storage; | ||
| 211 | R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage, | ||
| 212 | fs_data_offset, out_header_reader->GetAesCtrUpperIv(), | ||
| 213 | AlignmentStorageRequirement::None)); | ||
| 214 | |||
| 215 | // Create region switch storage. | ||
| 216 | R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader, | ||
| 217 | std::move(storage), std::move(aes_ctr_storage))); | ||
| 218 | } break; | ||
| 219 | default: | ||
| 220 | R_THROW(ResultInvalidNcaFsHeaderEncryptionType); | ||
| 221 | } | ||
| 222 | |||
| 223 | // Potentially save storages to our context. | ||
| 224 | if (ctx != nullptr) { | ||
| 225 | ctx->fs_data_storage = storage; | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | // Process indirect layer. | ||
| 230 | if (patch_info.HasIndirectTable()) { | ||
| 231 | // Create the indirect meta storage | ||
| 232 | VirtualFile indirect_storage_meta_storage = patch_meta_indirect_meta_storage; | ||
| 233 | if (indirect_storage_meta_storage == nullptr) { | ||
| 234 | // If we don't have a meta storage, we must not have a patch meta hash layer. | ||
| 235 | ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); | ||
| 236 | |||
| 237 | R_TRY(this->CreateIndirectStorageMetaStorage( | ||
| 238 | std::addressof(indirect_storage_meta_storage), storage, patch_info)); | ||
| 239 | } | ||
| 240 | |||
| 241 | // Potentially save the indirect meta storage to our context. | ||
| 242 | if (ctx != nullptr) { | ||
| 243 | ctx->indirect_storage_meta_storage = indirect_storage_meta_storage; | ||
| 244 | } | ||
| 245 | |||
| 246 | // Get the original indirectable storage. | ||
| 247 | VirtualFile original_indirectable_storage; | ||
| 248 | if (m_original_reader != nullptr && m_original_reader->HasFsInfo(fs_index)) { | ||
| 249 | // Create a driver for the original. | ||
| 250 | NcaFileSystemDriver original_driver(m_original_reader); | ||
| 251 | |||
| 252 | // Create a header reader for the original. | ||
| 253 | NcaFsHeaderReader original_header_reader; | ||
| 254 | R_TRY(original_header_reader.Initialize(*m_original_reader, fs_index)); | ||
| 255 | |||
| 256 | // Open original indirectable storage. | ||
| 257 | R_TRY(original_driver.OpenIndirectableStorageAsOriginal( | ||
| 258 | std::addressof(original_indirectable_storage), | ||
| 259 | std::addressof(original_header_reader), ctx)); | ||
| 260 | } else if (ctx != nullptr && ctx->external_original_storage != nullptr) { | ||
| 261 | // Use the external original storage. | ||
| 262 | original_indirectable_storage = ctx->external_original_storage; | ||
| 263 | } else { | ||
| 264 | // Allocate a dummy memory storage as original storage. | ||
| 265 | original_indirectable_storage = std::make_shared<VectorVfsFile>(); | ||
| 266 | R_UNLESS(original_indirectable_storage != nullptr, | ||
| 267 | ResultAllocationMemoryFailedAllocateShared); | ||
| 268 | } | ||
| 269 | |||
| 270 | // Create the indirect storage. | ||
| 271 | VirtualFile indirect_storage; | ||
| 272 | R_TRY(this->CreateIndirectStorage( | ||
| 273 | std::addressof(indirect_storage), | ||
| 274 | ctx != nullptr ? std::addressof(ctx->indirect_storage) : nullptr, std::move(storage), | ||
| 275 | std::move(original_indirectable_storage), std::move(indirect_storage_meta_storage), | ||
| 276 | patch_info)); | ||
| 277 | |||
| 278 | // Set storage as the indirect storage. | ||
| 279 | storage = std::move(indirect_storage); | ||
| 280 | } | ||
| 281 | |||
| 282 | // Check if we're sparse or requested to skip the integrity layer. | ||
| 283 | if (out_header_reader->ExistsSparseLayer() || (ctx != nullptr && ctx->open_raw_storage)) { | ||
| 284 | *out = std::move(storage); | ||
| 285 | R_SUCCEED(); | ||
| 286 | } | ||
| 287 | |||
| 288 | // Create the non-raw storage. | ||
| 289 | R_RETURN(this->CreateStorageByRawStorage(out, out_header_reader, std::move(storage), ctx)); | ||
| 290 | } | ||
| 291 | |||
| 292 | Result NcaFileSystemDriver::CreateStorageByRawStorage(VirtualFile* out, | ||
| 293 | const NcaFsHeaderReader* header_reader, | ||
| 294 | VirtualFile raw_storage, | ||
| 295 | StorageContext* ctx) { | ||
| 296 | // Initialize storage as raw storage. | ||
| 297 | VirtualFile storage = std::move(raw_storage); | ||
| 298 | |||
| 299 | // Process hash/integrity layer. | ||
| 300 | switch (header_reader->GetHashType()) { | ||
| 301 | case NcaFsHeader::HashType::HierarchicalSha256Hash: | ||
| 302 | R_TRY(this->CreateSha256Storage(std::addressof(storage), std::move(storage), | ||
| 303 | header_reader->GetHashData().hierarchical_sha256_data)); | ||
| 304 | break; | ||
| 305 | case NcaFsHeader::HashType::HierarchicalIntegrityHash: | ||
| 306 | R_TRY(this->CreateIntegrityVerificationStorage( | ||
| 307 | std::addressof(storage), std::move(storage), | ||
| 308 | header_reader->GetHashData().integrity_meta_info)); | ||
| 309 | break; | ||
| 310 | default: | ||
| 311 | R_THROW(ResultInvalidNcaFsHeaderHashType); | ||
| 312 | } | ||
| 313 | |||
| 314 | // Process compression layer. | ||
| 315 | if (header_reader->ExistsCompressionLayer()) { | ||
| 316 | R_TRY(this->CreateCompressedStorage( | ||
| 317 | std::addressof(storage), | ||
| 318 | ctx != nullptr ? std::addressof(ctx->compressed_storage) : nullptr, | ||
| 319 | ctx != nullptr ? std::addressof(ctx->compressed_storage_meta_storage) : nullptr, | ||
| 320 | std::move(storage), header_reader->GetCompressionInfo())); | ||
| 321 | } | ||
| 322 | |||
| 323 | // Set output storage. | ||
| 324 | *out = std::move(storage); | ||
| 325 | R_SUCCEED(); | ||
| 326 | } | ||
| 327 | |||
| 328 | Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal( | ||
| 329 | VirtualFile* out, const NcaFsHeaderReader* header_reader, StorageContext* ctx) { | ||
| 330 | // Get the fs index. | ||
| 331 | const auto fs_index = header_reader->GetFsIndex(); | ||
| 332 | |||
| 333 | // Declare the storage we're opening. | ||
| 334 | VirtualFile storage; | ||
| 335 | |||
| 336 | // Process sparse layer. | ||
| 337 | s64 fs_data_offset = 0; | ||
| 338 | if (header_reader->ExistsSparseLayer()) { | ||
| 339 | // Get the sparse info. | ||
| 340 | const auto& sparse_info = header_reader->GetSparseInfo(); | ||
| 341 | |||
| 342 | // Create based on whether we have a meta hash layer. | ||
| 343 | if (header_reader->ExistsSparseMetaHashLayer()) { | ||
| 344 | // Create the sparse storage with verification. | ||
| 345 | R_TRY(this->CreateSparseStorageWithVerification( | ||
| 346 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 347 | ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, | ||
| 348 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 349 | ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, | ||
| 350 | header_reader->GetAesCtrUpperIv(), sparse_info, | ||
| 351 | header_reader->GetSparseMetaDataHashDataInfo(), | ||
| 352 | header_reader->GetSparseMetaHashType())); | ||
| 353 | } else { | ||
| 354 | // Create the sparse storage. | ||
| 355 | R_TRY(this->CreateSparseStorage( | ||
| 356 | std::addressof(storage), std::addressof(fs_data_offset), | ||
| 357 | ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, | ||
| 358 | ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, | ||
| 359 | fs_index, header_reader->GetAesCtrUpperIv(), sparse_info)); | ||
| 360 | } | ||
| 361 | } else { | ||
| 362 | // Get the data offsets. | ||
| 363 | fs_data_offset = GetFsOffset(*m_reader, fs_index); | ||
| 364 | const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); | ||
| 365 | |||
| 366 | // Validate that we're within range. | ||
| 367 | const auto data_size = fs_end_offset - fs_data_offset; | ||
| 368 | R_UNLESS(data_size > 0, ResultInvalidNcaHeader); | ||
| 369 | |||
| 370 | // Create the body substorage. | ||
| 371 | R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); | ||
| 372 | } | ||
| 373 | |||
| 374 | // Create the appropriate storage for the encryption type. | ||
| 375 | switch (header_reader->GetEncryptionType()) { | ||
| 376 | case NcaFsHeader::EncryptionType::None: | ||
| 377 | // If there's no encryption, use the base storage we made previously. | ||
| 378 | break; | ||
| 379 | case NcaFsHeader::EncryptionType::AesXts: | ||
| 380 | R_TRY( | ||
| 381 | this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset)); | ||
| 382 | break; | ||
| 383 | case NcaFsHeader::EncryptionType::AesCtr: | ||
| 384 | R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset, | ||
| 385 | header_reader->GetAesCtrUpperIv(), | ||
| 386 | AlignmentStorageRequirement::CacheBlockSize)); | ||
| 387 | break; | ||
| 388 | default: | ||
| 389 | R_THROW(ResultInvalidNcaFsHeaderEncryptionType); | ||
| 390 | } | ||
| 391 | |||
| 392 | // Set output storage. | ||
| 393 | *out = std::move(storage); | ||
| 394 | R_SUCCEED(); | ||
| 395 | } | ||
| 396 | |||
| 397 | Result NcaFileSystemDriver::CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size) { | ||
| 398 | // Create the body storage. | ||
| 399 | auto body_storage = | ||
| 400 | std::make_shared<SharedNcaBodyStorage>(m_reader->GetSharedBodyStorage(), m_reader); | ||
| 401 | R_UNLESS(body_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 402 | |||
| 403 | // Get the body storage size. | ||
| 404 | s64 body_size = body_storage->GetSize(); | ||
| 405 | |||
| 406 | // Check that we're within range. | ||
| 407 | R_UNLESS(offset + size <= body_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 408 | |||
| 409 | // Create substorage. | ||
| 410 | auto body_substorage = std::make_shared<OffsetVfsFile>(std::move(body_storage), size, offset); | ||
| 411 | R_UNLESS(body_substorage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 412 | |||
| 413 | // Set the output storage. | ||
| 414 | *out = std::move(body_substorage); | ||
| 415 | R_SUCCEED(); | ||
| 416 | } | ||
| 417 | |||
| 418 | Result NcaFileSystemDriver::CreateAesCtrStorage( | ||
| 419 | VirtualFile* out, VirtualFile base_storage, s64 offset, const NcaAesCtrUpperIv& upper_iv, | ||
| 420 | AlignmentStorageRequirement alignment_storage_requirement) { | ||
| 421 | // Check pre-conditions. | ||
| 422 | ASSERT(out != nullptr); | ||
| 423 | ASSERT(base_storage != nullptr); | ||
| 424 | |||
| 425 | // Create the iv. | ||
| 426 | std::array<u8, AesCtrStorage::IvSize> iv{}; | ||
| 427 | AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset); | ||
| 428 | |||
| 429 | // Create the ctr storage. | ||
| 430 | VirtualFile aes_ctr_storage; | ||
| 431 | if (m_reader->HasExternalDecryptionKey()) { | ||
| 432 | aes_ctr_storage = std::make_shared<AesCtrStorage>( | ||
| 433 | std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, | ||
| 434 | iv.data(), AesCtrStorage::IvSize); | ||
| 435 | R_UNLESS(aes_ctr_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 436 | } else { | ||
| 437 | // Create software decryption storage. | ||
| 438 | auto sw_storage = std::make_shared<AesCtrStorage>( | ||
| 439 | base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), | ||
| 440 | AesCtrStorage::KeySize, iv.data(), AesCtrStorage::IvSize); | ||
| 441 | R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 442 | |||
| 443 | aes_ctr_storage = std::move(sw_storage); | ||
| 444 | } | ||
| 445 | |||
| 446 | // Create alignment matching storage. | ||
| 447 | auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>>( | ||
| 448 | std::move(aes_ctr_storage)); | ||
| 449 | R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 450 | |||
| 451 | // Set the out storage. | ||
| 452 | *out = std::move(aligned_storage); | ||
| 453 | R_SUCCEED(); | ||
| 454 | } | ||
| 455 | |||
| 456 | Result NcaFileSystemDriver::CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, | ||
| 457 | s64 offset) { | ||
| 458 | // Check pre-conditions. | ||
| 459 | ASSERT(out != nullptr); | ||
| 460 | ASSERT(base_storage != nullptr); | ||
| 461 | |||
| 462 | // Create the iv. | ||
| 463 | std::array<u8, AesXtsStorage::IvSize> iv{}; | ||
| 464 | AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize); | ||
| 465 | |||
| 466 | // Make the aes xts storage. | ||
| 467 | const auto* const key1 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts1); | ||
| 468 | const auto* const key2 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts2); | ||
| 469 | auto xts_storage = | ||
| 470 | std::make_shared<AesXtsStorage>(std::move(base_storage), key1, key2, AesXtsStorage::KeySize, | ||
| 471 | iv.data(), AesXtsStorage::IvSize, NcaHeader::XtsBlockSize); | ||
| 472 | R_UNLESS(xts_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 473 | |||
| 474 | // Create alignment matching storage. | ||
| 475 | auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::XtsBlockSize, 1>>( | ||
| 476 | std::move(xts_storage)); | ||
| 477 | R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 478 | |||
| 479 | // Set the out storage. | ||
| 480 | *out = std::move(xts_storage); | ||
| 481 | R_SUCCEED(); | ||
| 482 | } | ||
| 483 | |||
| 484 | Result NcaFileSystemDriver::CreateSparseStorageMetaStorage(VirtualFile* out, | ||
| 485 | VirtualFile base_storage, s64 offset, | ||
| 486 | const NcaAesCtrUpperIv& upper_iv, | ||
| 487 | const NcaSparseInfo& sparse_info) { | ||
| 488 | // Validate preconditions. | ||
| 489 | ASSERT(out != nullptr); | ||
| 490 | ASSERT(base_storage != nullptr); | ||
| 491 | |||
| 492 | // Get the base storage size. | ||
| 493 | s64 base_size = base_storage->GetSize(); | ||
| 494 | |||
| 495 | // Get the meta extents. | ||
| 496 | const auto meta_offset = sparse_info.bucket.offset; | ||
| 497 | const auto meta_size = sparse_info.bucket.size; | ||
| 498 | R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 499 | |||
| 500 | // Create the encrypted storage. | ||
| 501 | auto enc_storage = | ||
| 502 | std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset); | ||
| 503 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 504 | |||
| 505 | // Create the decrypted storage. | ||
| 506 | VirtualFile decrypted_storage; | ||
| 507 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 508 | offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), | ||
| 509 | AlignmentStorageRequirement::None)); | ||
| 510 | |||
| 511 | // Create meta storage. | ||
| 512 | auto meta_storage = std::make_shared<OffsetVfsFile>(decrypted_storage, meta_size, 0); | ||
| 513 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 514 | |||
| 515 | // Set the output. | ||
| 516 | *out = std::move(meta_storage); | ||
| 517 | R_SUCCEED(); | ||
| 518 | } | ||
| 519 | |||
| 520 | Result NcaFileSystemDriver::CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, | ||
| 521 | VirtualFile base_storage, s64 base_size, | ||
| 522 | VirtualFile meta_storage, | ||
| 523 | const NcaSparseInfo& sparse_info, | ||
| 524 | bool external_info) { | ||
| 525 | // Validate preconditions. | ||
| 526 | ASSERT(out != nullptr); | ||
| 527 | ASSERT(base_storage != nullptr); | ||
| 528 | ASSERT(meta_storage != nullptr); | ||
| 529 | |||
| 530 | // Read and verify the bucket tree header. | ||
| 531 | BucketTree::Header header; | ||
| 532 | std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); | ||
| 533 | R_TRY(header.Verify()); | ||
| 534 | |||
| 535 | // Determine storage extents. | ||
| 536 | const auto node_offset = 0; | ||
| 537 | const auto node_size = SparseStorage::QueryNodeStorageSize(header.entry_count); | ||
| 538 | const auto entry_offset = node_offset + node_size; | ||
| 539 | const auto entry_size = SparseStorage::QueryEntryStorageSize(header.entry_count); | ||
| 540 | |||
| 541 | // Create the sparse storage. | ||
| 542 | auto sparse_storage = std::make_shared<SparseStorage>(); | ||
| 543 | R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 544 | |||
| 545 | // Sanity check that we can be doing this. | ||
| 546 | ASSERT(header.entry_count != 0); | ||
| 547 | |||
| 548 | // Initialize the sparse storage. | ||
| 549 | R_TRY(sparse_storage->Initialize( | ||
| 550 | std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset), | ||
| 551 | std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset), | ||
| 552 | header.entry_count)); | ||
| 553 | |||
| 554 | // If not external, set the data storage. | ||
| 555 | if (!external_info) { | ||
| 556 | sparse_storage->SetDataStorage( | ||
| 557 | std::make_shared<OffsetVfsFile>(std::move(base_storage), base_size, 0)); | ||
| 558 | } | ||
| 559 | |||
| 560 | // Set the output. | ||
| 561 | *out = std::move(sparse_storage); | ||
| 562 | R_SUCCEED(); | ||
| 563 | } | ||
| 564 | |||
| 565 | Result NcaFileSystemDriver::CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset, | ||
| 566 | std::shared_ptr<SparseStorage>* out_sparse_storage, | ||
| 567 | VirtualFile* out_meta_storage, s32 index, | ||
| 568 | const NcaAesCtrUpperIv& upper_iv, | ||
| 569 | const NcaSparseInfo& sparse_info) { | ||
| 570 | // Validate preconditions. | ||
| 571 | ASSERT(out != nullptr); | ||
| 572 | ASSERT(out_fs_data_offset != nullptr); | ||
| 573 | |||
| 574 | // Check the sparse info generation. | ||
| 575 | R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader); | ||
| 576 | |||
| 577 | // Read and verify the bucket tree header. | ||
| 578 | BucketTree::Header header; | ||
| 579 | std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); | ||
| 580 | R_TRY(header.Verify()); | ||
| 581 | |||
| 582 | // Determine the storage extents. | ||
| 583 | const auto fs_offset = GetFsOffset(*m_reader, index); | ||
| 584 | const auto fs_end_offset = GetFsEndOffset(*m_reader, index); | ||
| 585 | const auto fs_size = fs_end_offset - fs_offset; | ||
| 586 | |||
| 587 | // Create the sparse storage. | ||
| 588 | std::shared_ptr<SparseStorage> sparse_storage; | ||
| 589 | if (header.entry_count != 0) { | ||
| 590 | // Create the body substorage. | ||
| 591 | VirtualFile body_substorage; | ||
| 592 | R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage), | ||
| 593 | sparse_info.physical_offset, | ||
| 594 | sparse_info.GetPhysicalSize())); | ||
| 595 | |||
| 596 | // Create the meta storage. | ||
| 597 | VirtualFile meta_storage; | ||
| 598 | R_TRY(this->CreateSparseStorageMetaStorage(std::addressof(meta_storage), body_substorage, | ||
| 599 | sparse_info.physical_offset, upper_iv, | ||
| 600 | sparse_info)); | ||
| 601 | |||
| 602 | // Potentially set the output meta storage. | ||
| 603 | if (out_meta_storage != nullptr) { | ||
| 604 | *out_meta_storage = meta_storage; | ||
| 605 | } | ||
| 606 | |||
| 607 | // Create the sparse storage. | ||
| 608 | R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, | ||
| 609 | sparse_info.GetPhysicalSize(), std::move(meta_storage), | ||
| 610 | sparse_info, false)); | ||
| 611 | } else { | ||
| 612 | // If there are no entries, there's nothing to actually do. | ||
| 613 | sparse_storage = std::make_shared<SparseStorage>(); | ||
| 614 | R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 615 | |||
| 616 | sparse_storage->Initialize(fs_size); | ||
| 617 | } | ||
| 618 | |||
| 619 | // Potentially set the output sparse storage. | ||
| 620 | if (out_sparse_storage != nullptr) { | ||
| 621 | *out_sparse_storage = sparse_storage; | ||
| 622 | } | ||
| 623 | |||
| 624 | // Set the output fs data offset. | ||
| 625 | *out_fs_data_offset = fs_offset; | ||
| 626 | |||
| 627 | // Set the output storage. | ||
| 628 | *out = std::move(sparse_storage); | ||
| 629 | R_SUCCEED(); | ||
| 630 | } | ||
| 631 | |||
| 632 | Result NcaFileSystemDriver::CreateSparseStorageMetaStorageWithVerification( | ||
| 633 | VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, | ||
| 634 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, | ||
| 635 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { | ||
| 636 | // Validate preconditions. | ||
| 637 | ASSERT(out != nullptr); | ||
| 638 | ASSERT(base_storage != nullptr); | ||
| 639 | |||
| 640 | // Get the base storage size. | ||
| 641 | s64 base_size = base_storage->GetSize(); | ||
| 642 | |||
| 643 | // Get the meta extents. | ||
| 644 | const auto meta_offset = sparse_info.bucket.offset; | ||
| 645 | const auto meta_size = sparse_info.bucket.size; | ||
| 646 | R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 647 | |||
| 648 | // Get the meta data hash data extents. | ||
| 649 | const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; | ||
| 650 | const s64 meta_data_hash_data_size = | ||
| 651 | Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); | ||
| 652 | R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, | ||
| 653 | ResultNcaBaseStorageOutOfRangeB); | ||
| 654 | |||
| 655 | // Check that the meta is before the hash data. | ||
| 656 | R_UNLESS(meta_offset + meta_size <= meta_data_hash_data_offset, | ||
| 657 | ResultRomNcaInvalidSparseMetaDataHashDataOffset); | ||
| 658 | |||
| 659 | // Check that offsets are appropriately aligned. | ||
| 660 | R_UNLESS(Common::IsAligned<s64>(meta_data_hash_data_offset, NcaHeader::CtrBlockSize), | ||
| 661 | ResultRomNcaInvalidSparseMetaDataHashDataOffset); | ||
| 662 | R_UNLESS(Common::IsAligned<s64>(meta_offset, NcaHeader::CtrBlockSize), | ||
| 663 | ResultInvalidNcaFsHeader); | ||
| 664 | |||
| 665 | // Create the meta storage. | ||
| 666 | auto enc_storage = std::make_shared<OffsetVfsFile>( | ||
| 667 | std::move(base_storage), | ||
| 668 | meta_data_hash_data_offset + meta_data_hash_data_size - meta_offset, meta_offset); | ||
| 669 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 670 | |||
| 671 | // Create the decrypted storage. | ||
| 672 | VirtualFile decrypted_storage; | ||
| 673 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 674 | offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), | ||
| 675 | AlignmentStorageRequirement::None)); | ||
| 676 | |||
| 677 | // Create the verification storage. | ||
| 678 | VirtualFile integrity_storage; | ||
| 679 | Result rc = this->CreateIntegrityVerificationStorageForMeta( | ||
| 680 | std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), | ||
| 681 | meta_offset, meta_data_hash_data_info); | ||
| 682 | if (rc == ResultInvalidNcaMetaDataHashDataSize) { | ||
| 683 | R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataSize); | ||
| 684 | } | ||
| 685 | if (rc == ResultInvalidNcaMetaDataHashDataHash) { | ||
| 686 | R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataHash); | ||
| 687 | } | ||
| 688 | R_TRY(rc); | ||
| 689 | |||
| 690 | // Create the meta storage. | ||
| 691 | auto meta_storage = std::make_shared<OffsetVfsFile>(std::move(integrity_storage), meta_size, 0); | ||
| 692 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 693 | |||
| 694 | // Set the output. | ||
| 695 | *out = std::move(meta_storage); | ||
| 696 | R_SUCCEED(); | ||
| 697 | } | ||
| 698 | |||
| 699 | Result NcaFileSystemDriver::CreateSparseStorageWithVerification( | ||
| 700 | VirtualFile* out, s64* out_fs_data_offset, std::shared_ptr<SparseStorage>* out_sparse_storage, | ||
| 701 | VirtualFile* out_meta_storage, VirtualFile* out_layer_info_storage, s32 index, | ||
| 702 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, | ||
| 703 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info, | ||
| 704 | NcaFsHeader::MetaDataHashType meta_data_hash_type) { | ||
| 705 | // Validate preconditions. | ||
| 706 | ASSERT(out != nullptr); | ||
| 707 | ASSERT(out_fs_data_offset != nullptr); | ||
| 708 | |||
| 709 | // Check the sparse info generation. | ||
| 710 | R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader); | ||
| 711 | |||
| 712 | // Read and verify the bucket tree header. | ||
| 713 | BucketTree::Header header; | ||
| 714 | std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); | ||
| 715 | R_TRY(header.Verify()); | ||
| 716 | |||
| 717 | // Determine the storage extents. | ||
| 718 | const auto fs_offset = GetFsOffset(*m_reader, index); | ||
| 719 | const auto fs_end_offset = GetFsEndOffset(*m_reader, index); | ||
| 720 | const auto fs_size = fs_end_offset - fs_offset; | ||
| 721 | |||
| 722 | // Create the sparse storage. | ||
| 723 | std::shared_ptr<SparseStorage> sparse_storage; | ||
| 724 | if (header.entry_count != 0) { | ||
| 725 | // Create the body substorage. | ||
| 726 | VirtualFile body_substorage; | ||
| 727 | R_TRY(this->CreateBodySubStorage( | ||
| 728 | std::addressof(body_substorage), sparse_info.physical_offset, | ||
| 729 | Common::AlignUp<s64>(static_cast<s64>(meta_data_hash_data_info.offset) + | ||
| 730 | static_cast<s64>(meta_data_hash_data_info.size), | ||
| 731 | NcaHeader::CtrBlockSize))); | ||
| 732 | |||
| 733 | // Check the meta data hash type. | ||
| 734 | R_UNLESS(meta_data_hash_type == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, | ||
| 735 | ResultRomNcaInvalidSparseMetaDataHashType); | ||
| 736 | |||
| 737 | // Create the meta storage. | ||
| 738 | VirtualFile meta_storage; | ||
| 739 | R_TRY(this->CreateSparseStorageMetaStorageWithVerification( | ||
| 740 | std::addressof(meta_storage), out_layer_info_storage, body_substorage, | ||
| 741 | sparse_info.physical_offset, upper_iv, sparse_info, meta_data_hash_data_info)); | ||
| 742 | |||
| 743 | // Potentially set the output meta storage. | ||
| 744 | if (out_meta_storage != nullptr) { | ||
| 745 | *out_meta_storage = meta_storage; | ||
| 746 | } | ||
| 747 | |||
| 748 | // Create the sparse storage. | ||
| 749 | R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, | ||
| 750 | sparse_info.GetPhysicalSize(), std::move(meta_storage), | ||
| 751 | sparse_info, false)); | ||
| 752 | } else { | ||
| 753 | // If there are no entries, there's nothing to actually do. | ||
| 754 | sparse_storage = std::make_shared<SparseStorage>(); | ||
| 755 | R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 756 | |||
| 757 | sparse_storage->Initialize(fs_size); | ||
| 758 | } | ||
| 759 | |||
| 760 | // Potentially set the output sparse storage. | ||
| 761 | if (out_sparse_storage != nullptr) { | ||
| 762 | *out_sparse_storage = sparse_storage; | ||
| 763 | } | ||
| 764 | |||
| 765 | // Set the output fs data offset. | ||
| 766 | *out_fs_data_offset = fs_offset; | ||
| 767 | |||
| 768 | // Set the output storage. | ||
| 769 | *out = std::move(sparse_storage); | ||
| 770 | R_SUCCEED(); | ||
| 771 | } | ||
| 772 | |||
| 773 | Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage( | ||
| 774 | VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 775 | NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv& upper_iv, | ||
| 776 | const NcaPatchInfo& patch_info) { | ||
| 777 | // Validate preconditions. | ||
| 778 | ASSERT(out != nullptr); | ||
| 779 | ASSERT(base_storage != nullptr); | ||
| 780 | ASSERT(encryption_type == NcaFsHeader::EncryptionType::None || | ||
| 781 | encryption_type == NcaFsHeader::EncryptionType::AesCtrEx || | ||
| 782 | encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); | ||
| 783 | ASSERT(patch_info.HasAesCtrExTable()); | ||
| 784 | |||
| 785 | // Validate patch info extents. | ||
| 786 | R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize); | ||
| 787 | R_UNLESS(patch_info.aes_ctr_ex_size > 0, ResultInvalidNcaPatchInfoAesCtrExSize); | ||
| 788 | R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, | ||
| 789 | ResultInvalidNcaPatchInfoAesCtrExOffset); | ||
| 790 | |||
| 791 | // Get the base storage size. | ||
| 792 | s64 base_size = base_storage->GetSize(); | ||
| 793 | |||
| 794 | // Get and validate the meta extents. | ||
| 795 | const s64 meta_offset = patch_info.aes_ctr_ex_offset; | ||
| 796 | const s64 meta_size = | ||
| 797 | Common::AlignUp(static_cast<s64>(patch_info.aes_ctr_ex_size), NcaHeader::XtsBlockSize); | ||
| 798 | R_UNLESS(meta_offset + meta_size <= base_size, ResultNcaBaseStorageOutOfRangeB); | ||
| 799 | |||
| 800 | // Create the encrypted storage. | ||
| 801 | auto enc_storage = | ||
| 802 | std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset); | ||
| 803 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 804 | |||
| 805 | // Create the decrypted storage. | ||
| 806 | VirtualFile decrypted_storage; | ||
| 807 | if (encryption_type != NcaFsHeader::EncryptionType::None) { | ||
| 808 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 809 | offset + meta_offset, upper_iv, | ||
| 810 | AlignmentStorageRequirement::None)); | ||
| 811 | } else { | ||
| 812 | // If encryption type is none, don't do any decryption. | ||
| 813 | decrypted_storage = std::move(enc_storage); | ||
| 814 | } | ||
| 815 | |||
| 816 | // Create meta storage. | ||
| 817 | auto meta_storage = std::make_shared<OffsetVfsFile>(decrypted_storage, meta_size, 0); | ||
| 818 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 819 | |||
| 820 | // Create an alignment-matching storage. | ||
| 821 | using AlignedStorage = AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>; | ||
| 822 | auto aligned_storage = std::make_shared<AlignedStorage>(std::move(meta_storage)); | ||
| 823 | R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 824 | |||
| 825 | // Set the output. | ||
| 826 | *out = std::move(aligned_storage); | ||
| 827 | R_SUCCEED(); | ||
| 828 | } | ||
| 829 | |||
| 830 | Result NcaFileSystemDriver::CreateAesCtrExStorage( | ||
| 831 | VirtualFile* out, std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext, | ||
| 832 | VirtualFile base_storage, VirtualFile meta_storage, s64 counter_offset, | ||
| 833 | const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info) { | ||
| 834 | // Validate pre-conditions. | ||
| 835 | ASSERT(out != nullptr); | ||
| 836 | ASSERT(base_storage != nullptr); | ||
| 837 | ASSERT(meta_storage != nullptr); | ||
| 838 | ASSERT(patch_info.HasAesCtrExTable()); | ||
| 839 | |||
| 840 | // Read the bucket tree header. | ||
| 841 | BucketTree::Header header; | ||
| 842 | std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header)); | ||
| 843 | R_TRY(header.Verify()); | ||
| 844 | |||
| 845 | // Determine the bucket extents. | ||
| 846 | const auto entry_count = header.entry_count; | ||
| 847 | const s64 data_offset = 0; | ||
| 848 | const s64 data_size = patch_info.aes_ctr_ex_offset; | ||
| 849 | const s64 node_offset = 0; | ||
| 850 | const s64 node_size = AesCtrCounterExtendedStorage::QueryNodeStorageSize(entry_count); | ||
| 851 | const s64 entry_offset = node_offset + node_size; | ||
| 852 | const s64 entry_size = AesCtrCounterExtendedStorage::QueryEntryStorageSize(entry_count); | ||
| 853 | |||
| 854 | // Create bucket storages. | ||
| 855 | auto data_storage = | ||
| 856 | std::make_shared<OffsetVfsFile>(std::move(base_storage), data_size, data_offset); | ||
| 857 | auto node_storage = std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset); | ||
| 858 | auto entry_storage = std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset); | ||
| 859 | |||
| 860 | // Get the secure value. | ||
| 861 | const auto secure_value = upper_iv.part.secure_value; | ||
| 862 | |||
| 863 | // Create the aes ctr ex storage. | ||
| 864 | VirtualFile aes_ctr_ex_storage; | ||
| 865 | if (m_reader->HasExternalDecryptionKey()) { | ||
| 866 | // Create the decryptor. | ||
| 867 | std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> decryptor; | ||
| 868 | R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(decryptor))); | ||
| 869 | |||
| 870 | // Create the aes ctr ex storage. | ||
| 871 | auto impl_storage = std::make_shared<AesCtrCounterExtendedStorage>(); | ||
| 872 | R_UNLESS(impl_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 873 | |||
| 874 | // Initialize the aes ctr ex storage. | ||
| 875 | R_TRY(impl_storage->Initialize(m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, | ||
| 876 | secure_value, counter_offset, data_storage, node_storage, | ||
| 877 | entry_storage, entry_count, std::move(decryptor))); | ||
| 878 | |||
| 879 | // Potentially set the output implementation storage. | ||
| 880 | if (out_ext != nullptr) { | ||
| 881 | *out_ext = impl_storage; | ||
| 882 | } | ||
| 883 | |||
| 884 | // Set the implementation storage. | ||
| 885 | aes_ctr_ex_storage = std::move(impl_storage); | ||
| 886 | } else { | ||
| 887 | // Create the software decryptor. | ||
| 888 | std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> sw_decryptor; | ||
| 889 | R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(sw_decryptor))); | ||
| 890 | |||
| 891 | // Make the software storage. | ||
| 892 | auto sw_storage = std::make_shared<AesCtrCounterExtendedStorage>(); | ||
| 893 | R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 894 | |||
| 895 | // Initialize the software storage. | ||
| 896 | R_TRY(sw_storage->Initialize(m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), | ||
| 897 | AesCtrStorage::KeySize, secure_value, counter_offset, | ||
| 898 | data_storage, node_storage, entry_storage, entry_count, | ||
| 899 | std::move(sw_decryptor))); | ||
| 900 | |||
| 901 | // Potentially set the output implementation storage. | ||
| 902 | if (out_ext != nullptr) { | ||
| 903 | *out_ext = sw_storage; | ||
| 904 | } | ||
| 905 | |||
| 906 | // Set the implementation storage. | ||
| 907 | aes_ctr_ex_storage = std::move(sw_storage); | ||
| 908 | } | ||
| 909 | |||
| 910 | // Create an alignment-matching storage. | ||
| 911 | using AlignedStorage = AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>; | ||
| 912 | auto aligned_storage = std::make_shared<AlignedStorage>(std::move(aes_ctr_ex_storage)); | ||
| 913 | R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 914 | |||
| 915 | // Set the output. | ||
| 916 | *out = std::move(aligned_storage); | ||
| 917 | R_SUCCEED(); | ||
| 918 | } | ||
| 919 | |||
| 920 | Result NcaFileSystemDriver::CreateIndirectStorageMetaStorage(VirtualFile* out, | ||
| 921 | VirtualFile base_storage, | ||
| 922 | const NcaPatchInfo& patch_info) { | ||
| 923 | // Validate preconditions. | ||
| 924 | ASSERT(out != nullptr); | ||
| 925 | ASSERT(base_storage != nullptr); | ||
| 926 | ASSERT(patch_info.HasIndirectTable()); | ||
| 927 | |||
| 928 | // Get the base storage size. | ||
| 929 | s64 base_size = base_storage->GetSize(); | ||
| 930 | |||
| 931 | // Check that we're within range. | ||
| 932 | R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, | ||
| 933 | ResultNcaBaseStorageOutOfRangeE); | ||
| 934 | |||
| 935 | // Create the meta storage. | ||
| 936 | auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, patch_info.indirect_size, | ||
| 937 | patch_info.indirect_offset); | ||
| 938 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 939 | |||
| 940 | // Set the output. | ||
| 941 | *out = std::move(meta_storage); | ||
| 942 | R_SUCCEED(); | ||
| 943 | } | ||
| 944 | |||
| 945 | Result NcaFileSystemDriver::CreateIndirectStorage( | ||
| 946 | VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, VirtualFile base_storage, | ||
| 947 | VirtualFile original_data_storage, VirtualFile meta_storage, const NcaPatchInfo& patch_info) { | ||
| 948 | // Validate preconditions. | ||
| 949 | ASSERT(out != nullptr); | ||
| 950 | ASSERT(base_storage != nullptr); | ||
| 951 | ASSERT(meta_storage != nullptr); | ||
| 952 | ASSERT(patch_info.HasIndirectTable()); | ||
| 953 | |||
| 954 | // Read the bucket tree header. | ||
| 955 | BucketTree::Header header; | ||
| 956 | std::memcpy(std::addressof(header), patch_info.indirect_header.data(), sizeof(header)); | ||
| 957 | R_TRY(header.Verify()); | ||
| 958 | |||
| 959 | // Determine the storage sizes. | ||
| 960 | const auto node_size = IndirectStorage::QueryNodeStorageSize(header.entry_count); | ||
| 961 | const auto entry_size = IndirectStorage::QueryEntryStorageSize(header.entry_count); | ||
| 962 | R_UNLESS(node_size + entry_size <= patch_info.indirect_size, | ||
| 963 | ResultInvalidNcaIndirectStorageOutOfRange); | ||
| 964 | |||
| 965 | // Get the indirect data size. | ||
| 966 | const s64 indirect_data_size = patch_info.indirect_offset; | ||
| 967 | ASSERT(Common::IsAligned(indirect_data_size, NcaHeader::XtsBlockSize)); | ||
| 968 | |||
| 969 | // Create the indirect data storage. | ||
| 970 | auto indirect_data_storage = | ||
| 971 | std::make_shared<OffsetVfsFile>(base_storage, indirect_data_size, 0); | ||
| 972 | R_UNLESS(indirect_data_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 973 | |||
| 974 | // Create the indirect storage. | ||
| 975 | auto indirect_storage = std::make_shared<IndirectStorage>(); | ||
| 976 | R_UNLESS(indirect_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 977 | |||
| 978 | // Initialize the indirect storage. | ||
| 979 | R_TRY(indirect_storage->Initialize( | ||
| 980 | std::make_shared<OffsetVfsFile>(meta_storage, node_size, 0), | ||
| 981 | std::make_shared<OffsetVfsFile>(meta_storage, entry_size, node_size), header.entry_count)); | ||
| 982 | |||
| 983 | // Get the original data size. | ||
| 984 | s64 original_data_size = original_data_storage->GetSize(); | ||
| 985 | |||
| 986 | // Set the indirect storages. | ||
| 987 | indirect_storage->SetStorage( | ||
| 988 | 0, std::make_shared<OffsetVfsFile>(original_data_storage, original_data_size, 0)); | ||
| 989 | indirect_storage->SetStorage( | ||
| 990 | 1, std::make_shared<OffsetVfsFile>(indirect_data_storage, indirect_data_size, 0)); | ||
| 991 | |||
| 992 | // If necessary, set the output indirect storage. | ||
| 993 | if (out_ind != nullptr) { | ||
| 994 | *out_ind = indirect_storage; | ||
| 995 | } | ||
| 996 | |||
| 997 | // Set the output. | ||
| 998 | *out = std::move(indirect_storage); | ||
| 999 | R_SUCCEED(); | ||
| 1000 | } | ||
| 1001 | |||
| 1002 | Result NcaFileSystemDriver::CreatePatchMetaStorage( | ||
| 1003 | VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta, | ||
| 1004 | VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, | ||
| 1005 | const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info, | ||
| 1006 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { | ||
| 1007 | // Validate preconditions. | ||
| 1008 | ASSERT(out_aes_ctr_ex_meta != nullptr); | ||
| 1009 | ASSERT(out_indirect_meta != nullptr); | ||
| 1010 | ASSERT(base_storage != nullptr); | ||
| 1011 | ASSERT(patch_info.HasAesCtrExTable()); | ||
| 1012 | ASSERT(patch_info.HasIndirectTable()); | ||
| 1013 | ASSERT(Common::IsAligned<s64>(patch_info.aes_ctr_ex_size, NcaHeader::XtsBlockSize)); | ||
| 1014 | |||
| 1015 | // Validate patch info extents. | ||
| 1016 | R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize); | ||
| 1017 | R_UNLESS(patch_info.aes_ctr_ex_size >= 0, ResultInvalidNcaPatchInfoAesCtrExSize); | ||
| 1018 | R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, | ||
| 1019 | ResultInvalidNcaPatchInfoAesCtrExOffset); | ||
| 1020 | R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= | ||
| 1021 | meta_data_hash_data_info.offset, | ||
| 1022 | ResultRomNcaInvalidPatchMetaDataHashDataOffset); | ||
| 1023 | |||
| 1024 | // Get the base storage size. | ||
| 1025 | s64 base_size = base_storage->GetSize(); | ||
| 1026 | |||
| 1027 | // Check that extents remain within range. | ||
| 1028 | R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, | ||
| 1029 | ResultNcaBaseStorageOutOfRangeE); | ||
| 1030 | R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= base_size, | ||
| 1031 | ResultNcaBaseStorageOutOfRangeB); | ||
| 1032 | |||
| 1033 | // Check that metadata hash data extents remain within range. | ||
| 1034 | const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; | ||
| 1035 | const s64 meta_data_hash_data_size = | ||
| 1036 | Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); | ||
| 1037 | R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, | ||
| 1038 | ResultNcaBaseStorageOutOfRangeB); | ||
| 1039 | |||
| 1040 | // Create the encrypted storage. | ||
| 1041 | auto enc_storage = std::make_shared<OffsetVfsFile>( | ||
| 1042 | std::move(base_storage), | ||
| 1043 | meta_data_hash_data_offset + meta_data_hash_data_size - patch_info.indirect_offset, | ||
| 1044 | patch_info.indirect_offset); | ||
| 1045 | R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1046 | |||
| 1047 | // Create the decrypted storage. | ||
| 1048 | VirtualFile decrypted_storage; | ||
| 1049 | R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), | ||
| 1050 | offset + patch_info.indirect_offset, upper_iv, | ||
| 1051 | AlignmentStorageRequirement::None)); | ||
| 1052 | |||
| 1053 | // Create the verification storage. | ||
| 1054 | VirtualFile integrity_storage; | ||
| 1055 | Result rc = this->CreateIntegrityVerificationStorageForMeta( | ||
| 1056 | std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), | ||
| 1057 | patch_info.indirect_offset, meta_data_hash_data_info); | ||
| 1058 | if (rc == ResultInvalidNcaMetaDataHashDataSize) { | ||
| 1059 | R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataSize); | ||
| 1060 | } | ||
| 1061 | if (rc == ResultInvalidNcaMetaDataHashDataHash) { | ||
| 1062 | R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataHash); | ||
| 1063 | } | ||
| 1064 | R_TRY(rc); | ||
| 1065 | |||
| 1066 | // Create the indirect meta storage. | ||
| 1067 | auto indirect_meta_storage = | ||
| 1068 | std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.indirect_size, | ||
| 1069 | patch_info.indirect_offset - patch_info.indirect_offset); | ||
| 1070 | R_UNLESS(indirect_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1071 | |||
| 1072 | // Create the aes ctr ex meta storage. | ||
| 1073 | auto aes_ctr_ex_meta_storage = | ||
| 1074 | std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.aes_ctr_ex_size, | ||
| 1075 | patch_info.aes_ctr_ex_offset - patch_info.indirect_offset); | ||
| 1076 | R_UNLESS(aes_ctr_ex_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1077 | |||
| 1078 | // Set the output. | ||
| 1079 | *out_aes_ctr_ex_meta = std::move(aes_ctr_ex_meta_storage); | ||
| 1080 | *out_indirect_meta = std::move(indirect_meta_storage); | ||
| 1081 | R_SUCCEED(); | ||
| 1082 | } | ||
| 1083 | |||
| 1084 | Result NcaFileSystemDriver::CreateSha256Storage( | ||
| 1085 | VirtualFile* out, VirtualFile base_storage, | ||
| 1086 | const NcaFsHeader::HashData::HierarchicalSha256Data& hash_data) { | ||
| 1087 | // Validate preconditions. | ||
| 1088 | ASSERT(out != nullptr); | ||
| 1089 | ASSERT(base_storage != nullptr); | ||
| 1090 | |||
| 1091 | // Define storage types. | ||
| 1092 | using VerificationStorage = HierarchicalSha256Storage; | ||
| 1093 | using AlignedStorage = AlignmentMatchingStoragePooledBuffer<1>; | ||
| 1094 | |||
| 1095 | // Validate the hash data. | ||
| 1096 | R_UNLESS(Common::IsPowerOfTwo(hash_data.hash_block_size), | ||
| 1097 | ResultInvalidHierarchicalSha256BlockSize); | ||
| 1098 | R_UNLESS(hash_data.hash_layer_count == VerificationStorage::LayerCount - 1, | ||
| 1099 | ResultInvalidHierarchicalSha256LayerCount); | ||
| 1100 | |||
| 1101 | // Get the regions. | ||
| 1102 | const auto& hash_region = hash_data.hash_layer_region[0]; | ||
| 1103 | const auto& data_region = hash_data.hash_layer_region[1]; | ||
| 1104 | |||
| 1105 | // Determine buffer sizes. | ||
| 1106 | constexpr s32 CacheBlockCount = 2; | ||
| 1107 | const auto hash_buffer_size = static_cast<size_t>(hash_region.size); | ||
| 1108 | const auto cache_buffer_size = CacheBlockCount * hash_data.hash_block_size; | ||
| 1109 | const auto total_buffer_size = hash_buffer_size + cache_buffer_size; | ||
| 1110 | |||
| 1111 | // Make a buffer holder storage. | ||
| 1112 | auto buffer_hold_storage = std::make_shared<MemoryResourceBufferHoldStorage>( | ||
| 1113 | std::move(base_storage), total_buffer_size); | ||
| 1114 | R_UNLESS(buffer_hold_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1115 | R_UNLESS(buffer_hold_storage->IsValid(), ResultAllocationMemoryFailedInNcaFileSystemDriverI); | ||
| 1116 | |||
| 1117 | // Get storage size. | ||
| 1118 | s64 base_size = buffer_hold_storage->GetSize(); | ||
| 1119 | |||
| 1120 | // Check that we're within range. | ||
| 1121 | R_UNLESS(hash_region.offset + hash_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); | ||
| 1122 | R_UNLESS(data_region.offset + data_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); | ||
| 1123 | |||
| 1124 | // Create the master hash storage. | ||
| 1125 | auto master_hash_storage = | ||
| 1126 | std::make_shared<ArrayVfsFile<sizeof(Hash)>>(hash_data.fs_data_master_hash.value); | ||
| 1127 | |||
| 1128 | // Make the verification storage. | ||
| 1129 | auto verification_storage = std::make_shared<VerificationStorage>(); | ||
| 1130 | R_UNLESS(verification_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1131 | |||
| 1132 | // Make layer storages. | ||
| 1133 | std::array<VirtualFile, VerificationStorage::LayerCount> layer_storages{ | ||
| 1134 | std::make_shared<OffsetVfsFile>(master_hash_storage, sizeof(Hash), 0), | ||
| 1135 | std::make_shared<OffsetVfsFile>(buffer_hold_storage, hash_region.size, hash_region.offset), | ||
| 1136 | std::make_shared<OffsetVfsFile>(buffer_hold_storage, data_region.size, data_region.offset), | ||
| 1137 | }; | ||
| 1138 | |||
| 1139 | // Initialize the verification storage. | ||
| 1140 | R_TRY(verification_storage->Initialize(layer_storages.data(), VerificationStorage::LayerCount, | ||
| 1141 | hash_data.hash_block_size, | ||
| 1142 | buffer_hold_storage->GetBuffer(), hash_buffer_size)); | ||
| 1143 | |||
| 1144 | // Make the aligned storage. | ||
| 1145 | auto aligned_storage = std::make_shared<AlignedStorage>(std::move(verification_storage), | ||
| 1146 | hash_data.hash_block_size); | ||
| 1147 | R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1148 | |||
| 1149 | // Set the output. | ||
| 1150 | *out = std::move(aligned_storage); | ||
| 1151 | R_SUCCEED(); | ||
| 1152 | } | ||
| 1153 | |||
| 1154 | Result NcaFileSystemDriver::CreateIntegrityVerificationStorage( | ||
| 1155 | VirtualFile* out, VirtualFile base_storage, | ||
| 1156 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info) { | ||
| 1157 | R_RETURN(this->CreateIntegrityVerificationStorageImpl( | ||
| 1158 | out, base_storage, meta_info, 0, IntegrityDataCacheCount, IntegrityHashCacheCount, | ||
| 1159 | HierarchicalIntegrityVerificationStorage::GetDefaultDataCacheBufferLevel( | ||
| 1160 | meta_info.level_hash_info.max_layers))); | ||
| 1161 | } | ||
| 1162 | |||
| 1163 | Result NcaFileSystemDriver::CreateIntegrityVerificationStorageForMeta( | ||
| 1164 | VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, | ||
| 1165 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { | ||
| 1166 | // Validate preconditions. | ||
| 1167 | ASSERT(out != nullptr); | ||
| 1168 | |||
| 1169 | // Check the meta data hash data size. | ||
| 1170 | R_UNLESS(meta_data_hash_data_info.size == sizeof(NcaMetaDataHashData), | ||
| 1171 | ResultInvalidNcaMetaDataHashDataSize); | ||
| 1172 | |||
| 1173 | // Read the meta data hash data. | ||
| 1174 | NcaMetaDataHashData meta_data_hash_data; | ||
| 1175 | base_storage->ReadObject(std::addressof(meta_data_hash_data), | ||
| 1176 | meta_data_hash_data_info.offset - offset); | ||
| 1177 | |||
| 1178 | // Set the out layer info storage, if necessary. | ||
| 1179 | if (out_layer_info_storage != nullptr) { | ||
| 1180 | auto layer_info_storage = std::make_shared<OffsetVfsFile>( | ||
| 1181 | base_storage, | ||
| 1182 | meta_data_hash_data_info.offset + meta_data_hash_data_info.size - | ||
| 1183 | meta_data_hash_data.layer_info_offset, | ||
| 1184 | meta_data_hash_data.layer_info_offset - offset); | ||
| 1185 | R_UNLESS(layer_info_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1186 | |||
| 1187 | *out_layer_info_storage = std::move(layer_info_storage); | ||
| 1188 | } | ||
| 1189 | |||
| 1190 | // Create the meta storage. | ||
| 1191 | auto meta_storage = std::make_shared<OffsetVfsFile>( | ||
| 1192 | std::move(base_storage), meta_data_hash_data_info.offset - offset, 0); | ||
| 1193 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1194 | |||
| 1195 | // Create the integrity verification storage. | ||
| 1196 | R_RETURN(this->CreateIntegrityVerificationStorageImpl( | ||
| 1197 | out, std::move(meta_storage), meta_data_hash_data.integrity_meta_info, | ||
| 1198 | meta_data_hash_data.layer_info_offset - offset, IntegrityDataCacheCountForMeta, | ||
| 1199 | IntegrityHashCacheCountForMeta, 0)); | ||
| 1200 | } | ||
| 1201 | |||
| 1202 | Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl( | ||
| 1203 | VirtualFile* out, VirtualFile base_storage, | ||
| 1204 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset, | ||
| 1205 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) { | ||
| 1206 | // Validate preconditions. | ||
| 1207 | ASSERT(out != nullptr); | ||
| 1208 | ASSERT(base_storage != nullptr); | ||
| 1209 | ASSERT(layer_info_offset >= 0); | ||
| 1210 | |||
| 1211 | // Define storage types. | ||
| 1212 | using VerificationStorage = HierarchicalIntegrityVerificationStorage; | ||
| 1213 | using StorageInfo = VerificationStorage::HierarchicalStorageInformation; | ||
| 1214 | |||
| 1215 | // Validate the meta info. | ||
| 1216 | HierarchicalIntegrityVerificationInformation level_hash_info; | ||
| 1217 | std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info), | ||
| 1218 | sizeof(level_hash_info)); | ||
| 1219 | |||
| 1220 | R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers, | ||
| 1221 | ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); | ||
| 1222 | R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount, | ||
| 1223 | ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); | ||
| 1224 | |||
| 1225 | // Get the base storage size. | ||
| 1226 | s64 base_storage_size = base_storage->GetSize(); | ||
| 1227 | |||
| 1228 | // Create storage info. | ||
| 1229 | StorageInfo storage_info; | ||
| 1230 | for (s32 i = 0; i < static_cast<s32>(level_hash_info.max_layers - 2); ++i) { | ||
| 1231 | const auto& layer_info = level_hash_info.info[i]; | ||
| 1232 | R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, | ||
| 1233 | ResultNcaBaseStorageOutOfRangeD); | ||
| 1234 | |||
| 1235 | storage_info[i + 1] = std::make_shared<OffsetVfsFile>( | ||
| 1236 | base_storage, layer_info.size, layer_info_offset + layer_info.offset); | ||
| 1237 | } | ||
| 1238 | |||
| 1239 | // Set the last layer info. | ||
| 1240 | const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; | ||
| 1241 | const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get(); | ||
| 1242 | R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, | ||
| 1243 | ResultNcaBaseStorageOutOfRangeD); | ||
| 1244 | if (layer_info_offset > 0) { | ||
| 1245 | R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, | ||
| 1246 | ResultRomNcaInvalidIntegrityLayerInfoOffset); | ||
| 1247 | } | ||
| 1248 | storage_info.SetDataStorage(std::make_shared<OffsetVfsFile>( | ||
| 1249 | std::move(base_storage), layer_info.size, last_layer_info_offset)); | ||
| 1250 | |||
| 1251 | // Make the integrity romfs storage. | ||
| 1252 | auto integrity_storage = std::make_shared<IntegrityRomFsStorage>(); | ||
| 1253 | R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1254 | |||
| 1255 | // Initialize the integrity storage. | ||
| 1256 | R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, | ||
| 1257 | max_data_cache_entries, max_hash_cache_entries, | ||
| 1258 | buffer_level)); | ||
| 1259 | |||
| 1260 | // Set the output. | ||
| 1261 | *out = std::move(integrity_storage); | ||
| 1262 | R_SUCCEED(); | ||
| 1263 | } | ||
| 1264 | |||
| 1265 | Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out, | ||
| 1266 | const NcaFsHeaderReader* header_reader, | ||
| 1267 | VirtualFile inside_storage, | ||
| 1268 | VirtualFile outside_storage) { | ||
| 1269 | // Check pre-conditions. | ||
| 1270 | ASSERT(header_reader->GetHashType() == NcaFsHeader::HashType::HierarchicalIntegrityHash); | ||
| 1271 | |||
| 1272 | // Create the region. | ||
| 1273 | RegionSwitchStorage::Region region = {}; | ||
| 1274 | R_TRY(header_reader->GetHashTargetOffset(std::addressof(region.size))); | ||
| 1275 | |||
| 1276 | // Create the region switch storage. | ||
| 1277 | auto region_switch_storage = std::make_shared<RegionSwitchStorage>( | ||
| 1278 | std::move(inside_storage), std::move(outside_storage), region); | ||
| 1279 | R_UNLESS(region_switch_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1280 | |||
| 1281 | // Set the output. | ||
| 1282 | *out = std::move(region_switch_storage); | ||
| 1283 | R_SUCCEED(); | ||
| 1284 | } | ||
| 1285 | |||
| 1286 | Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out, | ||
| 1287 | std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 1288 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 1289 | const NcaCompressionInfo& compression_info) { | ||
| 1290 | R_RETURN(this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage), | ||
| 1291 | compression_info, m_reader->GetDecompressor())); | ||
| 1292 | } | ||
| 1293 | |||
| 1294 | Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out, | ||
| 1295 | std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 1296 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 1297 | const NcaCompressionInfo& compression_info, | ||
| 1298 | GetDecompressorFunction get_decompressor) { | ||
| 1299 | // Check pre-conditions. | ||
| 1300 | ASSERT(out != nullptr); | ||
| 1301 | ASSERT(base_storage != nullptr); | ||
| 1302 | ASSERT(get_decompressor != nullptr); | ||
| 1303 | |||
| 1304 | // Read and verify the bucket tree header. | ||
| 1305 | BucketTree::Header header; | ||
| 1306 | std::memcpy(std::addressof(header), compression_info.bucket.header.data(), sizeof(header)); | ||
| 1307 | R_TRY(header.Verify()); | ||
| 1308 | |||
| 1309 | // Determine the storage extents. | ||
| 1310 | const auto table_offset = compression_info.bucket.offset; | ||
| 1311 | const auto table_size = compression_info.bucket.size; | ||
| 1312 | const auto node_size = CompressedStorage::QueryNodeStorageSize(header.entry_count); | ||
| 1313 | const auto entry_size = CompressedStorage::QueryEntryStorageSize(header.entry_count); | ||
| 1314 | R_UNLESS(node_size + entry_size <= table_size, ResultInvalidCompressedStorageSize); | ||
| 1315 | |||
| 1316 | // If we should, set the output meta storage. | ||
| 1317 | if (out_meta != nullptr) { | ||
| 1318 | auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, table_size, table_offset); | ||
| 1319 | R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1320 | |||
| 1321 | *out_meta = std::move(meta_storage); | ||
| 1322 | } | ||
| 1323 | |||
| 1324 | // Allocate the compressed storage. | ||
| 1325 | auto compressed_storage = std::make_shared<CompressedStorage>(); | ||
| 1326 | R_UNLESS(compressed_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); | ||
| 1327 | |||
| 1328 | // Initialize the compressed storage. | ||
| 1329 | R_TRY(compressed_storage->Initialize( | ||
| 1330 | std::make_shared<OffsetVfsFile>(base_storage, table_offset, 0), | ||
| 1331 | std::make_shared<OffsetVfsFile>(base_storage, node_size, table_offset), | ||
| 1332 | std::make_shared<OffsetVfsFile>(base_storage, entry_size, table_offset + node_size), | ||
| 1333 | header.entry_count, 64_KiB, 640_KiB, get_decompressor, 16_KiB, 16_KiB, 32)); | ||
| 1334 | |||
| 1335 | // Potentially set the output compressed storage. | ||
| 1336 | if (out_cmp) { | ||
| 1337 | *out_cmp = compressed_storage; | ||
| 1338 | } | ||
| 1339 | |||
| 1340 | // Set the output. | ||
| 1341 | *out = std::move(compressed_storage); | ||
| 1342 | R_SUCCEED(); | ||
| 1343 | } | ||
| 1344 | |||
| 1345 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h new file mode 100644 index 000000000..d317b35ac --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h | |||
| @@ -0,0 +1,360 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_compression_common.h" | ||
| 7 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 8 | #include "core/file_sys/vfs.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | class CompressedStorage; | ||
| 13 | class AesCtrCounterExtendedStorage; | ||
| 14 | class IndirectStorage; | ||
| 15 | class SparseStorage; | ||
| 16 | |||
| 17 | struct NcaCryptoConfiguration; | ||
| 18 | |||
| 19 | using KeyGenerationFunction = void (*)(void* dst_key, size_t dst_key_size, const void* src_key, | ||
| 20 | size_t src_key_size, s32 key_type); | ||
| 21 | using VerifySign1Function = bool (*)(const void* sig, size_t sig_size, const void* data, | ||
| 22 | size_t data_size, u8 generation); | ||
| 23 | |||
| 24 | struct NcaCryptoConfiguration { | ||
| 25 | static constexpr size_t Rsa2048KeyModulusSize = 2048 / 8; | ||
| 26 | static constexpr size_t Rsa2048KeyPublicExponentSize = 3; | ||
| 27 | static constexpr size_t Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize; | ||
| 28 | |||
| 29 | static constexpr size_t Aes128KeySize = 128 / 8; | ||
| 30 | |||
| 31 | static constexpr size_t Header1SignatureKeyGenerationMax = 1; | ||
| 32 | |||
| 33 | static constexpr s32 KeyAreaEncryptionKeyIndexCount = 3; | ||
| 34 | static constexpr s32 HeaderEncryptionKeyCount = 2; | ||
| 35 | |||
| 36 | static constexpr u8 KeyAreaEncryptionKeyIndexZeroKey = 0xFF; | ||
| 37 | |||
| 38 | static constexpr size_t KeyGenerationMax = 32; | ||
| 39 | |||
| 40 | const u8* header_1_sign_key_moduli[Header1SignatureKeyGenerationMax + 1]; | ||
| 41 | u8 header_1_sign_key_public_exponent[Rsa2048KeyPublicExponentSize]; | ||
| 42 | u8 key_area_encryption_key_source[KeyAreaEncryptionKeyIndexCount][Aes128KeySize]; | ||
| 43 | u8 header_encryption_key_source[Aes128KeySize]; | ||
| 44 | u8 header_encrypted_encryption_keys[HeaderEncryptionKeyCount][Aes128KeySize]; | ||
| 45 | KeyGenerationFunction generate_key; | ||
| 46 | VerifySign1Function verify_sign1; | ||
| 47 | bool is_plaintext_header_available; | ||
| 48 | bool is_available_sw_key; | ||
| 49 | }; | ||
| 50 | static_assert(std::is_trivial_v<NcaCryptoConfiguration>); | ||
| 51 | |||
| 52 | struct NcaCompressionConfiguration { | ||
| 53 | GetDecompressorFunction get_decompressor; | ||
| 54 | }; | ||
| 55 | static_assert(std::is_trivial_v<NcaCompressionConfiguration>); | ||
| 56 | |||
| 57 | constexpr inline s32 KeyAreaEncryptionKeyCount = | ||
| 58 | NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * | ||
| 59 | NcaCryptoConfiguration::KeyGenerationMax; | ||
| 60 | |||
| 61 | enum class KeyType : s32 { | ||
| 62 | ZeroKey = -2, | ||
| 63 | InvalidKey = -1, | ||
| 64 | NcaHeaderKey1 = KeyAreaEncryptionKeyCount + 0, | ||
| 65 | NcaHeaderKey2 = KeyAreaEncryptionKeyCount + 1, | ||
| 66 | NcaExternalKey = KeyAreaEncryptionKeyCount + 2, | ||
| 67 | SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 3, | ||
| 68 | SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 4, | ||
| 69 | SaveDataTransferMac = KeyAreaEncryptionKeyCount + 5, | ||
| 70 | }; | ||
| 71 | |||
| 72 | constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) { | ||
| 73 | return key_type < 0; | ||
| 74 | } | ||
| 75 | |||
| 76 | constexpr inline s32 GetKeyTypeValue(u8 key_index, u8 key_generation) { | ||
| 77 | if (key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey) { | ||
| 78 | return static_cast<s32>(KeyType::ZeroKey); | ||
| 79 | } | ||
| 80 | |||
| 81 | if (key_index >= NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount) { | ||
| 82 | return static_cast<s32>(KeyType::InvalidKey); | ||
| 83 | } | ||
| 84 | |||
| 85 | return NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * key_generation + key_index; | ||
| 86 | } | ||
| 87 | |||
| 88 | class NcaReader { | ||
| 89 | YUZU_NON_COPYABLE(NcaReader); | ||
| 90 | YUZU_NON_MOVEABLE(NcaReader); | ||
| 91 | |||
| 92 | private: | ||
| 93 | NcaHeader m_header; | ||
| 94 | u8 m_decryption_keys[NcaHeader::DecryptionKey_Count][NcaCryptoConfiguration::Aes128KeySize]; | ||
| 95 | VirtualFile m_body_storage; | ||
| 96 | VirtualFile m_header_storage; | ||
| 97 | u8 m_external_decryption_key[NcaCryptoConfiguration::Aes128KeySize]; | ||
| 98 | bool m_is_software_aes_prioritized; | ||
| 99 | bool m_is_available_sw_key; | ||
| 100 | NcaHeader::EncryptionType m_header_encryption_type; | ||
| 101 | bool m_is_header_sign1_signature_valid; | ||
| 102 | GetDecompressorFunction m_get_decompressor; | ||
| 103 | |||
| 104 | public: | ||
| 105 | NcaReader(); | ||
| 106 | ~NcaReader(); | ||
| 107 | |||
| 108 | Result Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg, | ||
| 109 | const NcaCompressionConfiguration& compression_cfg); | ||
| 110 | |||
| 111 | VirtualFile GetSharedBodyStorage(); | ||
| 112 | u32 GetMagic() const; | ||
| 113 | NcaHeader::DistributionType GetDistributionType() const; | ||
| 114 | NcaHeader::ContentType GetContentType() const; | ||
| 115 | u8 GetHeaderSign1KeyGeneration() const; | ||
| 116 | u8 GetKeyGeneration() const; | ||
| 117 | u8 GetKeyIndex() const; | ||
| 118 | u64 GetContentSize() const; | ||
| 119 | u64 GetProgramId() const; | ||
| 120 | u32 GetContentIndex() const; | ||
| 121 | u32 GetSdkAddonVersion() const; | ||
| 122 | void GetRightsId(u8* dst, size_t dst_size) const; | ||
| 123 | bool HasFsInfo(s32 index) const; | ||
| 124 | s32 GetFsCount() const; | ||
| 125 | const Hash& GetFsHeaderHash(s32 index) const; | ||
| 126 | void GetFsHeaderHash(Hash* dst, s32 index) const; | ||
| 127 | void GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const; | ||
| 128 | u64 GetFsOffset(s32 index) const; | ||
| 129 | u64 GetFsEndOffset(s32 index) const; | ||
| 130 | u64 GetFsSize(s32 index) const; | ||
| 131 | void GetEncryptedKey(void* dst, size_t size) const; | ||
| 132 | const void* GetDecryptionKey(s32 index) const; | ||
| 133 | bool HasValidInternalKey() const; | ||
| 134 | bool HasInternalDecryptionKeyForAesHw() const; | ||
| 135 | bool IsSoftwareAesPrioritized() const; | ||
| 136 | void PrioritizeSoftwareAes(); | ||
| 137 | bool IsAvailableSwKey() const; | ||
| 138 | bool HasExternalDecryptionKey() const; | ||
| 139 | const void* GetExternalDecryptionKey() const; | ||
| 140 | void SetExternalDecryptionKey(const void* src, size_t size); | ||
| 141 | void GetRawData(void* dst, size_t dst_size) const; | ||
| 142 | NcaHeader::EncryptionType GetEncryptionType() const; | ||
| 143 | Result ReadHeader(NcaFsHeader* dst, s32 index) const; | ||
| 144 | |||
| 145 | GetDecompressorFunction GetDecompressor() const; | ||
| 146 | |||
| 147 | bool GetHeaderSign1Valid() const; | ||
| 148 | |||
| 149 | void GetHeaderSign2(void* dst, size_t size) const; | ||
| 150 | }; | ||
| 151 | |||
| 152 | class NcaFsHeaderReader { | ||
| 153 | YUZU_NON_COPYABLE(NcaFsHeaderReader); | ||
| 154 | YUZU_NON_MOVEABLE(NcaFsHeaderReader); | ||
| 155 | |||
| 156 | private: | ||
| 157 | NcaFsHeader m_data; | ||
| 158 | s32 m_fs_index; | ||
| 159 | |||
| 160 | public: | ||
| 161 | NcaFsHeaderReader() : m_fs_index(-1) { | ||
| 162 | std::memset(std::addressof(m_data), 0, sizeof(m_data)); | ||
| 163 | } | ||
| 164 | |||
| 165 | Result Initialize(const NcaReader& reader, s32 index); | ||
| 166 | bool IsInitialized() const { | ||
| 167 | return m_fs_index >= 0; | ||
| 168 | } | ||
| 169 | |||
| 170 | void GetRawData(void* dst, size_t dst_size) const; | ||
| 171 | |||
| 172 | NcaFsHeader::HashData& GetHashData(); | ||
| 173 | const NcaFsHeader::HashData& GetHashData() const; | ||
| 174 | u16 GetVersion() const; | ||
| 175 | s32 GetFsIndex() const; | ||
| 176 | NcaFsHeader::FsType GetFsType() const; | ||
| 177 | NcaFsHeader::HashType GetHashType() const; | ||
| 178 | NcaFsHeader::EncryptionType GetEncryptionType() const; | ||
| 179 | NcaPatchInfo& GetPatchInfo(); | ||
| 180 | const NcaPatchInfo& GetPatchInfo() const; | ||
| 181 | const NcaAesCtrUpperIv GetAesCtrUpperIv() const; | ||
| 182 | |||
| 183 | bool IsSkipLayerHashEncryption() const; | ||
| 184 | Result GetHashTargetOffset(s64* out) const; | ||
| 185 | |||
| 186 | bool ExistsSparseLayer() const; | ||
| 187 | NcaSparseInfo& GetSparseInfo(); | ||
| 188 | const NcaSparseInfo& GetSparseInfo() const; | ||
| 189 | |||
| 190 | bool ExistsCompressionLayer() const; | ||
| 191 | NcaCompressionInfo& GetCompressionInfo(); | ||
| 192 | const NcaCompressionInfo& GetCompressionInfo() const; | ||
| 193 | |||
| 194 | bool ExistsPatchMetaHashLayer() const; | ||
| 195 | NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo(); | ||
| 196 | const NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo() const; | ||
| 197 | NcaFsHeader::MetaDataHashType GetPatchMetaHashType() const; | ||
| 198 | |||
| 199 | bool ExistsSparseMetaHashLayer() const; | ||
| 200 | NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo(); | ||
| 201 | const NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo() const; | ||
| 202 | NcaFsHeader::MetaDataHashType GetSparseMetaHashType() const; | ||
| 203 | }; | ||
| 204 | |||
| 205 | class NcaFileSystemDriver { | ||
| 206 | YUZU_NON_COPYABLE(NcaFileSystemDriver); | ||
| 207 | YUZU_NON_MOVEABLE(NcaFileSystemDriver); | ||
| 208 | |||
| 209 | public: | ||
| 210 | struct StorageContext { | ||
| 211 | bool open_raw_storage; | ||
| 212 | VirtualFile body_substorage; | ||
| 213 | std::shared_ptr<SparseStorage> current_sparse_storage; | ||
| 214 | VirtualFile sparse_storage_meta_storage; | ||
| 215 | std::shared_ptr<SparseStorage> original_sparse_storage; | ||
| 216 | void* external_current_sparse_storage; | ||
| 217 | void* external_original_sparse_storage; | ||
| 218 | VirtualFile aes_ctr_ex_storage_meta_storage; | ||
| 219 | VirtualFile aes_ctr_ex_storage_data_storage; | ||
| 220 | std::shared_ptr<AesCtrCounterExtendedStorage> aes_ctr_ex_storage; | ||
| 221 | VirtualFile indirect_storage_meta_storage; | ||
| 222 | std::shared_ptr<IndirectStorage> indirect_storage; | ||
| 223 | VirtualFile fs_data_storage; | ||
| 224 | VirtualFile compressed_storage_meta_storage; | ||
| 225 | std::shared_ptr<CompressedStorage> compressed_storage; | ||
| 226 | |||
| 227 | VirtualFile patch_layer_info_storage; | ||
| 228 | VirtualFile sparse_layer_info_storage; | ||
| 229 | |||
| 230 | VirtualFile external_original_storage; | ||
| 231 | }; | ||
| 232 | |||
| 233 | private: | ||
| 234 | enum class AlignmentStorageRequirement { | ||
| 235 | CacheBlockSize = 0, | ||
| 236 | None = 1, | ||
| 237 | }; | ||
| 238 | |||
| 239 | private: | ||
| 240 | std::shared_ptr<NcaReader> m_original_reader; | ||
| 241 | std::shared_ptr<NcaReader> m_reader; | ||
| 242 | |||
| 243 | public: | ||
| 244 | static Result SetupFsHeaderReader(NcaFsHeaderReader* out, const NcaReader& reader, | ||
| 245 | s32 fs_index); | ||
| 246 | |||
| 247 | public: | ||
| 248 | NcaFileSystemDriver(std::shared_ptr<NcaReader> reader) : m_original_reader(), m_reader(reader) { | ||
| 249 | ASSERT(m_reader != nullptr); | ||
| 250 | } | ||
| 251 | |||
| 252 | NcaFileSystemDriver(std::shared_ptr<NcaReader> original_reader, | ||
| 253 | std::shared_ptr<NcaReader> reader) | ||
| 254 | : m_original_reader(original_reader), m_reader(reader) { | ||
| 255 | ASSERT(m_reader != nullptr); | ||
| 256 | } | ||
| 257 | |||
| 258 | Result OpenStorageWithContext(VirtualFile* out, NcaFsHeaderReader* out_header_reader, | ||
| 259 | s32 fs_index, StorageContext* ctx); | ||
| 260 | |||
| 261 | Result OpenStorage(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index) { | ||
| 262 | // Create a storage context. | ||
| 263 | StorageContext ctx{}; | ||
| 264 | |||
| 265 | // Open the storage. | ||
| 266 | R_RETURN(OpenStorageWithContext(out, out_header_reader, fs_index, std::addressof(ctx))); | ||
| 267 | } | ||
| 268 | |||
| 269 | public: | ||
| 270 | Result CreateStorageByRawStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader, | ||
| 271 | VirtualFile raw_storage, StorageContext* ctx); | ||
| 272 | |||
| 273 | private: | ||
| 274 | Result OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index, | ||
| 275 | StorageContext* ctx); | ||
| 276 | |||
| 277 | Result OpenIndirectableStorageAsOriginal(VirtualFile* out, | ||
| 278 | const NcaFsHeaderReader* header_reader, | ||
| 279 | StorageContext* ctx); | ||
| 280 | |||
| 281 | Result CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size); | ||
| 282 | |||
| 283 | Result CreateAesCtrStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 284 | const NcaAesCtrUpperIv& upper_iv, | ||
| 285 | AlignmentStorageRequirement alignment_storage_requirement); | ||
| 286 | Result CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, s64 offset); | ||
| 287 | |||
| 288 | Result CreateSparseStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 289 | const NcaAesCtrUpperIv& upper_iv, | ||
| 290 | const NcaSparseInfo& sparse_info); | ||
| 291 | Result CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, VirtualFile base_storage, | ||
| 292 | s64 base_size, VirtualFile meta_storage, | ||
| 293 | const NcaSparseInfo& sparse_info, bool external_info); | ||
| 294 | Result CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset, | ||
| 295 | std::shared_ptr<SparseStorage>* out_sparse_storage, | ||
| 296 | VirtualFile* out_meta_storage, s32 index, | ||
| 297 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info); | ||
| 298 | |||
| 299 | Result CreateSparseStorageMetaStorageWithVerification( | ||
| 300 | VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset, | ||
| 301 | const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, | ||
| 302 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info); | ||
| 303 | Result CreateSparseStorageWithVerification( | ||
| 304 | VirtualFile* out, s64* out_fs_data_offset, | ||
| 305 | std::shared_ptr<SparseStorage>* out_sparse_storage, VirtualFile* out_meta_storage, | ||
| 306 | VirtualFile* out_verification, s32 index, const NcaAesCtrUpperIv& upper_iv, | ||
| 307 | const NcaSparseInfo& sparse_info, const NcaMetaDataHashDataInfo& meta_data_hash_data_info, | ||
| 308 | NcaFsHeader::MetaDataHashType meta_data_hash_type); | ||
| 309 | |||
| 310 | Result CreateAesCtrExStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, | ||
| 311 | NcaFsHeader::EncryptionType encryption_type, | ||
| 312 | const NcaAesCtrUpperIv& upper_iv, | ||
| 313 | const NcaPatchInfo& patch_info); | ||
| 314 | Result CreateAesCtrExStorage(VirtualFile* out, | ||
| 315 | std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext, | ||
| 316 | VirtualFile base_storage, VirtualFile meta_storage, | ||
| 317 | s64 counter_offset, const NcaAesCtrUpperIv& upper_iv, | ||
| 318 | const NcaPatchInfo& patch_info); | ||
| 319 | |||
| 320 | Result CreateIndirectStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, | ||
| 321 | const NcaPatchInfo& patch_info); | ||
| 322 | Result CreateIndirectStorage(VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, | ||
| 323 | VirtualFile base_storage, VirtualFile original_data_storage, | ||
| 324 | VirtualFile meta_storage, const NcaPatchInfo& patch_info); | ||
| 325 | |||
| 326 | Result CreatePatchMetaStorage(VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta, | ||
| 327 | VirtualFile* out_verification, VirtualFile base_storage, | ||
| 328 | s64 offset, const NcaAesCtrUpperIv& upper_iv, | ||
| 329 | const NcaPatchInfo& patch_info, | ||
| 330 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info); | ||
| 331 | |||
| 332 | Result CreateSha256Storage(VirtualFile* out, VirtualFile base_storage, | ||
| 333 | const NcaFsHeader::HashData::HierarchicalSha256Data& sha256_data); | ||
| 334 | |||
| 335 | Result CreateIntegrityVerificationStorage( | ||
| 336 | VirtualFile* out, VirtualFile base_storage, | ||
| 337 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info); | ||
| 338 | Result CreateIntegrityVerificationStorageForMeta( | ||
| 339 | VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset, | ||
| 340 | const NcaMetaDataHashDataInfo& meta_data_hash_data_info); | ||
| 341 | Result CreateIntegrityVerificationStorageImpl( | ||
| 342 | VirtualFile* out, VirtualFile base_storage, | ||
| 343 | const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset, | ||
| 344 | int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level); | ||
| 345 | |||
| 346 | Result CreateRegionSwitchStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader, | ||
| 347 | VirtualFile inside_storage, VirtualFile outside_storage); | ||
| 348 | |||
| 349 | Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 350 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 351 | const NcaCompressionInfo& compression_info); | ||
| 352 | |||
| 353 | public: | ||
| 354 | Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp, | ||
| 355 | VirtualFile* out_meta, VirtualFile base_storage, | ||
| 356 | const NcaCompressionInfo& compression_info, | ||
| 357 | GetDecompressorFunction get_decompressor); | ||
| 358 | }; | ||
| 359 | |||
| 360 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.cpp b/src/core/file_sys/fssystem/fssystem_nca_header.cpp new file mode 100644 index 000000000..bf5742d39 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_header.cpp | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_nca_header.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | u8 NcaHeader::GetProperKeyGeneration() const { | ||
| 9 | return std::max(this->key_generation, this->key_generation_2); | ||
| 10 | } | ||
| 11 | |||
| 12 | bool NcaPatchInfo::HasIndirectTable() const { | ||
| 13 | return this->indirect_size != 0; | ||
| 14 | } | ||
| 15 | |||
| 16 | bool NcaPatchInfo::HasAesCtrExTable() const { | ||
| 17 | return this->aes_ctr_ex_size != 0; | ||
| 18 | } | ||
| 19 | |||
| 20 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.h b/src/core/file_sys/fssystem/fssystem_nca_header.h new file mode 100644 index 000000000..a02c5d881 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_header.h | |||
| @@ -0,0 +1,338 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | #include "common/literals.h" | ||
| 9 | |||
| 10 | #include "core/file_sys/errors.h" | ||
| 11 | #include "core/file_sys/fssystem/fs_types.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | using namespace Common::Literals; | ||
| 16 | |||
| 17 | struct Hash { | ||
| 18 | static constexpr std::size_t Size = 256 / 8; | ||
| 19 | std::array<u8, Size> value; | ||
| 20 | }; | ||
| 21 | static_assert(sizeof(Hash) == Hash::Size); | ||
| 22 | static_assert(std::is_trivial_v<Hash>); | ||
| 23 | |||
| 24 | using NcaDigest = Hash; | ||
| 25 | |||
| 26 | struct NcaHeader { | ||
| 27 | enum class ContentType : u8 { | ||
| 28 | Program = 0, | ||
| 29 | Meta = 1, | ||
| 30 | Control = 2, | ||
| 31 | Manual = 3, | ||
| 32 | Data = 4, | ||
| 33 | PublicData = 5, | ||
| 34 | |||
| 35 | Start = Program, | ||
| 36 | End = PublicData, | ||
| 37 | }; | ||
| 38 | |||
| 39 | enum class DistributionType : u8 { | ||
| 40 | Download = 0, | ||
| 41 | GameCard = 1, | ||
| 42 | |||
| 43 | Start = Download, | ||
| 44 | End = GameCard, | ||
| 45 | }; | ||
| 46 | |||
| 47 | enum class EncryptionType : u8 { | ||
| 48 | Auto = 0, | ||
| 49 | None = 1, | ||
| 50 | }; | ||
| 51 | |||
| 52 | enum DecryptionKey { | ||
| 53 | DecryptionKey_AesXts = 0, | ||
| 54 | DecryptionKey_AesXts1 = DecryptionKey_AesXts, | ||
| 55 | DecryptionKey_AesXts2 = 1, | ||
| 56 | DecryptionKey_AesCtr = 2, | ||
| 57 | DecryptionKey_AesCtrEx = 3, | ||
| 58 | DecryptionKey_AesCtrHw = 4, | ||
| 59 | DecryptionKey_Count, | ||
| 60 | }; | ||
| 61 | |||
| 62 | struct FsInfo { | ||
| 63 | u32 start_sector; | ||
| 64 | u32 end_sector; | ||
| 65 | u32 hash_sectors; | ||
| 66 | u32 reserved; | ||
| 67 | }; | ||
| 68 | static_assert(sizeof(FsInfo) == 0x10); | ||
| 69 | static_assert(std::is_trivial_v<FsInfo>); | ||
| 70 | |||
| 71 | static constexpr u32 Magic0 = Common::MakeMagic('N', 'C', 'A', '0'); | ||
| 72 | static constexpr u32 Magic1 = Common::MakeMagic('N', 'C', 'A', '1'); | ||
| 73 | static constexpr u32 Magic2 = Common::MakeMagic('N', 'C', 'A', '2'); | ||
| 74 | static constexpr u32 Magic3 = Common::MakeMagic('N', 'C', 'A', '3'); | ||
| 75 | |||
| 76 | static constexpr u32 Magic = Magic3; | ||
| 77 | |||
| 78 | static constexpr std::size_t Size = 1_KiB; | ||
| 79 | static constexpr s32 FsCountMax = 4; | ||
| 80 | static constexpr std::size_t HeaderSignCount = 2; | ||
| 81 | static constexpr std::size_t HeaderSignSize = 0x100; | ||
| 82 | static constexpr std::size_t EncryptedKeyAreaSize = 0x100; | ||
| 83 | static constexpr std::size_t SectorSize = 0x200; | ||
| 84 | static constexpr std::size_t SectorShift = 9; | ||
| 85 | static constexpr std::size_t RightsIdSize = 0x10; | ||
| 86 | static constexpr std::size_t XtsBlockSize = 0x200; | ||
| 87 | static constexpr std::size_t CtrBlockSize = 0x10; | ||
| 88 | |||
| 89 | static_assert(SectorSize == (1 << SectorShift)); | ||
| 90 | |||
| 91 | // Data members. | ||
| 92 | std::array<u8, HeaderSignSize> header_sign_1; | ||
| 93 | std::array<u8, HeaderSignSize> header_sign_2; | ||
| 94 | u32 magic; | ||
| 95 | DistributionType distribution_type; | ||
| 96 | ContentType content_type; | ||
| 97 | u8 key_generation; | ||
| 98 | u8 key_index; | ||
| 99 | u64 content_size; | ||
| 100 | u64 program_id; | ||
| 101 | u32 content_index; | ||
| 102 | u32 sdk_addon_version; | ||
| 103 | u8 key_generation_2; | ||
| 104 | u8 header1_signature_key_generation; | ||
| 105 | std::array<u8, 2> reserved_222; | ||
| 106 | std::array<u32, 3> reserved_224; | ||
| 107 | std::array<u8, RightsIdSize> rights_id; | ||
| 108 | std::array<FsInfo, FsCountMax> fs_info; | ||
| 109 | std::array<Hash, FsCountMax> fs_header_hash; | ||
| 110 | std::array<u8, EncryptedKeyAreaSize> encrypted_key_area; | ||
| 111 | |||
| 112 | static constexpr u64 SectorToByte(u32 sector) { | ||
| 113 | return static_cast<u64>(sector) << SectorShift; | ||
| 114 | } | ||
| 115 | |||
| 116 | static constexpr u32 ByteToSector(u64 byte) { | ||
| 117 | return static_cast<u32>(byte >> SectorShift); | ||
| 118 | } | ||
| 119 | |||
| 120 | u8 GetProperKeyGeneration() const; | ||
| 121 | }; | ||
| 122 | static_assert(sizeof(NcaHeader) == NcaHeader::Size); | ||
| 123 | static_assert(std::is_trivial_v<NcaHeader>); | ||
| 124 | |||
| 125 | struct NcaBucketInfo { | ||
| 126 | static constexpr size_t HeaderSize = 0x10; | ||
| 127 | Int64 offset; | ||
| 128 | Int64 size; | ||
| 129 | std::array<u8, HeaderSize> header; | ||
| 130 | }; | ||
| 131 | static_assert(std::is_trivial_v<NcaBucketInfo>); | ||
| 132 | |||
| 133 | struct NcaPatchInfo { | ||
| 134 | static constexpr size_t Size = 0x40; | ||
| 135 | static constexpr size_t Offset = 0x100; | ||
| 136 | |||
| 137 | Int64 indirect_offset; | ||
| 138 | Int64 indirect_size; | ||
| 139 | std::array<u8, NcaBucketInfo::HeaderSize> indirect_header; | ||
| 140 | Int64 aes_ctr_ex_offset; | ||
| 141 | Int64 aes_ctr_ex_size; | ||
| 142 | std::array<u8, NcaBucketInfo::HeaderSize> aes_ctr_ex_header; | ||
| 143 | |||
| 144 | bool HasIndirectTable() const; | ||
| 145 | bool HasAesCtrExTable() const; | ||
| 146 | }; | ||
| 147 | static_assert(std::is_trivial_v<NcaPatchInfo>); | ||
| 148 | |||
| 149 | union NcaAesCtrUpperIv { | ||
| 150 | u64 value; | ||
| 151 | struct { | ||
| 152 | u32 generation; | ||
| 153 | u32 secure_value; | ||
| 154 | } part; | ||
| 155 | }; | ||
| 156 | static_assert(std::is_trivial_v<NcaAesCtrUpperIv>); | ||
| 157 | |||
| 158 | struct NcaSparseInfo { | ||
| 159 | NcaBucketInfo bucket; | ||
| 160 | Int64 physical_offset; | ||
| 161 | u16 generation; | ||
| 162 | std::array<u8, 6> reserved; | ||
| 163 | |||
| 164 | s64 GetPhysicalSize() const { | ||
| 165 | return this->bucket.offset + this->bucket.size; | ||
| 166 | } | ||
| 167 | |||
| 168 | u32 GetGeneration() const { | ||
| 169 | return static_cast<u32>(this->generation) << 16; | ||
| 170 | } | ||
| 171 | |||
| 172 | const NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upper_iv) const { | ||
| 173 | NcaAesCtrUpperIv sparse_upper_iv = upper_iv; | ||
| 174 | sparse_upper_iv.part.generation = this->GetGeneration(); | ||
| 175 | return sparse_upper_iv; | ||
| 176 | } | ||
| 177 | }; | ||
| 178 | static_assert(std::is_trivial_v<NcaSparseInfo>); | ||
| 179 | |||
| 180 | struct NcaCompressionInfo { | ||
| 181 | NcaBucketInfo bucket; | ||
| 182 | std::array<u8, 8> resreved; | ||
| 183 | }; | ||
| 184 | static_assert(std::is_trivial_v<NcaCompressionInfo>); | ||
| 185 | |||
| 186 | struct NcaMetaDataHashDataInfo { | ||
| 187 | Int64 offset; | ||
| 188 | Int64 size; | ||
| 189 | Hash hash; | ||
| 190 | }; | ||
| 191 | static_assert(std::is_trivial_v<NcaMetaDataHashDataInfo>); | ||
| 192 | |||
| 193 | struct NcaFsHeader { | ||
| 194 | static constexpr size_t Size = 0x200; | ||
| 195 | static constexpr size_t HashDataOffset = 0x8; | ||
| 196 | |||
| 197 | struct Region { | ||
| 198 | Int64 offset; | ||
| 199 | Int64 size; | ||
| 200 | }; | ||
| 201 | static_assert(std::is_trivial_v<Region>); | ||
| 202 | |||
| 203 | enum class FsType : u8 { | ||
| 204 | RomFs = 0, | ||
| 205 | PartitionFs = 1, | ||
| 206 | }; | ||
| 207 | |||
| 208 | enum class EncryptionType : u8 { | ||
| 209 | Auto = 0, | ||
| 210 | None = 1, | ||
| 211 | AesXts = 2, | ||
| 212 | AesCtr = 3, | ||
| 213 | AesCtrEx = 4, | ||
| 214 | AesCtrSkipLayerHash = 5, | ||
| 215 | AesCtrExSkipLayerHash = 6, | ||
| 216 | }; | ||
| 217 | |||
| 218 | enum class HashType : u8 { | ||
| 219 | Auto = 0, | ||
| 220 | None = 1, | ||
| 221 | HierarchicalSha256Hash = 2, | ||
| 222 | HierarchicalIntegrityHash = 3, | ||
| 223 | AutoSha3 = 4, | ||
| 224 | HierarchicalSha3256Hash = 5, | ||
| 225 | HierarchicalIntegritySha3Hash = 6, | ||
| 226 | }; | ||
| 227 | |||
| 228 | enum class MetaDataHashType : u8 { | ||
| 229 | None = 0, | ||
| 230 | HierarchicalIntegrity = 1, | ||
| 231 | }; | ||
| 232 | |||
| 233 | union HashData { | ||
| 234 | struct HierarchicalSha256Data { | ||
| 235 | static constexpr size_t HashLayerCountMax = 5; | ||
| 236 | static const size_t MasterHashOffset; | ||
| 237 | |||
| 238 | Hash fs_data_master_hash; | ||
| 239 | s32 hash_block_size; | ||
| 240 | s32 hash_layer_count; | ||
| 241 | std::array<Region, HashLayerCountMax> hash_layer_region; | ||
| 242 | } hierarchical_sha256_data; | ||
| 243 | static_assert(std::is_trivial_v<HierarchicalSha256Data>); | ||
| 244 | |||
| 245 | struct IntegrityMetaInfo { | ||
| 246 | static const size_t MasterHashOffset; | ||
| 247 | |||
| 248 | u32 magic; | ||
| 249 | u32 version; | ||
| 250 | u32 master_hash_size; | ||
| 251 | |||
| 252 | struct LevelHashInfo { | ||
| 253 | u32 max_layers; | ||
| 254 | |||
| 255 | struct HierarchicalIntegrityVerificationLevelInformation { | ||
| 256 | static constexpr size_t IntegrityMaxLayerCount = 7; | ||
| 257 | Int64 offset; | ||
| 258 | Int64 size; | ||
| 259 | s32 block_order; | ||
| 260 | std::array<u8, 4> reserved; | ||
| 261 | }; | ||
| 262 | std::array< | ||
| 263 | HierarchicalIntegrityVerificationLevelInformation, | ||
| 264 | HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1> | ||
| 265 | info; | ||
| 266 | |||
| 267 | struct SignatureSalt { | ||
| 268 | static constexpr size_t Size = 0x20; | ||
| 269 | std::array<u8, Size> value; | ||
| 270 | }; | ||
| 271 | SignatureSalt seed; | ||
| 272 | } level_hash_info; | ||
| 273 | |||
| 274 | Hash master_hash; | ||
| 275 | } integrity_meta_info; | ||
| 276 | static_assert(std::is_trivial_v<IntegrityMetaInfo>); | ||
| 277 | |||
| 278 | std::array<u8, NcaPatchInfo::Offset - HashDataOffset> padding; | ||
| 279 | }; | ||
| 280 | |||
| 281 | u16 version; | ||
| 282 | FsType fs_type; | ||
| 283 | HashType hash_type; | ||
| 284 | EncryptionType encryption_type; | ||
| 285 | MetaDataHashType meta_data_hash_type; | ||
| 286 | std::array<u8, 2> reserved; | ||
| 287 | HashData hash_data; | ||
| 288 | NcaPatchInfo patch_info; | ||
| 289 | NcaAesCtrUpperIv aes_ctr_upper_iv; | ||
| 290 | NcaSparseInfo sparse_info; | ||
| 291 | NcaCompressionInfo compression_info; | ||
| 292 | NcaMetaDataHashDataInfo meta_data_hash_data_info; | ||
| 293 | std::array<u8, 0x30> pad; | ||
| 294 | |||
| 295 | bool IsSkipLayerHashEncryption() const { | ||
| 296 | return this->encryption_type == EncryptionType::AesCtrSkipLayerHash || | ||
| 297 | this->encryption_type == EncryptionType::AesCtrExSkipLayerHash; | ||
| 298 | } | ||
| 299 | |||
| 300 | Result GetHashTargetOffset(s64* out) const { | ||
| 301 | switch (this->hash_type) { | ||
| 302 | case HashType::HierarchicalIntegrityHash: | ||
| 303 | case HashType::HierarchicalIntegritySha3Hash: | ||
| 304 | *out = this->hash_data.integrity_meta_info.level_hash_info | ||
| 305 | .info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2] | ||
| 306 | .offset; | ||
| 307 | R_SUCCEED(); | ||
| 308 | case HashType::HierarchicalSha256Hash: | ||
| 309 | case HashType::HierarchicalSha3256Hash: | ||
| 310 | *out = | ||
| 311 | this->hash_data.hierarchical_sha256_data | ||
| 312 | .hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count - | ||
| 313 | 1] | ||
| 314 | .offset; | ||
| 315 | R_SUCCEED(); | ||
| 316 | default: | ||
| 317 | R_THROW(ResultInvalidNcaFsHeader); | ||
| 318 | } | ||
| 319 | } | ||
| 320 | }; | ||
| 321 | static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size); | ||
| 322 | static_assert(std::is_trivial_v<NcaFsHeader>); | ||
| 323 | static_assert(offsetof(NcaFsHeader, patch_info) == NcaPatchInfo::Offset); | ||
| 324 | |||
| 325 | inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset = | ||
| 326 | offsetof(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash); | ||
| 327 | inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset = | ||
| 328 | offsetof(NcaFsHeader, hash_data.integrity_meta_info.master_hash); | ||
| 329 | |||
| 330 | struct NcaMetaDataHashData { | ||
| 331 | s64 layer_info_offset; | ||
| 332 | NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info; | ||
| 333 | }; | ||
| 334 | static_assert(sizeof(NcaMetaDataHashData) == | ||
| 335 | sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64)); | ||
| 336 | static_assert(std::is_trivial_v<NcaMetaDataHashData>); | ||
| 337 | |||
| 338 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_nca_reader.cpp b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp new file mode 100644 index 000000000..cd4c49069 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp | |||
| @@ -0,0 +1,542 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" | ||
| 6 | #include "core/file_sys/vfs_offset.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | namespace { | ||
| 11 | |||
| 12 | constexpr inline u32 SdkAddonVersionMin = 0x000B0000; | ||
| 13 | constexpr inline size_t Aes128KeySize = 0x10; | ||
| 14 | constexpr const std::array<u8, Aes128KeySize> ZeroKey{}; | ||
| 15 | |||
| 16 | constexpr Result CheckNcaMagic(u32 magic) { | ||
| 17 | // Verify the magic is not a deprecated one. | ||
| 18 | R_UNLESS(magic != NcaHeader::Magic0, ResultUnsupportedSdkVersion); | ||
| 19 | R_UNLESS(magic != NcaHeader::Magic1, ResultUnsupportedSdkVersion); | ||
| 20 | R_UNLESS(magic != NcaHeader::Magic2, ResultUnsupportedSdkVersion); | ||
| 21 | |||
| 22 | // Verify the magic is the current one. | ||
| 23 | R_UNLESS(magic == NcaHeader::Magic3, ResultInvalidNcaSignature); | ||
| 24 | |||
| 25 | R_SUCCEED(); | ||
| 26 | } | ||
| 27 | |||
| 28 | } // namespace | ||
| 29 | |||
| 30 | NcaReader::NcaReader() | ||
| 31 | : m_body_storage(), m_header_storage(), m_is_software_aes_prioritized(false), | ||
| 32 | m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto), | ||
| 33 | m_get_decompressor() { | ||
| 34 | std::memset(std::addressof(m_header), 0, sizeof(m_header)); | ||
| 35 | std::memset(std::addressof(m_decryption_keys), 0, sizeof(m_decryption_keys)); | ||
| 36 | std::memset(std::addressof(m_external_decryption_key), 0, sizeof(m_external_decryption_key)); | ||
| 37 | } | ||
| 38 | |||
| 39 | NcaReader::~NcaReader() {} | ||
| 40 | |||
| 41 | Result NcaReader::Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg, | ||
| 42 | const NcaCompressionConfiguration& compression_cfg) { | ||
| 43 | // Validate preconditions. | ||
| 44 | ASSERT(base_storage != nullptr); | ||
| 45 | ASSERT(m_body_storage == nullptr); | ||
| 46 | |||
| 47 | // Create the work header storage storage. | ||
| 48 | VirtualFile work_header_storage; | ||
| 49 | |||
| 50 | // We need to be able to generate keys. | ||
| 51 | R_UNLESS(crypto_cfg.generate_key != nullptr, ResultInvalidArgument); | ||
| 52 | |||
| 53 | // Generate keys for header. | ||
| 54 | using AesXtsStorageForNcaHeader = AesXtsStorage; | ||
| 55 | |||
| 56 | constexpr const s32 HeaderKeyTypeValues[NcaCryptoConfiguration::HeaderEncryptionKeyCount] = { | ||
| 57 | static_cast<s32>(KeyType::NcaHeaderKey1), | ||
| 58 | static_cast<s32>(KeyType::NcaHeaderKey2), | ||
| 59 | }; | ||
| 60 | |||
| 61 | u8 header_decryption_keys[NcaCryptoConfiguration::HeaderEncryptionKeyCount] | ||
| 62 | [NcaCryptoConfiguration::Aes128KeySize]; | ||
| 63 | for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) { | ||
| 64 | crypto_cfg.generate_key(header_decryption_keys[i], AesXtsStorageForNcaHeader::KeySize, | ||
| 65 | crypto_cfg.header_encrypted_encryption_keys[i], | ||
| 66 | AesXtsStorageForNcaHeader::KeySize, HeaderKeyTypeValues[i]); | ||
| 67 | } | ||
| 68 | |||
| 69 | // Create the header storage. | ||
| 70 | const u8 header_iv[AesXtsStorageForNcaHeader::IvSize] = {}; | ||
| 71 | work_header_storage = std::make_unique<AesXtsStorageForNcaHeader>( | ||
| 72 | base_storage, header_decryption_keys[0], header_decryption_keys[1], | ||
| 73 | AesXtsStorageForNcaHeader::KeySize, header_iv, AesXtsStorageForNcaHeader::IvSize, | ||
| 74 | NcaHeader::XtsBlockSize); | ||
| 75 | |||
| 76 | // Check that we successfully created the storage. | ||
| 77 | R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA); | ||
| 78 | |||
| 79 | // Read the header. | ||
| 80 | work_header_storage->ReadObject(std::addressof(m_header), 0); | ||
| 81 | |||
| 82 | // Validate the magic. | ||
| 83 | if (const Result magic_result = CheckNcaMagic(m_header.magic); R_FAILED(magic_result)) { | ||
| 84 | // Try to use a plaintext header. | ||
| 85 | base_storage->ReadObject(std::addressof(m_header), 0); | ||
| 86 | R_UNLESS(R_SUCCEEDED(CheckNcaMagic(m_header.magic)), magic_result); | ||
| 87 | |||
| 88 | // Configure to use the plaintext header. | ||
| 89 | auto base_storage_size = base_storage->GetSize(); | ||
| 90 | work_header_storage = std::make_shared<OffsetVfsFile>(base_storage, base_storage_size, 0); | ||
| 91 | R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA); | ||
| 92 | |||
| 93 | // Set encryption type as plaintext. | ||
| 94 | m_header_encryption_type = NcaHeader::EncryptionType::None; | ||
| 95 | } | ||
| 96 | |||
| 97 | // Validate the fixed key signature. | ||
| 98 | if (m_header.header1_signature_key_generation > | ||
| 99 | NcaCryptoConfiguration::Header1SignatureKeyGenerationMax) { | ||
| 100 | LOG_CRITICAL(Frontend, | ||
| 101 | "NcaCryptoConfiguration::Header1SignatureKeyGenerationMax = {}, " | ||
| 102 | "m_header.header1_signature_key_generation = {}", | ||
| 103 | NcaCryptoConfiguration::Header1SignatureKeyGenerationMax, | ||
| 104 | m_header.header1_signature_key_generation); | ||
| 105 | } | ||
| 106 | |||
| 107 | R_UNLESS(m_header.header1_signature_key_generation <= | ||
| 108 | NcaCryptoConfiguration::Header1SignatureKeyGenerationMax, | ||
| 109 | ResultInvalidNcaHeader1SignatureKeyGeneration); | ||
| 110 | |||
| 111 | // Verify the header sign1. | ||
| 112 | if (crypto_cfg.verify_sign1 != nullptr) { | ||
| 113 | const u8* sig = m_header.header_sign_1.data(); | ||
| 114 | const size_t sig_size = NcaHeader::HeaderSignSize; | ||
| 115 | const u8* msg = | ||
| 116 | static_cast<const u8*>(static_cast<const void*>(std::addressof(m_header.magic))); | ||
| 117 | const size_t msg_size = | ||
| 118 | NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount; | ||
| 119 | |||
| 120 | m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1( | ||
| 121 | sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation); | ||
| 122 | |||
| 123 | if (!m_is_header_sign1_signature_valid) { | ||
| 124 | LOG_WARNING(Common_Filesystem, "Invalid NCA header sign1"); | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | // Validate the sdk version. | ||
| 129 | R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, ResultUnsupportedSdkVersion); | ||
| 130 | |||
| 131 | // Validate the key index. | ||
| 132 | R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount || | ||
| 133 | m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey, | ||
| 134 | ResultInvalidNcaKeyIndex); | ||
| 135 | |||
| 136 | // Check if we have a rights id. | ||
| 137 | constexpr const std::array<u8, NcaHeader::RightsIdSize> ZeroRightsId{}; | ||
| 138 | if (std::memcmp(ZeroRightsId.data(), m_header.rights_id.data(), NcaHeader::RightsIdSize) == 0) { | ||
| 139 | // If we don't, then we don't have an external key, so we need to generate decryption keys. | ||
| 140 | crypto_cfg.generate_key( | ||
| 141 | m_decryption_keys[NcaHeader::DecryptionKey_AesCtr], Aes128KeySize, | ||
| 142 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtr * Aes128KeySize, | ||
| 143 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 144 | crypto_cfg.generate_key( | ||
| 145 | m_decryption_keys[NcaHeader::DecryptionKey_AesXts1], Aes128KeySize, | ||
| 146 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts1 * Aes128KeySize, | ||
| 147 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 148 | crypto_cfg.generate_key( | ||
| 149 | m_decryption_keys[NcaHeader::DecryptionKey_AesXts2], Aes128KeySize, | ||
| 150 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts2 * Aes128KeySize, | ||
| 151 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 152 | crypto_cfg.generate_key( | ||
| 153 | m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx], Aes128KeySize, | ||
| 154 | m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtrEx * Aes128KeySize, | ||
| 155 | Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); | ||
| 156 | |||
| 157 | // Copy the hardware speed emulation key. | ||
| 158 | std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw], | ||
| 159 | m_header.encrypted_key_area.data() + | ||
| 160 | NcaHeader::DecryptionKey_AesCtrHw * Aes128KeySize, | ||
| 161 | Aes128KeySize); | ||
| 162 | } | ||
| 163 | |||
| 164 | // Clear the external decryption key. | ||
| 165 | std::memset(m_external_decryption_key, 0, sizeof(m_external_decryption_key)); | ||
| 166 | |||
| 167 | // Set software key availability. | ||
| 168 | m_is_available_sw_key = crypto_cfg.is_available_sw_key; | ||
| 169 | |||
| 170 | // Set our decompressor function getter. | ||
| 171 | m_get_decompressor = compression_cfg.get_decompressor; | ||
| 172 | |||
| 173 | // Set our storages. | ||
| 174 | m_header_storage = std::move(work_header_storage); | ||
| 175 | m_body_storage = std::move(base_storage); | ||
| 176 | |||
| 177 | R_SUCCEED(); | ||
| 178 | } | ||
| 179 | |||
| 180 | VirtualFile NcaReader::GetSharedBodyStorage() { | ||
| 181 | ASSERT(m_body_storage != nullptr); | ||
| 182 | return m_body_storage; | ||
| 183 | } | ||
| 184 | |||
| 185 | u32 NcaReader::GetMagic() const { | ||
| 186 | ASSERT(m_body_storage != nullptr); | ||
| 187 | return m_header.magic; | ||
| 188 | } | ||
| 189 | |||
| 190 | NcaHeader::DistributionType NcaReader::GetDistributionType() const { | ||
| 191 | ASSERT(m_body_storage != nullptr); | ||
| 192 | return m_header.distribution_type; | ||
| 193 | } | ||
| 194 | |||
| 195 | NcaHeader::ContentType NcaReader::GetContentType() const { | ||
| 196 | ASSERT(m_body_storage != nullptr); | ||
| 197 | return m_header.content_type; | ||
| 198 | } | ||
| 199 | |||
| 200 | u8 NcaReader::GetHeaderSign1KeyGeneration() const { | ||
| 201 | ASSERT(m_body_storage != nullptr); | ||
| 202 | return m_header.header1_signature_key_generation; | ||
| 203 | } | ||
| 204 | |||
| 205 | u8 NcaReader::GetKeyGeneration() const { | ||
| 206 | ASSERT(m_body_storage != nullptr); | ||
| 207 | return m_header.GetProperKeyGeneration(); | ||
| 208 | } | ||
| 209 | |||
| 210 | u8 NcaReader::GetKeyIndex() const { | ||
| 211 | ASSERT(m_body_storage != nullptr); | ||
| 212 | return m_header.key_index; | ||
| 213 | } | ||
| 214 | |||
| 215 | u64 NcaReader::GetContentSize() const { | ||
| 216 | ASSERT(m_body_storage != nullptr); | ||
| 217 | return m_header.content_size; | ||
| 218 | } | ||
| 219 | |||
| 220 | u64 NcaReader::GetProgramId() const { | ||
| 221 | ASSERT(m_body_storage != nullptr); | ||
| 222 | return m_header.program_id; | ||
| 223 | } | ||
| 224 | |||
| 225 | u32 NcaReader::GetContentIndex() const { | ||
| 226 | ASSERT(m_body_storage != nullptr); | ||
| 227 | return m_header.content_index; | ||
| 228 | } | ||
| 229 | |||
| 230 | u32 NcaReader::GetSdkAddonVersion() const { | ||
| 231 | ASSERT(m_body_storage != nullptr); | ||
| 232 | return m_header.sdk_addon_version; | ||
| 233 | } | ||
| 234 | |||
| 235 | void NcaReader::GetRightsId(u8* dst, size_t dst_size) const { | ||
| 236 | ASSERT(dst != nullptr); | ||
| 237 | ASSERT(dst_size >= NcaHeader::RightsIdSize); | ||
| 238 | |||
| 239 | std::memcpy(dst, m_header.rights_id.data(), NcaHeader::RightsIdSize); | ||
| 240 | } | ||
| 241 | |||
| 242 | bool NcaReader::HasFsInfo(s32 index) const { | ||
| 243 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 244 | return m_header.fs_info[index].start_sector != 0 || m_header.fs_info[index].end_sector != 0; | ||
| 245 | } | ||
| 246 | |||
| 247 | s32 NcaReader::GetFsCount() const { | ||
| 248 | ASSERT(m_body_storage != nullptr); | ||
| 249 | for (s32 i = 0; i < NcaHeader::FsCountMax; i++) { | ||
| 250 | if (!this->HasFsInfo(i)) { | ||
| 251 | return i; | ||
| 252 | } | ||
| 253 | } | ||
| 254 | return NcaHeader::FsCountMax; | ||
| 255 | } | ||
| 256 | |||
| 257 | const Hash& NcaReader::GetFsHeaderHash(s32 index) const { | ||
| 258 | ASSERT(m_body_storage != nullptr); | ||
| 259 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 260 | return m_header.fs_header_hash[index]; | ||
| 261 | } | ||
| 262 | |||
| 263 | void NcaReader::GetFsHeaderHash(Hash* dst, s32 index) const { | ||
| 264 | ASSERT(m_body_storage != nullptr); | ||
| 265 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 266 | ASSERT(dst != nullptr); | ||
| 267 | std::memcpy(dst, std::addressof(m_header.fs_header_hash[index]), sizeof(*dst)); | ||
| 268 | } | ||
| 269 | |||
| 270 | void NcaReader::GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const { | ||
| 271 | ASSERT(m_body_storage != nullptr); | ||
| 272 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 273 | ASSERT(dst != nullptr); | ||
| 274 | std::memcpy(dst, std::addressof(m_header.fs_info[index]), sizeof(*dst)); | ||
| 275 | } | ||
| 276 | |||
| 277 | u64 NcaReader::GetFsOffset(s32 index) const { | ||
| 278 | ASSERT(m_body_storage != nullptr); | ||
| 279 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 280 | return NcaHeader::SectorToByte(m_header.fs_info[index].start_sector); | ||
| 281 | } | ||
| 282 | |||
| 283 | u64 NcaReader::GetFsEndOffset(s32 index) const { | ||
| 284 | ASSERT(m_body_storage != nullptr); | ||
| 285 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 286 | return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector); | ||
| 287 | } | ||
| 288 | |||
| 289 | u64 NcaReader::GetFsSize(s32 index) const { | ||
| 290 | ASSERT(m_body_storage != nullptr); | ||
| 291 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 292 | return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector - | ||
| 293 | m_header.fs_info[index].start_sector); | ||
| 294 | } | ||
| 295 | |||
| 296 | void NcaReader::GetEncryptedKey(void* dst, size_t size) const { | ||
| 297 | ASSERT(m_body_storage != nullptr); | ||
| 298 | ASSERT(dst != nullptr); | ||
| 299 | ASSERT(size >= NcaHeader::EncryptedKeyAreaSize); | ||
| 300 | |||
| 301 | std::memcpy(dst, m_header.encrypted_key_area.data(), NcaHeader::EncryptedKeyAreaSize); | ||
| 302 | } | ||
| 303 | |||
| 304 | const void* NcaReader::GetDecryptionKey(s32 index) const { | ||
| 305 | ASSERT(m_body_storage != nullptr); | ||
| 306 | ASSERT(0 <= index && index < NcaHeader::DecryptionKey_Count); | ||
| 307 | return m_decryption_keys[index]; | ||
| 308 | } | ||
| 309 | |||
| 310 | bool NcaReader::HasValidInternalKey() const { | ||
| 311 | for (s32 i = 0; i < NcaHeader::DecryptionKey_Count; i++) { | ||
| 312 | if (std::memcmp(ZeroKey.data(), m_header.encrypted_key_area.data() + i * Aes128KeySize, | ||
| 313 | Aes128KeySize) != 0) { | ||
| 314 | return true; | ||
| 315 | } | ||
| 316 | } | ||
| 317 | return false; | ||
| 318 | } | ||
| 319 | |||
| 320 | bool NcaReader::HasInternalDecryptionKeyForAesHw() const { | ||
| 321 | return std::memcmp(ZeroKey.data(), this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), | ||
| 322 | Aes128KeySize) != 0; | ||
| 323 | } | ||
| 324 | |||
| 325 | bool NcaReader::IsSoftwareAesPrioritized() const { | ||
| 326 | return m_is_software_aes_prioritized; | ||
| 327 | } | ||
| 328 | |||
| 329 | void NcaReader::PrioritizeSoftwareAes() { | ||
| 330 | m_is_software_aes_prioritized = true; | ||
| 331 | } | ||
| 332 | |||
| 333 | bool NcaReader::IsAvailableSwKey() const { | ||
| 334 | return m_is_available_sw_key; | ||
| 335 | } | ||
| 336 | |||
| 337 | bool NcaReader::HasExternalDecryptionKey() const { | ||
| 338 | return std::memcmp(ZeroKey.data(), this->GetExternalDecryptionKey(), Aes128KeySize) != 0; | ||
| 339 | } | ||
| 340 | |||
| 341 | const void* NcaReader::GetExternalDecryptionKey() const { | ||
| 342 | return m_external_decryption_key; | ||
| 343 | } | ||
| 344 | |||
| 345 | void NcaReader::SetExternalDecryptionKey(const void* src, size_t size) { | ||
| 346 | ASSERT(src != nullptr); | ||
| 347 | ASSERT(size == sizeof(m_external_decryption_key)); | ||
| 348 | |||
| 349 | std::memcpy(m_external_decryption_key, src, sizeof(m_external_decryption_key)); | ||
| 350 | } | ||
| 351 | |||
| 352 | void NcaReader::GetRawData(void* dst, size_t dst_size) const { | ||
| 353 | ASSERT(m_body_storage != nullptr); | ||
| 354 | ASSERT(dst != nullptr); | ||
| 355 | ASSERT(dst_size >= sizeof(NcaHeader)); | ||
| 356 | |||
| 357 | std::memcpy(dst, std::addressof(m_header), sizeof(NcaHeader)); | ||
| 358 | } | ||
| 359 | |||
| 360 | GetDecompressorFunction NcaReader::GetDecompressor() const { | ||
| 361 | ASSERT(m_get_decompressor != nullptr); | ||
| 362 | return m_get_decompressor; | ||
| 363 | } | ||
| 364 | |||
| 365 | NcaHeader::EncryptionType NcaReader::GetEncryptionType() const { | ||
| 366 | return m_header_encryption_type; | ||
| 367 | } | ||
| 368 | |||
| 369 | Result NcaReader::ReadHeader(NcaFsHeader* dst, s32 index) const { | ||
| 370 | ASSERT(dst != nullptr); | ||
| 371 | ASSERT(0 <= index && index < NcaHeader::FsCountMax); | ||
| 372 | |||
| 373 | const s64 offset = sizeof(NcaHeader) + sizeof(NcaFsHeader) * index; | ||
| 374 | m_header_storage->ReadObject(dst, offset); | ||
| 375 | |||
| 376 | R_SUCCEED(); | ||
| 377 | } | ||
| 378 | |||
| 379 | bool NcaReader::GetHeaderSign1Valid() const { | ||
| 380 | return m_is_header_sign1_signature_valid; | ||
| 381 | } | ||
| 382 | |||
| 383 | void NcaReader::GetHeaderSign2(void* dst, size_t size) const { | ||
| 384 | ASSERT(dst != nullptr); | ||
| 385 | ASSERT(size == NcaHeader::HeaderSignSize); | ||
| 386 | |||
| 387 | std::memcpy(dst, m_header.header_sign_2.data(), size); | ||
| 388 | } | ||
| 389 | |||
| 390 | Result NcaFsHeaderReader::Initialize(const NcaReader& reader, s32 index) { | ||
| 391 | // Reset ourselves to uninitialized. | ||
| 392 | m_fs_index = -1; | ||
| 393 | |||
| 394 | // Read the header. | ||
| 395 | R_TRY(reader.ReadHeader(std::addressof(m_data), index)); | ||
| 396 | |||
| 397 | // Set our index. | ||
| 398 | m_fs_index = index; | ||
| 399 | R_SUCCEED(); | ||
| 400 | } | ||
| 401 | |||
| 402 | void NcaFsHeaderReader::GetRawData(void* dst, size_t dst_size) const { | ||
| 403 | ASSERT(this->IsInitialized()); | ||
| 404 | ASSERT(dst != nullptr); | ||
| 405 | ASSERT(dst_size >= sizeof(NcaFsHeader)); | ||
| 406 | |||
| 407 | std::memcpy(dst, std::addressof(m_data), sizeof(NcaFsHeader)); | ||
| 408 | } | ||
| 409 | |||
| 410 | NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() { | ||
| 411 | ASSERT(this->IsInitialized()); | ||
| 412 | return m_data.hash_data; | ||
| 413 | } | ||
| 414 | |||
| 415 | const NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() const { | ||
| 416 | ASSERT(this->IsInitialized()); | ||
| 417 | return m_data.hash_data; | ||
| 418 | } | ||
| 419 | |||
| 420 | u16 NcaFsHeaderReader::GetVersion() const { | ||
| 421 | ASSERT(this->IsInitialized()); | ||
| 422 | return m_data.version; | ||
| 423 | } | ||
| 424 | |||
| 425 | s32 NcaFsHeaderReader::GetFsIndex() const { | ||
| 426 | ASSERT(this->IsInitialized()); | ||
| 427 | return m_fs_index; | ||
| 428 | } | ||
| 429 | |||
| 430 | NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const { | ||
| 431 | ASSERT(this->IsInitialized()); | ||
| 432 | return m_data.fs_type; | ||
| 433 | } | ||
| 434 | |||
| 435 | NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const { | ||
| 436 | ASSERT(this->IsInitialized()); | ||
| 437 | return m_data.hash_type; | ||
| 438 | } | ||
| 439 | |||
| 440 | NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const { | ||
| 441 | ASSERT(this->IsInitialized()); | ||
| 442 | return m_data.encryption_type; | ||
| 443 | } | ||
| 444 | |||
| 445 | NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() { | ||
| 446 | ASSERT(this->IsInitialized()); | ||
| 447 | return m_data.patch_info; | ||
| 448 | } | ||
| 449 | |||
| 450 | const NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() const { | ||
| 451 | ASSERT(this->IsInitialized()); | ||
| 452 | return m_data.patch_info; | ||
| 453 | } | ||
| 454 | |||
| 455 | const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const { | ||
| 456 | ASSERT(this->IsInitialized()); | ||
| 457 | return m_data.aes_ctr_upper_iv; | ||
| 458 | } | ||
| 459 | |||
| 460 | bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const { | ||
| 461 | ASSERT(this->IsInitialized()); | ||
| 462 | return m_data.IsSkipLayerHashEncryption(); | ||
| 463 | } | ||
| 464 | |||
| 465 | Result NcaFsHeaderReader::GetHashTargetOffset(s64* out) const { | ||
| 466 | ASSERT(out != nullptr); | ||
| 467 | ASSERT(this->IsInitialized()); | ||
| 468 | |||
| 469 | R_RETURN(m_data.GetHashTargetOffset(out)); | ||
| 470 | } | ||
| 471 | |||
| 472 | bool NcaFsHeaderReader::ExistsSparseLayer() const { | ||
| 473 | ASSERT(this->IsInitialized()); | ||
| 474 | return m_data.sparse_info.generation != 0; | ||
| 475 | } | ||
| 476 | |||
| 477 | NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() { | ||
| 478 | ASSERT(this->IsInitialized()); | ||
| 479 | return m_data.sparse_info; | ||
| 480 | } | ||
| 481 | |||
| 482 | const NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() const { | ||
| 483 | ASSERT(this->IsInitialized()); | ||
| 484 | return m_data.sparse_info; | ||
| 485 | } | ||
| 486 | |||
| 487 | bool NcaFsHeaderReader::ExistsCompressionLayer() const { | ||
| 488 | ASSERT(this->IsInitialized()); | ||
| 489 | return m_data.compression_info.bucket.offset != 0 && m_data.compression_info.bucket.size != 0; | ||
| 490 | } | ||
| 491 | |||
| 492 | NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() { | ||
| 493 | ASSERT(this->IsInitialized()); | ||
| 494 | return m_data.compression_info; | ||
| 495 | } | ||
| 496 | |||
| 497 | const NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() const { | ||
| 498 | ASSERT(this->IsInitialized()); | ||
| 499 | return m_data.compression_info; | ||
| 500 | } | ||
| 501 | |||
| 502 | bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const { | ||
| 503 | ASSERT(this->IsInitialized()); | ||
| 504 | return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable(); | ||
| 505 | } | ||
| 506 | |||
| 507 | NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() { | ||
| 508 | ASSERT(this->IsInitialized()); | ||
| 509 | return m_data.meta_data_hash_data_info; | ||
| 510 | } | ||
| 511 | |||
| 512 | const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const { | ||
| 513 | ASSERT(this->IsInitialized()); | ||
| 514 | return m_data.meta_data_hash_data_info; | ||
| 515 | } | ||
| 516 | |||
| 517 | NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const { | ||
| 518 | ASSERT(this->IsInitialized()); | ||
| 519 | return m_data.meta_data_hash_type; | ||
| 520 | } | ||
| 521 | |||
| 522 | bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const { | ||
| 523 | ASSERT(this->IsInitialized()); | ||
| 524 | return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer(); | ||
| 525 | } | ||
| 526 | |||
| 527 | NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() { | ||
| 528 | ASSERT(this->IsInitialized()); | ||
| 529 | return m_data.meta_data_hash_data_info; | ||
| 530 | } | ||
| 531 | |||
| 532 | const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const { | ||
| 533 | ASSERT(this->IsInitialized()); | ||
| 534 | return m_data.meta_data_hash_data_info; | ||
| 535 | } | ||
| 536 | |||
| 537 | NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const { | ||
| 538 | ASSERT(this->IsInitialized()); | ||
| 539 | return m_data.meta_data_hash_type; | ||
| 540 | } | ||
| 541 | |||
| 542 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp b/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp new file mode 100644 index 000000000..bbfaab255 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "core/file_sys/fssystem/fssystem_pooled_buffer.h" | ||
| 6 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | namespace { | ||
| 10 | |||
| 11 | constexpr size_t HeapBlockSize = BufferPoolAlignment; | ||
| 12 | static_assert(HeapBlockSize == 4_KiB); | ||
| 13 | |||
| 14 | // A heap block is 4KiB. An order is a power of two. | ||
| 15 | // This gives blocks of the order 32KiB, 512KiB, 4MiB. | ||
| 16 | constexpr s32 HeapOrderMax = 7; | ||
| 17 | constexpr s32 HeapOrderMaxForLarge = HeapOrderMax + 3; | ||
| 18 | |||
| 19 | constexpr size_t HeapAllocatableSizeMax = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMax); | ||
| 20 | constexpr size_t HeapAllocatableSizeMaxForLarge = | ||
| 21 | HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMaxForLarge); | ||
| 22 | |||
| 23 | } // namespace | ||
| 24 | |||
| 25 | size_t PooledBuffer::GetAllocatableSizeMaxCore(bool large) { | ||
| 26 | return large ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax; | ||
| 27 | } | ||
| 28 | |||
| 29 | void PooledBuffer::AllocateCore(size_t ideal_size, size_t required_size, bool large) { | ||
| 30 | // Ensure preconditions. | ||
| 31 | ASSERT(m_buffer == nullptr); | ||
| 32 | |||
| 33 | // Check that we can allocate this size. | ||
| 34 | ASSERT(required_size <= GetAllocatableSizeMaxCore(large)); | ||
| 35 | |||
| 36 | const size_t target_size = | ||
| 37 | std::min(std::max(ideal_size, required_size), GetAllocatableSizeMaxCore(large)); | ||
| 38 | |||
| 39 | // Dummy implementation for allocate. | ||
| 40 | if (target_size > 0) { | ||
| 41 | m_buffer = | ||
| 42 | reinterpret_cast<char*>(::operator new(target_size, std::align_val_t{HeapBlockSize})); | ||
| 43 | m_size = target_size; | ||
| 44 | |||
| 45 | // Ensure postconditions. | ||
| 46 | ASSERT(m_buffer != nullptr); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | void PooledBuffer::Shrink(size_t ideal_size) { | ||
| 51 | ASSERT(ideal_size <= GetAllocatableSizeMaxCore(true)); | ||
| 52 | |||
| 53 | // Shrinking to zero means that we have no buffer. | ||
| 54 | if (ideal_size == 0) { | ||
| 55 | ::operator delete(m_buffer, std::align_val_t{HeapBlockSize}); | ||
| 56 | m_buffer = nullptr; | ||
| 57 | m_size = ideal_size; | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_pooled_buffer.h b/src/core/file_sys/fssystem/fssystem_pooled_buffer.h new file mode 100644 index 000000000..1df3153a1 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_pooled_buffer.h | |||
| @@ -0,0 +1,96 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | #include "common/literals.h" | ||
| 9 | #include "core/hle/result.h" | ||
| 10 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | using namespace Common::Literals; | ||
| 14 | |||
| 15 | constexpr inline size_t BufferPoolAlignment = 4_KiB; | ||
| 16 | constexpr inline size_t BufferPoolWorkSize = 320; | ||
| 17 | |||
| 18 | class PooledBuffer { | ||
| 19 | YUZU_NON_COPYABLE(PooledBuffer); | ||
| 20 | |||
| 21 | private: | ||
| 22 | char* m_buffer; | ||
| 23 | size_t m_size; | ||
| 24 | |||
| 25 | private: | ||
| 26 | static size_t GetAllocatableSizeMaxCore(bool large); | ||
| 27 | |||
| 28 | public: | ||
| 29 | static size_t GetAllocatableSizeMax() { | ||
| 30 | return GetAllocatableSizeMaxCore(false); | ||
| 31 | } | ||
| 32 | static size_t GetAllocatableParticularlyLargeSizeMax() { | ||
| 33 | return GetAllocatableSizeMaxCore(true); | ||
| 34 | } | ||
| 35 | |||
| 36 | private: | ||
| 37 | void Swap(PooledBuffer& rhs) { | ||
| 38 | std::swap(m_buffer, rhs.m_buffer); | ||
| 39 | std::swap(m_size, rhs.m_size); | ||
| 40 | } | ||
| 41 | |||
| 42 | public: | ||
| 43 | // Constructor/Destructor. | ||
| 44 | constexpr PooledBuffer() : m_buffer(), m_size() {} | ||
| 45 | |||
| 46 | PooledBuffer(size_t ideal_size, size_t required_size) : m_buffer(), m_size() { | ||
| 47 | this->Allocate(ideal_size, required_size); | ||
| 48 | } | ||
| 49 | |||
| 50 | ~PooledBuffer() { | ||
| 51 | this->Deallocate(); | ||
| 52 | } | ||
| 53 | |||
| 54 | // Move and assignment. | ||
| 55 | explicit PooledBuffer(PooledBuffer&& rhs) : m_buffer(rhs.m_buffer), m_size(rhs.m_size) { | ||
| 56 | rhs.m_buffer = nullptr; | ||
| 57 | rhs.m_size = 0; | ||
| 58 | } | ||
| 59 | |||
| 60 | PooledBuffer& operator=(PooledBuffer&& rhs) { | ||
| 61 | PooledBuffer(std::move(rhs)).Swap(*this); | ||
| 62 | return *this; | ||
| 63 | } | ||
| 64 | |||
| 65 | // Allocation API. | ||
| 66 | void Allocate(size_t ideal_size, size_t required_size) { | ||
| 67 | return this->AllocateCore(ideal_size, required_size, false); | ||
| 68 | } | ||
| 69 | |||
| 70 | void AllocateParticularlyLarge(size_t ideal_size, size_t required_size) { | ||
| 71 | return this->AllocateCore(ideal_size, required_size, true); | ||
| 72 | } | ||
| 73 | |||
| 74 | void Shrink(size_t ideal_size); | ||
| 75 | |||
| 76 | void Deallocate() { | ||
| 77 | // Shrink the buffer to empty. | ||
| 78 | this->Shrink(0); | ||
| 79 | ASSERT(m_buffer == nullptr); | ||
| 80 | } | ||
| 81 | |||
| 82 | char* GetBuffer() const { | ||
| 83 | ASSERT(m_buffer != nullptr); | ||
| 84 | return m_buffer; | ||
| 85 | } | ||
| 86 | |||
| 87 | size_t GetSize() const { | ||
| 88 | ASSERT(m_buffer != nullptr); | ||
| 89 | return m_size; | ||
| 90 | } | ||
| 91 | |||
| 92 | private: | ||
| 93 | void AllocateCore(size_t ideal_size, size_t required_size, bool large); | ||
| 94 | }; | ||
| 95 | |||
| 96 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp b/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp new file mode 100644 index 000000000..05e8820f7 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/file_sys/fssystem/fssystem_sparse_storage.h" | ||
| 5 | |||
| 6 | namespace FileSys { | ||
| 7 | |||
| 8 | size_t SparseStorage::Read(u8* buffer, size_t size, size_t offset) const { | ||
| 9 | // Validate preconditions. | ||
| 10 | ASSERT(offset >= 0); | ||
| 11 | ASSERT(this->IsInitialized()); | ||
| 12 | ASSERT(buffer != nullptr); | ||
| 13 | |||
| 14 | // Allow zero size. | ||
| 15 | if (size == 0) { | ||
| 16 | return size; | ||
| 17 | } | ||
| 18 | |||
| 19 | SparseStorage* self = const_cast<SparseStorage*>(this); | ||
| 20 | |||
| 21 | if (self->GetEntryTable().IsEmpty()) { | ||
| 22 | BucketTree::Offsets table_offsets; | ||
| 23 | ASSERT(R_SUCCEEDED(self->GetEntryTable().GetOffsets(std::addressof(table_offsets)))); | ||
| 24 | ASSERT(table_offsets.IsInclude(offset, size)); | ||
| 25 | |||
| 26 | std::memset(buffer, 0, size); | ||
| 27 | } else { | ||
| 28 | self->OperatePerEntry<false, true>( | ||
| 29 | offset, size, | ||
| 30 | [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { | ||
| 31 | storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset), | ||
| 32 | static_cast<size_t>(cur_size), data_offset); | ||
| 33 | R_SUCCEED(); | ||
| 34 | }); | ||
| 35 | } | ||
| 36 | |||
| 37 | return size; | ||
| 38 | } | ||
| 39 | |||
| 40 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_sparse_storage.h b/src/core/file_sys/fssystem/fssystem_sparse_storage.h new file mode 100644 index 000000000..c1ade7195 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_sparse_storage.h | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fssystem_indirect_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | class SparseStorage : public IndirectStorage { | ||
| 11 | YUZU_NON_COPYABLE(SparseStorage); | ||
| 12 | YUZU_NON_MOVEABLE(SparseStorage); | ||
| 13 | |||
| 14 | private: | ||
| 15 | class ZeroStorage : public IReadOnlyStorage { | ||
| 16 | public: | ||
| 17 | ZeroStorage() {} | ||
| 18 | virtual ~ZeroStorage() {} | ||
| 19 | |||
| 20 | virtual size_t GetSize() const override { | ||
| 21 | return std::numeric_limits<size_t>::max(); | ||
| 22 | } | ||
| 23 | |||
| 24 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 25 | ASSERT(offset >= 0); | ||
| 26 | ASSERT(buffer != nullptr || size == 0); | ||
| 27 | |||
| 28 | if (size > 0) { | ||
| 29 | std::memset(buffer, 0, size); | ||
| 30 | } | ||
| 31 | |||
| 32 | return size; | ||
| 33 | } | ||
| 34 | }; | ||
| 35 | |||
| 36 | private: | ||
| 37 | VirtualFile m_zero_storage; | ||
| 38 | |||
| 39 | public: | ||
| 40 | SparseStorage() : IndirectStorage(), m_zero_storage(std::make_shared<ZeroStorage>()) {} | ||
| 41 | virtual ~SparseStorage() {} | ||
| 42 | |||
| 43 | using IndirectStorage::Initialize; | ||
| 44 | |||
| 45 | void Initialize(s64 end_offset) { | ||
| 46 | this->GetEntryTable().Initialize(NodeSize, end_offset); | ||
| 47 | this->SetZeroStorage(); | ||
| 48 | } | ||
| 49 | |||
| 50 | void SetDataStorage(VirtualFile storage) { | ||
| 51 | ASSERT(this->IsInitialized()); | ||
| 52 | |||
| 53 | this->SetStorage(0, storage); | ||
| 54 | this->SetZeroStorage(); | ||
| 55 | } | ||
| 56 | |||
| 57 | template <typename T> | ||
| 58 | void SetDataStorage(T storage, s64 offset, s64 size) { | ||
| 59 | ASSERT(this->IsInitialized()); | ||
| 60 | |||
| 61 | this->SetStorage(0, storage, offset, size); | ||
| 62 | this->SetZeroStorage(); | ||
| 63 | } | ||
| 64 | |||
| 65 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; | ||
| 66 | |||
| 67 | private: | ||
| 68 | void SetZeroStorage() { | ||
| 69 | return this->SetStorage(1, m_zero_storage, 0, std::numeric_limits<s64>::max()); | ||
| 70 | } | ||
| 71 | }; | ||
| 72 | |||
| 73 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_switch_storage.h b/src/core/file_sys/fssystem/fssystem_switch_storage.h new file mode 100644 index 000000000..140f21ab7 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_switch_storage.h | |||
| @@ -0,0 +1,80 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "core/file_sys/fssystem/fs_i_storage.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | class RegionSwitchStorage : public IReadOnlyStorage { | ||
| 11 | YUZU_NON_COPYABLE(RegionSwitchStorage); | ||
| 12 | YUZU_NON_MOVEABLE(RegionSwitchStorage); | ||
| 13 | |||
| 14 | public: | ||
| 15 | struct Region { | ||
| 16 | s64 offset; | ||
| 17 | s64 size; | ||
| 18 | }; | ||
| 19 | |||
| 20 | private: | ||
| 21 | VirtualFile m_inside_region_storage; | ||
| 22 | VirtualFile m_outside_region_storage; | ||
| 23 | Region m_region; | ||
| 24 | |||
| 25 | public: | ||
| 26 | RegionSwitchStorage(VirtualFile&& i, VirtualFile&& o, Region r) | ||
| 27 | : m_inside_region_storage(std::move(i)), m_outside_region_storage(std::move(o)), | ||
| 28 | m_region(r) {} | ||
| 29 | |||
| 30 | virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { | ||
| 31 | // Process until we're done. | ||
| 32 | size_t processed = 0; | ||
| 33 | while (processed < size) { | ||
| 34 | // Process on the appropriate storage. | ||
| 35 | s64 cur_size = 0; | ||
| 36 | if (this->CheckRegions(std::addressof(cur_size), offset + processed, | ||
| 37 | size - processed)) { | ||
| 38 | m_inside_region_storage->Read(buffer + processed, cur_size, offset + processed); | ||
| 39 | } else { | ||
| 40 | m_outside_region_storage->Read(buffer + processed, cur_size, offset + processed); | ||
| 41 | } | ||
| 42 | |||
| 43 | // Advance. | ||
| 44 | processed += cur_size; | ||
| 45 | } | ||
| 46 | |||
| 47 | return size; | ||
| 48 | } | ||
| 49 | |||
| 50 | virtual size_t GetSize() const override { | ||
| 51 | return m_inside_region_storage->GetSize(); | ||
| 52 | } | ||
| 53 | |||
| 54 | private: | ||
| 55 | bool CheckRegions(s64* out_current_size, s64 offset, s64 size) const { | ||
| 56 | // Check if our region contains the access. | ||
| 57 | if (m_region.offset <= offset) { | ||
| 58 | if (offset < m_region.offset + m_region.size) { | ||
| 59 | if (m_region.offset + m_region.size <= offset + size) { | ||
| 60 | *out_current_size = m_region.offset + m_region.size - offset; | ||
| 61 | } else { | ||
| 62 | *out_current_size = size; | ||
| 63 | } | ||
| 64 | return true; | ||
| 65 | } else { | ||
| 66 | *out_current_size = size; | ||
| 67 | return false; | ||
| 68 | } | ||
| 69 | } else { | ||
| 70 | if (m_region.offset <= offset + size) { | ||
| 71 | *out_current_size = m_region.offset - offset; | ||
| 72 | } else { | ||
| 73 | *out_current_size = size; | ||
| 74 | } | ||
| 75 | return false; | ||
| 76 | } | ||
| 77 | } | ||
| 78 | }; | ||
| 79 | |||
| 80 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_utility.cpp b/src/core/file_sys/fssystem/fssystem_utility.cpp new file mode 100644 index 000000000..4dddfd75a --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_utility.cpp | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | #include "core/file_sys/fssystem/fssystem_utility.h" | ||
| 2 | |||
| 3 | namespace FileSys { | ||
| 4 | |||
| 5 | void AddCounter(void* counter_, size_t counter_size, u64 value) { | ||
| 6 | u8* counter = static_cast<u8*>(counter_); | ||
| 7 | u64 remaining = value; | ||
| 8 | u8 carry = 0; | ||
| 9 | |||
| 10 | for (size_t i = 0; i < counter_size; i++) { | ||
| 11 | auto sum = counter[counter_size - 1 - i] + (remaining & 0xFF) + carry; | ||
| 12 | carry = static_cast<u8>(sum >> (sizeof(u8) * 8)); | ||
| 13 | auto sum8 = static_cast<u8>(sum & 0xFF); | ||
| 14 | |||
| 15 | counter[counter_size - 1 - i] = sum8; | ||
| 16 | |||
| 17 | remaining >>= (sizeof(u8) * 8); | ||
| 18 | if (carry == 0 && remaining == 0) { | ||
| 19 | break; | ||
| 20 | } | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 24 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/fssystem/fssystem_utility.h b/src/core/file_sys/fssystem/fssystem_utility.h new file mode 100644 index 000000000..284b8b811 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_utility.h | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_funcs.h" | ||
| 7 | |||
| 8 | namespace FileSys { | ||
| 9 | |||
| 10 | void AddCounter(void* counter, size_t counter_size, u64 value); | ||
| 11 | |||
| 12 | } | ||
diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp deleted file mode 100644 index 2735d053b..000000000 --- a/src/core/file_sys/nca_patch.cpp +++ /dev/null | |||
| @@ -1,217 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <array> | ||
| 6 | #include <cstddef> | ||
| 7 | #include <cstring> | ||
| 8 | |||
| 9 | #include "common/assert.h" | ||
| 10 | #include "core/crypto/aes_util.h" | ||
| 11 | #include "core/file_sys/nca_patch.h" | ||
| 12 | |||
| 13 | namespace FileSys { | ||
| 14 | namespace { | ||
| 15 | template <bool Subsection, typename BlockType, typename BucketType> | ||
| 16 | std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockType& block, | ||
| 17 | const BucketType& buckets) { | ||
| 18 | if constexpr (Subsection) { | ||
| 19 | const auto& last_bucket = buckets[block.number_buckets - 1]; | ||
| 20 | if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) { | ||
| 21 | return {block.number_buckets - 1, last_bucket.number_entries}; | ||
| 22 | } | ||
| 23 | } else { | ||
| 24 | ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block."); | ||
| 25 | } | ||
| 26 | |||
| 27 | std::size_t bucket_id = std::count_if( | ||
| 28 | block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets, | ||
| 29 | [&offset](u64 base_offset) { return base_offset <= offset; }); | ||
| 30 | |||
| 31 | const auto& bucket = buckets[bucket_id]; | ||
| 32 | |||
| 33 | if (bucket.number_entries == 1) { | ||
| 34 | return {bucket_id, 0}; | ||
| 35 | } | ||
| 36 | |||
| 37 | std::size_t low = 0; | ||
| 38 | std::size_t mid = 0; | ||
| 39 | std::size_t high = bucket.number_entries - 1; | ||
| 40 | while (low <= high) { | ||
| 41 | mid = (low + high) / 2; | ||
| 42 | if (bucket.entries[mid].address_patch > offset) { | ||
| 43 | high = mid - 1; | ||
| 44 | } else { | ||
| 45 | if (mid == bucket.number_entries - 1 || | ||
| 46 | bucket.entries[mid + 1].address_patch > offset) { | ||
| 47 | return {bucket_id, mid}; | ||
| 48 | } | ||
| 49 | |||
| 50 | low = mid + 1; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | ASSERT_MSG(false, "Offset could not be found in BKTR block."); | ||
| 54 | return {0, 0}; | ||
| 55 | } | ||
| 56 | } // Anonymous namespace | ||
| 57 | |||
| 58 | BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_, | ||
| 59 | std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_, | ||
| 60 | std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_, | ||
| 61 | Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_, | ||
| 62 | std::array<u8, 8> section_ctr_) | ||
| 63 | : relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)), | ||
| 64 | subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)), | ||
| 65 | base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)), | ||
| 66 | encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_), | ||
| 67 | section_ctr(section_ctr_) { | ||
| 68 | for (std::size_t i = 0; i < relocation.number_buckets - 1; ++i) { | ||
| 69 | relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0}); | ||
| 70 | } | ||
| 71 | |||
| 72 | for (std::size_t i = 0; i < subsection.number_buckets - 1; ++i) { | ||
| 73 | subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch, | ||
| 74 | {0}, | ||
| 75 | subsection_buckets[i + 1].entries[0].ctr}); | ||
| 76 | } | ||
| 77 | |||
| 78 | relocation_buckets.back().entries.push_back({relocation.size, 0, 0}); | ||
| 79 | } | ||
| 80 | |||
| 81 | BKTR::~BKTR() = default; | ||
| 82 | |||
| 83 | std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const { | ||
| 84 | // Read out of bounds. | ||
| 85 | if (offset >= relocation.size) { | ||
| 86 | return 0; | ||
| 87 | } | ||
| 88 | |||
| 89 | const auto relocation_entry = GetRelocationEntry(offset); | ||
| 90 | const auto section_offset = | ||
| 91 | offset - relocation_entry.address_patch + relocation_entry.address_source; | ||
| 92 | const auto bktr_read = relocation_entry.from_patch; | ||
| 93 | |||
| 94 | const auto next_relocation = GetNextRelocationEntry(offset); | ||
| 95 | |||
| 96 | if (offset + length > next_relocation.address_patch) { | ||
| 97 | const u64 partition = next_relocation.address_patch - offset; | ||
| 98 | return Read(data, partition, offset) + | ||
| 99 | Read(data + partition, length - partition, offset + partition); | ||
| 100 | } | ||
| 101 | |||
| 102 | if (!bktr_read) { | ||
| 103 | ASSERT_MSG(section_offset >= ivfc_offset, "Offset calculation negative."); | ||
| 104 | return base_romfs->Read(data, length, section_offset - ivfc_offset); | ||
| 105 | } | ||
| 106 | |||
| 107 | if (!encrypted) { | ||
| 108 | return bktr_romfs->Read(data, length, section_offset); | ||
| 109 | } | ||
| 110 | |||
| 111 | const auto subsection_entry = GetSubsectionEntry(section_offset); | ||
| 112 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR); | ||
| 113 | |||
| 114 | // Calculate AES IV | ||
| 115 | std::array<u8, 16> iv{}; | ||
| 116 | auto subsection_ctr = subsection_entry.ctr; | ||
| 117 | auto offset_iv = section_offset + base_offset; | ||
| 118 | for (std::size_t i = 0; i < section_ctr.size(); ++i) { | ||
| 119 | iv[i] = section_ctr[0x8 - i - 1]; | ||
| 120 | } | ||
| 121 | offset_iv >>= 4; | ||
| 122 | for (std::size_t i = 0; i < sizeof(u64); ++i) { | ||
| 123 | iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF); | ||
| 124 | offset_iv >>= 8; | ||
| 125 | } | ||
| 126 | for (std::size_t i = 0; i < sizeof(u32); ++i) { | ||
| 127 | iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF); | ||
| 128 | subsection_ctr >>= 8; | ||
| 129 | } | ||
| 130 | cipher.SetIV(iv); | ||
| 131 | |||
| 132 | const auto next_subsection = GetNextSubsectionEntry(section_offset); | ||
| 133 | |||
| 134 | if (section_offset + length > next_subsection.address_patch) { | ||
| 135 | const u64 partition = next_subsection.address_patch - section_offset; | ||
| 136 | return Read(data, partition, offset) + | ||
| 137 | Read(data + partition, length - partition, offset + partition); | ||
| 138 | } | ||
| 139 | |||
| 140 | const auto block_offset = section_offset & 0xF; | ||
| 141 | if (block_offset != 0) { | ||
| 142 | auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF); | ||
| 143 | cipher.Transcode(block.data(), block.size(), block.data(), Core::Crypto::Op::Decrypt); | ||
| 144 | if (length + block_offset < 0x10) { | ||
| 145 | std::memcpy(data, block.data() + block_offset, std::min(length, block.size())); | ||
| 146 | return std::min(length, block.size()); | ||
| 147 | } | ||
| 148 | |||
| 149 | const auto read = 0x10 - block_offset; | ||
| 150 | std::memcpy(data, block.data() + block_offset, read); | ||
| 151 | return read + Read(data + read, length - read, offset + read); | ||
| 152 | } | ||
| 153 | |||
| 154 | const auto raw_read = bktr_romfs->Read(data, length, section_offset); | ||
| 155 | cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt); | ||
| 156 | return raw_read; | ||
| 157 | } | ||
| 158 | |||
| 159 | RelocationEntry BKTR::GetRelocationEntry(u64 offset) const { | ||
| 160 | const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); | ||
| 161 | return relocation_buckets[res.first].entries[res.second]; | ||
| 162 | } | ||
| 163 | |||
| 164 | RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const { | ||
| 165 | const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); | ||
| 166 | const auto bucket = relocation_buckets[res.first]; | ||
| 167 | if (res.second + 1 < bucket.entries.size()) | ||
| 168 | return bucket.entries[res.second + 1]; | ||
| 169 | return relocation_buckets[res.first + 1].entries[0]; | ||
| 170 | } | ||
| 171 | |||
| 172 | SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const { | ||
| 173 | const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); | ||
| 174 | return subsection_buckets[res.first].entries[res.second]; | ||
| 175 | } | ||
| 176 | |||
| 177 | SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const { | ||
| 178 | const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); | ||
| 179 | const auto bucket = subsection_buckets[res.first]; | ||
| 180 | if (res.second + 1 < bucket.entries.size()) | ||
| 181 | return bucket.entries[res.second + 1]; | ||
| 182 | return subsection_buckets[res.first + 1].entries[0]; | ||
| 183 | } | ||
| 184 | |||
| 185 | std::string BKTR::GetName() const { | ||
| 186 | return base_romfs->GetName(); | ||
| 187 | } | ||
| 188 | |||
| 189 | std::size_t BKTR::GetSize() const { | ||
| 190 | return relocation.size; | ||
| 191 | } | ||
| 192 | |||
| 193 | bool BKTR::Resize(std::size_t new_size) { | ||
| 194 | return false; | ||
| 195 | } | ||
| 196 | |||
| 197 | VirtualDir BKTR::GetContainingDirectory() const { | ||
| 198 | return base_romfs->GetContainingDirectory(); | ||
| 199 | } | ||
| 200 | |||
| 201 | bool BKTR::IsWritable() const { | ||
| 202 | return false; | ||
| 203 | } | ||
| 204 | |||
| 205 | bool BKTR::IsReadable() const { | ||
| 206 | return true; | ||
| 207 | } | ||
| 208 | |||
| 209 | std::size_t BKTR::Write(const u8* data, std::size_t length, std::size_t offset) { | ||
| 210 | return 0; | ||
| 211 | } | ||
| 212 | |||
| 213 | bool BKTR::Rename(std::string_view name) { | ||
| 214 | return base_romfs->Rename(name); | ||
| 215 | } | ||
| 216 | |||
| 217 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h deleted file mode 100644 index 595e3ef09..000000000 --- a/src/core/file_sys/nca_patch.h +++ /dev/null | |||
| @@ -1,145 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <memory> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "common/common_funcs.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/swap.h" | ||
| 13 | #include "core/crypto/key_manager.h" | ||
| 14 | |||
| 15 | namespace FileSys { | ||
| 16 | |||
| 17 | #pragma pack(push, 1) | ||
| 18 | struct RelocationEntry { | ||
| 19 | u64_le address_patch; | ||
| 20 | u64_le address_source; | ||
| 21 | u32 from_patch; | ||
| 22 | }; | ||
| 23 | #pragma pack(pop) | ||
| 24 | static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size."); | ||
| 25 | |||
| 26 | struct RelocationBucketRaw { | ||
| 27 | INSERT_PADDING_BYTES(4); | ||
| 28 | u32_le number_entries; | ||
| 29 | u64_le end_offset; | ||
| 30 | std::array<RelocationEntry, 0x332> relocation_entries; | ||
| 31 | INSERT_PADDING_BYTES(8); | ||
| 32 | }; | ||
| 33 | static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size."); | ||
| 34 | |||
| 35 | // Vector version of RelocationBucketRaw | ||
| 36 | struct RelocationBucket { | ||
| 37 | u32 number_entries; | ||
| 38 | u64 end_offset; | ||
| 39 | std::vector<RelocationEntry> entries; | ||
| 40 | }; | ||
| 41 | |||
| 42 | struct RelocationBlock { | ||
| 43 | INSERT_PADDING_BYTES(4); | ||
| 44 | u32_le number_buckets; | ||
| 45 | u64_le size; | ||
| 46 | std::array<u64, 0x7FE> base_offsets; | ||
| 47 | }; | ||
| 48 | static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size."); | ||
| 49 | |||
| 50 | struct SubsectionEntry { | ||
| 51 | u64_le address_patch; | ||
| 52 | INSERT_PADDING_BYTES(0x4); | ||
| 53 | u32_le ctr; | ||
| 54 | }; | ||
| 55 | static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size."); | ||
| 56 | |||
| 57 | struct SubsectionBucketRaw { | ||
| 58 | INSERT_PADDING_BYTES(4); | ||
| 59 | u32_le number_entries; | ||
| 60 | u64_le end_offset; | ||
| 61 | std::array<SubsectionEntry, 0x3FF> subsection_entries; | ||
| 62 | }; | ||
| 63 | static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size."); | ||
| 64 | |||
| 65 | // Vector version of SubsectionBucketRaw | ||
| 66 | struct SubsectionBucket { | ||
| 67 | u32 number_entries; | ||
| 68 | u64 end_offset; | ||
| 69 | std::vector<SubsectionEntry> entries; | ||
| 70 | }; | ||
| 71 | |||
| 72 | struct SubsectionBlock { | ||
| 73 | INSERT_PADDING_BYTES(4); | ||
| 74 | u32_le number_buckets; | ||
| 75 | u64_le size; | ||
| 76 | std::array<u64, 0x7FE> base_offsets; | ||
| 77 | }; | ||
| 78 | static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size."); | ||
| 79 | |||
| 80 | inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) { | ||
| 81 | return {raw.number_entries, | ||
| 82 | raw.end_offset, | ||
| 83 | {raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}}; | ||
| 84 | } | ||
| 85 | |||
| 86 | inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) { | ||
| 87 | return {raw.number_entries, | ||
| 88 | raw.end_offset, | ||
| 89 | {raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}}; | ||
| 90 | } | ||
| 91 | |||
| 92 | class BKTR : public VfsFile { | ||
| 93 | public: | ||
| 94 | BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation, | ||
| 95 | std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection, | ||
| 96 | std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted, | ||
| 97 | Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr); | ||
| 98 | ~BKTR() override; | ||
| 99 | |||
| 100 | std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; | ||
| 101 | |||
| 102 | std::string GetName() const override; | ||
| 103 | |||
| 104 | std::size_t GetSize() const override; | ||
| 105 | |||
| 106 | bool Resize(std::size_t new_size) override; | ||
| 107 | |||
| 108 | VirtualDir GetContainingDirectory() const override; | ||
| 109 | |||
| 110 | bool IsWritable() const override; | ||
| 111 | |||
| 112 | bool IsReadable() const override; | ||
| 113 | |||
| 114 | std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; | ||
| 115 | |||
| 116 | bool Rename(std::string_view name) override; | ||
| 117 | |||
| 118 | private: | ||
| 119 | RelocationEntry GetRelocationEntry(u64 offset) const; | ||
| 120 | RelocationEntry GetNextRelocationEntry(u64 offset) const; | ||
| 121 | |||
| 122 | SubsectionEntry GetSubsectionEntry(u64 offset) const; | ||
| 123 | SubsectionEntry GetNextSubsectionEntry(u64 offset) const; | ||
| 124 | |||
| 125 | RelocationBlock relocation; | ||
| 126 | std::vector<RelocationBucket> relocation_buckets; | ||
| 127 | SubsectionBlock subsection; | ||
| 128 | std::vector<SubsectionBucket> subsection_buckets; | ||
| 129 | |||
| 130 | // Should be the raw base romfs, decrypted. | ||
| 131 | VirtualFile base_romfs; | ||
| 132 | // Should be the raw BKTR romfs, (located at media_offset with size media_size). | ||
| 133 | VirtualFile bktr_romfs; | ||
| 134 | |||
| 135 | bool encrypted; | ||
| 136 | Core::Crypto::Key128 key; | ||
| 137 | |||
| 138 | // Base offset into NCA, used for IV calculation. | ||
| 139 | u64 base_offset; | ||
| 140 | // Distance between IVFC start and RomFS start, used for base reads | ||
| 141 | u64 ivfc_offset; | ||
| 142 | std::array<u8, 8> section_ctr; | ||
| 143 | }; | ||
| 144 | |||
| 145 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 2ba1b34a4..0701e3f0e 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp | |||
| @@ -141,8 +141,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { | |||
| 141 | const auto update_tid = GetUpdateTitleID(title_id); | 141 | const auto update_tid = GetUpdateTitleID(title_id); |
| 142 | const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program); | 142 | const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program); |
| 143 | 143 | ||
| 144 | if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr && | 144 | if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) { |
| 145 | update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { | ||
| 146 | LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", | 145 | LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", |
| 147 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); | 146 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); |
| 148 | exefs = update->GetExeFS(); | 147 | exefs = update->GetExeFS(); |
| @@ -358,11 +357,6 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 358 | return; | 357 | return; |
| 359 | } | 358 | } |
| 360 | 359 | ||
| 361 | auto extracted = ExtractRomFS(romfs); | ||
| 362 | if (extracted == nullptr) { | ||
| 363 | return; | ||
| 364 | } | ||
| 365 | |||
| 366 | const auto& disabled = Settings::values.disabled_addons[title_id]; | 360 | const auto& disabled = Settings::values.disabled_addons[title_id]; |
| 367 | std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories(); | 361 | std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories(); |
| 368 | if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { | 362 | if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { |
| @@ -394,6 +388,11 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 394 | return; | 388 | return; |
| 395 | } | 389 | } |
| 396 | 390 | ||
| 391 | auto extracted = ExtractRomFS(romfs); | ||
| 392 | if (extracted == nullptr) { | ||
| 393 | return; | ||
| 394 | } | ||
| 395 | |||
| 397 | layers.push_back(std::move(extracted)); | 396 | layers.push_back(std::move(extracted)); |
| 398 | 397 | ||
| 399 | auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); | 398 | auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); |
| @@ -412,39 +411,43 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t | |||
| 412 | romfs = std::move(packed); | 411 | romfs = std::move(packed); |
| 413 | } | 412 | } |
| 414 | 413 | ||
| 415 | VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, | 414 | VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs, |
| 416 | VirtualFile update_raw, bool apply_layeredfs) const { | 415 | ContentRecordType type, VirtualFile packed_update_raw, |
| 416 | bool apply_layeredfs) const { | ||
| 417 | const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", | 417 | const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", |
| 418 | title_id, static_cast<u8>(type)); | 418 | title_id, static_cast<u8>(type)); |
| 419 | |||
| 420 | if (type == ContentRecordType::Program || type == ContentRecordType::Data) { | 419 | if (type == ContentRecordType::Program || type == ContentRecordType::Data) { |
| 421 | LOG_INFO(Loader, "{}", log_string); | 420 | LOG_INFO(Loader, "{}", log_string); |
| 422 | } else { | 421 | } else { |
| 423 | LOG_DEBUG(Loader, "{}", log_string); | 422 | LOG_DEBUG(Loader, "{}", log_string); |
| 424 | } | 423 | } |
| 425 | 424 | ||
| 426 | if (romfs == nullptr) { | 425 | if (base_romfs == nullptr) { |
| 427 | return romfs; | 426 | return base_romfs; |
| 428 | } | 427 | } |
| 429 | 428 | ||
| 429 | auto romfs = base_romfs; | ||
| 430 | |||
| 430 | // Game Updates | 431 | // Game Updates |
| 431 | const auto update_tid = GetUpdateTitleID(title_id); | 432 | const auto update_tid = GetUpdateTitleID(title_id); |
| 432 | const auto update = content_provider.GetEntryRaw(update_tid, type); | 433 | const auto update_raw = content_provider.GetEntryRaw(update_tid, type); |
| 433 | 434 | ||
| 434 | const auto& disabled = Settings::values.disabled_addons[title_id]; | 435 | const auto& disabled = Settings::values.disabled_addons[title_id]; |
| 435 | const auto update_disabled = | 436 | const auto update_disabled = |
| 436 | std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); | 437 | std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); |
| 437 | 438 | ||
| 438 | if (!update_disabled && update != nullptr) { | 439 | if (!update_disabled && update_raw != nullptr && base_nca != nullptr) { |
| 439 | const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset); | 440 | const auto new_nca = std::make_shared<NCA>(update_raw, base_nca); |
| 440 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && | 441 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && |
| 441 | new_nca->GetRomFS() != nullptr) { | 442 | new_nca->GetRomFS() != nullptr) { |
| 442 | LOG_INFO(Loader, " RomFS: Update ({}) applied successfully", | 443 | LOG_INFO(Loader, " RomFS: Update ({}) applied successfully", |
| 443 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); | 444 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); |
| 444 | romfs = new_nca->GetRomFS(); | 445 | romfs = new_nca->GetRomFS(); |
| 446 | const auto version = | ||
| 447 | FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)); | ||
| 445 | } | 448 | } |
| 446 | } else if (!update_disabled && update_raw != nullptr) { | 449 | } else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) { |
| 447 | const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset); | 450 | const auto new_nca = std::make_shared<NCA>(packed_update_raw, base_nca); |
| 448 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && | 451 | if (new_nca->GetStatus() == Loader::ResultStatus::Success && |
| 449 | new_nca->GetRomFS() != nullptr) { | 452 | new_nca->GetRomFS() != nullptr) { |
| 450 | LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully"); | 453 | LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully"); |
| @@ -608,7 +611,7 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const { | |||
| 608 | return {}; | 611 | return {}; |
| 609 | } | 612 | } |
| 610 | 613 | ||
| 611 | const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control); | 614 | const auto romfs = PatchRomFS(&nca, base_romfs, ContentRecordType::Control); |
| 612 | if (romfs == nullptr) { | 615 | if (romfs == nullptr) { |
| 613 | return {}; | 616 | return {}; |
| 614 | } | 617 | } |
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 69d15e2f8..adcde7b7d 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h | |||
| @@ -61,9 +61,9 @@ public: | |||
| 61 | // Currently tracked RomFS patches: | 61 | // Currently tracked RomFS patches: |
| 62 | // - Game Updates | 62 | // - Game Updates |
| 63 | // - LayeredFS | 63 | // - LayeredFS |
| 64 | [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, | 64 | [[nodiscard]] VirtualFile PatchRomFS(const NCA* base_nca, VirtualFile base_romfs, |
| 65 | ContentRecordType type = ContentRecordType::Program, | 65 | ContentRecordType type = ContentRecordType::Program, |
| 66 | VirtualFile update_raw = nullptr, | 66 | VirtualFile packed_update_raw = nullptr, |
| 67 | bool apply_layeredfs = true) const; | 67 | bool apply_layeredfs = true) const; |
| 68 | 68 | ||
| 69 | // Returns a vector of pairs between patch names and patch versions. | 69 | // Returns a vector of pairs between patch names and patch versions. |
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index a6960170c..e4218523a 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp | |||
| @@ -416,7 +416,7 @@ void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) { | |||
| 416 | 416 | ||
| 417 | if (file == nullptr) | 417 | if (file == nullptr) |
| 418 | continue; | 418 | continue; |
| 419 | const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0); | 419 | const auto nca = std::make_shared<NCA>(parser(file, id)); |
| 420 | if (nca->GetStatus() != Loader::ResultStatus::Success || | 420 | if (nca->GetStatus() != Loader::ResultStatus::Success || |
| 421 | nca->GetType() != NCAContentType::Meta) { | 421 | nca->GetType() != NCAContentType::Meta) { |
| 422 | continue; | 422 | continue; |
| @@ -500,7 +500,7 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t | |||
| 500 | const auto raw = GetEntryRaw(title_id, type); | 500 | const auto raw = GetEntryRaw(title_id, type); |
| 501 | if (raw == nullptr) | 501 | if (raw == nullptr) |
| 502 | return nullptr; | 502 | return nullptr; |
| 503 | return std::make_unique<NCA>(raw, nullptr, 0); | 503 | return std::make_unique<NCA>(raw); |
| 504 | } | 504 | } |
| 505 | 505 | ||
| 506 | template <typename T> | 506 | template <typename T> |
| @@ -964,7 +964,7 @@ std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecord | |||
| 964 | const auto res = GetEntryRaw(title_id, type); | 964 | const auto res = GetEntryRaw(title_id, type); |
| 965 | if (res == nullptr) | 965 | if (res == nullptr) |
| 966 | return nullptr; | 966 | return nullptr; |
| 967 | return std::make_unique<NCA>(res, nullptr, 0); | 967 | return std::make_unique<NCA>(res); |
| 968 | } | 968 | } |
| 969 | 969 | ||
| 970 | std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( | 970 | std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( |
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index aa4726cfa..1bc07dae5 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp | |||
| @@ -26,13 +26,12 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provi | |||
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | updatable = app_loader.IsRomFSUpdatable(); | 28 | updatable = app_loader.IsRomFSUpdatable(); |
| 29 | ivfc_offset = app_loader.ReadRomFSIVFCOffset(); | ||
| 30 | } | 29 | } |
| 31 | 30 | ||
| 32 | RomFSFactory::~RomFSFactory() = default; | 31 | RomFSFactory::~RomFSFactory() = default; |
| 33 | 32 | ||
| 34 | void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) { | 33 | void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) { |
| 35 | update_raw = std::move(update_raw_file); | 34 | packed_update_raw = std::move(update_raw_file); |
| 36 | } | 35 | } |
| 37 | 36 | ||
| 38 | VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { | 37 | VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { |
| @@ -40,9 +39,11 @@ VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const | |||
| 40 | return file; | 39 | return file; |
| 41 | } | 40 | } |
| 42 | 41 | ||
| 42 | const auto type = ContentRecordType::Program; | ||
| 43 | const auto nca = content_provider.GetEntry(current_process_title_id, type); | ||
| 43 | const PatchManager patch_manager{current_process_title_id, filesystem_controller, | 44 | const PatchManager patch_manager{current_process_title_id, filesystem_controller, |
| 44 | content_provider}; | 45 | content_provider}; |
| 45 | return patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw); | 46 | return patch_manager.PatchRomFS(nca.get(), file, ContentRecordType::Program, packed_update_raw); |
| 46 | } | 47 | } |
| 47 | 48 | ||
| 48 | VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { | 49 | VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { |
| @@ -54,7 +55,7 @@ VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) | |||
| 54 | 55 | ||
| 55 | const PatchManager patch_manager{title_id, filesystem_controller, content_provider}; | 56 | const PatchManager patch_manager{title_id, filesystem_controller, content_provider}; |
| 56 | 57 | ||
| 57 | return patch_manager.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), type); | 58 | return patch_manager.PatchRomFS(nca.get(), nca->GetRomFS(), type); |
| 58 | } | 59 | } |
| 59 | 60 | ||
| 60 | VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, | 61 | VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, |
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index 7ec40d19d..e4809bc94 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h | |||
| @@ -40,21 +40,22 @@ public: | |||
| 40 | Service::FileSystem::FileSystemController& controller); | 40 | Service::FileSystem::FileSystemController& controller); |
| 41 | ~RomFSFactory(); | 41 | ~RomFSFactory(); |
| 42 | 42 | ||
| 43 | void SetPackedUpdate(VirtualFile update_raw_file); | 43 | void SetPackedUpdate(VirtualFile packed_update_raw); |
| 44 | [[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const; | 44 | [[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const; |
| 45 | [[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const; | 45 | [[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const; |
| 46 | [[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, | 46 | [[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, |
| 47 | ContentRecordType type) const; | 47 | ContentRecordType type) const; |
| 48 | [[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const; | 48 | [[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const; |
| 49 | |||
| 50 | private: | ||
| 51 | [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage, | 49 | [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage, |
| 52 | ContentRecordType type) const; | 50 | ContentRecordType type) const; |
| 53 | 51 | ||
| 52 | private: | ||
| 54 | VirtualFile file; | 53 | VirtualFile file; |
| 55 | VirtualFile update_raw; | 54 | VirtualFile packed_update_raw; |
| 55 | |||
| 56 | VirtualFile base; | ||
| 57 | |||
| 56 | bool updatable; | 58 | bool updatable; |
| 57 | u64 ivfc_offset; | ||
| 58 | 59 | ||
| 59 | ContentProvider& content_provider; | 60 | ContentProvider& content_provider; |
| 60 | Service::FileSystem::FileSystemController& filesystem_controller; | 61 | Service::FileSystem::FileSystemController& filesystem_controller; |
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index c90e6e372..aa41c7c31 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp | |||
| @@ -19,9 +19,9 @@ | |||
| 19 | namespace FileSys { | 19 | namespace FileSys { |
| 20 | 20 | ||
| 21 | NSP::NSP(VirtualFile file_, u64 title_id_, std::size_t program_index_) | 21 | NSP::NSP(VirtualFile file_, u64 title_id_, std::size_t program_index_) |
| 22 | : file(std::move(file_)), expected_program_id(title_id_), | 22 | : file(std::move(file_)), expected_program_id(title_id_), program_index(program_index_), |
| 23 | program_index(program_index_), status{Loader::ResultStatus::Success}, | 23 | status{Loader::ResultStatus::Success}, pfs(std::make_shared<PartitionFilesystem>(file)), |
| 24 | pfs(std::make_shared<PartitionFilesystem>(file)), keys{Core::Crypto::KeyManager::Instance()} { | 24 | keys{Core::Crypto::KeyManager::Instance()} { |
| 25 | if (pfs->GetStatus() != Loader::ResultStatus::Success) { | 25 | if (pfs->GetStatus() != Loader::ResultStatus::Success) { |
| 26 | status = pfs->GetStatus(); | 26 | status = pfs->GetStatus(); |
| 27 | return; | 27 | return; |
| @@ -280,7 +280,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { | |||
| 280 | continue; | 280 | continue; |
| 281 | } | 281 | } |
| 282 | 282 | ||
| 283 | auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0); | 283 | auto next_nca = std::make_shared<NCA>(std::move(next_file)); |
| 284 | 284 | ||
| 285 | if (next_nca->GetType() == NCAContentType::Program) { | 285 | if (next_nca->GetType() == NCAContentType::Program) { |
| 286 | program_status[next_nca->GetTitleId()] = next_nca->GetStatus(); | 286 | program_status[next_nca->GetTitleId()] = next_nca->GetStatus(); |
diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp index 2accf7898..1c9a1dc29 100644 --- a/src/core/hle/service/am/applets/applet_web_browser.cpp +++ b/src/core/hle/service/am/applets/applet_web_browser.cpp | |||
| @@ -139,7 +139,7 @@ FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id, | |||
| 139 | const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), | 139 | const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), |
| 140 | system.GetContentProvider()}; | 140 | system.GetContentProvider()}; |
| 141 | 141 | ||
| 142 | return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type); | 142 | return pm.PatchRomFS(nca.get(), nca->GetRomFS(), nca_type); |
| 143 | } | 143 | } |
| 144 | } | 144 | } |
| 145 | 145 | ||
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index ac465d5a9..4c1ea1a5b 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp | |||
| @@ -373,6 +373,11 @@ FileSys::VirtualFile FileSystemController::OpenRomFS(u64 title_id, FileSys::Stor | |||
| 373 | return romfs_factory->Open(title_id, storage_id, type); | 373 | return romfs_factory->Open(title_id, storage_id, type); |
| 374 | } | 374 | } |
| 375 | 375 | ||
| 376 | std::shared_ptr<FileSys::NCA> FileSystemController::OpenBaseNca( | ||
| 377 | u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const { | ||
| 378 | return romfs_factory->GetEntry(title_id, storage_id, type); | ||
| 379 | } | ||
| 380 | |||
| 376 | Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data, | 381 | Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data, |
| 377 | FileSys::SaveDataSpaceId space, | 382 | FileSys::SaveDataSpaceId space, |
| 378 | const FileSys::SaveDataAttribute& save_struct) const { | 383 | const FileSys::SaveDataAttribute& save_struct) const { |
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index fd991f976..e7e7c4c28 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h | |||
| @@ -15,6 +15,7 @@ class System; | |||
| 15 | 15 | ||
| 16 | namespace FileSys { | 16 | namespace FileSys { |
| 17 | class BISFactory; | 17 | class BISFactory; |
| 18 | class NCA; | ||
| 18 | class RegisteredCache; | 19 | class RegisteredCache; |
| 19 | class RegisteredCacheUnion; | 20 | class RegisteredCacheUnion; |
| 20 | class PlaceholderCache; | 21 | class PlaceholderCache; |
| @@ -70,6 +71,8 @@ public: | |||
| 70 | FileSys::ContentRecordType type) const; | 71 | FileSys::ContentRecordType type) const; |
| 71 | FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id, | 72 | FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id, |
| 72 | FileSys::ContentRecordType type) const; | 73 | FileSys::ContentRecordType type) const; |
| 74 | std::shared_ptr<FileSys::NCA> OpenBaseNca(u64 title_id, FileSys::StorageId storage_id, | ||
| 75 | FileSys::ContentRecordType type) const; | ||
| 73 | 76 | ||
| 74 | Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space, | 77 | Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space, |
| 75 | const FileSys::SaveDataAttribute& save_struct) const; | 78 | const FileSys::SaveDataAttribute& save_struct) const; |
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 423a814cb..eaaf8cdd9 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp | |||
| @@ -310,8 +310,8 @@ private: | |||
| 310 | class IFileSystem final : public ServiceFramework<IFileSystem> { | 310 | class IFileSystem final : public ServiceFramework<IFileSystem> { |
| 311 | public: | 311 | public: |
| 312 | explicit IFileSystem(Core::System& system_, FileSys::VirtualDir backend_, SizeGetter size_) | 312 | explicit IFileSystem(Core::System& system_, FileSys::VirtualDir backend_, SizeGetter size_) |
| 313 | : ServiceFramework{system_, "IFileSystem"}, backend{std::move(backend_)}, size{std::move( | 313 | : ServiceFramework{system_, "IFileSystem"}, backend{std::move(backend_)}, |
| 314 | size_)} { | 314 | size{std::move(size_)} { |
| 315 | static const FunctionInfo functions[] = { | 315 | static const FunctionInfo functions[] = { |
| 316 | {0, &IFileSystem::CreateFile, "CreateFile"}, | 316 | {0, &IFileSystem::CreateFile, "CreateFile"}, |
| 317 | {1, &IFileSystem::DeleteFile, "DeleteFile"}, | 317 | {1, &IFileSystem::DeleteFile, "DeleteFile"}, |
| @@ -1029,8 +1029,9 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) { | |||
| 1029 | 1029 | ||
| 1030 | const FileSys::PatchManager pm{title_id, fsc, content_provider}; | 1030 | const FileSys::PatchManager pm{title_id, fsc, content_provider}; |
| 1031 | 1031 | ||
| 1032 | auto base = fsc.OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data); | ||
| 1032 | auto storage = std::make_shared<IStorage>( | 1033 | auto storage = std::make_shared<IStorage>( |
| 1033 | system, pm.PatchRomFS(std::move(data), 0, FileSys::ContentRecordType::Data)); | 1034 | system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data)); |
| 1034 | 1035 | ||
| 1035 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; | 1036 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
| 1036 | rb.Push(ResultSuccess); | 1037 | rb.Push(ResultSuccess); |
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index f24474ed8..07c65dc1a 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp | |||
| @@ -135,7 +135,7 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ | |||
| 135 | "The titlekey and/or titlekek is incorrect or the section header is invalid.", | 135 | "The titlekey and/or titlekek is incorrect or the section header is invalid.", |
| 136 | "The XCI file is missing a Program-type NCA.", | 136 | "The XCI file is missing a Program-type NCA.", |
| 137 | "The NCA file is not an application.", | 137 | "The NCA file is not an application.", |
| 138 | "The ExeFS partition could not be found.", | 138 | "The Program-type NCA contains no executable. An update may be required.", |
| 139 | "The XCI file has a bad header.", | 139 | "The XCI file has a bad header.", |
| 140 | "The XCI file is missing a partition.", | 140 | "The XCI file is missing a partition.", |
| 141 | "The file could not be found or does not exist.", | 141 | "The file could not be found or does not exist.", |
| @@ -169,7 +169,7 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ | |||
| 169 | "The BKTR-type NCA has a bad Subsection block.", | 169 | "The BKTR-type NCA has a bad Subsection block.", |
| 170 | "The BKTR-type NCA has a bad Relocation bucket.", | 170 | "The BKTR-type NCA has a bad Relocation bucket.", |
| 171 | "The BKTR-type NCA has a bad Subsection bucket.", | 171 | "The BKTR-type NCA has a bad Subsection bucket.", |
| 172 | "The BKTR-type NCA is missing the base RomFS.", | 172 | "Game updates cannot be loaded directly. Load the base game instead.", |
| 173 | "The NSP or XCI does not contain an update in addition to the base game.", | 173 | "The NSP or XCI does not contain an update in addition to the base game.", |
| 174 | "The KIP file has a bad header.", | 174 | "The KIP file has a bad header.", |
| 175 | "The KIP BLZ decompression of the section failed unexpectedly.", | 175 | "The KIP BLZ decompression of the section failed unexpectedly.", |
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 7a2a52fd4..721eb8e8c 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h | |||
| @@ -79,8 +79,6 @@ enum class ResultStatus : u16 { | |||
| 79 | ErrorBadPFSHeader, | 79 | ErrorBadPFSHeader, |
| 80 | ErrorIncorrectPFSFileSize, | 80 | ErrorIncorrectPFSFileSize, |
| 81 | ErrorBadNCAHeader, | 81 | ErrorBadNCAHeader, |
| 82 | ErrorCompressedNCA, | ||
| 83 | ErrorSparseNCA, | ||
| 84 | ErrorMissingProductionKeyFile, | 82 | ErrorMissingProductionKeyFile, |
| 85 | ErrorMissingHeaderKey, | 83 | ErrorMissingHeaderKey, |
| 86 | ErrorIncorrectHeaderKey, | 84 | ErrorIncorrectHeaderKey, |
| @@ -276,16 +274,6 @@ public: | |||
| 276 | } | 274 | } |
| 277 | 275 | ||
| 278 | /** | 276 | /** |
| 279 | * Gets the difference between the start of the IVFC header and the start of level 6 (RomFS) | ||
| 280 | * data. Needed for BKTR patching. | ||
| 281 | * | ||
| 282 | * @return IVFC offset for RomFS. | ||
| 283 | */ | ||
| 284 | virtual u64 ReadRomFSIVFCOffset() const { | ||
| 285 | return 0; | ||
| 286 | } | ||
| 287 | |||
| 288 | /** | ||
| 289 | * Get the title of the application | 277 | * Get the title of the application |
| 290 | * | 278 | * |
| 291 | * @param[out] title Reference to store the application title into | 279 | * @param[out] title Reference to store the application title into |
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index cf35b1249..3b7b005ff 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp | |||
| @@ -76,10 +76,6 @@ ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) { | |||
| 76 | return nca_loader->ReadRomFS(dir); | 76 | return nca_loader->ReadRomFS(dir); |
| 77 | } | 77 | } |
| 78 | 78 | ||
| 79 | u64 AppLoader_NAX::ReadRomFSIVFCOffset() const { | ||
| 80 | return nca_loader->ReadRomFSIVFCOffset(); | ||
| 81 | } | ||
| 82 | |||
| 83 | ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { | 79 | ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { |
| 84 | return nca_loader->ReadProgramId(out_program_id); | 80 | return nca_loader->ReadProgramId(out_program_id); |
| 85 | } | 81 | } |
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index d7f70db43..81df2bbcd 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h | |||
| @@ -39,7 +39,6 @@ public: | |||
| 39 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 39 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 40 | 40 | ||
| 41 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | 41 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; |
| 42 | u64 ReadRomFSIVFCOffset() const override; | ||
| 43 | ResultStatus ReadProgramId(u64& out_program_id) override; | 42 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 44 | 43 | ||
| 45 | ResultStatus ReadBanner(std::vector<u8>& buffer) override; | 44 | ResultStatus ReadBanner(std::vector<u8>& buffer) override; |
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index 513af194d..09d40e695 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp | |||
| @@ -5,6 +5,8 @@ | |||
| 5 | 5 | ||
| 6 | #include "core/core.h" | 6 | #include "core/core.h" |
| 7 | #include "core/file_sys/content_archive.h" | 7 | #include "core/file_sys/content_archive.h" |
| 8 | #include "core/file_sys/nca_metadata.h" | ||
| 9 | #include "core/file_sys/registered_cache.h" | ||
| 8 | #include "core/file_sys/romfs_factory.h" | 10 | #include "core/file_sys/romfs_factory.h" |
| 9 | #include "core/hle/kernel/k_process.h" | 11 | #include "core/hle/kernel/k_process.h" |
| 10 | #include "core/hle/service/filesystem/filesystem.h" | 12 | #include "core/hle/service/filesystem/filesystem.h" |
| @@ -43,9 +45,23 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S | |||
| 43 | return {ResultStatus::ErrorNCANotProgram, {}}; | 45 | return {ResultStatus::ErrorNCANotProgram, {}}; |
| 44 | } | 46 | } |
| 45 | 47 | ||
| 46 | const auto exefs = nca->GetExeFS(); | 48 | auto exefs = nca->GetExeFS(); |
| 47 | if (exefs == nullptr) { | 49 | if (exefs == nullptr) { |
| 48 | return {ResultStatus::ErrorNoExeFS, {}}; | 50 | LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update"); |
| 51 | |||
| 52 | // This NCA may be a sparse base of an installed title. | ||
| 53 | // Try to fetch the ExeFS from the installed update. | ||
| 54 | const auto& installed = system.GetContentProvider(); | ||
| 55 | const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()), | ||
| 56 | FileSys::ContentRecordType::Program); | ||
| 57 | |||
| 58 | if (update_nca) { | ||
| 59 | exefs = update_nca->GetExeFS(); | ||
| 60 | } | ||
| 61 | |||
| 62 | if (exefs == nullptr) { | ||
| 63 | return {ResultStatus::ErrorNoExeFS, {}}; | ||
| 64 | } | ||
| 49 | } | 65 | } |
| 50 | 66 | ||
| 51 | directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); | 67 | directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); |
| @@ -77,14 +93,6 @@ ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { | |||
| 77 | return ResultStatus::Success; | 93 | return ResultStatus::Success; |
| 78 | } | 94 | } |
| 79 | 95 | ||
| 80 | u64 AppLoader_NCA::ReadRomFSIVFCOffset() const { | ||
| 81 | if (nca == nullptr) { | ||
| 82 | return 0; | ||
| 83 | } | ||
| 84 | |||
| 85 | return nca->GetBaseIVFCOffset(); | ||
| 86 | } | ||
| 87 | |||
| 88 | ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { | 96 | ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { |
| 89 | if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) { | 97 | if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) { |
| 90 | return ResultStatus::ErrorNotInitialized; | 98 | return ResultStatus::ErrorNotInitialized; |
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index d22d9146e..cf356ce63 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h | |||
| @@ -40,7 +40,6 @@ public: | |||
| 40 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 40 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 41 | 41 | ||
| 42 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | 42 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; |
| 43 | u64 ReadRomFSIVFCOffset() const override; | ||
| 44 | ResultStatus ReadProgramId(u64& out_program_id) override; | 43 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 45 | 44 | ||
| 46 | ResultStatus ReadBanner(std::vector<u8>& buffer) override; | 45 | ResultStatus ReadBanner(std::vector<u8>& buffer) override; |
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 80663e0e0..f9b2549a3 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp | |||
| @@ -121,10 +121,6 @@ ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { | |||
| 121 | return secondary_loader->ReadRomFS(out_file); | 121 | return secondary_loader->ReadRomFS(out_file); |
| 122 | } | 122 | } |
| 123 | 123 | ||
| 124 | u64 AppLoader_NSP::ReadRomFSIVFCOffset() const { | ||
| 125 | return secondary_loader->ReadRomFSIVFCOffset(); | ||
| 126 | } | ||
| 127 | |||
| 128 | ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { | 124 | ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { |
| 129 | if (nsp->IsExtractedType()) { | 125 | if (nsp->IsExtractedType()) { |
| 130 | return ResultStatus::ErrorNoPackedUpdate; | 126 | return ResultStatus::ErrorNoPackedUpdate; |
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index 003cc345c..79df4586a 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h | |||
| @@ -46,7 +46,6 @@ public: | |||
| 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 47 | 47 | ||
| 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; | 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; |
| 49 | u64 ReadRomFSIVFCOffset() const override; | ||
| 50 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; | 49 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; |
| 51 | ResultStatus ReadProgramId(u64& out_program_id) override; | 50 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 52 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; | 51 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; |
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index c7b1b3815..3a76bc788 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp | |||
| @@ -89,10 +89,6 @@ ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { | |||
| 89 | return nca_loader->ReadRomFS(out_file); | 89 | return nca_loader->ReadRomFS(out_file); |
| 90 | } | 90 | } |
| 91 | 91 | ||
| 92 | u64 AppLoader_XCI::ReadRomFSIVFCOffset() const { | ||
| 93 | return nca_loader->ReadRomFSIVFCOffset(); | ||
| 94 | } | ||
| 95 | |||
| 96 | ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) { | 92 | ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) { |
| 97 | u64 program_id{}; | 93 | u64 program_id{}; |
| 98 | nca_loader->ReadProgramId(program_id); | 94 | nca_loader->ReadProgramId(program_id); |
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 2affb6c6e..ff05e6f62 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h | |||
| @@ -46,7 +46,6 @@ public: | |||
| 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 47 | 47 | ||
| 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; | 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; |
| 49 | u64 ReadRomFSIVFCOffset() const override; | ||
| 50 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; | 49 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; |
| 51 | ResultStatus ReadProgramId(u64& out_program_id) override; | 50 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 52 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; | 51 | ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 97ae9e49a..a9d035f3d 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -2535,8 +2535,8 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2535 | return; | 2535 | return; |
| 2536 | } | 2536 | } |
| 2537 | 2537 | ||
| 2538 | FileSys::VirtualFile file; | 2538 | FileSys::VirtualFile base_romfs; |
| 2539 | if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) { | 2539 | if (loader->ReadRomFS(base_romfs) != Loader::ResultStatus::Success) { |
| 2540 | failed(); | 2540 | failed(); |
| 2541 | return; | 2541 | return; |
| 2542 | } | 2542 | } |
| @@ -2549,6 +2549,14 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2549 | return; | 2549 | return; |
| 2550 | } | 2550 | } |
| 2551 | 2551 | ||
| 2552 | const auto type = *romfs_title_id == program_id ? FileSys::ContentRecordType::Program | ||
| 2553 | : FileSys::ContentRecordType::Data; | ||
| 2554 | const auto base_nca = installed.GetEntry(*romfs_title_id, type); | ||
| 2555 | if (!base_nca) { | ||
| 2556 | failed(); | ||
| 2557 | return; | ||
| 2558 | } | ||
| 2559 | |||
| 2552 | const auto dump_dir = | 2560 | const auto dump_dir = |
| 2553 | target == DumpRomFSTarget::Normal | 2561 | target == DumpRomFSTarget::Normal |
| 2554 | ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir) | 2562 | ? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir) |
| @@ -2560,12 +2568,10 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2560 | FileSys::VirtualFile romfs; | 2568 | FileSys::VirtualFile romfs; |
| 2561 | 2569 | ||
| 2562 | if (*romfs_title_id == program_id) { | 2570 | if (*romfs_title_id == program_id) { |
| 2563 | const u64 ivfc_offset = loader->ReadRomFSIVFCOffset(); | ||
| 2564 | const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), installed}; | 2571 | const FileSys::PatchManager pm{program_id, system->GetFileSystemController(), installed}; |
| 2565 | romfs = | 2572 | romfs = pm.PatchRomFS(base_nca.get(), base_romfs, type, nullptr, false); |
| 2566 | pm.PatchRomFS(file, ivfc_offset, FileSys::ContentRecordType::Program, nullptr, false); | ||
| 2567 | } else { | 2573 | } else { |
| 2568 | romfs = installed.GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); | 2574 | romfs = installed.GetEntry(*romfs_title_id, type)->GetRomFS(); |
| 2569 | } | 2575 | } |
| 2570 | 2576 | ||
| 2571 | const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); | 2577 | const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); |