diff options
| author | 2018-08-04 14:33:11 -0400 | |
|---|---|---|
| committer | 2018-08-04 14:33:11 -0400 | |
| commit | 2b06301dbfbfe79687219bf7783a6d1b47695401 (patch) | |
| tree | 222cc27ecbc7f7e86d2edef8d36436600dee7d7a /src/core/file_sys | |
| parent | Merge pull request #919 from lioncash/sign (diff) | |
| parent | Add missing parameter to files.push_back() (diff) | |
| download | yuzu-2b06301dbfbfe79687219bf7783a6d1b47695401.tar.gz yuzu-2b06301dbfbfe79687219bf7783a6d1b47695401.tar.xz yuzu-2b06301dbfbfe79687219bf7783a6d1b47695401.zip | |
Merge pull request #849 from DarkLordZach/xci
XCI and Encrypted NCA Support
Diffstat (limited to 'src/core/file_sys')
| -rw-r--r-- | src/core/file_sys/card_image.cpp | 149 | ||||
| -rw-r--r-- | src/core/file_sys/card_image.h | 96 | ||||
| -rw-r--r-- | src/core/file_sys/content_archive.cpp | 193 | ||||
| -rw-r--r-- | src/core/file_sys/content_archive.h | 29 | ||||
| -rw-r--r-- | src/core/file_sys/vfs.cpp | 20 | ||||
| -rw-r--r-- | src/core/file_sys/vfs.h | 3 |
6 files changed, 446 insertions, 44 deletions
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp new file mode 100644 index 000000000..395eea8ae --- /dev/null +++ b/src/core/file_sys/card_image.cpp | |||
| @@ -0,0 +1,149 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <array> | ||
| 6 | #include <string> | ||
| 7 | #include <core/loader/loader.h> | ||
| 8 | #include "core/file_sys/card_image.h" | ||
| 9 | #include "core/file_sys/partition_filesystem.h" | ||
| 10 | #include "core/file_sys/vfs_offset.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { | ||
| 15 | if (file->ReadObject(&header) != sizeof(GamecardHeader)) { | ||
| 16 | status = Loader::ResultStatus::ErrorInvalidFormat; | ||
| 17 | return; | ||
| 18 | } | ||
| 19 | |||
| 20 | if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) { | ||
| 21 | status = Loader::ResultStatus::ErrorInvalidFormat; | ||
| 22 | return; | ||
| 23 | } | ||
| 24 | |||
| 25 | PartitionFilesystem main_hfs( | ||
| 26 | std::make_shared<OffsetVfsFile>(file, header.hfs_size, header.hfs_offset)); | ||
| 27 | |||
| 28 | if (main_hfs.GetStatus() != Loader::ResultStatus::Success) { | ||
| 29 | status = main_hfs.GetStatus(); | ||
| 30 | return; | ||
| 31 | } | ||
| 32 | |||
| 33 | static constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", | ||
| 34 | "logo"}; | ||
| 35 | |||
| 36 | for (XCIPartition partition : | ||
| 37 | {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) { | ||
| 38 | auto raw = main_hfs.GetFile(partition_names[static_cast<size_t>(partition)]); | ||
| 39 | if (raw != nullptr) | ||
| 40 | partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw); | ||
| 41 | } | ||
| 42 | |||
| 43 | auto result = AddNCAFromPartition(XCIPartition::Secure); | ||
| 44 | if (result != Loader::ResultStatus::Success) { | ||
| 45 | status = result; | ||
| 46 | return; | ||
| 47 | } | ||
| 48 | |||
| 49 | result = AddNCAFromPartition(XCIPartition::Update); | ||
| 50 | if (result != Loader::ResultStatus::Success) { | ||
| 51 | status = result; | ||
| 52 | return; | ||
| 53 | } | ||
| 54 | |||
| 55 | result = AddNCAFromPartition(XCIPartition::Normal); | ||
| 56 | if (result != Loader::ResultStatus::Success) { | ||
| 57 | status = result; | ||
| 58 | return; | ||
| 59 | } | ||
| 60 | |||
| 61 | if (GetFormatVersion() >= 0x2) { | ||
| 62 | result = AddNCAFromPartition(XCIPartition::Logo); | ||
| 63 | if (result != Loader::ResultStatus::Success) { | ||
| 64 | status = result; | ||
| 65 | return; | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | status = Loader::ResultStatus::Success; | ||
| 70 | } | ||
| 71 | |||
| 72 | Loader::ResultStatus XCI::GetStatus() const { | ||
| 73 | return status; | ||
| 74 | } | ||
| 75 | |||
| 76 | VirtualDir XCI::GetPartition(XCIPartition partition) const { | ||
| 77 | return partitions[static_cast<size_t>(partition)]; | ||
| 78 | } | ||
| 79 | |||
| 80 | VirtualDir XCI::GetSecurePartition() const { | ||
| 81 | return GetPartition(XCIPartition::Secure); | ||
| 82 | } | ||
| 83 | |||
| 84 | VirtualDir XCI::GetNormalPartition() const { | ||
| 85 | return GetPartition(XCIPartition::Normal); | ||
| 86 | } | ||
| 87 | |||
| 88 | VirtualDir XCI::GetUpdatePartition() const { | ||
| 89 | return GetPartition(XCIPartition::Update); | ||
| 90 | } | ||
| 91 | |||
| 92 | VirtualDir XCI::GetLogoPartition() const { | ||
| 93 | return GetPartition(XCIPartition::Logo); | ||
| 94 | } | ||
| 95 | |||
| 96 | std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const { | ||
| 97 | const auto iter = | ||
| 98 | std::find_if(ncas.begin(), ncas.end(), | ||
| 99 | [type](const std::shared_ptr<NCA>& nca) { return nca->GetType() == type; }); | ||
| 100 | return iter == ncas.end() ? nullptr : *iter; | ||
| 101 | } | ||
| 102 | |||
| 103 | VirtualFile XCI::GetNCAFileByType(NCAContentType type) const { | ||
| 104 | auto nca = GetNCAByType(type); | ||
| 105 | if (nca != nullptr) | ||
| 106 | return nca->GetBaseFile(); | ||
| 107 | return nullptr; | ||
| 108 | } | ||
| 109 | |||
| 110 | std::vector<std::shared_ptr<VfsFile>> XCI::GetFiles() const { | ||
| 111 | return {}; | ||
| 112 | } | ||
| 113 | |||
| 114 | std::vector<std::shared_ptr<VfsDirectory>> XCI::GetSubdirectories() const { | ||
| 115 | return std::vector<std::shared_ptr<VfsDirectory>>(); | ||
| 116 | } | ||
| 117 | |||
| 118 | std::string XCI::GetName() const { | ||
| 119 | return file->GetName(); | ||
| 120 | } | ||
| 121 | |||
| 122 | std::shared_ptr<VfsDirectory> XCI::GetParentDirectory() const { | ||
| 123 | return file->GetContainingDirectory(); | ||
| 124 | } | ||
| 125 | |||
| 126 | bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | ||
| 127 | return false; | ||
| 128 | } | ||
| 129 | |||
| 130 | Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { | ||
| 131 | if (partitions[static_cast<size_t>(part)] == nullptr) { | ||
| 132 | return Loader::ResultStatus::ErrorInvalidFormat; | ||
| 133 | } | ||
| 134 | |||
| 135 | for (const VirtualFile& file : partitions[static_cast<size_t>(part)]->GetFiles()) { | ||
| 136 | if (file->GetExtension() != "nca") | ||
| 137 | continue; | ||
| 138 | auto nca = std::make_shared<NCA>(file); | ||
| 139 | if (nca->GetStatus() == Loader::ResultStatus::Success) | ||
| 140 | ncas.push_back(std::move(nca)); | ||
| 141 | } | ||
| 142 | |||
| 143 | return Loader::ResultStatus::Success; | ||
| 144 | } | ||
| 145 | |||
| 146 | u8 XCI::GetFormatVersion() const { | ||
| 147 | return GetLogoPartition() == nullptr ? 0x1 : 0x2; | ||
| 148 | } | ||
| 149 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h new file mode 100644 index 000000000..e089d737c --- /dev/null +++ b/src/core/file_sys/card_image.h | |||
| @@ -0,0 +1,96 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | #include <vector> | ||
| 9 | #include "common/common_types.h" | ||
| 10 | #include "common/swap.h" | ||
| 11 | #include "core/file_sys/content_archive.h" | ||
| 12 | #include "core/file_sys/vfs.h" | ||
| 13 | #include "core/loader/loader.h" | ||
| 14 | |||
| 15 | namespace FileSys { | ||
| 16 | |||
| 17 | enum class GamecardSize : u8 { | ||
| 18 | S_1GB = 0xFA, | ||
| 19 | S_2GB = 0xF8, | ||
| 20 | S_4GB = 0xF0, | ||
| 21 | S_8GB = 0xE0, | ||
| 22 | S_16GB = 0xE1, | ||
| 23 | S_32GB = 0xE2, | ||
| 24 | }; | ||
| 25 | |||
| 26 | struct GamecardInfo { | ||
| 27 | std::array<u8, 0x70> data; | ||
| 28 | }; | ||
| 29 | static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size."); | ||
| 30 | |||
| 31 | struct GamecardHeader { | ||
| 32 | std::array<u8, 0x100> signature; | ||
| 33 | u32_le magic; | ||
| 34 | u32_le secure_area_start; | ||
| 35 | u32_le backup_area_start; | ||
| 36 | u8 kek_index; | ||
| 37 | GamecardSize size; | ||
| 38 | u8 header_version; | ||
| 39 | u8 flags; | ||
| 40 | u64_le package_id; | ||
| 41 | u64_le valid_data_end; | ||
| 42 | u128 info_iv; | ||
| 43 | u64_le hfs_offset; | ||
| 44 | u64_le hfs_size; | ||
| 45 | std::array<u8, 0x20> hfs_header_hash; | ||
| 46 | std::array<u8, 0x20> initial_data_hash; | ||
| 47 | u32_le secure_mode_flag; | ||
| 48 | u32_le title_key_flag; | ||
| 49 | u32_le key_flag; | ||
| 50 | u32_le normal_area_end; | ||
| 51 | GamecardInfo info; | ||
| 52 | }; | ||
| 53 | static_assert(sizeof(GamecardHeader) == 0x200, "GamecardHeader has incorrect size."); | ||
| 54 | |||
| 55 | enum class XCIPartition : u8 { Update, Normal, Secure, Logo }; | ||
| 56 | |||
| 57 | class XCI : public ReadOnlyVfsDirectory { | ||
| 58 | public: | ||
| 59 | explicit XCI(VirtualFile file); | ||
| 60 | |||
| 61 | Loader::ResultStatus GetStatus() const; | ||
| 62 | |||
| 63 | u8 GetFormatVersion() const; | ||
| 64 | |||
| 65 | VirtualDir GetPartition(XCIPartition partition) const; | ||
| 66 | VirtualDir GetSecurePartition() const; | ||
| 67 | VirtualDir GetNormalPartition() const; | ||
| 68 | VirtualDir GetUpdatePartition() const; | ||
| 69 | VirtualDir GetLogoPartition() const; | ||
| 70 | |||
| 71 | std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const; | ||
| 72 | VirtualFile GetNCAFileByType(NCAContentType type) const; | ||
| 73 | |||
| 74 | std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; | ||
| 75 | |||
| 76 | std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; | ||
| 77 | |||
| 78 | std::string GetName() const override; | ||
| 79 | |||
| 80 | std::shared_ptr<VfsDirectory> GetParentDirectory() const override; | ||
| 81 | |||
| 82 | protected: | ||
| 83 | bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | ||
| 84 | |||
| 85 | private: | ||
| 86 | Loader::ResultStatus AddNCAFromPartition(XCIPartition part); | ||
| 87 | |||
| 88 | VirtualFile file; | ||
| 89 | GamecardHeader header{}; | ||
| 90 | |||
| 91 | Loader::ResultStatus status; | ||
| 92 | |||
| 93 | std::vector<VirtualDir> partitions; | ||
| 94 | std::vector<std::shared_ptr<NCA>> ncas; | ||
| 95 | }; | ||
| 96 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 61cb0bbe3..79e70f6ef 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp | |||
| @@ -4,12 +4,14 @@ | |||
| 4 | 4 | ||
| 5 | #include <algorithm> | 5 | #include <algorithm> |
| 6 | #include <utility> | 6 | #include <utility> |
| 7 | 7 | #include <boost/optional.hpp> | |
| 8 | #include "common/logging/log.h" | 8 | #include "common/logging/log.h" |
| 9 | #include "core/crypto/aes_util.h" | ||
| 10 | #include "core/crypto/ctr_encryption_layer.h" | ||
| 9 | #include "core/file_sys/content_archive.h" | 11 | #include "core/file_sys/content_archive.h" |
| 12 | #include "core/file_sys/romfs.h" | ||
| 10 | #include "core/file_sys/vfs_offset.h" | 13 | #include "core/file_sys/vfs_offset.h" |
| 11 | #include "core/loader/loader.h" | 14 | #include "core/loader/loader.h" |
| 12 | #include "romfs.h" | ||
| 13 | 15 | ||
| 14 | namespace FileSys { | 16 | namespace FileSys { |
| 15 | 17 | ||
| @@ -29,11 +31,19 @@ enum class NCASectionFilesystemType : u8 { | |||
| 29 | struct NCASectionHeaderBlock { | 31 | struct NCASectionHeaderBlock { |
| 30 | INSERT_PADDING_BYTES(3); | 32 | INSERT_PADDING_BYTES(3); |
| 31 | NCASectionFilesystemType filesystem_type; | 33 | NCASectionFilesystemType filesystem_type; |
| 32 | u8 crypto_type; | 34 | NCASectionCryptoType crypto_type; |
| 33 | INSERT_PADDING_BYTES(3); | 35 | INSERT_PADDING_BYTES(3); |
| 34 | }; | 36 | }; |
| 35 | static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); | 37 | static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); |
| 36 | 38 | ||
| 39 | struct NCASectionRaw { | ||
| 40 | NCASectionHeaderBlock header; | ||
| 41 | std::array<u8, 0x138> block_data; | ||
| 42 | std::array<u8, 0x8> section_ctr; | ||
| 43 | INSERT_PADDING_BYTES(0xB8); | ||
| 44 | }; | ||
| 45 | static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size."); | ||
| 46 | |||
| 37 | struct PFS0Superblock { | 47 | struct PFS0Superblock { |
| 38 | NCASectionHeaderBlock header_block; | 48 | NCASectionHeaderBlock header_block; |
| 39 | std::array<u8, 0x20> hash; | 49 | std::array<u8, 0x20> hash; |
| @@ -43,67 +53,170 @@ struct PFS0Superblock { | |||
| 43 | u64_le hash_table_size; | 53 | u64_le hash_table_size; |
| 44 | u64_le pfs0_header_offset; | 54 | u64_le pfs0_header_offset; |
| 45 | u64_le pfs0_size; | 55 | u64_le pfs0_size; |
| 46 | INSERT_PADDING_BYTES(432); | 56 | INSERT_PADDING_BYTES(0x1B0); |
| 47 | }; | 57 | }; |
| 48 | static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); | 58 | static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); |
| 49 | 59 | ||
| 50 | struct RomFSSuperblock { | 60 | struct RomFSSuperblock { |
| 51 | NCASectionHeaderBlock header_block; | 61 | NCASectionHeaderBlock header_block; |
| 52 | IVFCHeader ivfc; | 62 | IVFCHeader ivfc; |
| 63 | INSERT_PADDING_BYTES(0x118); | ||
| 53 | }; | 64 | }; |
| 54 | static_assert(sizeof(RomFSSuperblock) == 0xE8, "RomFSSuperblock has incorrect size."); | 65 | static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); |
| 66 | |||
| 67 | union NCASectionHeader { | ||
| 68 | NCASectionRaw raw; | ||
| 69 | PFS0Superblock pfs0; | ||
| 70 | RomFSSuperblock romfs; | ||
| 71 | }; | ||
| 72 | static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size."); | ||
| 73 | |||
| 74 | bool IsValidNCA(const NCAHeader& header) { | ||
| 75 | // TODO(DarkLordZach): Add NCA2/NCA0 support. | ||
| 76 | return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); | ||
| 77 | } | ||
| 78 | |||
| 79 | boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const { | ||
| 80 | u8 master_key_id = header.crypto_type; | ||
| 81 | if (header.crypto_type_2 > master_key_id) | ||
| 82 | master_key_id = header.crypto_type_2; | ||
| 83 | if (master_key_id > 0) | ||
| 84 | --master_key_id; | ||
| 85 | |||
| 86 | if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) | ||
| 87 | return boost::none; | ||
| 88 | |||
| 89 | std::vector<u8> key_area(header.key_area.begin(), header.key_area.end()); | ||
| 90 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | ||
| 91 | keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index), | ||
| 92 | Core::Crypto::Mode::ECB); | ||
| 93 | cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt); | ||
| 94 | |||
| 95 | Core::Crypto::Key128 out; | ||
| 96 | if (type == NCASectionCryptoType::XTS) | ||
| 97 | std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); | ||
| 98 | else if (type == NCASectionCryptoType::CTR) | ||
| 99 | std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin()); | ||
| 100 | else | ||
| 101 | LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", | ||
| 102 | static_cast<u8>(type)); | ||
| 103 | u128 out_128{}; | ||
| 104 | memcpy(out_128.data(), out.data(), 16); | ||
| 105 | LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", | ||
| 106 | master_key_id, header.key_index, out_128[1], out_128[0]); | ||
| 107 | |||
| 108 | return out; | ||
| 109 | } | ||
| 110 | |||
| 111 | VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const { | ||
| 112 | if (!encrypted) | ||
| 113 | return in; | ||
| 114 | |||
| 115 | switch (header.raw.header.crypto_type) { | ||
| 116 | case NCASectionCryptoType::NONE: | ||
| 117 | LOG_DEBUG(Crypto, "called with mode=NONE"); | ||
| 118 | return in; | ||
| 119 | case NCASectionCryptoType::CTR: | ||
| 120 | LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); | ||
| 121 | { | ||
| 122 | const auto key = GetKeyAreaKey(NCASectionCryptoType::CTR); | ||
| 123 | if (key == boost::none) | ||
| 124 | return nullptr; | ||
| 125 | auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>( | ||
| 126 | std::move(in), key.value(), starting_offset); | ||
| 127 | std::vector<u8> iv(16); | ||
| 128 | for (u8 i = 0; i < 8; ++i) | ||
| 129 | iv[i] = header.raw.section_ctr[0x8 - i - 1]; | ||
| 130 | out->SetIV(iv); | ||
| 131 | return std::static_pointer_cast<VfsFile>(out); | ||
| 132 | } | ||
| 133 | case NCASectionCryptoType::XTS: | ||
| 134 | // TODO(DarkLordZach): Implement XTSEncryptionLayer and title key encryption. | ||
| 135 | default: | ||
| 136 | LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", | ||
| 137 | static_cast<u8>(header.raw.header.crypto_type)); | ||
| 138 | return nullptr; | ||
| 139 | } | ||
| 140 | } | ||
| 55 | 141 | ||
| 56 | NCA::NCA(VirtualFile file_) : file(std::move(file_)) { | 142 | NCA::NCA(VirtualFile file_) : file(std::move(file_)) { |
| 57 | if (sizeof(NCAHeader) != file->ReadObject(&header)) | 143 | if (sizeof(NCAHeader) != file->ReadObject(&header)) |
| 58 | LOG_CRITICAL(Loader, "File reader errored out during header read."); | 144 | LOG_ERROR(Loader, "File reader errored out during header read."); |
| 145 | |||
| 146 | encrypted = false; | ||
| 59 | 147 | ||
| 60 | if (!IsValidNCA(header)) { | 148 | if (!IsValidNCA(header)) { |
| 61 | status = Loader::ResultStatus::ErrorInvalidFormat; | 149 | NCAHeader dec_header{}; |
| 62 | return; | 150 | Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( |
| 151 | keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); | ||
| 152 | cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, | ||
| 153 | Core::Crypto::Op::Decrypt); | ||
| 154 | if (IsValidNCA(dec_header)) { | ||
| 155 | header = dec_header; | ||
| 156 | encrypted = true; | ||
| 157 | } else { | ||
| 158 | if (!keys.HasKey(Core::Crypto::S256KeyType::Header)) | ||
| 159 | status = Loader::ResultStatus::ErrorMissingKeys; | ||
| 160 | else | ||
| 161 | status = Loader::ResultStatus::ErrorDecrypting; | ||
| 162 | return; | ||
| 163 | } | ||
| 63 | } | 164 | } |
| 64 | 165 | ||
| 65 | std::ptrdiff_t number_sections = | 166 | const std::ptrdiff_t number_sections = |
| 66 | std::count_if(std::begin(header.section_tables), std::end(header.section_tables), | 167 | std::count_if(std::begin(header.section_tables), std::end(header.section_tables), |
| 67 | [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); | 168 | [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); |
| 68 | 169 | ||
| 170 | std::vector<NCASectionHeader> sections(number_sections); | ||
| 171 | const auto length_sections = SECTION_HEADER_SIZE * number_sections; | ||
| 172 | |||
| 173 | if (encrypted) { | ||
| 174 | auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET); | ||
| 175 | Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( | ||
| 176 | keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); | ||
| 177 | cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, | ||
| 178 | Core::Crypto::Op::Decrypt); | ||
| 179 | } else { | ||
| 180 | file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); | ||
| 181 | } | ||
| 182 | |||
| 69 | for (std::ptrdiff_t i = 0; i < number_sections; ++i) { | 183 | for (std::ptrdiff_t i = 0; i < number_sections; ++i) { |
| 70 | // Seek to beginning of this section. | 184 | auto section = sections[i]; |
| 71 | NCASectionHeaderBlock block{}; | ||
| 72 | if (sizeof(NCASectionHeaderBlock) != | ||
| 73 | file->ReadObject(&block, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) | ||
| 74 | LOG_CRITICAL(Loader, "File reader errored out during header read."); | ||
| 75 | |||
| 76 | if (block.filesystem_type == NCASectionFilesystemType::ROMFS) { | ||
| 77 | RomFSSuperblock sb{}; | ||
| 78 | if (sizeof(RomFSSuperblock) != | ||
| 79 | file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) | ||
| 80 | LOG_CRITICAL(Loader, "File reader errored out during header read."); | ||
| 81 | 185 | ||
| 186 | if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { | ||
| 82 | const size_t romfs_offset = | 187 | const size_t romfs_offset = |
| 83 | header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER + | 188 | header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER + |
| 84 | sb.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; | 189 | section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; |
| 85 | const size_t romfs_size = sb.ivfc.levels[IVFC_MAX_LEVEL - 1].size; | 190 | const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; |
| 86 | files.emplace_back(std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset)); | 191 | auto dec = |
| 87 | romfs = files.back(); | 192 | Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset), |
| 88 | } else if (block.filesystem_type == NCASectionFilesystemType::PFS0) { | 193 | romfs_offset); |
| 89 | PFS0Superblock sb{}; | 194 | if (dec != nullptr) { |
| 90 | // Seek back to beginning of this section. | 195 | files.push_back(std::move(dec)); |
| 91 | if (sizeof(PFS0Superblock) != | 196 | romfs = files.back(); |
| 92 | file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) | 197 | } else { |
| 93 | LOG_CRITICAL(Loader, "File reader errored out during header read."); | 198 | status = Loader::ResultStatus::ErrorMissingKeys; |
| 94 | 199 | return; | |
| 200 | } | ||
| 201 | } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { | ||
| 95 | u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * | 202 | u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * |
| 96 | MEDIA_OFFSET_MULTIPLIER) + | 203 | MEDIA_OFFSET_MULTIPLIER) + |
| 97 | sb.pfs0_header_offset; | 204 | section.pfs0.pfs0_header_offset; |
| 98 | u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - | 205 | u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - |
| 99 | header.section_tables[i].media_offset); | 206 | header.section_tables[i].media_offset); |
| 100 | auto npfs = std::make_shared<PartitionFilesystem>( | 207 | auto dec = |
| 101 | std::make_shared<OffsetVfsFile>(file, size, offset)); | 208 | Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset); |
| 209 | if (dec != nullptr) { | ||
| 210 | auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec)); | ||
| 102 | 211 | ||
| 103 | if (npfs->GetStatus() == Loader::ResultStatus::Success) { | 212 | if (npfs->GetStatus() == Loader::ResultStatus::Success) { |
| 104 | dirs.emplace_back(npfs); | 213 | dirs.push_back(std::move(npfs)); |
| 105 | if (IsDirectoryExeFS(dirs.back())) | 214 | if (IsDirectoryExeFS(dirs.back())) |
| 106 | exefs = dirs.back(); | 215 | exefs = dirs.back(); |
| 216 | } | ||
| 217 | } else { | ||
| 218 | status = Loader::ResultStatus::ErrorMissingKeys; | ||
| 219 | return; | ||
| 107 | } | 220 | } |
| 108 | } | 221 | } |
| 109 | } | 222 | } |
| @@ -153,6 +266,10 @@ VirtualDir NCA::GetExeFS() const { | |||
| 153 | return exefs; | 266 | return exefs; |
| 154 | } | 267 | } |
| 155 | 268 | ||
| 269 | VirtualFile NCA::GetBaseFile() const { | ||
| 270 | return file; | ||
| 271 | } | ||
| 272 | |||
| 156 | bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | 273 | bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { |
| 157 | return false; | 274 | return false; |
| 158 | } | 275 | } |
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 0b8b9db61..6492163b5 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h | |||
| @@ -8,14 +8,18 @@ | |||
| 8 | #include <memory> | 8 | #include <memory> |
| 9 | #include <string> | 9 | #include <string> |
| 10 | #include <vector> | 10 | #include <vector> |
| 11 | 11 | #include <boost/optional.hpp> | |
| 12 | #include "common/common_funcs.h" | 12 | #include "common/common_funcs.h" |
| 13 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 14 | #include "common/swap.h" | 14 | #include "common/swap.h" |
| 15 | #include "core/crypto/key_manager.h" | ||
| 15 | #include "core/file_sys/partition_filesystem.h" | 16 | #include "core/file_sys/partition_filesystem.h" |
| 17 | #include "core/loader/loader.h" | ||
| 16 | 18 | ||
| 17 | namespace FileSys { | 19 | namespace FileSys { |
| 18 | 20 | ||
| 21 | union NCASectionHeader; | ||
| 22 | |||
| 19 | enum class NCAContentType : u8 { | 23 | enum class NCAContentType : u8 { |
| 20 | Program = 0, | 24 | Program = 0, |
| 21 | Meta = 1, | 25 | Meta = 1, |
| @@ -24,6 +28,13 @@ enum class NCAContentType : u8 { | |||
| 24 | Data = 4, | 28 | Data = 4, |
| 25 | }; | 29 | }; |
| 26 | 30 | ||
| 31 | enum class NCASectionCryptoType : u8 { | ||
| 32 | NONE = 1, | ||
| 33 | XTS = 2, | ||
| 34 | CTR = 3, | ||
| 35 | BKTR = 4, | ||
| 36 | }; | ||
| 37 | |||
| 27 | struct NCASectionTableEntry { | 38 | struct NCASectionTableEntry { |
| 28 | u32_le media_offset; | 39 | u32_le media_offset; |
| 29 | u32_le media_end_offset; | 40 | u32_le media_end_offset; |
| @@ -48,7 +59,7 @@ struct NCAHeader { | |||
| 48 | std::array<u8, 0x10> rights_id; | 59 | std::array<u8, 0x10> rights_id; |
| 49 | std::array<NCASectionTableEntry, 0x4> section_tables; | 60 | std::array<NCASectionTableEntry, 0x4> section_tables; |
| 50 | std::array<std::array<u8, 0x20>, 0x4> hash_tables; | 61 | std::array<std::array<u8, 0x20>, 0x4> hash_tables; |
| 51 | std::array<std::array<u8, 0x10>, 0x4> key_area; | 62 | std::array<u8, 0x40> key_area; |
| 52 | INSERT_PADDING_BYTES(0xC0); | 63 | INSERT_PADDING_BYTES(0xC0); |
| 53 | }; | 64 | }; |
| 54 | static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); | 65 | static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); |
| @@ -58,10 +69,7 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) { | |||
| 58 | return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; | 69 | return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; |
| 59 | } | 70 | } |
| 60 | 71 | ||
| 61 | inline bool IsValidNCA(const NCAHeader& header) { | 72 | bool IsValidNCA(const NCAHeader& header); |
| 62 | return header.magic == Common::MakeMagic('N', 'C', 'A', '2') || | ||
| 63 | header.magic == Common::MakeMagic('N', 'C', 'A', '3'); | ||
| 64 | } | ||
| 65 | 73 | ||
| 66 | // An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner. | 74 | // An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner. |
| 67 | // After construction, use GetStatus to determine if the file is valid and ready to be used. | 75 | // After construction, use GetStatus to determine if the file is valid and ready to be used. |
| @@ -81,10 +89,15 @@ public: | |||
| 81 | VirtualFile GetRomFS() const; | 89 | VirtualFile GetRomFS() const; |
| 82 | VirtualDir GetExeFS() const; | 90 | VirtualDir GetExeFS() const; |
| 83 | 91 | ||
| 92 | VirtualFile GetBaseFile() const; | ||
| 93 | |||
| 84 | protected: | 94 | protected: |
| 85 | bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | 95 | bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; |
| 86 | 96 | ||
| 87 | private: | 97 | private: |
| 98 | boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const; | ||
| 99 | VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const; | ||
| 100 | |||
| 88 | std::vector<VirtualDir> dirs; | 101 | std::vector<VirtualDir> dirs; |
| 89 | std::vector<VirtualFile> files; | 102 | std::vector<VirtualFile> files; |
| 90 | 103 | ||
| @@ -95,6 +108,10 @@ private: | |||
| 95 | NCAHeader header{}; | 108 | NCAHeader header{}; |
| 96 | 109 | ||
| 97 | Loader::ResultStatus status{}; | 110 | Loader::ResultStatus status{}; |
| 111 | |||
| 112 | bool encrypted; | ||
| 113 | |||
| 114 | Core::Crypto::KeyManager keys; | ||
| 98 | }; | 115 | }; |
| 99 | 116 | ||
| 100 | } // namespace FileSys | 117 | } // namespace FileSys |
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index 84a6a7397..dae1c16ef 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp | |||
| @@ -285,6 +285,26 @@ bool ReadOnlyVfsDirectory::Rename(std::string_view name) { | |||
| 285 | return false; | 285 | return false; |
| 286 | } | 286 | } |
| 287 | 287 | ||
| 288 | bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size) { | ||
| 289 | if (file1->GetSize() != file2->GetSize()) | ||
| 290 | return false; | ||
| 291 | |||
| 292 | std::vector<u8> f1_v(block_size); | ||
| 293 | std::vector<u8> f2_v(block_size); | ||
| 294 | for (size_t i = 0; i < file1->GetSize(); i += block_size) { | ||
| 295 | auto f1_vs = file1->Read(f1_v.data(), block_size, i); | ||
| 296 | auto f2_vs = file2->Read(f2_v.data(), block_size, i); | ||
| 297 | |||
| 298 | if (f1_vs != f2_vs) | ||
| 299 | return false; | ||
| 300 | auto iters = std::mismatch(f1_v.begin(), f1_v.end(), f2_v.begin(), f2_v.end()); | ||
| 301 | if (iters.first != f1_v.end() && iters.second != f2_v.end()) | ||
| 302 | return false; | ||
| 303 | } | ||
| 304 | |||
| 305 | return true; | ||
| 306 | } | ||
| 307 | |||
| 288 | bool VfsRawCopy(VirtualFile src, VirtualFile dest) { | 308 | bool VfsRawCopy(VirtualFile src, VirtualFile dest) { |
| 289 | if (src == nullptr || dest == nullptr) | 309 | if (src == nullptr || dest == nullptr) |
| 290 | return false; | 310 | return false; |
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index cf871edd6..fab9e2b45 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h | |||
| @@ -245,6 +245,9 @@ struct ReadOnlyVfsDirectory : public VfsDirectory { | |||
| 245 | bool Rename(std::string_view name) override; | 245 | bool Rename(std::string_view name) override; |
| 246 | }; | 246 | }; |
| 247 | 247 | ||
| 248 | // Compare the two files, byte-for-byte, in increments specificed by block_size | ||
| 249 | bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size = 0x200); | ||
| 250 | |||
| 248 | // A method that copies the raw data between two different implementations of VirtualFile. If you | 251 | // A method that copies the raw data between two different implementations of VirtualFile. If you |
| 249 | // are using the same implementation, it is probably better to use the Copy method in the parent | 252 | // are using the same implementation, it is probably better to use the Copy method in the parent |
| 250 | // directory of src/dest. | 253 | // directory of src/dest. |