summaryrefslogtreecommitdiff
path: root/src/core/file_sys
diff options
context:
space:
mode:
authorGravatar bunnei2018-08-04 14:33:11 -0400
committerGravatar GitHub2018-08-04 14:33:11 -0400
commit2b06301dbfbfe79687219bf7783a6d1b47695401 (patch)
tree222cc27ecbc7f7e86d2edef8d36436600dee7d7a /src/core/file_sys
parentMerge pull request #919 from lioncash/sign (diff)
parentAdd missing parameter to files.push_back() (diff)
downloadyuzu-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.cpp149
-rw-r--r--src/core/file_sys/card_image.h96
-rw-r--r--src/core/file_sys/content_archive.cpp193
-rw-r--r--src/core/file_sys/content_archive.h29
-rw-r--r--src/core/file_sys/vfs.cpp20
-rw-r--r--src/core/file_sys/vfs.h3
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
12namespace FileSys {
13
14XCI::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
72Loader::ResultStatus XCI::GetStatus() const {
73 return status;
74}
75
76VirtualDir XCI::GetPartition(XCIPartition partition) const {
77 return partitions[static_cast<size_t>(partition)];
78}
79
80VirtualDir XCI::GetSecurePartition() const {
81 return GetPartition(XCIPartition::Secure);
82}
83
84VirtualDir XCI::GetNormalPartition() const {
85 return GetPartition(XCIPartition::Normal);
86}
87
88VirtualDir XCI::GetUpdatePartition() const {
89 return GetPartition(XCIPartition::Update);
90}
91
92VirtualDir XCI::GetLogoPartition() const {
93 return GetPartition(XCIPartition::Logo);
94}
95
96std::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
103VirtualFile XCI::GetNCAFileByType(NCAContentType type) const {
104 auto nca = GetNCAByType(type);
105 if (nca != nullptr)
106 return nca->GetBaseFile();
107 return nullptr;
108}
109
110std::vector<std::shared_ptr<VfsFile>> XCI::GetFiles() const {
111 return {};
112}
113
114std::vector<std::shared_ptr<VfsDirectory>> XCI::GetSubdirectories() const {
115 return std::vector<std::shared_ptr<VfsDirectory>>();
116}
117
118std::string XCI::GetName() const {
119 return file->GetName();
120}
121
122std::shared_ptr<VfsDirectory> XCI::GetParentDirectory() const {
123 return file->GetContainingDirectory();
124}
125
126bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
127 return false;
128}
129
130Loader::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
146u8 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
15namespace FileSys {
16
17enum 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
26struct GamecardInfo {
27 std::array<u8, 0x70> data;
28};
29static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size.");
30
31struct 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};
53static_assert(sizeof(GamecardHeader) == 0x200, "GamecardHeader has incorrect size.");
54
55enum class XCIPartition : u8 { Update, Normal, Secure, Logo };
56
57class XCI : public ReadOnlyVfsDirectory {
58public:
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
82protected:
83 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
84
85private:
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
14namespace FileSys { 16namespace FileSys {
15 17
@@ -29,11 +31,19 @@ enum class NCASectionFilesystemType : u8 {
29struct NCASectionHeaderBlock { 31struct 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};
35static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); 37static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
36 38
39struct NCASectionRaw {
40 NCASectionHeaderBlock header;
41 std::array<u8, 0x138> block_data;
42 std::array<u8, 0x8> section_ctr;
43 INSERT_PADDING_BYTES(0xB8);
44};
45static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size.");
46
37struct PFS0Superblock { 47struct 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};
48static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); 58static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
49 59
50struct RomFSSuperblock { 60struct RomFSSuperblock {
51 NCASectionHeaderBlock header_block; 61 NCASectionHeaderBlock header_block;
52 IVFCHeader ivfc; 62 IVFCHeader ivfc;
63 INSERT_PADDING_BYTES(0x118);
53}; 64};
54static_assert(sizeof(RomFSSuperblock) == 0xE8, "RomFSSuperblock has incorrect size."); 65static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
66
67union NCASectionHeader {
68 NCASectionRaw raw;
69 PFS0Superblock pfs0;
70 RomFSSuperblock romfs;
71};
72static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
73
74bool IsValidNCA(const NCAHeader& header) {
75 // TODO(DarkLordZach): Add NCA2/NCA0 support.
76 return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
77}
78
79boost::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
111VirtualFile 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
56NCA::NCA(VirtualFile file_) : file(std::move(file_)) { 142NCA::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
269VirtualFile NCA::GetBaseFile() const {
270 return file;
271}
272
156bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { 273bool 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
17namespace FileSys { 19namespace FileSys {
18 20
21union NCASectionHeader;
22
19enum class NCAContentType : u8 { 23enum 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
31enum class NCASectionCryptoType : u8 {
32 NONE = 1,
33 XTS = 2,
34 CTR = 3,
35 BKTR = 4,
36};
37
27struct NCASectionTableEntry { 38struct 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};
54static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); 65static_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
61inline bool IsValidNCA(const NCAHeader& header) { 72bool 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
84protected: 94protected:
85 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; 95 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
86 96
87private: 97private:
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
288bool 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
288bool VfsRawCopy(VirtualFile src, VirtualFile dest) { 308bool 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
249bool 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.