summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/CMakeLists.txt6
-rw-r--r--src/core/crypto/aes_util.cpp7
-rw-r--r--src/core/crypto/ctr_encryption_layer.cpp8
-rw-r--r--src/core/crypto/key_manager.cpp172
-rw-r--r--src/core/crypto/key_manager.h68
-rw-r--r--src/core/crypto/xts_encryption_layer.cpp58
-rw-r--r--src/core/crypto/xts_encryption_layer.h25
-rw-r--r--src/core/file_sys/bis_factory.cpp11
-rw-r--r--src/core/file_sys/card_image.cpp12
-rw-r--r--src/core/file_sys/card_image.h2
-rw-r--r--src/core/file_sys/content_archive.cpp10
-rw-r--r--src/core/file_sys/content_archive.h3
-rw-r--r--src/core/file_sys/registered_cache.cpp14
-rw-r--r--src/core/file_sys/registered_cache.h4
-rw-r--r--src/core/file_sys/sdmc_factory.cpp15
-rw-r--r--src/core/file_sys/sdmc_factory.h7
-rw-r--r--src/core/file_sys/vfs.cpp7
-rw-r--r--src/core/file_sys/vfs.h4
-rw-r--r--src/core/file_sys/xts_archive.cpp169
-rw-r--r--src/core/file_sys/xts_archive.h69
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp42
-rw-r--r--src/core/hle/service/filesystem/filesystem.h6
-rw-r--r--src/core/loader/loader.cpp29
-rw-r--r--src/core/loader/loader.h14
-rw-r--r--src/core/loader/nax.cpp66
-rw-r--r--src/core/loader/nax.h48
-rw-r--r--src/core/loader/xci.cpp11
-rw-r--r--src/yuzu/configuration/config.cpp4
-rw-r--r--src/yuzu/game_list.cpp22
-rw-r--r--src/yuzu/game_list_p.h2
-rw-r--r--src/yuzu/main.cpp3
31 files changed, 821 insertions, 97 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 31a7bf6fd..a74270a0f 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -20,6 +20,8 @@ add_library(core STATIC
20 crypto/key_manager.h 20 crypto/key_manager.h
21 crypto/ctr_encryption_layer.cpp 21 crypto/ctr_encryption_layer.cpp
22 crypto/ctr_encryption_layer.h 22 crypto/ctr_encryption_layer.h
23 crypto/xts_encryption_layer.cpp
24 crypto/xts_encryption_layer.h
23 file_sys/bis_factory.cpp 25 file_sys/bis_factory.cpp
24 file_sys/bis_factory.h 26 file_sys/bis_factory.h
25 file_sys/card_image.cpp 27 file_sys/card_image.cpp
@@ -57,6 +59,8 @@ add_library(core STATIC
57 file_sys/vfs_real.h 59 file_sys/vfs_real.h
58 file_sys/vfs_vector.cpp 60 file_sys/vfs_vector.cpp
59 file_sys/vfs_vector.h 61 file_sys/vfs_vector.h
62 file_sys/xts_archive.cpp
63 file_sys/xts_archive.h
60 frontend/emu_window.cpp 64 frontend/emu_window.cpp
61 frontend/emu_window.h 65 frontend/emu_window.h
62 frontend/framebuffer_layout.cpp 66 frontend/framebuffer_layout.cpp
@@ -347,6 +351,8 @@ add_library(core STATIC
347 loader/linker.h 351 loader/linker.h
348 loader/loader.cpp 352 loader/loader.cpp
349 loader/loader.h 353 loader/loader.h
354 loader/nax.cpp
355 loader/nax.h
350 loader/nca.cpp 356 loader/nca.cpp
351 loader/nca.h 357 loader/nca.h
352 loader/nro.cpp 358 loader/nro.cpp
diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp
index a9876c83e..72e4bed67 100644
--- a/src/core/crypto/aes_util.cpp
+++ b/src/core/crypto/aes_util.cpp
@@ -99,10 +99,7 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op
99template <typename Key, size_t KeySize> 99template <typename Key, size_t KeySize>
100void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, 100void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id,
101 size_t sector_size, Op op) { 101 size_t sector_size, Op op) {
102 if (size % sector_size > 0) { 102 ASSERT_MSG(size % sector_size == 0, "XTS decryption size must be a multiple of sector size.");
103 LOG_CRITICAL(Crypto, "Data size must be a multiple of sector size.");
104 return;
105 }
106 103
107 for (size_t i = 0; i < size; i += sector_size) { 104 for (size_t i = 0; i < size; i += sector_size) {
108 SetIV(CalculateNintendoTweak(sector_id++)); 105 SetIV(CalculateNintendoTweak(sector_id++));
@@ -112,4 +109,4 @@ void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest,
112 109
113template class AESCipher<Key128>; 110template class AESCipher<Key128>;
114template class AESCipher<Key256>; 111template class AESCipher<Key256>;
115} // namespace Core::Crypto \ No newline at end of file 112} // namespace Core::Crypto
diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp
index 106db02b3..3ea60dbd0 100644
--- a/src/core/crypto/ctr_encryption_layer.cpp
+++ b/src/core/crypto/ctr_encryption_layer.cpp
@@ -20,10 +20,8 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
20 if (sector_offset == 0) { 20 if (sector_offset == 0) {
21 UpdateIV(base_offset + offset); 21 UpdateIV(base_offset + offset);
22 std::vector<u8> raw = base->ReadBytes(length, offset); 22 std::vector<u8> raw = base->ReadBytes(length, offset);
23 if (raw.size() != length) 23 cipher.Transcode(raw.data(), raw.size(), data, Op::Decrypt);
24 return Read(data, raw.size(), offset); 24 return raw.size();
25 cipher.Transcode(raw.data(), length, data, Op::Decrypt);
26 return length;
27 } 25 }
28 26
29 // offset does not fall on block boundary (0x10) 27 // offset does not fall on block boundary (0x10)
@@ -34,7 +32,7 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
34 32
35 if (length + sector_offset < 0x10) { 33 if (length + sector_offset < 0x10) {
36 std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read)); 34 std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read));
37 return read; 35 return std::min<u64>(length, read);
38 } 36 }
39 std::memcpy(data, block.data() + sector_offset, read); 37 std::memcpy(data, block.data() + sector_offset, read);
40 return read + Read(data + read, length - read, offset + read); 38 return read + Read(data + read, length - read, offset + read);
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index db8b22c85..0b6c07de8 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -12,11 +12,112 @@
12#include "common/file_util.h" 12#include "common/file_util.h"
13#include "common/hex_util.h" 13#include "common/hex_util.h"
14#include "common/logging/log.h" 14#include "common/logging/log.h"
15#include "core/crypto/aes_util.h"
15#include "core/crypto/key_manager.h" 16#include "core/crypto/key_manager.h"
16#include "core/settings.h" 17#include "core/settings.h"
17 18
18namespace Core::Crypto { 19namespace Core::Crypto {
19 20
21Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
22 Key128 out{};
23
24 AESCipher<Key128> cipher1(master, Mode::ECB);
25 cipher1.Transcode(kek_seed.data(), kek_seed.size(), out.data(), Op::Decrypt);
26 AESCipher<Key128> cipher2(out, Mode::ECB);
27 cipher2.Transcode(source.data(), source.size(), out.data(), Op::Decrypt);
28
29 if (key_seed != Key128{}) {
30 AESCipher<Key128> cipher3(out, Mode::ECB);
31 cipher3.Transcode(key_seed.data(), key_seed.size(), out.data(), Op::Decrypt);
32 }
33
34 return out;
35}
36
37boost::optional<Key128> DeriveSDSeed() {
38 const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
39 "/system/save/8000000000000043",
40 "rb+");
41 if (!save_43.IsOpen())
42 return boost::none;
43 const FileUtil::IOFile sd_private(
44 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+");
45 if (!sd_private.IsOpen())
46 return boost::none;
47
48 sd_private.Seek(0, SEEK_SET);
49 std::array<u8, 0x10> private_seed{};
50 if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != 0x10)
51 return boost::none;
52
53 std::array<u8, 0x10> buffer{};
54 size_t offset = 0;
55 for (; offset + 0x10 < save_43.GetSize(); ++offset) {
56 save_43.Seek(offset, SEEK_SET);
57 save_43.ReadBytes(buffer.data(), buffer.size());
58 if (buffer == private_seed)
59 break;
60 }
61
62 if (offset + 0x10 >= save_43.GetSize())
63 return boost::none;
64
65 Key128 seed{};
66 save_43.Seek(offset + 0x10, SEEK_SET);
67 save_43.ReadBytes(seed.data(), seed.size());
68 return seed;
69}
70
71Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys) {
72 if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK)))
73 return Loader::ResultStatus::ErrorMissingSDKEKSource;
74 if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration)))
75 return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource;
76 if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)))
77 return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource;
78
79 const auto sd_kek_source =
80 keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK));
81 const auto aes_kek_gen =
82 keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration));
83 const auto aes_key_gen =
84 keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration));
85 const auto master_00 = keys.GetKey(S128KeyType::Master);
86 const auto sd_kek =
87 GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen);
88
89 if (!keys.HasKey(S128KeyType::SDSeed))
90 return Loader::ResultStatus::ErrorMissingSDSeed;
91 const auto sd_seed = keys.GetKey(S128KeyType::SDSeed);
92
93 if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save)))
94 return Loader::ResultStatus::ErrorMissingSDSaveKeySource;
95 if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA)))
96 return Loader::ResultStatus::ErrorMissingSDNCAKeySource;
97
98 std::array<Key256, 2> sd_key_sources{
99 keys.GetKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save)),
100 keys.GetKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA)),
101 };
102
103 // Combine sources and seed
104 for (auto& source : sd_key_sources) {
105 for (size_t i = 0; i < source.size(); ++i)
106 source[i] ^= sd_seed[i & 0xF];
107 }
108
109 AESCipher<Key128> cipher(sd_kek, Mode::ECB);
110 // The transform manipulates sd_keys as part of the Transcode, so the return/output is
111 // unnecessary. This does not alter sd_keys_sources.
112 std::transform(sd_key_sources.begin(), sd_key_sources.end(), sd_keys.begin(),
113 sd_key_sources.begin(), [&cipher](const Key256& source, Key256& out) {
114 cipher.Transcode(source.data(), source.size(), out.data(), Op::Decrypt);
115 return source; ///< Return unaltered source to satisfy output requirement.
116 });
117
118 return Loader::ResultStatus::Success;
119}
120
20KeyManager::KeyManager() { 121KeyManager::KeyManager() {
21 // Initialize keys 122 // Initialize keys
22 const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); 123 const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
@@ -24,12 +125,15 @@ KeyManager::KeyManager() {
24 if (Settings::values.use_dev_keys) { 125 if (Settings::values.use_dev_keys) {
25 dev_mode = true; 126 dev_mode = true;
26 AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); 127 AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false);
128 AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "dev.keys_autogenerated", false);
27 } else { 129 } else {
28 dev_mode = false; 130 dev_mode = false;
29 AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false); 131 AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false);
132 AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "prod.keys_autogenerated", false);
30 } 133 }
31 134
32 AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true); 135 AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true);
136 AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true);
33} 137}
34 138
35void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { 139void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
@@ -56,17 +160,17 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
56 u128 rights_id{}; 160 u128 rights_id{};
57 std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size()); 161 std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size());
58 Key128 key = Common::HexStringToArray<16>(out[1]); 162 Key128 key = Common::HexStringToArray<16>(out[1]);
59 SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); 163 s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key;
60 } else { 164 } else {
61 std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower); 165 std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower);
62 if (s128_file_id.find(out[0]) != s128_file_id.end()) { 166 if (s128_file_id.find(out[0]) != s128_file_id.end()) {
63 const auto index = s128_file_id.at(out[0]); 167 const auto index = s128_file_id.at(out[0]);
64 Key128 key = Common::HexStringToArray<16>(out[1]); 168 Key128 key = Common::HexStringToArray<16>(out[1]);
65 SetKey(index.type, key, index.field1, index.field2); 169 s128_keys[{index.type, index.field1, index.field2}] = key;
66 } else if (s256_file_id.find(out[0]) != s256_file_id.end()) { 170 } else if (s256_file_id.find(out[0]) != s256_file_id.end()) {
67 const auto index = s256_file_id.at(out[0]); 171 const auto index = s256_file_id.at(out[0]);
68 Key256 key = Common::HexStringToArray<32>(out[1]); 172 Key256 key = Common::HexStringToArray<32>(out[1]);
69 SetKey(index.type, key, index.field1, index.field2); 173 s256_keys[{index.type, index.field1, index.field2}] = key;
70 } 174 }
71 } 175 }
72 } 176 }
@@ -100,11 +204,50 @@ Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const {
100 return s256_keys.at({id, field1, field2}); 204 return s256_keys.at({id, field1, field2});
101} 205}
102 206
207template <size_t Size>
208void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname,
209 const std::array<u8, Size>& key) {
210 const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
211 std::string filename = "title.keys_autogenerated";
212 if (!title_key)
213 filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated";
214 const auto add_info_text = !FileUtil::Exists(yuzu_keys_dir + DIR_SEP + filename);
215 FileUtil::CreateFullPath(yuzu_keys_dir + DIR_SEP + filename);
216 std::ofstream file(yuzu_keys_dir + DIR_SEP + filename, std::ios::app);
217 if (!file.is_open())
218 return;
219 if (add_info_text) {
220 file
221 << "# This file is autogenerated by Yuzu\n"
222 << "# It serves to store keys that were automatically generated from the normal keys\n"
223 << "# If you are experiencing issues involving keys, it may help to delete this file\n";
224 }
225
226 file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key));
227 AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, title_key);
228}
229
103void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { 230void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
231 const auto iter = std::find_if(
232 s128_file_id.begin(), s128_file_id.end(),
233 [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) {
234 return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
235 std::tie(id, field1, field2);
236 });
237 if (iter != s128_file_id.end())
238 WriteKeyToFile(id == S128KeyType::Titlekey, iter->first, key);
104 s128_keys[{id, field1, field2}] = key; 239 s128_keys[{id, field1, field2}] = key;
105} 240}
106 241
107void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { 242void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
243 const auto iter = std::find_if(
244 s256_file_id.begin(), s256_file_id.end(),
245 [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) {
246 return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
247 std::tie(id, field1, field2);
248 });
249 if (iter != s256_file_id.end())
250 WriteKeyToFile(false, iter->first, key);
108 s256_keys[{id, field1, field2}] = key; 251 s256_keys[{id, field1, field2}] = key;
109} 252}
110 253
@@ -125,7 +268,16 @@ bool KeyManager::KeyFileExists(bool title) {
125 FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys"); 268 FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys");
126} 269}
127 270
128const std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { 271void KeyManager::DeriveSDSeedLazy() {
272 if (HasKey(S128KeyType::SDSeed))
273 return;
274
275 const auto res = DeriveSDSeed();
276 if (res != boost::none)
277 SetKey(S128KeyType::SDSeed, res.get());
278}
279
280const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
129 {"master_key_00", {S128KeyType::Master, 0, 0}}, 281 {"master_key_00", {S128KeyType::Master, 0, 0}},
130 {"master_key_01", {S128KeyType::Master, 1, 0}}, 282 {"master_key_01", {S128KeyType::Master, 1, 0}},
131 {"master_key_02", {S128KeyType::Master, 2, 0}}, 283 {"master_key_02", {S128KeyType::Master, 2, 0}},
@@ -167,11 +319,17 @@ const std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_fi
167 {"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}}, 319 {"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}},
168 {"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}}, 320 {"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}},
169 {"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}}, 321 {"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}},
322 {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK), 0}},
323 {"aes_kek_generation_source",
324 {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration), 0}},
325 {"aes_key_generation_source",
326 {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}},
327 {"sd_seed", {S128KeyType::SDSeed, 0, 0}},
170}; 328};
171 329
172const std::unordered_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = { 330const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
173 {"header_key", {S256KeyType::Header, 0, 0}}, 331 {"header_key", {S256KeyType::Header, 0, 0}},
174 {"sd_card_save_key", {S256KeyType::SDSave, 0, 0}}, 332 {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}},
175 {"sd_card_nca_key", {S256KeyType::SDNCA, 0, 0}}, 333 {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}},
176}; 334};
177} // namespace Core::Crypto 335} // namespace Core::Crypto
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 0c62d4421..7ca3e6cbc 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -6,11 +6,13 @@
6 6
7#include <array> 7#include <array>
8#include <string> 8#include <string>
9#include <string_view>
9#include <type_traits> 10#include <type_traits>
10#include <unordered_map>
11#include <vector> 11#include <vector>
12#include <boost/container/flat_map.hpp>
12#include <fmt/format.h> 13#include <fmt/format.h>
13#include "common/common_types.h" 14#include "common/common_types.h"
15#include "core/loader/loader.h"
14 16
15namespace Core::Crypto { 17namespace Core::Crypto {
16 18
@@ -22,9 +24,8 @@ static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
22static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big."); 24static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big.");
23 25
24enum class S256KeyType : u64 { 26enum class S256KeyType : u64 {
25 Header, // 27 Header, //
26 SDSave, // 28 SDKeySource, // f1=SDKeyType
27 SDNCA, //
28}; 29};
29 30
30enum class S128KeyType : u64 { 31enum class S128KeyType : u64 {
@@ -36,6 +37,7 @@ enum class S128KeyType : u64 {
36 KeyArea, // f1=crypto revision f2=type {app, ocean, system} 37 KeyArea, // f1=crypto revision f2=type {app, ocean, system}
37 SDSeed, // 38 SDSeed, //
38 Titlekey, // f1=rights id LSB f2=rights id MSB 39 Titlekey, // f1=rights id LSB f2=rights id MSB
40 Source, // f1=source type, f2= sub id
39}; 41};
40 42
41enum class KeyAreaKeyType : u8 { 43enum class KeyAreaKeyType : u8 {
@@ -44,6 +46,17 @@ enum class KeyAreaKeyType : u8 {
44 System, 46 System,
45}; 47};
46 48
49enum class SourceKeyType : u8 {
50 SDKEK,
51 AESKEKGeneration,
52 AESKeyGeneration,
53};
54
55enum class SDKeyType : u8 {
56 Save,
57 NCA,
58};
59
47template <typename KeyType> 60template <typename KeyType>
48struct KeyIndex { 61struct KeyIndex {
49 KeyType type; 62 KeyType type;
@@ -59,34 +72,12 @@ struct KeyIndex {
59 } 72 }
60}; 73};
61 74
62// The following two (== and hash) are so KeyIndex can be a key in unordered_map 75// boost flat_map requires operator< for O(log(n)) lookups.
63
64template <typename KeyType> 76template <typename KeyType>
65bool operator==(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) { 77bool operator<(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
66 return std::tie(lhs.type, lhs.field1, lhs.field2) == std::tie(rhs.type, rhs.field1, rhs.field2); 78 return std::tie(lhs.type, lhs.field1, lhs.field2) < std::tie(rhs.type, rhs.field1, rhs.field2);
67} 79}
68 80
69template <typename KeyType>
70bool operator!=(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
71 return !operator==(lhs, rhs);
72}
73
74} // namespace Core::Crypto
75
76namespace std {
77template <typename KeyType>
78struct hash<Core::Crypto::KeyIndex<KeyType>> {
79 size_t operator()(const Core::Crypto::KeyIndex<KeyType>& k) const {
80 using std::hash;
81
82 return ((hash<u64>()(static_cast<u64>(k.type)) ^ (hash<u64>()(k.field1) << 1)) >> 1) ^
83 (hash<u64>()(k.field2) << 1);
84 }
85};
86} // namespace std
87
88namespace Core::Crypto {
89
90class KeyManager { 81class KeyManager {
91public: 82public:
92 KeyManager(); 83 KeyManager();
@@ -102,16 +93,27 @@ public:
102 93
103 static bool KeyFileExists(bool title); 94 static bool KeyFileExists(bool title);
104 95
96 // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system save
97 // 8*43 and the private file to exist.
98 void DeriveSDSeedLazy();
99
105private: 100private:
106 std::unordered_map<KeyIndex<S128KeyType>, Key128> s128_keys; 101 boost::container::flat_map<KeyIndex<S128KeyType>, Key128> s128_keys;
107 std::unordered_map<KeyIndex<S256KeyType>, Key256> s256_keys; 102 boost::container::flat_map<KeyIndex<S256KeyType>, Key256> s256_keys;
108 103
109 bool dev_mode; 104 bool dev_mode;
110 void LoadFromFile(const std::string& filename, bool is_title_keys); 105 void LoadFromFile(const std::string& filename, bool is_title_keys);
111 void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, 106 void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
112 const std::string& filename, bool title); 107 const std::string& filename, bool title);
108 template <size_t Size>
109 void WriteKeyToFile(bool title_key, std::string_view keyname, const std::array<u8, Size>& key);
113 110
114 static const std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id; 111 static const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
115 static const std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id; 112 static const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
116}; 113};
114
115Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed);
116boost::optional<Key128> DeriveSDSeed();
117Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys);
118
117} // namespace Core::Crypto 119} // namespace Core::Crypto
diff --git a/src/core/crypto/xts_encryption_layer.cpp b/src/core/crypto/xts_encryption_layer.cpp
new file mode 100644
index 000000000..c10832cfe
--- /dev/null
+++ b/src/core/crypto/xts_encryption_layer.cpp
@@ -0,0 +1,58 @@
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 <algorithm>
6#include <cstring>
7#include "common/assert.h"
8#include "core/crypto/xts_encryption_layer.h"
9
10namespace Core::Crypto {
11
12constexpr u64 XTS_SECTOR_SIZE = 0x4000;
13
14XTSEncryptionLayer::XTSEncryptionLayer(FileSys::VirtualFile base_, Key256 key_)
15 : EncryptionLayer(std::move(base_)), cipher(key_, Mode::XTS) {}
16
17size_t XTSEncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
18 if (length == 0)
19 return 0;
20
21 const auto sector_offset = offset & 0x3FFF;
22 if (sector_offset == 0) {
23 if (length % XTS_SECTOR_SIZE == 0) {
24 std::vector<u8> raw = base->ReadBytes(length, offset);
25 cipher.XTSTranscode(raw.data(), raw.size(), data, offset / XTS_SECTOR_SIZE,
26 XTS_SECTOR_SIZE, Op::Decrypt);
27 return raw.size();
28 }
29 if (length > XTS_SECTOR_SIZE) {
30 const auto rem = length % XTS_SECTOR_SIZE;
31 const auto read = length - rem;
32 return Read(data, read, offset) + Read(data + read, rem, offset + read);
33 }
34 std::vector<u8> buffer = base->ReadBytes(XTS_SECTOR_SIZE, offset);
35 if (buffer.size() < XTS_SECTOR_SIZE)
36 buffer.resize(XTS_SECTOR_SIZE);
37 cipher.XTSTranscode(buffer.data(), buffer.size(), buffer.data(), offset / XTS_SECTOR_SIZE,
38 XTS_SECTOR_SIZE, Op::Decrypt);
39 std::memcpy(data, buffer.data(), std::min(buffer.size(), length));
40 return std::min(buffer.size(), length);
41 }
42
43 // offset does not fall on block boundary (0x4000)
44 std::vector<u8> block = base->ReadBytes(0x4000, offset - sector_offset);
45 if (block.size() < XTS_SECTOR_SIZE)
46 block.resize(XTS_SECTOR_SIZE);
47 cipher.XTSTranscode(block.data(), block.size(), block.data(),
48 (offset - sector_offset) / XTS_SECTOR_SIZE, XTS_SECTOR_SIZE, Op::Decrypt);
49 const size_t read = XTS_SECTOR_SIZE - sector_offset;
50
51 if (length + sector_offset < XTS_SECTOR_SIZE) {
52 std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read));
53 return std::min<u64>(length, read);
54 }
55 std::memcpy(data, block.data() + sector_offset, read);
56 return read + Read(data + read, length - read, offset + read);
57}
58} // namespace Core::Crypto
diff --git a/src/core/crypto/xts_encryption_layer.h b/src/core/crypto/xts_encryption_layer.h
new file mode 100644
index 000000000..7a1f1dc64
--- /dev/null
+++ b/src/core/crypto/xts_encryption_layer.h
@@ -0,0 +1,25 @@
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 "core/crypto/aes_util.h"
8#include "core/crypto/encryption_layer.h"
9#include "core/crypto/key_manager.h"
10
11namespace Core::Crypto {
12
13// Sits on top of a VirtualFile and provides XTS-mode AES decription.
14class XTSEncryptionLayer : public EncryptionLayer {
15public:
16 XTSEncryptionLayer(FileSys::VirtualFile base, Key256 key);
17
18 size_t Read(u8* data, size_t length, size_t offset) const override;
19
20private:
21 // Must be mutable as operations modify cipher contexts.
22 mutable AESCipher<Key256> cipher;
23};
24
25} // namespace Core::Crypto
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index ae4e33800..08a7cea5a 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -6,19 +6,12 @@
6 6
7namespace FileSys { 7namespace FileSys {
8 8
9static VirtualDir GetOrCreateDirectory(const VirtualDir& dir, std::string_view path) {
10 const auto res = dir->GetDirectoryRelative(path);
11 if (res == nullptr)
12 return dir->CreateDirectoryRelative(path);
13 return res;
14}
15
16BISFactory::BISFactory(VirtualDir nand_root_) 9BISFactory::BISFactory(VirtualDir nand_root_)
17 : nand_root(std::move(nand_root_)), 10 : nand_root(std::move(nand_root_)),
18 sysnand_cache(std::make_shared<RegisteredCache>( 11 sysnand_cache(std::make_shared<RegisteredCache>(
19 GetOrCreateDirectory(nand_root, "/system/Contents/registered"))), 12 GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
20 usrnand_cache(std::make_shared<RegisteredCache>( 13 usrnand_cache(std::make_shared<RegisteredCache>(
21 GetOrCreateDirectory(nand_root, "/user/Contents/registered"))) {} 14 GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {}
22 15
23std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const { 16std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
24 return sysnand_cache; 17 return sysnand_cache;
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 1d7c7fb10..d61a2ebe1 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -43,6 +43,8 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
43 partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw); 43 partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw);
44 } 44 }
45 45
46 program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
47
46 auto result = AddNCAFromPartition(XCIPartition::Secure); 48 auto result = AddNCAFromPartition(XCIPartition::Secure);
47 if (result != Loader::ResultStatus::Success) { 49 if (result != Loader::ResultStatus::Success) {
48 status = result; 50 status = result;
@@ -76,6 +78,10 @@ Loader::ResultStatus XCI::GetStatus() const {
76 return status; 78 return status;
77} 79}
78 80
81Loader::ResultStatus XCI::GetProgramNCAStatus() const {
82 return program_nca_status;
83}
84
79VirtualDir XCI::GetPartition(XCIPartition partition) const { 85VirtualDir XCI::GetPartition(XCIPartition partition) const {
80 return partitions[static_cast<size_t>(partition)]; 86 return partitions[static_cast<size_t>(partition)];
81} 87}
@@ -143,6 +149,12 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
143 if (file->GetExtension() != "nca") 149 if (file->GetExtension() != "nca")
144 continue; 150 continue;
145 auto nca = std::make_shared<NCA>(file); 151 auto nca = std::make_shared<NCA>(file);
152 // TODO(DarkLordZach): Add proper Rev1+ Support
153 if (nca->IsUpdate())
154 continue;
155 if (nca->GetType() == NCAContentType::Program) {
156 program_nca_status = nca->GetStatus();
157 }
146 if (nca->GetStatus() == Loader::ResultStatus::Success) { 158 if (nca->GetStatus() == Loader::ResultStatus::Success) {
147 ncas.push_back(std::move(nca)); 159 ncas.push_back(std::move(nca));
148 } else { 160 } else {
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index a03d5264e..54ab828d1 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -59,6 +59,7 @@ public:
59 explicit XCI(VirtualFile file); 59 explicit XCI(VirtualFile file);
60 60
61 Loader::ResultStatus GetStatus() const; 61 Loader::ResultStatus GetStatus() const;
62 Loader::ResultStatus GetProgramNCAStatus() const;
62 63
63 u8 GetFormatVersion() const; 64 u8 GetFormatVersion() const;
64 65
@@ -90,6 +91,7 @@ private:
90 GamecardHeader header{}; 91 GamecardHeader header{};
91 92
92 Loader::ResultStatus status; 93 Loader::ResultStatus status;
94 Loader::ResultStatus program_nca_status;
93 95
94 std::vector<VirtualDir> partitions; 96 std::vector<VirtualDir> partitions;
95 std::vector<std::shared_ptr<NCA>> ncas; 97 std::vector<std::shared_ptr<NCA>> ncas;
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 47afcad9b..e8b5d6ece 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -178,7 +178,7 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
178 return std::static_pointer_cast<VfsFile>(out); 178 return std::static_pointer_cast<VfsFile>(out);
179 } 179 }
180 case NCASectionCryptoType::XTS: 180 case NCASectionCryptoType::XTS:
181 // TODO(DarkLordZach): Implement XTSEncryptionLayer. 181 // TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs
182 default: 182 default:
183 LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", 183 LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}",
184 static_cast<u8>(s_header.raw.header.crypto_type)); 184 static_cast<u8>(s_header.raw.header.crypto_type));
@@ -258,6 +258,10 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
258 file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); 258 file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
259 } 259 }
260 260
261 is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
262 return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
263 }) != sections.end();
264
261 for (std::ptrdiff_t i = 0; i < number_sections; ++i) { 265 for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
262 auto section = sections[i]; 266 auto section = sections[i];
263 267
@@ -358,6 +362,10 @@ VirtualFile NCA::GetBaseFile() const {
358 return file; 362 return file;
359} 363}
360 364
365bool NCA::IsUpdate() const {
366 return is_update;
367}
368
361bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { 369bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
362 return false; 370 return false;
363} 371}
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 4b74c54ec..b961cfde7 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -93,6 +93,8 @@ public:
93 93
94 VirtualFile GetBaseFile() const; 94 VirtualFile GetBaseFile() const;
95 95
96 bool IsUpdate() const;
97
96protected: 98protected:
97 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; 99 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
98 100
@@ -111,6 +113,7 @@ private:
111 113
112 NCAHeader header{}; 114 NCAHeader header{};
113 bool has_rights_id{}; 115 bool has_rights_id{};
116 bool is_update{};
114 117
115 Loader::ResultStatus status{}; 118 Loader::ResultStatus status{};
116 119
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index e90dc6695..a02efc71e 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -254,6 +254,8 @@ RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction
254 Refresh(); 254 Refresh();
255} 255}
256 256
257RegisteredCache::~RegisteredCache() = default;
258
257bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const { 259bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const {
258 return GetEntryRaw(title_id, type) != nullptr; 260 return GetEntryRaw(title_id, type) != nullptr;
259} 261}
@@ -262,6 +264,18 @@ bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
262 return GetEntryRaw(entry) != nullptr; 264 return GetEntryRaw(entry) != nullptr;
263} 265}
264 266
267VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
268 const auto id = GetNcaIDFromMetadata(title_id, type);
269 if (id == boost::none)
270 return nullptr;
271
272 return GetFileAtID(id.get());
273}
274
275VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const {
276 return GetEntryUnparsed(entry.title_id, entry.type);
277}
278
265VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const { 279VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
266 const auto id = GetNcaIDFromMetadata(title_id, type); 280 const auto id = GetNcaIDFromMetadata(title_id, type);
267 if (id == boost::none) 281 if (id == boost::none)
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index a7c51a59c..7b8955dfa 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -63,12 +63,16 @@ public:
63 explicit RegisteredCache(VirtualDir dir, 63 explicit RegisteredCache(VirtualDir dir,
64 RegisteredCacheParsingFunction parsing_function = 64 RegisteredCacheParsingFunction parsing_function =
65 [](const VirtualFile& file, const NcaID& id) { return file; }); 65 [](const VirtualFile& file, const NcaID& id) { return file; });
66 ~RegisteredCache();
66 67
67 void Refresh(); 68 void Refresh();
68 69
69 bool HasEntry(u64 title_id, ContentRecordType type) const; 70 bool HasEntry(u64 title_id, ContentRecordType type) const;
70 bool HasEntry(RegisteredCacheEntry entry) const; 71 bool HasEntry(RegisteredCacheEntry entry) const;
71 72
73 VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
74 VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
75
72 VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const; 76 VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
73 VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const; 77 VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
74 78
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
index d1acf379f..d66a9c9a4 100644
--- a/src/core/file_sys/sdmc_factory.cpp
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -3,14 +3,27 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <memory> 5#include <memory>
6#include "core/file_sys/registered_cache.h"
6#include "core/file_sys/sdmc_factory.h" 7#include "core/file_sys/sdmc_factory.h"
8#include "core/file_sys/xts_archive.h"
7 9
8namespace FileSys { 10namespace FileSys {
9 11
10SDMCFactory::SDMCFactory(VirtualDir dir) : dir(std::move(dir)) {} 12SDMCFactory::SDMCFactory(VirtualDir dir_)
13 : dir(std::move(dir_)), contents(std::make_shared<RegisteredCache>(
14 GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"),
15 [](const VirtualFile& file, const NcaID& id) {
16 return std::make_shared<NAX>(file, id)->GetDecrypted();
17 })) {}
18
19SDMCFactory::~SDMCFactory() = default;
11 20
12ResultVal<VirtualDir> SDMCFactory::Open() { 21ResultVal<VirtualDir> SDMCFactory::Open() {
13 return MakeResult<VirtualDir>(dir); 22 return MakeResult<VirtualDir>(dir);
14} 23}
15 24
25std::shared_ptr<RegisteredCache> SDMCFactory::GetSDMCContents() const {
26 return contents;
27}
28
16} // namespace FileSys 29} // namespace FileSys
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
index 245060690..ea12149de 100644
--- a/src/core/file_sys/sdmc_factory.h
+++ b/src/core/file_sys/sdmc_factory.h
@@ -4,20 +4,27 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <memory>
7#include "core/file_sys/vfs.h" 8#include "core/file_sys/vfs.h"
8#include "core/hle/result.h" 9#include "core/hle/result.h"
9 10
10namespace FileSys { 11namespace FileSys {
11 12
13class RegisteredCache;
14
12/// File system interface to the SDCard archive 15/// File system interface to the SDCard archive
13class SDMCFactory { 16class SDMCFactory {
14public: 17public:
15 explicit SDMCFactory(VirtualDir dir); 18 explicit SDMCFactory(VirtualDir dir);
19 ~SDMCFactory();
16 20
17 ResultVal<VirtualDir> Open(); 21 ResultVal<VirtualDir> Open();
22 std::shared_ptr<RegisteredCache> GetSDMCContents() const;
18 23
19private: 24private:
20 VirtualDir dir; 25 VirtualDir dir;
26
27 std::shared_ptr<RegisteredCache> contents;
21}; 28};
22 29
23} // namespace FileSys 30} // namespace FileSys
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index b915b4c11..146c839f4 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -462,4 +462,11 @@ bool VfsRawCopy(VirtualFile src, VirtualFile dest) {
462 std::vector<u8> data = src->ReadAllBytes(); 462 std::vector<u8> data = src->ReadAllBytes();
463 return dest->WriteBytes(data, 0) == data.size(); 463 return dest->WriteBytes(data, 0) == data.size();
464} 464}
465
466VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) {
467 const auto res = rel->GetDirectoryRelative(path);
468 if (res == nullptr)
469 return rel->CreateDirectoryRelative(path);
470 return res;
471}
465} // namespace FileSys 472} // namespace FileSys
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h
index 22db08b59..5142a3e86 100644
--- a/src/core/file_sys/vfs.h
+++ b/src/core/file_sys/vfs.h
@@ -318,4 +318,8 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block
318// directory of src/dest. 318// directory of src/dest.
319bool VfsRawCopy(VirtualFile src, VirtualFile dest); 319bool VfsRawCopy(VirtualFile src, VirtualFile dest);
320 320
321// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not
322// it attempts to create it and returns the new dir or nullptr on failure.
323VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path);
324
321} // namespace FileSys 325} // namespace FileSys
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp
new file mode 100644
index 000000000..552835738
--- /dev/null
+++ b/src/core/file_sys/xts_archive.cpp
@@ -0,0 +1,169 @@
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 <algorithm>
6#include <array>
7#include <cstring>
8#include <regex>
9#include <string>
10#include <mbedtls/md.h>
11#include <mbedtls/sha256.h>
12#include "common/assert.h"
13#include "common/hex_util.h"
14#include "common/logging/log.h"
15#include "core/crypto/aes_util.h"
16#include "core/crypto/xts_encryption_layer.h"
17#include "core/file_sys/partition_filesystem.h"
18#include "core/file_sys/vfs_offset.h"
19#include "core/file_sys/xts_archive.h"
20#include "core/loader/loader.h"
21
22namespace FileSys {
23
24constexpr u64 NAX_HEADER_PADDING_SIZE = 0x4000;
25
26template <typename SourceData, typename SourceKey, typename Destination>
27static bool CalculateHMAC256(Destination* out, const SourceKey* key, size_t key_length,
28 const SourceData* data, size_t data_length) {
29 mbedtls_md_context_t context;
30 mbedtls_md_init(&context);
31
32 const auto key_f = reinterpret_cast<const u8*>(key);
33 const std::vector<u8> key_v(key_f, key_f + key_length);
34
35 if (mbedtls_md_setup(&context, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1) ||
36 mbedtls_md_hmac_starts(&context, reinterpret_cast<const u8*>(key), key_length) ||
37 mbedtls_md_hmac_update(&context, reinterpret_cast<const u8*>(data), data_length) ||
38 mbedtls_md_hmac_finish(&context, reinterpret_cast<u8*>(out))) {
39 mbedtls_md_free(&context);
40 return false;
41 }
42
43 mbedtls_md_free(&context);
44 return true;
45}
46
47NAX::NAX(VirtualFile file_) : file(std::move(file_)), header(std::make_unique<NAXHeader>()) {
48 std::string path = FileUtil::SanitizePath(file->GetFullPath());
49 static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca",
50 std::regex_constants::ECMAScript |
51 std::regex_constants::icase);
52 std::smatch match;
53 if (!std::regex_search(path, match, nax_path_regex)) {
54 status = Loader::ResultStatus::ErrorBadNAXFilePath;
55 return;
56 }
57
58 std::string two_dir = match[1];
59 std::string nca_id = match[2];
60 std::transform(two_dir.begin(), two_dir.end(), two_dir.begin(), ::toupper);
61 std::transform(nca_id.begin(), nca_id.end(), nca_id.begin(), ::tolower);
62
63 status = Parse(fmt::format("/registered/{}/{}.nca", two_dir, nca_id));
64}
65
66NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id)
67 : file(std::move(file_)), header(std::make_unique<NAXHeader>()) {
68 Core::Crypto::SHA256Hash hash{};
69 mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
70 status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0],
71 Common::HexArrayToString(nca_id, false)));
72}
73
74Loader::ResultStatus NAX::Parse(std::string_view path) {
75 if (file->ReadObject(header.get()) != sizeof(NAXHeader))
76 return Loader::ResultStatus::ErrorBadNAXHeader;
77
78 if (header->magic != Common::MakeMagic('N', 'A', 'X', '0'))
79 return Loader::ResultStatus::ErrorBadNAXHeader;
80
81 if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size)
82 return Loader::ResultStatus::ErrorIncorrectNAXFileSize;
83
84 keys.DeriveSDSeedLazy();
85 std::array<Core::Crypto::Key256, 2> sd_keys{};
86 const auto sd_keys_res = Core::Crypto::DeriveSDKeys(sd_keys, keys);
87 if (sd_keys_res != Loader::ResultStatus::Success) {
88 return sd_keys_res;
89 }
90
91 const auto enc_keys = header->key_area;
92
93 size_t i = 0;
94 for (; i < sd_keys.size(); ++i) {
95 std::array<Core::Crypto::Key128, 2> nax_keys{};
96 if (!CalculateHMAC256(nax_keys.data(), sd_keys[i].data(), 0x10, std::string(path).c_str(),
97 path.size())) {
98 return Loader::ResultStatus::ErrorNAXKeyHMACFailed;
99 }
100
101 for (size_t j = 0; j < nax_keys.size(); ++j) {
102 Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(nax_keys[j],
103 Core::Crypto::Mode::ECB);
104 cipher.Transcode(enc_keys[j].data(), 0x10, header->key_area[j].data(),
105 Core::Crypto::Op::Decrypt);
106 }
107
108 Core::Crypto::SHA256Hash validation{};
109 if (!CalculateHMAC256(validation.data(), &header->magic, 0x60, sd_keys[i].data() + 0x10,
110 0x10)) {
111 return Loader::ResultStatus::ErrorNAXValidationHMACFailed;
112 }
113 if (header->hmac == validation)
114 break;
115 }
116
117 if (i == 2) {
118 return Loader::ResultStatus::ErrorNAXKeyDerivationFailed;
119 }
120
121 type = static_cast<NAXContentType>(i);
122
123 Core::Crypto::Key256 final_key{};
124 std::memcpy(final_key.data(), &header->key_area, final_key.size());
125 const auto enc_file =
126 std::make_shared<OffsetVfsFile>(file, header->file_size, NAX_HEADER_PADDING_SIZE);
127 dec_file = std::make_shared<Core::Crypto::XTSEncryptionLayer>(enc_file, final_key);
128
129 return Loader::ResultStatus::Success;
130}
131
132Loader::ResultStatus NAX::GetStatus() const {
133 return status;
134}
135
136VirtualFile NAX::GetDecrypted() const {
137 return dec_file;
138}
139
140std::shared_ptr<NCA> NAX::AsNCA() const {
141 if (type == NAXContentType::NCA)
142 return std::make_shared<NCA>(GetDecrypted());
143 return nullptr;
144}
145
146NAXContentType NAX::GetContentType() const {
147 return type;
148}
149
150std::vector<std::shared_ptr<VfsFile>> NAX::GetFiles() const {
151 return {dec_file};
152}
153
154std::vector<std::shared_ptr<VfsDirectory>> NAX::GetSubdirectories() const {
155 return {};
156}
157
158std::string NAX::GetName() const {
159 return file->GetName();
160}
161
162std::shared_ptr<VfsDirectory> NAX::GetParentDirectory() const {
163 return file->GetContainingDirectory();
164}
165
166bool NAX::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
167 return false;
168}
169} // namespace FileSys
diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h
new file mode 100644
index 000000000..55d2154a6
--- /dev/null
+++ b/src/core/file_sys/xts_archive.h
@@ -0,0 +1,69 @@
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/crypto/key_manager.h"
12#include "core/file_sys/content_archive.h"
13#include "core/file_sys/vfs.h"
14#include "core/loader/loader.h"
15
16namespace FileSys {
17
18struct NAXHeader {
19 std::array<u8, 0x20> hmac;
20 u64_le magic;
21 std::array<Core::Crypto::Key128, 2> key_area;
22 u64_le file_size;
23 INSERT_PADDING_BYTES(0x30);
24};
25static_assert(sizeof(NAXHeader) == 0x80, "NAXHeader has incorrect size.");
26
27enum class NAXContentType : u8 {
28 Save = 0,
29 NCA = 1,
30};
31
32class NAX : public ReadOnlyVfsDirectory {
33public:
34 explicit NAX(VirtualFile file);
35 explicit NAX(VirtualFile file, std::array<u8, 0x10> nca_id);
36
37 Loader::ResultStatus GetStatus() const;
38
39 VirtualFile GetDecrypted() const;
40
41 std::shared_ptr<NCA> AsNCA() const;
42
43 NAXContentType GetContentType() const;
44
45 std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
46
47 std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
48
49 std::string GetName() const override;
50
51 std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
52
53protected:
54 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
55
56private:
57 Loader::ResultStatus Parse(std::string_view path);
58
59 std::unique_ptr<NAXHeader> header;
60
61 VirtualFile file;
62 Loader::ResultStatus status;
63 NAXContentType type;
64
65 VirtualFile dec_file;
66
67 Core::Crypto::KeyManager keys;
68};
69} // namespace FileSys
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 6f9c64263..914315d20 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -305,17 +305,38 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
305} 305}
306 306
307std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() { 307std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
308 LOG_TRACE(Service_FS, "Opening System NAND Contents");
309
310 if (bis_factory == nullptr)
311 return nullptr;
312
308 return bis_factory->GetSystemNANDContents(); 313 return bis_factory->GetSystemNANDContents();
309} 314}
310 315
311std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() { 316std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
317 LOG_TRACE(Service_FS, "Opening User NAND Contents");
318
319 if (bis_factory == nullptr)
320 return nullptr;
321
312 return bis_factory->GetUserNANDContents(); 322 return bis_factory->GetUserNANDContents();
313} 323}
314 324
315void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) { 325std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents() {
316 romfs_factory = nullptr; 326 LOG_TRACE(Service_FS, "Opening SDMC Contents");
317 save_data_factory = nullptr; 327
318 sdmc_factory = nullptr; 328 if (sdmc_factory == nullptr)
329 return nullptr;
330
331 return sdmc_factory->GetSDMCContents();
332}
333
334void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) {
335 if (overwrite) {
336 bis_factory = nullptr;
337 save_data_factory = nullptr;
338 sdmc_factory = nullptr;
339 }
319 340
320 auto nand_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), 341 auto nand_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
321 FileSys::Mode::ReadWrite); 342 FileSys::Mode::ReadWrite);
@@ -324,16 +345,15 @@ void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
324 345
325 if (bis_factory == nullptr) 346 if (bis_factory == nullptr)
326 bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory); 347 bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory);
327 348 if (save_data_factory == nullptr)
328 auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory)); 349 save_data_factory = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
329 save_data_factory = std::move(savedata); 350 if (sdmc_factory == nullptr)
330 351 sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
331 auto sdcard = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
332 sdmc_factory = std::move(sdcard);
333} 352}
334 353
335void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs) { 354void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs) {
336 RegisterFileSystems(vfs); 355 romfs_factory = nullptr;
356 CreateFactories(vfs, false);
337 std::make_shared<FSP_LDR>()->InstallAsService(service_manager); 357 std::make_shared<FSP_LDR>()->InstallAsService(service_manager);
338 std::make_shared<FSP_PR>()->InstallAsService(service_manager); 358 std::make_shared<FSP_PR>()->InstallAsService(service_manager);
339 std::make_shared<FSP_SRV>()->InstallAsService(service_manager); 359 std::make_shared<FSP_SRV>()->InstallAsService(service_manager);
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index df78be44a..d88a66825 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -46,8 +46,12 @@ ResultVal<FileSys::VirtualDir> OpenSDMC();
46 46
47std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents(); 47std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
48std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); 48std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
49std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents();
50
51// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
52// above is called.
53void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true);
49 54
50/// Registers all Filesystem services with the specified service manager.
51void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs); 55void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs);
52 56
53// A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of 57// A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 70ef5d240..c13fb49b8 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -11,6 +11,7 @@
11#include "core/hle/kernel/process.h" 11#include "core/hle/kernel/process.h"
12#include "core/loader/deconstructed_rom_directory.h" 12#include "core/loader/deconstructed_rom_directory.h"
13#include "core/loader/elf.h" 13#include "core/loader/elf.h"
14#include "core/loader/nax.h"
14#include "core/loader/nca.h" 15#include "core/loader/nca.h"
15#include "core/loader/nro.h" 16#include "core/loader/nro.h"
16#include "core/loader/nso.h" 17#include "core/loader/nso.h"
@@ -32,6 +33,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
32 CHECK_TYPE(NRO) 33 CHECK_TYPE(NRO)
33 CHECK_TYPE(NCA) 34 CHECK_TYPE(NCA)
34 CHECK_TYPE(XCI) 35 CHECK_TYPE(XCI)
36 CHECK_TYPE(NAX)
35 37
36#undef CHECK_TYPE 38#undef CHECK_TYPE
37 39
@@ -73,6 +75,8 @@ std::string GetFileTypeString(FileType type) {
73 return "NCA"; 75 return "NCA";
74 case FileType::XCI: 76 case FileType::XCI:
75 return "XCI"; 77 return "XCI";
78 case FileType::NAX:
79 return "NAX";
76 case FileType::DeconstructedRomDirectory: 80 case FileType::DeconstructedRomDirectory:
77 return "Directory"; 81 return "Directory";
78 case FileType::Error: 82 case FileType::Error:
@@ -83,7 +87,7 @@ std::string GetFileTypeString(FileType type) {
83 return "unknown"; 87 return "unknown";
84} 88}
85 89
86constexpr std::array<const char*, 36> RESULT_MESSAGES{ 90constexpr std::array<const char*, 49> RESULT_MESSAGES{
87 "The operation completed successfully.", 91 "The operation completed successfully.",
88 "The loader requested to load is already loaded.", 92 "The loader requested to load is already loaded.",
89 "The operation is not implemented.", 93 "The operation is not implemented.",
@@ -120,6 +124,19 @@ constexpr std::array<const char*, 36> RESULT_MESSAGES{
120 "There was a general error loading the NRO into emulated memory.", 124 "There was a general error loading the NRO into emulated memory.",
121 "There is no icon available.", 125 "There is no icon available.",
122 "There is no control data available.", 126 "There is no control data available.",
127 "The NAX file has a bad header.",
128 "The NAX file has incorrect size as determined by the header.",
129 "The HMAC to generated the NAX decryption keys failed.",
130 "The HMAC to validate the NAX decryption keys failed.",
131 "The NAX key derivation failed.",
132 "The NAX file cannot be interpreted as an NCA file.",
133 "The NAX file has an incorrect path.",
134 "The SD seed could not be found or derived.",
135 "The SD KEK Source could not be found.",
136 "The AES KEK Generation Source could not be found.",
137 "The AES Key Generation Source could not be found.",
138 "The SD Save Key Source could not be found.",
139 "The SD NCA Key Source could not be found.",
123}; 140};
124 141
125std::ostream& operator<<(std::ostream& os, ResultStatus status) { 142std::ostream& operator<<(std::ostream& os, ResultStatus status) {
@@ -150,13 +167,18 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
150 case FileType::NRO: 167 case FileType::NRO:
151 return std::make_unique<AppLoader_NRO>(std::move(file)); 168 return std::make_unique<AppLoader_NRO>(std::move(file));
152 169
153 // NX NCA file format. 170 // NX NCA (Nintendo Content Archive) file format.
154 case FileType::NCA: 171 case FileType::NCA:
155 return std::make_unique<AppLoader_NCA>(std::move(file)); 172 return std::make_unique<AppLoader_NCA>(std::move(file));
156 173
174 // NX XCI (nX Card Image) file format.
157 case FileType::XCI: 175 case FileType::XCI:
158 return std::make_unique<AppLoader_XCI>(std::move(file)); 176 return std::make_unique<AppLoader_XCI>(std::move(file));
159 177
178 // NX NAX (NintendoAesXts) file format.
179 case FileType::NAX:
180 return std::make_unique<AppLoader_NAX>(std::move(file));
181
160 // NX deconstructed ROM directory. 182 // NX deconstructed ROM directory.
161 case FileType::DeconstructedRomDirectory: 183 case FileType::DeconstructedRomDirectory:
162 return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); 184 return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file));
@@ -170,7 +192,8 @@ std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file) {
170 FileType type = IdentifyFile(file); 192 FileType type = IdentifyFile(file);
171 FileType filename_type = GuessFromFilename(file->GetName()); 193 FileType filename_type = GuessFromFilename(file->GetName());
172 194
173 if (type != filename_type) { 195 // Special case: 00 is either a NCA or NAX.
196 if (type != filename_type && !(file->GetName() == "00" && type == FileType::NAX)) {
174 LOG_WARNING(Loader, "File {} has a different type than its extension.", file->GetName()); 197 LOG_WARNING(Loader, "File {} has a different type than its extension.", file->GetName());
175 if (FileType::Unknown == type) 198 if (FileType::Unknown == type)
176 type = filename_type; 199 type = filename_type;
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index b74cfbf8a..885fee84c 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -32,6 +32,7 @@ enum class FileType {
32 NRO, 32 NRO,
33 NCA, 33 NCA,
34 XCI, 34 XCI,
35 NAX,
35 DeconstructedRomDirectory, 36 DeconstructedRomDirectory,
36}; 37};
37 38
@@ -93,6 +94,19 @@ enum class ResultStatus : u16 {
93 ErrorLoadingNRO, 94 ErrorLoadingNRO,
94 ErrorNoIcon, 95 ErrorNoIcon,
95 ErrorNoControl, 96 ErrorNoControl,
97 ErrorBadNAXHeader,
98 ErrorIncorrectNAXFileSize,
99 ErrorNAXKeyHMACFailed,
100 ErrorNAXValidationHMACFailed,
101 ErrorNAXKeyDerivationFailed,
102 ErrorNAXInconvertibleToNCA,
103 ErrorBadNAXFilePath,
104 ErrorMissingSDSeed,
105 ErrorMissingSDKEKSource,
106 ErrorMissingAESKEKGenerationSource,
107 ErrorMissingAESKeyGenerationSource,
108 ErrorMissingSDSaveKeySource,
109 ErrorMissingSDNCAKeySource,
96}; 110};
97 111
98std::ostream& operator<<(std::ostream& os, ResultStatus status); 112std::ostream& operator<<(std::ostream& os, ResultStatus status);
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp
new file mode 100644
index 000000000..b46d81c02
--- /dev/null
+++ b/src/core/loader/nax.cpp
@@ -0,0 +1,66 @@
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 "common/logging/log.h"
6#include "core/file_sys/content_archive.h"
7#include "core/file_sys/romfs.h"
8#include "core/file_sys/xts_archive.h"
9#include "core/hle/kernel/process.h"
10#include "core/loader/nax.h"
11#include "core/loader/nca.h"
12
13namespace Loader {
14
15AppLoader_NAX::AppLoader_NAX(FileSys::VirtualFile file)
16 : AppLoader(file), nax(std::make_unique<FileSys::NAX>(file)),
17 nca_loader(std::make_unique<AppLoader_NCA>(nax->GetDecrypted())) {}
18
19AppLoader_NAX::~AppLoader_NAX() = default;
20
21FileType AppLoader_NAX::IdentifyType(const FileSys::VirtualFile& file) {
22 FileSys::NAX nax(file);
23
24 if (nax.GetStatus() == ResultStatus::Success && nax.AsNCA() != nullptr &&
25 nax.AsNCA()->GetStatus() == ResultStatus::Success) {
26 return FileType::NAX;
27 }
28
29 return FileType::Error;
30}
31
32ResultStatus AppLoader_NAX::Load(Kernel::SharedPtr<Kernel::Process>& process) {
33 if (is_loaded) {
34 return ResultStatus::ErrorAlreadyLoaded;
35 }
36
37 if (nax->GetStatus() != ResultStatus::Success)
38 return nax->GetStatus();
39
40 const auto nca = nax->AsNCA();
41 if (nca == nullptr) {
42 if (!Core::Crypto::KeyManager::KeyFileExists(false))
43 return ResultStatus::ErrorMissingProductionKeyFile;
44 return ResultStatus::ErrorNAXInconvertibleToNCA;
45 }
46
47 if (nca->GetStatus() != ResultStatus::Success)
48 return nca->GetStatus();
49
50 const auto result = nca_loader->Load(process);
51 if (result != ResultStatus::Success)
52 return result;
53
54 is_loaded = true;
55
56 return ResultStatus::Success;
57}
58
59ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) {
60 return nca_loader->ReadRomFS(dir);
61}
62
63ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) {
64 return nca_loader->ReadProgramId(out_program_id);
65}
66} // namespace Loader
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h
new file mode 100644
index 000000000..4dbae2918
--- /dev/null
+++ b/src/core/loader/nax.h
@@ -0,0 +1,48 @@
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 <memory>
8#include "common/common_types.h"
9#include "core/loader/loader.h"
10
11namespace FileSys {
12
13class NAX;
14
15} // namespace FileSys
16
17namespace Loader {
18
19class AppLoader_NCA;
20
21/// Loads a NAX file
22class AppLoader_NAX final : public AppLoader {
23public:
24 explicit AppLoader_NAX(FileSys::VirtualFile file);
25 ~AppLoader_NAX() override;
26
27 /**
28 * Returns the type of the file
29 * @param file std::shared_ptr<VfsFile> open file
30 * @return FileType found, or FileType::Error if this loader doesn't know it
31 */
32 static FileType IdentifyType(const FileSys::VirtualFile& file);
33
34 FileType GetFileType() override {
35 return IdentifyType(file);
36 }
37
38 ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
39
40 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
41 ResultStatus ReadProgramId(u64& out_program_id) override;
42
43private:
44 std::unique_ptr<FileSys::NAX> nax;
45 std::unique_ptr<AppLoader_NCA> nca_loader;
46};
47
48} // namespace Loader
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 4c4979545..9dc4d1f35 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -61,11 +61,12 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) {
61 if (xci->GetStatus() != ResultStatus::Success) 61 if (xci->GetStatus() != ResultStatus::Success)
62 return xci->GetStatus(); 62 return xci->GetStatus();
63 63
64 if (xci->GetNCAFileByType(FileSys::NCAContentType::Program) == nullptr) { 64 if (xci->GetProgramNCAStatus() != ResultStatus::Success)
65 if (!Core::Crypto::KeyManager::KeyFileExists(false)) 65 return xci->GetProgramNCAStatus();
66 return ResultStatus::ErrorMissingProductionKeyFile; 66
67 return ResultStatus::ErrorXCIMissingProgramNCA; 67 const auto nca = xci->GetNCAFileByType(FileSys::NCAContentType::Program);
68 } 68 if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false))
69 return ResultStatus::ErrorMissingProductionKeyFile;
69 70
70 auto result = nca_loader->Load(process); 71 auto result = nca_loader->Load(process);
71 if (result != ResultStatus::Success) 72 if (result != ResultStatus::Success)
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 76c4cb165..60b6d6d44 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -126,8 +126,8 @@ void Config::ReadValues() {
126 qt_config->beginGroup("UIGameList"); 126 qt_config->beginGroup("UIGameList");
127 UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool(); 127 UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
128 UISettings::values.icon_size = qt_config->value("icon_size", 64).toUInt(); 128 UISettings::values.icon_size = qt_config->value("icon_size", 64).toUInt();
129 UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 0).toUInt(); 129 UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 3).toUInt();
130 UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 3).toUInt(); 130 UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 2).toUInt();
131 qt_config->endGroup(); 131 qt_config->endGroup();
132 132
133 qt_config->beginGroup("UILayout"); 133 qt_config->beginGroup("UILayout");
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index d5726b8b3..867a3c6f1 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -426,13 +426,12 @@ static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca,
426 } 426 }
427} 427}
428 428
429void GameListWorker::AddInstalledTitlesToGameList() { 429void GameListWorker::AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache) {
430 const auto usernand = Service::FileSystem::GetUserNANDContents(); 430 const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
431 const auto installed_games = usernand->ListEntriesFilter(FileSys::TitleType::Application, 431 FileSys::ContentRecordType::Program);
432 FileSys::ContentRecordType::Program);
433 432
434 for (const auto& game : installed_games) { 433 for (const auto& game : installed_games) {
435 const auto& file = usernand->GetEntryRaw(game); 434 const auto& file = cache->GetEntryUnparsed(game);
436 std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file); 435 std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
437 if (!loader) 436 if (!loader)
438 continue; 437 continue;
@@ -442,8 +441,7 @@ void GameListWorker::AddInstalledTitlesToGameList() {
442 u64 program_id = 0; 441 u64 program_id = 0;
443 loader->ReadProgramId(program_id); 442 loader->ReadProgramId(program_id);
444 443
445 const auto& control = 444 const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
446 usernand->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
447 if (control != nullptr) 445 if (control != nullptr)
448 GetMetadataFromControlNCA(control, icon, name); 446 GetMetadataFromControlNCA(control, icon, name);
449 emit EntryReady({ 447 emit EntryReady({
@@ -457,11 +455,11 @@ void GameListWorker::AddInstalledTitlesToGameList() {
457 }); 455 });
458 } 456 }
459 457
460 const auto control_data = usernand->ListEntriesFilter(FileSys::TitleType::Application, 458 const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application,
461 FileSys::ContentRecordType::Control); 459 FileSys::ContentRecordType::Control);
462 460
463 for (const auto& entry : control_data) { 461 for (const auto& entry : control_data) {
464 const auto nca = usernand->GetEntry(entry); 462 const auto nca = cache->GetEntry(entry);
465 if (nca != nullptr) 463 if (nca != nullptr)
466 nca_control_map.insert_or_assign(entry.title_id, nca); 464 nca_control_map.insert_or_assign(entry.title_id, nca);
467 } 465 }
@@ -549,7 +547,9 @@ void GameListWorker::run() {
549 stop_processing = false; 547 stop_processing = false;
550 watch_list.append(dir_path); 548 watch_list.append(dir_path);
551 FillControlMap(dir_path.toStdString()); 549 FillControlMap(dir_path.toStdString());
552 AddInstalledTitlesToGameList(); 550 AddInstalledTitlesToGameList(Service::FileSystem::GetUserNANDContents());
551 AddInstalledTitlesToGameList(Service::FileSystem::GetSystemNANDContents());
552 AddInstalledTitlesToGameList(Service::FileSystem::GetSDMCContents());
553 AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); 553 AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
554 nca_control_map.clear(); 554 nca_control_map.clear();
555 emit Finished(watch_list); 555 emit Finished(watch_list);
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index c59613769..1d6c85400 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -172,7 +172,7 @@ private:
172 bool deep_scan; 172 bool deep_scan;
173 std::atomic_bool stop_processing; 173 std::atomic_bool stop_processing;
174 174
175 void AddInstalledTitlesToGameList(); 175 void AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache);
176 void FillControlMap(const std::string& dir_path); 176 void FillControlMap(const std::string& dir_path);
177 void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); 177 void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
178}; 178};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 53410fb91..9ca1626d1 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -133,8 +133,7 @@ GMainWindow::GMainWindow()
133 show(); 133 show();
134 134
135 // Necessary to load titles from nand in gamelist. 135 // Necessary to load titles from nand in gamelist.
136 Service::FileSystem::RegisterBIS(std::make_unique<FileSys::BISFactory>(vfs->OpenDirectory( 136 Service::FileSystem::CreateFactories(vfs);
137 FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite)));
138 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); 137 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
139 138
140 // Show one-time "callout" messages to the user 139 // Show one-time "callout" messages to the user