summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/common/file_util.cpp12
-rw-r--r--src/common/file_util.h2
-rw-r--r--src/common/logging/backend.cpp1
-rw-r--r--src/common/logging/log.h1
-rw-r--r--src/core/CMakeLists.txt11
-rw-r--r--src/core/crypto/aes_util.cpp6
-rw-r--r--src/core/crypto/aes_util.h118
-rw-r--r--src/core/crypto/ctr_encryption_layer.cpp56
-rw-r--r--src/core/crypto/ctr_encryption_layer.h31
-rw-r--r--src/core/crypto/encryption_layer.cpp42
-rw-r--r--src/core/crypto/encryption_layer.h30
-rw-r--r--src/core/crypto/key_manager.cpp410
-rw-r--r--src/core/crypto/key_manager.h116
-rw-r--r--src/core/crypto/sha_util.cpp5
-rw-r--r--src/core/crypto/sha_util.h20
-rw-r--r--src/core/file_sys/card_image.cpp150
-rw-r--r--src/core/file_sys/card_image.h93
-rw-r--r--src/core/file_sys/content_archive.cpp168
-rw-r--r--src/core/file_sys/content_archive.h26
-rw-r--r--src/core/file_sys/vfs.cpp21
-rw-r--r--src/core/file_sys/vfs.h3
-rw-r--r--src/core/loader/loader.cpp9
-rw-r--r--src/core/loader/loader.h1
-rw-r--r--src/core/loader/nca.cpp19
-rw-r--r--src/core/loader/nca.h2
-rw-r--r--src/core/loader/xci.cpp67
-rw-r--r--src/core/loader/xci.h42
-rw-r--r--src/core/settings.h2
-rw-r--r--src/yuzu/configuration/config.cpp2
-rw-r--r--src/yuzu/game_list.cpp2
-rw-r--r--src/yuzu/main.cpp14
-rw-r--r--src/yuzu_cmd/config.cpp1
-rw-r--r--src/yuzu_cmd/yuzu.cpp15
33 files changed, 1455 insertions, 43 deletions
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index b8dd92b65..89004c3c0 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -736,6 +736,18 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
736 return paths[path]; 736 return paths[path];
737} 737}
738 738
739std::string GetHactoolConfigurationPath() {
740#ifdef _WIN32
741 char path[MAX_PATH];
742 if (SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, path) != S_OK)
743 return "";
744 std::string local_path = Common::StringFromFixedZeroTerminatedBuffer(path, MAX_PATH);
745 return local_path + "\\.switch";
746#else
747 return GetHomeDirectory() + "/.switch";
748#endif
749}
750
739size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) { 751size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) {
740 return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size()); 752 return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size());
741} 753}
diff --git a/src/common/file_util.h b/src/common/file_util.h
index bc9272d89..d530d86c9 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -125,6 +125,8 @@ bool SetCurrentDir(const std::string& directory);
125// directory. To be used in "multi-user" mode (that is, installed). 125// directory. To be used in "multi-user" mode (that is, installed).
126const std::string& GetUserPath(UserPath path, const std::string& new_path = ""); 126const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
127 127
128std::string GetHactoolConfigurationPath();
129
128// Returns the path to where the sys file are 130// Returns the path to where the sys file are
129std::string GetSysDirectory(); 131std::string GetSysDirectory();
130 132
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index d86c40d26..d7d2f9a20 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -212,6 +212,7 @@ void FileBackend::Write(const Entry& entry) {
212 CLS(Input) \ 212 CLS(Input) \
213 CLS(Network) \ 213 CLS(Network) \
214 CLS(Loader) \ 214 CLS(Loader) \
215 CLS(Crypto) \
215 CLS(WebService) 216 CLS(WebService)
216 217
217// GetClassName is a macro defined by Windows.h, grrr... 218// GetClassName is a macro defined by Windows.h, grrr...
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 140cd8e47..5a580b004 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -97,6 +97,7 @@ enum class Class : ClassType {
97 Audio_DSP, ///< The HLE implementation of the DSP 97 Audio_DSP, ///< The HLE implementation of the DSP
98 Audio_Sink, ///< Emulator audio output backend 98 Audio_Sink, ///< Emulator audio output backend
99 Loader, ///< ROM loader 99 Loader, ///< ROM loader
100 Crypto, ///< Cryptographic engine/functions
100 Input, ///< Input emulation 101 Input, ///< Input emulation
101 Network, ///< Network emulation 102 Network, ///< Network emulation
102 WebService, ///< Interface to yuzu Web Services 103 WebService, ///< Interface to yuzu Web Services
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index ccb0695e4..a69ee9146 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -12,6 +12,15 @@ add_library(core STATIC
12 core_timing.h 12 core_timing.h
13 core_timing_util.cpp 13 core_timing_util.cpp
14 core_timing_util.h 14 core_timing_util.h
15 crypto/aes_util.h
16 crypto/encryption_layer.cpp
17 crypto/encryption_layer.h
18 crypto/key_manager.cpp
19 crypto/key_manager.h
20 crypto/ctr_encryption_layer.cpp
21 crypto/ctr_encryption_layer.h
22 file_sys/card_image.cpp
23 file_sys/card_image.h
15 file_sys/content_archive.cpp 24 file_sys/content_archive.cpp
16 file_sys/content_archive.h 25 file_sys/content_archive.h
17 file_sys/control_metadata.cpp 26 file_sys/control_metadata.cpp
@@ -317,6 +326,8 @@ add_library(core STATIC
317 loader/nro.h 326 loader/nro.h
318 loader/nso.cpp 327 loader/nso.cpp
319 loader/nso.h 328 loader/nso.h
329 loader/xci.cpp
330 loader/xci.h
320 memory.cpp 331 memory.cpp
321 memory.h 332 memory.h
322 memory_hook.cpp 333 memory_hook.cpp
diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp
new file mode 100644
index 000000000..46326cdec
--- /dev/null
+++ b/src/core/crypto/aes_util.cpp
@@ -0,0 +1,6 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5namespace Crypto {
6} // namespace Crypto
diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h
new file mode 100644
index 000000000..9807b9234
--- /dev/null
+++ b/src/core/crypto/aes_util.h
@@ -0,0 +1,118 @@
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 "common/assert.h"
8#include "core/file_sys/vfs.h"
9#include "mbedtls/cipher.h"
10
11namespace Crypto {
12
13enum class Mode {
14 CTR = MBEDTLS_CIPHER_AES_128_CTR,
15 ECB = MBEDTLS_CIPHER_AES_128_ECB,
16 XTS = MBEDTLS_CIPHER_AES_128_XTS,
17};
18
19enum class Op {
20 ENCRYPT,
21 DECRYPT,
22};
23
24template <typename Key, size_t KeySize = sizeof(Key)>
25struct AESCipher {
26 static_assert(std::is_same_v<Key, std::array<u8, KeySize>>, "Key must be std::array of u8.");
27 static_assert(KeySize == 0x10 || KeySize == 0x20, "KeySize must be 128 or 256.");
28
29 AESCipher(Key key, Mode mode) {
30 mbedtls_cipher_init(&encryption_context);
31 mbedtls_cipher_init(&decryption_context);
32
33 ASSERT_MSG((mbedtls_cipher_setup(
34 &encryption_context,
35 mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode))) ||
36 mbedtls_cipher_setup(&decryption_context,
37 mbedtls_cipher_info_from_type(
38 static_cast<mbedtls_cipher_type_t>(mode)))) == 0,
39 "Failed to initialize mbedtls ciphers.");
40
41 ASSERT(
42 !mbedtls_cipher_setkey(&encryption_context, key.data(), KeySize * 8, MBEDTLS_ENCRYPT));
43 ASSERT(
44 !mbedtls_cipher_setkey(&decryption_context, key.data(), KeySize * 8, MBEDTLS_DECRYPT));
45 //"Failed to set key on mbedtls ciphers.");
46 }
47
48 ~AESCipher() {
49 mbedtls_cipher_free(&encryption_context);
50 mbedtls_cipher_free(&decryption_context);
51 }
52
53 void SetIV(std::vector<u8> iv) {
54 ASSERT_MSG((mbedtls_cipher_set_iv(&encryption_context, iv.data(), iv.size()) ||
55 mbedtls_cipher_set_iv(&decryption_context, iv.data(), iv.size())) == 0,
56 "Failed to set IV on mbedtls ciphers.");
57 }
58
59 template <typename Source, typename Dest>
60 void Transcode(const Source* src, size_t size, Dest* dest, Op op) {
61 size_t written = 0;
62
63 const auto context = op == Op::ENCRYPT ? &encryption_context : &decryption_context;
64
65 mbedtls_cipher_reset(context);
66
67 if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) {
68 mbedtls_cipher_update(context, reinterpret_cast<const u8*>(src), size,
69 reinterpret_cast<u8*>(dest), &written);
70 if (written != size)
71 LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
72 size, written);
73 } else {
74 const auto block_size = mbedtls_cipher_get_block_size(context);
75
76 for (size_t offset = 0; offset < size; offset += block_size) {
77 auto length = std::min<size_t>(block_size, size - offset);
78 mbedtls_cipher_update(context, reinterpret_cast<const u8*>(src) + offset, length,
79 reinterpret_cast<u8*>(dest) + offset, &written);
80 if (written != length)
81 LOG_WARNING(Crypto,
82 "Not all data was decrypted requested={:016X}, actual={:016X}.",
83 length, written);
84 }
85 }
86
87 mbedtls_cipher_finish(context, nullptr, nullptr);
88 }
89
90 template <typename Source, typename Dest>
91 void XTSTranscode(const Source* src, size_t size, Dest* dest, size_t sector_id,
92 size_t sector_size, Op op) {
93 if (size % sector_size > 0) {
94 LOG_CRITICAL(Crypto, "Data size must be a multiple of sector size.");
95 return;
96 }
97
98 for (size_t i = 0; i < size; i += sector_size) {
99 SetIV(CalculateNintendoTweak(sector_id++));
100 Transcode<u8, u8>(reinterpret_cast<const u8*>(src) + i, sector_size,
101 reinterpret_cast<u8*>(dest) + i, op);
102 }
103 }
104
105private:
106 mbedtls_cipher_context_t encryption_context;
107 mbedtls_cipher_context_t decryption_context;
108
109 static std::vector<u8> CalculateNintendoTweak(size_t sector_id) {
110 std::vector<u8> out(0x10);
111 for (size_t i = 0xF; i <= 0xF; --i) {
112 out[i] = sector_id & 0xFF;
113 sector_id >>= 8;
114 }
115 return out;
116 }
117};
118} // namespace Crypto
diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp
new file mode 100644
index 000000000..8799496e2
--- /dev/null
+++ b/src/core/crypto/ctr_encryption_layer.cpp
@@ -0,0 +1,56 @@
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/assert.h"
6#include "core/crypto/ctr_encryption_layer.h"
7
8namespace Crypto {
9CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_, size_t base_offset)
10 : EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR),
11 iv(16, 0) {}
12
13size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
14 if (length == 0)
15 return 0;
16
17 const auto sector_offset = offset & 0xF;
18 if (sector_offset == 0) {
19 UpdateIV(base_offset + offset);
20 std::vector<u8> raw = base->ReadBytes(length, offset);
21 if (raw.size() != length)
22 return Read(data, raw.size(), offset);
23 cipher.Transcode(raw.data(), length, data, Op::DECRYPT);
24 return length;
25 }
26
27 // offset does not fall on block boundary (0x10)
28 std::vector<u8> block = base->ReadBytes(0x10, offset - sector_offset);
29 UpdateIV(base_offset + offset - sector_offset);
30 cipher.Transcode(block.data(), block.size(), block.data(), Op::DECRYPT);
31 size_t read = 0x10 - sector_offset;
32
33 if (length + sector_offset < 0x10) {
34 memcpy_s(data, length, block.data() + sector_offset, std::min<u64>(length, read));
35 return read;
36 }
37
38 memcpy_s(data, length, block.data() + sector_offset, read);
39 return read + Read(data + read, length - read, offset + read);
40}
41
42void CTREncryptionLayer::SetIV(std::vector<u8> iv_) {
43 const auto length = std::min<size_t>(iv_.size(), iv.size());
44 for (size_t i = 0; i < length; ++i)
45 iv[i] = iv_[i];
46}
47
48void CTREncryptionLayer::UpdateIV(size_t offset) const {
49 offset >>= 4;
50 for (size_t i = 0; i < 8; ++i) {
51 iv[16 - i - 1] = offset & 0xFF;
52 offset >>= 8;
53 }
54 cipher.SetIV(iv);
55}
56} // namespace Crypto
diff --git a/src/core/crypto/ctr_encryption_layer.h b/src/core/crypto/ctr_encryption_layer.h
new file mode 100644
index 000000000..fe53e714b
--- /dev/null
+++ b/src/core/crypto/ctr_encryption_layer.h
@@ -0,0 +1,31 @@
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 "aes_util.h"
8#include "encryption_layer.h"
9#include "key_manager.h"
10
11namespace Crypto {
12
13// Sits on top of a VirtualFile and provides CTR-mode AES decription.
14struct CTREncryptionLayer : public EncryptionLayer {
15 CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, size_t base_offset);
16
17 size_t Read(u8* data, size_t length, size_t offset) const override;
18
19 void SetIV(std::vector<u8> iv);
20
21private:
22 size_t base_offset;
23
24 // Must be mutable as operations modify cipher contexts.
25 mutable AESCipher<Key128> cipher;
26 mutable std::vector<u8> iv;
27
28 void UpdateIV(size_t offset) const;
29};
30
31} // namespace Crypto
diff --git a/src/core/crypto/encryption_layer.cpp b/src/core/crypto/encryption_layer.cpp
new file mode 100644
index 000000000..4e243051e
--- /dev/null
+++ b/src/core/crypto/encryption_layer.cpp
@@ -0,0 +1,42 @@
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 "core/crypto/encryption_layer.h"
6
7namespace Crypto {
8
9EncryptionLayer::EncryptionLayer(FileSys::VirtualFile base_) : base(std::move(base_)) {}
10
11std::string EncryptionLayer::GetName() const {
12 return base->GetName();
13}
14
15size_t EncryptionLayer::GetSize() const {
16 return base->GetSize();
17}
18
19bool EncryptionLayer::Resize(size_t new_size) {
20 return false;
21}
22
23std::shared_ptr<FileSys::VfsDirectory> EncryptionLayer::GetContainingDirectory() const {
24 return base->GetContainingDirectory();
25}
26
27bool EncryptionLayer::IsWritable() const {
28 return false;
29}
30
31bool EncryptionLayer::IsReadable() const {
32 return true;
33}
34
35size_t EncryptionLayer::Write(const u8* data, size_t length, size_t offset) {
36 return 0;
37}
38
39bool EncryptionLayer::Rename(std::string_view name) {
40 return base->Rename(name);
41}
42} // namespace Crypto
diff --git a/src/core/crypto/encryption_layer.h b/src/core/crypto/encryption_layer.h
new file mode 100644
index 000000000..2312870d8
--- /dev/null
+++ b/src/core/crypto/encryption_layer.h
@@ -0,0 +1,30 @@
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#include "core/file_sys/vfs.h"
7
8namespace Crypto {
9
10// Basically non-functional class that implements all of the methods that are irrelevant to an
11// EncryptionLayer. Reduces duplicate code.
12struct EncryptionLayer : public FileSys::VfsFile {
13 explicit EncryptionLayer(FileSys::VirtualFile base);
14
15 size_t Read(u8* data, size_t length, size_t offset) const override = 0;
16
17 std::string GetName() const override;
18 size_t GetSize() const override;
19 bool Resize(size_t new_size) override;
20 std::shared_ptr<FileSys::VfsDirectory> GetContainingDirectory() const override;
21 bool IsWritable() const override;
22 bool IsReadable() const override;
23 size_t Write(const u8* data, size_t length, size_t offset) override;
24 bool Rename(std::string_view name) override;
25
26protected:
27 FileSys::VirtualFile base;
28};
29
30} // namespace Crypto
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
new file mode 100644
index 000000000..05c6a70a2
--- /dev/null
+++ b/src/core/crypto/key_manager.cpp
@@ -0,0 +1,410 @@
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 <fstream>
6#include <locale>
7#include <sstream>
8#include "common/assert.h"
9#include "common/logging/log.h"
10#include "core/crypto/key_manager.h"
11#include "mbedtls/sha256.h"
12
13namespace Crypto {
14KeyManager keys = {};
15
16std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> KeyManager::s128_hash_prod = {
17 {{S128KeyType::MASTER, 0, 0},
18 "0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32},
19 {{S128KeyType::MASTER, 1, 0},
20 "4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32},
21 {{S128KeyType::MASTER, 2, 0},
22 "79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32},
23 {{S128KeyType::MASTER, 3, 0},
24 "4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32},
25 {{S128KeyType::MASTER, 4, 0},
26 "75ff1d95d26113550ee6fcc20acb58e97edeb3a2ff52543ed5aec63bdcc3da50"_array32},
27 {{S128KeyType::PACKAGE1, 0, 0},
28 "4543CD1B7CAD7EE0466A3DE2086A0EF923805DCEA6C741541CDDB14F54F97B40"_array32},
29 {{S128KeyType::PACKAGE1, 1, 0},
30 "4A11DA019D26470C9B805F1721364830DC0096DD66EAC453B0D14455E5AF5CF8"_array32},
31 {{S128KeyType::PACKAGE1, 2, 0},
32 "CCA867360B3318246FBF0B8A86473176ED486DFE229772B941A02E84D50A3155"_array32},
33 {{S128KeyType::PACKAGE1, 3, 0},
34 "E65C383CDF526DFFAA77682868EBFA9535EE60D8075C961BBC1EDE5FBF7E3C5F"_array32},
35 {{S128KeyType::PACKAGE1, 4, 0},
36 "28ae73d6ae8f7206fca549e27097714e599df1208e57099416ff429b71370162"_array32},
37 {{S128KeyType::PACKAGE2, 0, 0},
38 "94D6F38B9D0456644E21DFF4707D092B70179B82D1AA2F5B6A76B8F9ED948264"_array32},
39 {{S128KeyType::PACKAGE2, 1, 0},
40 "7794F24FA879D378FEFDC8776B949B88AD89386410BE9025D463C619F1530509"_array32},
41 {{S128KeyType::PACKAGE2, 2, 0},
42 "5304BDDE6AC8E462961B5DB6E328B1816D245D36D6574BB78938B74D4418AF35"_array32},
43 {{S128KeyType::PACKAGE2, 3, 0},
44 "BE1E52C4345A979DDD4924375B91C902052C2E1CF8FBF2FAA42E8F26D5125B60"_array32},
45 {{S128KeyType::PACKAGE2, 4, 0},
46 "631b45d349ab8f76a050fe59512966fb8dbaf0755ef5b6903048bf036cfa611e"_array32},
47 {{S128KeyType::TITLEKEK, 0, 0},
48 "C2FA30CAC6AE1680466CB54750C24550E8652B3B6F38C30B49DADF067B5935E9"_array32},
49 {{S128KeyType::TITLEKEK, 1, 0},
50 "0D6B8F3746AD910D36438A859C11E8BE4310112425D63751D09B5043B87DE598"_array32},
51 {{S128KeyType::TITLEKEK, 2, 0},
52 "D09E18D3DB6BC7393536896F728528736FBEFCDD15C09D9D612FDE5C7BDCD821"_array32},
53 {{S128KeyType::TITLEKEK, 3, 0},
54 "47C6F9F7E99BB1F56DCDC93CDBD340EA82DCCD74DD8F3535ADA20ECF79D438ED"_array32},
55 {{S128KeyType::TITLEKEK, 4, 0},
56 "128610de8424cb29e08f9ee9a81c9e6ffd3c6662854aad0c8f937e0bcedc4d88"_array32},
57 {{S128KeyType::ETICKET_RSA_KEK, 0, 0},
58 "46cccf288286e31c931379de9efa288c95c9a15e40b00a4c563a8be244ece515"_array32},
59 {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Application)},
60 "592957F44FE5DB5EC6B095F568910E31A226D3B7FE42D64CFB9CE4051E90AEB6"_array32},
61 {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Application)},
62 "C2252A0FBF9D339ABC3D681351D00452F926E7CA0C6CA85F659078DE3FA647F3"_array32},
63 {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Application)},
64 "7C7722824B2F7C4938C40F3EA93E16CB69D3285EB133490EF8ECCD2C4B52DF41"_array32},
65 {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Application)},
66 "AFBB8EBFB2094F1CF71E330826AE06D64414FCA128C464618DF30EED92E62BE6"_array32},
67 {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Application)},
68 "5dc10eb81918da3f2fa90f69c8542511963656cfb31fb7c779581df8faf1f2f5"_array32},
69 {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Ocean)},
70 "AA2C65F0E27F730807A13F2ED5B99BE5183165B87C50B6ED48F5CAC2840687EB"_array32},
71 {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Ocean)},
72 "860185F2313A14F7006A029CB21A52750E7718C1E94FFB98C0AE2207D1A60165"_array32},
73 {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Ocean)},
74 "7283FB1EFBD42438DADF363FDB776ED355C98737A2AAE75D0E9283CE1C12A2E4"_array32},
75 {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Ocean)},
76 "9881C2D3AB70B14C8AA12016FC73ADAD93C6AD9FB59A9ECAD312B6F89E2413EC"_array32},
77 {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Ocean)},
78 "eaa6a8d242b89e174928fa9549a0f66ec1562e2576fac896f438a2b3c1fb6005"_array32},
79 {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::System)},
80 "194CF6BD14554DA8D457E14CBFE04E55C8FB8CA52E0AFB3D7CB7084AE435B801"_array32},
81 {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::System)},
82 "CE1DB7BB6E5962384889DB7A396AFD614F82F69DC38A33D2DEAF47F3E4B964B7"_array32},
83 {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::System)},
84 "42238DE5685DEF4FDE7BE42C0097CEB92447006386D6B5D5AAA2C9AFD2E28422"_array32},
85 {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::System)},
86 "1F6847F268E9D9C5D1AD4D7E226A63B833BF02071446957A962EF065521879C1"_array32},
87 {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::System)},
88 "644007f9913c3602399d4d75cc34faeb7f1faad18b23e34187b16fdc45f4980f"_array32},
89};
90
91std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> KeyManager::s256_hash_prod = {
92 {{S256KeyType::HEADER, 0, 0},
93 "8E03DE24818D96CE4F2A09B43AF979E679974F7570713A61EED8B314864A11D5"_array32},
94 {{S256KeyType::SD_SAVE, 0, 0},
95 "13020ee72d0f8b8f9112dc738b829fdb017102499a7c2259b52aeefc0a273f5c"_array32},
96 {{S256KeyType::SD_NCA, 0, 0},
97 "8a1c05b4f88bae5b04d77f632e6acfc8893c4a05fd701f53585daafc996b532a"_array32},
98};
99
100// TODO(DarkLordZach): Find missing hashes for dev keys.
101
102std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> KeyManager::s128_hash_dev = {
103 {{S128KeyType::MASTER, 0, 0},
104 "779dd8b533a2fb670f27b308cb8d0151c4a107568b817429172b7f80aa592c25"_array32},
105 {{S128KeyType::MASTER, 1, 0},
106 "0175c8bc49771576f75527be719098db4ebaf77707206749415663aa3a9ea9cc"_array32},
107 {{S128KeyType::MASTER, 2, 0},
108 "4f0b4d724e5a8787268157c7ce0767c26d2e2021832aa7020f306d6e260eea42"_array32},
109 {{S128KeyType::MASTER, 3, 0},
110 "7b5a29586c1f84f66fbfabb94518fc45408bb8e5445253d063dda7cfef2a818c"_array32},
111 {{S128KeyType::MASTER, 4, 0},
112 "87a61dbb05a8755de7fe069562aab38ebfb266c9eb835f09fa62dacc89c98341"_array32},
113 {{S128KeyType::PACKAGE1, 0, 0},
114 "166510bc63ae50391ebe4ee4ff90ca31cd0e2dd0ff6be839a2f573ec146cc23a"_array32},
115 {{S128KeyType::PACKAGE1, 1, 0},
116 "f74cd01b86743139c920ec54a8116c669eea805a0be1583e13fc5bc8de68645b"_array32},
117 {{S128KeyType::PACKAGE1, 2, 0},
118 "d0cdecd513bb6aa3d9dc6244c977dc8a5a7ea157d0a8747d79e7581146e1f768"_array32},
119 {{S128KeyType::PACKAGE1, 3, 0},
120 "aa39394d626b3b79f5b7ccc07378b5996b6d09bf0eb6771b0b40c9077fbfde8c"_array32},
121 {{S128KeyType::PACKAGE1, 4, 0},
122 "8f4754b8988c0e673fc2bbea0534cdd6075c815c9270754ae980aef3e4f0a508"_array32},
123 {{S128KeyType::PACKAGE2, 0, 0},
124 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
125 {{S128KeyType::PACKAGE2, 1, 0},
126 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
127 {{S128KeyType::PACKAGE2, 2, 0},
128 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
129 {{S128KeyType::PACKAGE2, 3, 0},
130 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
131 {{S128KeyType::PACKAGE2, 4, 0},
132 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
133 {{S128KeyType::TITLEKEK, 0, 0},
134 "C2FA30CAC6AE1680466CB54750C24550E8652B3B6F38C30B49DADF067B5935E9"_array32},
135 {{S128KeyType::TITLEKEK, 1, 0},
136 "0D6B8F3746AD910D36438A859C11E8BE4310112425D63751D09B5043B87DE598"_array32},
137 {{S128KeyType::TITLEKEK, 2, 0},
138 "D09E18D3DB6BC7393536896F728528736FBEFCDD15C09D9D612FDE5C7BDCD821"_array32},
139 {{S128KeyType::TITLEKEK, 3, 0},
140 "47C6F9F7E99BB1F56DCDC93CDBD340EA82DCCD74DD8F3535ADA20ECF79D438ED"_array32},
141 {{S128KeyType::TITLEKEK, 4, 0},
142 "128610de8424cb29e08f9ee9a81c9e6ffd3c6662854aad0c8f937e0bcedc4d88"_array32},
143 {{S128KeyType::ETICKET_RSA_KEK, 0, 0},
144 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
145 {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Application)},
146 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
147 {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Application)},
148 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
149 {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Application)},
150 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
151 {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Application)},
152 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
153 {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Application)},
154 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
155 {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Ocean)},
156 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
157 {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Ocean)},
158 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
159 {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Ocean)},
160 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
161 {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Ocean)},
162 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
163 {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Ocean)},
164 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
165 {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::System)},
166 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
167 {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::System)},
168 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
169 {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::System)},
170 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
171 {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::System)},
172 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
173 {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::System)},
174 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
175};
176
177std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> KeyManager::s256_hash_dev = {
178 {{S256KeyType::HEADER, 0, 0},
179 "ecde86a76e37ac4fd7591d3aa55c00cc77d8595fc27968052ec18a177d939060"_array32},
180 {{S256KeyType::SD_SAVE, 0, 0},
181 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
182 {{S256KeyType::SD_NCA, 0, 0},
183 "0000000000000000000000000000000000000000000000000000000000000000"_array32},
184};
185
186std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
187 {"master_key_00", {S128KeyType::MASTER, 0, 0}},
188 {"master_key_01", {S128KeyType::MASTER, 1, 0}},
189 {"master_key_02", {S128KeyType::MASTER, 2, 0}},
190 {"master_key_03", {S128KeyType::MASTER, 3, 0}},
191 {"master_key_04", {S128KeyType::MASTER, 4, 0}},
192 {"package1_key_00", {S128KeyType::PACKAGE1, 0, 0}},
193 {"package1_key_01", {S128KeyType::PACKAGE1, 1, 0}},
194 {"package1_key_02", {S128KeyType::PACKAGE1, 2, 0}},
195 {"package1_key_03", {S128KeyType::PACKAGE1, 3, 0}},
196 {"package1_key_04", {S128KeyType::PACKAGE1, 4, 0}},
197 {"package2_key_00", {S128KeyType::PACKAGE2, 0, 0}},
198 {"package2_key_01", {S128KeyType::PACKAGE2, 1, 0}},
199 {"package2_key_02", {S128KeyType::PACKAGE2, 2, 0}},
200 {"package2_key_03", {S128KeyType::PACKAGE2, 3, 0}},
201 {"package2_key_04", {S128KeyType::PACKAGE2, 4, 0}},
202 {"titlekek_00", {S128KeyType::TITLEKEK, 0, 0}},
203 {"titlekek_01", {S128KeyType::TITLEKEK, 1, 0}},
204 {"titlekek_02", {S128KeyType::TITLEKEK, 2, 0}},
205 {"titlekek_03", {S128KeyType::TITLEKEK, 3, 0}},
206 {"titlekek_04", {S128KeyType::TITLEKEK, 4, 0}},
207 {"eticket_rsa_kek", {S128KeyType::ETICKET_RSA_KEK, 0, 0}},
208 {"key_area_key_application_00",
209 {S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Application)}},
210 {"key_area_key_application_01",
211 {S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Application)}},
212 {"key_area_key_application_02",
213 {S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Application)}},
214 {"key_area_key_application_03",
215 {S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Application)}},
216 {"key_area_key_application_04",
217 {S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Application)}},
218 {"key_area_key_ocean_00", {S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}},
219 {"key_area_key_ocean_01", {S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}},
220 {"key_area_key_ocean_02", {S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}},
221 {"key_area_key_ocean_03", {S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}},
222 {"key_area_key_ocean_04", {S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}},
223 {"key_area_key_system_00",
224 {S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::System)}},
225 {"key_area_key_system_01",
226 {S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::System)}},
227 {"key_area_key_system_02",
228 {S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::System)}},
229 {"key_area_key_system_03",
230 {S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::System)}},
231 {"key_area_key_system_04",
232 {S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::System)}},
233};
234
235std::unordered_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
236 {"header_key", {S256KeyType::HEADER, 0, 0}},
237 {"sd_card_save_key", {S256KeyType::SD_SAVE, 0, 0}},
238 {"sd_card_nca_key", {S256KeyType::SD_NCA, 0, 0}},
239};
240
241static u8 ToHexNibble(char c1) {
242 if (c1 >= 65 && c1 <= 70)
243 return c1 - 55;
244 if (c1 >= 97 && c1 <= 102)
245 return c1 - 87;
246 if (c1 >= 48 && c1 <= 57)
247 return c1 - 48;
248 throw std::logic_error("Invalid hex digit");
249}
250
251template <size_t Size>
252static std::array<u8, Size> HexStringToArray(std::string_view str) {
253 std::array<u8, Size> out{};
254 for (size_t i = 0; i < 2 * Size; i += 2) {
255 auto d1 = str[i];
256 auto d2 = str[i + 1];
257 out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2);
258 }
259 return out;
260}
261
262std::array<u8, 16> operator""_array16(const char* str, size_t len) {
263 if (len != 32)
264 throw std::logic_error("Not of correct size.");
265 return HexStringToArray<16>(str);
266}
267
268std::array<u8, 32> operator""_array32(const char* str, size_t len) {
269 if (len != 64)
270 throw std::logic_error("Not of correct size.");
271 return HexStringToArray<32>(str);
272}
273
274void KeyManager::SetValidationMode(bool dev) {
275 dev_mode = dev;
276}
277
278void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) {
279 const auto filename = std::string(filename_);
280 std::ifstream file(filename);
281 if (!file.is_open())
282 return;
283
284 std::string line;
285 while (std::getline(file, line)) {
286 std::vector<std::string> out;
287 std::stringstream stream(line);
288 std::string item;
289 while (std::getline(stream, item, '='))
290 out.push_back(std::move(item));
291
292 if (out.size() != 2)
293 continue;
294
295 out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end());
296 out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end());
297
298 if (is_title_keys) {
299 auto rights_id_raw = HexStringToArray<16>(out[0]);
300 u128 rights_id = *reinterpret_cast<std::array<u64, 2>*>(&rights_id_raw);
301 Key128 key = HexStringToArray<16>(out[1]);
302 SetKey(S128KeyType::TITLEKEY, key, rights_id[1], rights_id[0]);
303 } else {
304 std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower);
305 if (s128_file_id.find(out[0]) != s128_file_id.end()) {
306 const auto index = s128_file_id[out[0]];
307 Key128 key = HexStringToArray<16>(out[1]);
308 SetKey(index.type, key, index.field1, index.field2);
309 } else if (s256_file_id.find(out[0]) != s256_file_id.end()) {
310 const auto index = s256_file_id[out[0]];
311 Key256 key = HexStringToArray<32>(out[1]);
312 SetKey(index.type, key, index.field1, index.field2);
313 }
314 }
315 }
316}
317
318bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) {
319 return s128_keys.find({id, field1, field2}) != s128_keys.end();
320}
321
322bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) {
323 return s256_keys.find({id, field1, field2}) != s256_keys.end();
324}
325
326Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) {
327 if (!HasKey(id, field1, field2))
328 return {};
329 return s128_keys[{id, field1, field2}];
330}
331
332Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) {
333 if (!HasKey(id, field1, field2))
334 return {};
335 return s256_keys[{id, field1, field2}];
336}
337
338void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
339 s128_keys[{id, field1, field2}] = key;
340}
341
342void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
343 s256_keys[{id, field1, field2}] = key;
344}
345
346bool KeyManager::ValidateKey(S128KeyType key, u64 field1, u64 field2) {
347 auto& hash = dev_mode ? s128_hash_dev : s128_hash_prod;
348
349 KeyIndex<S128KeyType> id = {key, field1, field2};
350 if (key == S128KeyType::SD_SEED || key == S128KeyType::TITLEKEY ||
351 hash.find(id) == hash.end()) {
352 LOG_WARNING(Crypto, "Could not validate [{}]", id.DebugInfo());
353 return true;
354 }
355
356 if (!HasKey(key, field1, field2)) {
357 LOG_CRITICAL(Crypto,
358 "System has requested validation of [{}], but user has not added it. Add this "
359 "key to use functionality.",
360 id.DebugInfo());
361 return false;
362 }
363
364 SHA256Hash key_hash{};
365 const auto a_key = GetKey(key, field1, field2);
366 mbedtls_sha256(a_key.data(), a_key.size(), key_hash.data(), 0);
367 if (key_hash != hash[id]) {
368 LOG_CRITICAL(Crypto,
369 "The hash of the provided key for [{}] does not match the one on file. This "
370 "means you probably have an incorrect key. If you believe this to be in "
371 "error, contact the yuzu devs.",
372 id.DebugInfo());
373 return false;
374 }
375
376 return true;
377}
378
379bool KeyManager::ValidateKey(S256KeyType key, u64 field1, u64 field2) {
380 auto& hash = dev_mode ? s256_hash_dev : s256_hash_prod;
381
382 KeyIndex<S256KeyType> id = {key, field1, field2};
383 if (hash.find(id) == hash.end()) {
384 LOG_ERROR(Crypto, "Could not validate [{}]", id.DebugInfo());
385 return true;
386 }
387
388 if (!HasKey(key, field1, field2)) {
389 LOG_ERROR(Crypto,
390 "System has requested validation of [{}], but user has not added it. Add this "
391 "key to use functionality.",
392 id.DebugInfo());
393 return false;
394 }
395
396 SHA256Hash key_hash{};
397 const auto a_key = GetKey(key, field1, field2);
398 mbedtls_sha256(a_key.data(), a_key.size(), key_hash.data(), 0);
399 if (key_hash != hash[id]) {
400 LOG_CRITICAL(Crypto,
401 "The hash of the provided key for [{}] does not match the one on file. This "
402 "means you probably have an incorrect key. If you believe this to be in "
403 "error, contact the yuzu devs.",
404 id.DebugInfo());
405 return false;
406 }
407
408 return true;
409}
410} // namespace Crypto
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
new file mode 100644
index 000000000..155989e46
--- /dev/null
+++ b/src/core/crypto/key_manager.h
@@ -0,0 +1,116 @@
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#include <array>
7#include <unordered_map>
8#include <vector>
9#include "common/common_types.h"
10
11namespace Crypto {
12
13typedef std::array<u8, 0x10> Key128;
14typedef std::array<u8, 0x20> Key256;
15typedef std::array<u8, 0x20> SHA256Hash;
16
17static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
18static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big.");
19
20enum class S256KeyType : u64 {
21 HEADER, //
22 SD_SAVE, //
23 SD_NCA, //
24};
25
26enum class S128KeyType : u64 {
27 MASTER, // f1=crypto revision
28 PACKAGE1, // f1=crypto revision
29 PACKAGE2, // f1=crypto revision
30 TITLEKEK, // f1=crypto revision
31 ETICKET_RSA_KEK, //
32 KEY_AREA, // f1=crypto revision f2=type {app, ocean, system}
33 SD_SEED, //
34 TITLEKEY, // f1=rights id LSB f2=rights id MSB
35};
36
37enum class KeyAreaKeyType : u8 {
38 Application,
39 Ocean,
40 System,
41};
42
43template <typename KeyType>
44struct KeyIndex {
45 KeyType type;
46 u64 field1;
47 u64 field2;
48
49 std::string DebugInfo() {
50 u8 key_size = 16;
51 if (std::is_same_v<KeyType, S256KeyType>)
52 key_size = 32;
53 return fmt::format("key_size={:02X}, key={:02X}, field1={:016X}, field2={:016X}", key_size,
54 static_cast<u8>(type), field1, field2);
55 }
56};
57
58// The following two (== and hash) are so KeyIndex can be a key in unordered_map
59
60template <typename KeyType>
61bool operator==(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
62 return lhs.type == rhs.type && lhs.field1 == rhs.field1 && lhs.field2 == rhs.field2;
63}
64
65} // namespace Crypto
66
67namespace std {
68template <typename KeyType>
69struct hash<Crypto::KeyIndex<KeyType>> {
70 size_t operator()(const Crypto::KeyIndex<KeyType>& k) const {
71 using std::hash;
72
73 return ((hash<u64>()(static_cast<u64>(k.type)) ^ (hash<u64>()(k.field1) << 1)) >> 1) ^
74 (hash<u64>()(k.field2) << 1);
75 }
76};
77} // namespace std
78
79namespace Crypto {
80
81std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
82std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
83
84struct KeyManager {
85 void SetValidationMode(bool dev);
86 void LoadFromFile(std::string_view filename, bool is_title_keys);
87
88 bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0);
89 bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0);
90
91 Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0);
92 Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0);
93
94 void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
95 void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
96
97 bool ValidateKey(S128KeyType key, u64 field1 = 0, u64 field2 = 0);
98 bool ValidateKey(S256KeyType key, u64 field1 = 0, u64 field2 = 0);
99
100private:
101 std::unordered_map<KeyIndex<S128KeyType>, Key128> s128_keys;
102 std::unordered_map<KeyIndex<S256KeyType>, Key256> s256_keys;
103
104 bool dev_mode = false;
105
106 static std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> s128_hash_prod;
107 static std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> s256_hash_prod;
108 static std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> s128_hash_dev;
109 static std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> s256_hash_dev;
110 static std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
111 static std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
112};
113
114extern KeyManager keys;
115
116} // namespace Crypto
diff --git a/src/core/crypto/sha_util.cpp b/src/core/crypto/sha_util.cpp
new file mode 100644
index 000000000..180008a85
--- /dev/null
+++ b/src/core/crypto/sha_util.cpp
@@ -0,0 +1,5 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5namespace Crypto {} // namespace Crypto
diff --git a/src/core/crypto/sha_util.h b/src/core/crypto/sha_util.h
new file mode 100644
index 000000000..fa3fa9d33
--- /dev/null
+++ b/src/core/crypto/sha_util.h
@@ -0,0 +1,20 @@
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 "common/assert.h"
8#include "core/file_sys/vfs.h"
9#include "key_manager.h"
10#include "mbedtls/cipher.h"
11
12namespace Crypto {
13typedef std::array<u8, 0x20> SHA256Hash;
14
15inline SHA256Hash operator"" _HASH(const char* data, size_t len) {
16 if (len != 0x40)
17 return {};
18}
19
20} // namespace Crypto
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
new file mode 100644
index 000000000..5ff09a362
--- /dev/null
+++ b/src/core/file_sys/card_image.cpp
@@ -0,0 +1,150 @@
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 const static std::array<std::string, 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 result = AddNCAFromPartition(XCIPartition::Update);
49 if (result != Loader::ResultStatus::Success) {
50 status = result;
51 return;
52 }
53
54 result = AddNCAFromPartition(XCIPartition::Normal);
55 if (result != Loader::ResultStatus::Success) {
56 status = result;
57 return;
58 }
59
60 if (GetFormatVersion() >= 0x2) {
61 result = AddNCAFromPartition(XCIPartition::Logo);
62 if (result != Loader::ResultStatus::Success) {
63 status = result;
64 return;
65 }
66 }
67
68 status = Loader::ResultStatus::Success;
69}
70
71Loader::ResultStatus XCI::GetStatus() const {
72 return status;
73}
74
75VirtualDir XCI::GetPartition(XCIPartition partition) const {
76 return partitions[static_cast<size_t>(partition)];
77}
78
79VirtualDir XCI::GetSecurePartition() const {
80 return GetPartition(XCIPartition::Secure);
81}
82
83VirtualDir XCI::GetNormalPartition() const {
84 return GetPartition(XCIPartition::Normal);
85}
86
87VirtualDir XCI::GetUpdatePartition() const {
88 return GetPartition(XCIPartition::Update);
89}
90
91VirtualDir XCI::GetLogoPartition() const {
92 return GetPartition(XCIPartition::Logo);
93}
94
95std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
96 for (const auto& nca : ncas) {
97 if (nca->GetType() == type)
98 return nca;
99 }
100
101 return nullptr;
102}
103
104VirtualFile XCI::GetNCAFileByType(NCAContentType type) const {
105 auto nca = GetNCAByType(type);
106 if (nca != nullptr)
107 return nca->GetBaseFile();
108 return nullptr;
109}
110
111std::vector<std::shared_ptr<VfsFile>> XCI::GetFiles() const {
112 return {};
113}
114
115std::vector<std::shared_ptr<VfsDirectory>> XCI::GetSubdirectories() const {
116 return std::vector<std::shared_ptr<VfsDirectory>>();
117}
118
119std::string XCI::GetName() const {
120 return file->GetName();
121}
122
123std::shared_ptr<VfsDirectory> XCI::GetParentDirectory() const {
124 return file->GetContainingDirectory();
125}
126
127bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
128 return false;
129}
130
131Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
132 if (partitions[static_cast<size_t>(part)] == nullptr) {
133 return Loader::ResultStatus::ErrorInvalidFormat;
134 }
135
136 for (VirtualFile file : partitions[static_cast<size_t>(part)]->GetFiles()) {
137 if (file->GetExtension() != "nca")
138 continue;
139 auto nca = std::make_shared<NCA>(file);
140 if (nca->GetStatus() == Loader::ResultStatus::Success)
141 ncas.push_back(std::move(nca));
142 }
143
144 return Loader::ResultStatus::Success;
145}
146
147u8 XCI::GetFormatVersion() const {
148 return GetLogoPartition() == nullptr ? 0x1 : 0x2;
149}
150} // 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..b765c8bc1
--- /dev/null
+++ b/src/core/file_sys/card_image.h
@@ -0,0 +1,93 @@
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 <vector>
8#include "common/swap.h"
9#include "core/file_sys/content_archive.h"
10#include "core/file_sys/vfs.h"
11
12namespace FileSys {
13
14enum class GamecardSize : u8 {
15 S_1GB = 0xFA,
16 S_2GB = 0xF8,
17 S_4GB = 0xF0,
18 S_8GB = 0xE0,
19 S_16GB = 0xE1,
20 S_32GB = 0xE2,
21};
22
23struct GamecardInfo {
24 std::array<u8, 0x70> data;
25};
26static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size.");
27
28struct GamecardHeader {
29 std::array<u8, 0x100> signature;
30 u32_le magic;
31 u32_le secure_area_start;
32 u32_le backup_area_start;
33 u8 kek_index;
34 GamecardSize size;
35 u8 header_version;
36 u8 flags;
37 u64_le package_id;
38 u64_le valid_data_end;
39 u128 info_iv;
40 u64_le hfs_offset;
41 u64_le hfs_size;
42 std::array<u8, 0x20> hfs_header_hash;
43 std::array<u8, 0x20> initial_data_hash;
44 u32_le secure_mode_flag;
45 u32_le title_key_flag;
46 u32_le key_flag;
47 u32_le normal_area_end;
48 GamecardInfo info;
49};
50static_assert(sizeof(GamecardHeader) == 0x200, "GamecardHeader has incorrect size.");
51
52enum class XCIPartition : u8 { Update, Normal, Secure, Logo };
53
54class XCI : public ReadOnlyVfsDirectory {
55public:
56 explicit XCI(VirtualFile file);
57
58 Loader::ResultStatus GetStatus() const;
59
60 u8 GetFormatVersion() const;
61
62 VirtualDir GetPartition(XCIPartition partition) const;
63 VirtualDir GetSecurePartition() const;
64 VirtualDir GetNormalPartition() const;
65 VirtualDir GetUpdatePartition() const;
66 VirtualDir GetLogoPartition() const;
67
68 std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
69 VirtualFile GetNCAFileByType(NCAContentType type) const;
70
71 std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
72
73 std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
74
75 std::string GetName() const override;
76
77 std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
78
79protected:
80 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
81
82private:
83 Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
84
85 VirtualFile file;
86 GamecardHeader header{};
87
88 Loader::ResultStatus status;
89
90 std::vector<VirtualDir> partitions;
91 std::vector<std::shared_ptr<NCA>> ncas;
92};
93} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 61cb0bbe3..add01974e 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -6,9 +6,12 @@
6#include <utility> 6#include <utility>
7 7
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"
10#include "core/file_sys/vfs_offset.h" 12#include "core/file_sys/vfs_offset.h"
11#include "core/loader/loader.h" 13#include "core/loader/loader.h"
14#include "mbedtls/cipher.h"
12#include "romfs.h" 15#include "romfs.h"
13 16
14namespace FileSys { 17namespace FileSys {
@@ -29,11 +32,19 @@ enum class NCASectionFilesystemType : u8 {
29struct NCASectionHeaderBlock { 32struct NCASectionHeaderBlock {
30 INSERT_PADDING_BYTES(3); 33 INSERT_PADDING_BYTES(3);
31 NCASectionFilesystemType filesystem_type; 34 NCASectionFilesystemType filesystem_type;
32 u8 crypto_type; 35 NCASectionCryptoType crypto_type;
33 INSERT_PADDING_BYTES(3); 36 INSERT_PADDING_BYTES(3);
34}; 37};
35static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); 38static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
36 39
40struct NCASectionRaw {
41 NCASectionHeaderBlock header;
42 std::array<u8, 0x138> block_data;
43 std::array<u8, 0x8> section_ctr;
44 INSERT_PADDING_BYTES(0xB8);
45};
46static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size.");
47
37struct PFS0Superblock { 48struct PFS0Superblock {
38 NCASectionHeaderBlock header_block; 49 NCASectionHeaderBlock header_block;
39 std::array<u8, 0x20> hash; 50 std::array<u8, 0x20> hash;
@@ -43,62 +54,155 @@ struct PFS0Superblock {
43 u64_le hash_table_size; 54 u64_le hash_table_size;
44 u64_le pfs0_header_offset; 55 u64_le pfs0_header_offset;
45 u64_le pfs0_size; 56 u64_le pfs0_size;
46 INSERT_PADDING_BYTES(432); 57 INSERT_PADDING_BYTES(0x1B0);
47}; 58};
48static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); 59static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
49 60
50struct RomFSSuperblock { 61struct RomFSSuperblock {
51 NCASectionHeaderBlock header_block; 62 NCASectionHeaderBlock header_block;
52 IVFCHeader ivfc; 63 IVFCHeader ivfc;
64 INSERT_PADDING_BYTES(0x118);
65};
66static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
67
68union NCASectionHeader {
69 NCASectionRaw raw;
70 PFS0Superblock pfs0;
71 RomFSSuperblock romfs;
53}; 72};
54static_assert(sizeof(RomFSSuperblock) == 0xE8, "RomFSSuperblock has incorrect size."); 73static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
74
75bool IsValidNCA(const NCAHeader& header) {
76 // TODO(DarkLordZach): Add NCA2/NCA0 support.
77 return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
78}
79
80Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) {
81 u8 master_key_id = header.crypto_type;
82 if (header.crypto_type_2 > master_key_id)
83 master_key_id = header.crypto_type_2;
84 if (master_key_id > 0)
85 --master_key_id;
86
87 std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
88 if (!Crypto::keys.ValidateKey(Crypto::S128KeyType::KEY_AREA, master_key_id, header.key_index)) {
89 status = Loader::ResultStatus::ErrorEncrypted;
90 return {};
91 }
92 Crypto::AESCipher<Crypto::Key128> cipher(
93 Crypto::keys.GetKey(Crypto::S128KeyType::KEY_AREA, master_key_id, header.key_index),
94 Crypto::Mode::ECB);
95 cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Crypto::Op::DECRYPT);
96
97 Crypto::Key128 out;
98 if (type == NCASectionCryptoType::XTS)
99 std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
100 else if (type == NCASectionCryptoType::CTR)
101 std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
102 else
103 LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
104 static_cast<u8>(type));
105
106 u128 out_128 = *reinterpret_cast<u128*>(&out);
107 LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
108 master_key_id, header.key_index, out_128[1], out_128[0]);
109
110 return out;
111}
112
113VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) {
114 if (!encrypted)
115 return in;
116
117 switch (header.raw.header.crypto_type) {
118 case NCASectionCryptoType::NONE:
119 LOG_DEBUG(Crypto, "called with mode=NONE");
120 return in;
121 case NCASectionCryptoType::CTR:
122 LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
123 {
124 auto out = std::make_shared<Crypto::CTREncryptionLayer>(
125 std::move(in), GetKeyAreaKey(NCASectionCryptoType::CTR), starting_offset);
126 std::vector<u8> iv(16, 0);
127 for (u8 i = 0; i < 8; ++i)
128 iv[i] = header.raw.section_ctr[0x8 - i - 1];
129 out->SetIV(iv);
130 return out;
131 }
132 case NCASectionCryptoType::XTS:
133 // TODO(DarkLordZach): Implement XTSEncryptionLayer and title key encryption.
134 default:
135 LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}",
136 static_cast<u8>(header.raw.header.crypto_type));
137 return nullptr;
138 }
139}
55 140
56NCA::NCA(VirtualFile file_) : file(std::move(file_)) { 141NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
57 if (sizeof(NCAHeader) != file->ReadObject(&header)) 142 if (sizeof(NCAHeader) != file->ReadObject(&header))
58 LOG_CRITICAL(Loader, "File reader errored out during header read."); 143 LOG_ERROR(Loader, "File reader errored out during header read.");
144
145 encrypted = false;
59 146
60 if (!IsValidNCA(header)) { 147 if (!IsValidNCA(header)) {
61 status = Loader::ResultStatus::ErrorInvalidFormat; 148 NCAHeader dec_header{};
62 return; 149 if (!Crypto::keys.ValidateKey(Crypto::S256KeyType::HEADER)) {
150 status = Loader::ResultStatus::ErrorEncrypted;
151 return;
152 }
153 Crypto::AESCipher<Crypto::Key256> cipher(Crypto::keys.GetKey(Crypto::S256KeyType::HEADER),
154 Crypto::Mode::XTS);
155 cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, Crypto::Op::DECRYPT);
156 if (IsValidNCA(dec_header)) {
157 header = dec_header;
158 encrypted = true;
159 } else {
160 status = Loader::ResultStatus::ErrorInvalidFormat;
161 return;
162 }
63 } 163 }
64 164
65 std::ptrdiff_t number_sections = 165 const std::ptrdiff_t number_sections =
66 std::count_if(std::begin(header.section_tables), std::end(header.section_tables), 166 std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
67 [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); 167 [](NCASectionTableEntry entry) { return entry.media_offset > 0; });
68 168
169 std::vector<NCASectionHeader> sections(number_sections);
170 const auto length_sections = SECTION_HEADER_SIZE * number_sections;
171
172 if (encrypted) {
173 auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
174 if (!Crypto::keys.ValidateKey(Crypto::S256KeyType::HEADER)) {
175 status = Loader::ResultStatus::ErrorEncrypted;
176 return;
177 }
178 Crypto::AESCipher<Crypto::Key256> cipher(Crypto::keys.GetKey(Crypto::S256KeyType::HEADER),
179 Crypto::Mode::XTS);
180 cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
181 Crypto::Op::DECRYPT);
182 } else {
183 file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
184 }
185
69 for (std::ptrdiff_t i = 0; i < number_sections; ++i) { 186 for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
70 // Seek to beginning of this section. 187 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 188
189 if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
82 const size_t romfs_offset = 190 const size_t romfs_offset =
83 header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER + 191 header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER +
84 sb.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; 192 section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
85 const size_t romfs_size = sb.ivfc.levels[IVFC_MAX_LEVEL - 1].size; 193 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)); 194 files.emplace_back(
195 Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset),
196 romfs_offset));
87 romfs = files.back(); 197 romfs = files.back();
88 } else if (block.filesystem_type == NCASectionFilesystemType::PFS0) { 198 } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
89 PFS0Superblock sb{};
90 // Seek back to beginning of this section.
91 if (sizeof(PFS0Superblock) !=
92 file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
93 LOG_CRITICAL(Loader, "File reader errored out during header read.");
94
95 u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * 199 u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
96 MEDIA_OFFSET_MULTIPLIER) + 200 MEDIA_OFFSET_MULTIPLIER) +
97 sb.pfs0_header_offset; 201 section.pfs0.pfs0_header_offset;
98 u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - 202 u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
99 header.section_tables[i].media_offset); 203 header.section_tables[i].media_offset);
100 auto npfs = std::make_shared<PartitionFilesystem>( 204 auto npfs = std::make_shared<PartitionFilesystem>(
101 std::make_shared<OffsetVfsFile>(file, size, offset)); 205 Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset));
102 206
103 if (npfs->GetStatus() == Loader::ResultStatus::Success) { 207 if (npfs->GetStatus() == Loader::ResultStatus::Success) {
104 dirs.emplace_back(npfs); 208 dirs.emplace_back(npfs);
@@ -153,6 +257,10 @@ VirtualDir NCA::GetExeFS() const {
153 return exefs; 257 return exefs;
154} 258}
155 259
260VirtualFile NCA::GetBaseFile() const {
261 return file;
262}
263
156bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { 264bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
157 return false; 265 return false;
158} 266}
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 0b8b9db61..1e8d9c8ae 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -12,10 +12,10 @@
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"
16 17
17namespace FileSys { 18namespace FileSys {
18
19enum class NCAContentType : u8 { 19enum class NCAContentType : u8 {
20 Program = 0, 20 Program = 0,
21 Meta = 1, 21 Meta = 1,
@@ -24,6 +24,13 @@ enum class NCAContentType : u8 {
24 Data = 4, 24 Data = 4,
25}; 25};
26 26
27enum class NCASectionCryptoType : u8 {
28 NONE = 1,
29 XTS = 2,
30 CTR = 3,
31 BKTR = 4,
32};
33
27struct NCASectionTableEntry { 34struct NCASectionTableEntry {
28 u32_le media_offset; 35 u32_le media_offset;
29 u32_le media_end_offset; 36 u32_le media_end_offset;
@@ -48,20 +55,19 @@ struct NCAHeader {
48 std::array<u8, 0x10> rights_id; 55 std::array<u8, 0x10> rights_id;
49 std::array<NCASectionTableEntry, 0x4> section_tables; 56 std::array<NCASectionTableEntry, 0x4> section_tables;
50 std::array<std::array<u8, 0x20>, 0x4> hash_tables; 57 std::array<std::array<u8, 0x20>, 0x4> hash_tables;
51 std::array<std::array<u8, 0x10>, 0x4> key_area; 58 std::array<u8, 0x40> key_area;
52 INSERT_PADDING_BYTES(0xC0); 59 INSERT_PADDING_BYTES(0xC0);
53}; 60};
54static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); 61static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
55 62
63union NCASectionHeader;
64
56inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) { 65inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
57 // According to switchbrew, an exefs must only contain these two files: 66 // According to switchbrew, an exefs must only contain these two files:
58 return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; 67 return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
59} 68}
60 69
61inline bool IsValidNCA(const NCAHeader& header) { 70bool 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 71
66// An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner. 72// 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. 73// After construction, use GetStatus to determine if the file is valid and ready to be used.
@@ -81,6 +87,8 @@ public:
81 VirtualFile GetRomFS() const; 87 VirtualFile GetRomFS() const;
82 VirtualDir GetExeFS() const; 88 VirtualDir GetExeFS() const;
83 89
90 VirtualFile GetBaseFile() const;
91
84protected: 92protected:
85 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; 93 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
86 94
@@ -95,6 +103,12 @@ private:
95 NCAHeader header{}; 103 NCAHeader header{};
96 104
97 Loader::ResultStatus status{}; 105 Loader::ResultStatus status{};
106
107 bool encrypted;
108
109 Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type);
110
111 VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, size_t starting_offset);
98}; 112};
99 113
100} // namespace FileSys 114} // namespace FileSys
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index 84a6a7397..57d0aeb85 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -285,6 +285,27 @@ 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 for (size_t j = 0; j < f1_vs; ++j) {
301 if (f1_v[j] != f2_v[j])
302 return false;
303 }
304 }
305
306 return true;
307}
308
288bool VfsRawCopy(VirtualFile src, VirtualFile dest) { 309bool VfsRawCopy(VirtualFile src, VirtualFile dest) {
289 if (src == nullptr || dest == nullptr) 310 if (src == nullptr || dest == nullptr)
290 return false; 311 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.
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index cbc4177c6..57e6c0365 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -13,6 +13,7 @@
13#include "core/loader/nca.h" 13#include "core/loader/nca.h"
14#include "core/loader/nro.h" 14#include "core/loader/nro.h"
15#include "core/loader/nso.h" 15#include "core/loader/nso.h"
16#include "core/loader/xci.h"
16 17
17namespace Loader { 18namespace Loader {
18 19
@@ -35,6 +36,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
35 CHECK_TYPE(NSO) 36 CHECK_TYPE(NSO)
36 CHECK_TYPE(NRO) 37 CHECK_TYPE(NRO)
37 CHECK_TYPE(NCA) 38 CHECK_TYPE(NCA)
39 CHECK_TYPE(XCI)
38 40
39#undef CHECK_TYPE 41#undef CHECK_TYPE
40 42
@@ -60,6 +62,8 @@ FileType GuessFromFilename(const std::string& name) {
60 return FileType::NSO; 62 return FileType::NSO;
61 if (extension == "nca") 63 if (extension == "nca")
62 return FileType::NCA; 64 return FileType::NCA;
65 if (extension == "xci")
66 return FileType::XCI;
63 67
64 return FileType::Unknown; 68 return FileType::Unknown;
65} 69}
@@ -74,6 +78,8 @@ const char* GetFileTypeString(FileType type) {
74 return "NSO"; 78 return "NSO";
75 case FileType::NCA: 79 case FileType::NCA:
76 return "NCA"; 80 return "NCA";
81 case FileType::XCI:
82 return "XCI";
77 case FileType::DeconstructedRomDirectory: 83 case FileType::DeconstructedRomDirectory:
78 return "Directory"; 84 return "Directory";
79 case FileType::Error: 85 case FileType::Error:
@@ -111,6 +117,9 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
111 case FileType::NCA: 117 case FileType::NCA:
112 return std::make_unique<AppLoader_NCA>(std::move(file)); 118 return std::make_unique<AppLoader_NCA>(std::move(file));
113 119
120 case FileType::XCI:
121 return std::make_unique<AppLoader_XCI>(std::move(file));
122
114 // NX deconstructed ROM directory. 123 // NX deconstructed ROM directory.
115 case FileType::DeconstructedRomDirectory: 124 case FileType::DeconstructedRomDirectory:
116 return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); 125 return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file));
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index fbf11e5d0..dc8d28311 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -31,6 +31,7 @@ enum class FileType {
31 NSO, 31 NSO,
32 NRO, 32 NRO,
33 NCA, 33 NCA,
34 XCI,
34 DeconstructedRomDirectory, 35 DeconstructedRomDirectory,
35}; 36};
36 37
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index c80df23be..a1f8235d1 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -25,12 +25,10 @@ namespace Loader {
25AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} 25AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file) : AppLoader(std::move(file)) {}
26 26
27FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) { 27FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) {
28 // TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support. 28 FileSys::NCA nca(file);
29 FileSys::NCAHeader header{};
30 if (sizeof(FileSys::NCAHeader) != file->ReadObject(&header))
31 return FileType::Error;
32 29
33 if (IsValidNCA(header) && header.content_type == FileSys::NCAContentType::Program) 30 if (nca.GetStatus() == ResultStatus::Success &&
31 nca.GetType() == FileSys::NCAContentType::Program)
34 return FileType::NCA; 32 return FileType::NCA;
35 33
36 return FileType::Error; 34 return FileType::Error;
@@ -98,12 +96,21 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) {
98} 96}
99 97
100ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { 98ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
101 if (nca == nullptr || nca->GetRomFS() == nullptr || nca->GetRomFS()->GetSize() == 0) 99 if (nca == nullptr)
100 return ResultStatus::ErrorNotLoaded;
101 if (nca->GetRomFS() == nullptr || nca->GetRomFS()->GetSize() == 0)
102 return ResultStatus::ErrorNotUsed; 102 return ResultStatus::ErrorNotUsed;
103 dir = nca->GetRomFS(); 103 dir = nca->GetRomFS();
104 return ResultStatus::Success; 104 return ResultStatus::Success;
105} 105}
106 106
107ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) {
108 if (nca == nullptr)
109 return ResultStatus::ErrorNotLoaded;
110 out_program_id = nca->GetTitleId();
111 return ResultStatus::Success;
112}
113
107AppLoader_NCA::~AppLoader_NCA() = default; 114AppLoader_NCA::~AppLoader_NCA() = default;
108 115
109} // namespace Loader 116} // namespace Loader
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h
index 52c95953a..fb96c0a22 100644
--- a/src/core/loader/nca.h
+++ b/src/core/loader/nca.h
@@ -33,6 +33,8 @@ public:
33 33
34 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; 34 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
35 35
36 ResultStatus ReadProgramId(u64& out_program_id) override;
37
36 ~AppLoader_NCA(); 38 ~AppLoader_NCA();
37 39
38private: 40private:
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
new file mode 100644
index 000000000..9759e33d1
--- /dev/null
+++ b/src/core/loader/xci.cpp
@@ -0,0 +1,67 @@
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 <vector>
6
7#include "common/file_util.h"
8#include "common/logging/log.h"
9#include "common/string_util.h"
10#include "common/swap.h"
11#include "core/core.h"
12#include "core/file_sys/content_archive.h"
13#include "core/file_sys/control_metadata.h"
14#include "core/file_sys/program_metadata.h"
15#include "core/file_sys/romfs.h"
16#include "core/gdbstub/gdbstub.h"
17#include "core/hle/kernel/process.h"
18#include "core/hle/kernel/resource_limit.h"
19#include "core/hle/service/filesystem/filesystem.h"
20#include "core/loader/nso.h"
21#include "core/loader/xci.h"
22#include "core/memory.h"
23
24namespace Loader {
25
26AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file)
27 : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)),
28 nca_loader(std::make_unique<AppLoader_NCA>(
29 xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {}
30
31FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& file) {
32 FileSys::XCI xci(file);
33
34 if (xci.GetStatus() == ResultStatus::Success &&
35 xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr &&
36 AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) ==
37 FileType::NCA)
38 return FileType::XCI;
39
40 return FileType::Error;
41}
42
43ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) {
44 if (is_loaded) {
45 return ResultStatus::ErrorAlreadyLoaded;
46 }
47
48 auto result = nca_loader->Load(process);
49 if (result != ResultStatus::Success)
50 return result;
51
52 is_loaded = true;
53
54 return ResultStatus::Success;
55}
56
57ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& dir) {
58 return nca_loader->ReadRomFS(dir);
59}
60
61ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) {
62 return nca_loader->ReadProgramId(out_program_id);
63}
64
65AppLoader_XCI::~AppLoader_XCI() = default;
66
67} // namespace Loader
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
new file mode 100644
index 000000000..a9cee1ca3
--- /dev/null
+++ b/src/core/loader/xci.h
@@ -0,0 +1,42 @@
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/file_sys/card_image.h"
8#include "core/loader/nca.h"
9
10namespace Loader {
11
12/// Loads an XCI file
13class AppLoader_XCI final : public AppLoader {
14public:
15 explicit AppLoader_XCI(FileSys::VirtualFile file);
16
17 /**
18 * Returns the type of the file
19 * @param file std::shared_ptr<VfsFile> open file
20 * @return FileType found, or FileType::Error if this loader doesn't know it
21 */
22 static FileType IdentifyType(const FileSys::VirtualFile& file);
23
24 FileType GetFileType() override {
25 return IdentifyType(file);
26 }
27
28 ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
29
30 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
31 ResultStatus ReadProgramId(u64& out_program_id) override;
32
33 ~AppLoader_XCI();
34
35private:
36 FileSys::ProgramMetadata metadata;
37
38 std::unique_ptr<FileSys::XCI> xci;
39 std::unique_ptr<AppLoader_NCA> nca_loader;
40};
41
42} // namespace Loader
diff --git a/src/core/settings.h b/src/core/settings.h
index 8cc65e434..55da39b6c 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -137,6 +137,8 @@ struct Values {
137 137
138 std::string log_filter; 138 std::string log_filter;
139 139
140 bool use_dev_keys;
141
140 // Audio 142 // Audio
141 std::string sink_id; 143 std::string sink_id;
142 std::string audio_device_id; 144 std::string audio_device_id;
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index e8b3a9866..2f2d8c4c5 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -109,6 +109,7 @@ void Config::ReadValues() {
109 109
110 qt_config->beginGroup("Miscellaneous"); 110 qt_config->beginGroup("Miscellaneous");
111 Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString(); 111 Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString();
112 Settings::values.use_dev_keys = qt_config->value("use_dev_keys", false).toBool();
112 qt_config->endGroup(); 113 qt_config->endGroup();
113 114
114 qt_config->beginGroup("Debugging"); 115 qt_config->beginGroup("Debugging");
@@ -218,6 +219,7 @@ void Config::SaveValues() {
218 219
219 qt_config->beginGroup("Miscellaneous"); 220 qt_config->beginGroup("Miscellaneous");
220 qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter)); 221 qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter));
222 qt_config->setValue("use_dev_keys", Settings::values.use_dev_keys);
221 qt_config->endGroup(); 223 qt_config->endGroup();
222 224
223 qt_config->beginGroup("Debugging"); 225 qt_config->beginGroup("Debugging");
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 99e6634a1..71e24a9e2 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -365,7 +365,7 @@ void GameList::LoadInterfaceLayout() {
365 item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); 365 item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder());
366} 366}
367 367
368const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca"}; 368const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci"};
369 369
370static bool HasSupportedFileExtension(const std::string& file_name) { 370static bool HasSupportedFileExtension(const std::string& file_name) {
371 QFileInfo file = QFileInfo(file_name.c_str()); 371 QFileInfo file = QFileInfo(file_name.c_str());
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index be38cfa9b..d3523e6ac 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -13,6 +13,7 @@
13#include <QMessageBox> 13#include <QMessageBox>
14#include <QtGui> 14#include <QtGui>
15#include <QtWidgets> 15#include <QtWidgets>
16#include <core/crypto/key_manager.h>
16#include "common/common_paths.h" 17#include "common/common_paths.h"
17#include "common/logging/backend.h" 18#include "common/logging/backend.h"
18#include "common/logging/filter.h" 19#include "common/logging/filter.h"
@@ -88,6 +89,19 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
88 ui.setupUi(this); 89 ui.setupUi(this);
89 statusBar()->hide(); 90 statusBar()->hide();
90 91
92 // Initialize keys
93 std::string keys_dir = FileUtil::GetHactoolConfigurationPath();
94 if (Settings::values.use_dev_keys) {
95 Crypto::keys.SetValidationMode(true);
96 if (FileUtil::Exists(keys_dir + DIR_SEP + "dev.keys"))
97 Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "dev.keys", false);
98 } else {
99 if (FileUtil::Exists(keys_dir + DIR_SEP + "prod.keys"))
100 Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "prod.keys", false);
101 }
102 if (FileUtil::Exists(keys_dir + DIR_SEP + "title.keys"))
103 Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "title.keys", true);
104
91 default_theme_paths = QIcon::themeSearchPaths(); 105 default_theme_paths = QIcon::themeSearchPaths();
92 UpdateUITheme(); 106 UpdateUITheme();
93 107
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index c581e9699..9bf26717f 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -119,6 +119,7 @@ void Config::ReadValues() {
119 119
120 // Miscellaneous 120 // Miscellaneous
121 Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); 121 Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");
122 Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false);
122 123
123 // Debugging 124 // Debugging
124 Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); 125 Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index b5392c499..955e2ba14 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -23,6 +23,7 @@
23#include "yuzu_cmd/emu_window/emu_window_sdl2.h" 23#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
24 24
25#include <getopt.h> 25#include <getopt.h>
26#include "core/crypto/key_manager.h"
26#ifndef _MSC_VER 27#ifndef _MSC_VER
27#include <unistd.h> 28#include <unistd.h>
28#endif 29#endif
@@ -71,6 +72,20 @@ static void InitializeLogging() {
71/// Application entry point 72/// Application entry point
72int main(int argc, char** argv) { 73int main(int argc, char** argv) {
73 Config config; 74 Config config;
75
76 // Initialize keys
77 std::string keys_dir = FileUtil::GetHactoolConfigurationPath();
78 if (Settings::values.use_dev_keys) {
79 Crypto::keys.SetValidationMode(true);
80 if (FileUtil::Exists(keys_dir + DIR_SEP + "dev.keys"))
81 Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "dev.keys", false);
82 } else {
83 if (FileUtil::Exists(keys_dir + DIR_SEP + "prod.keys"))
84 Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "prod.keys", false);
85 }
86 if (FileUtil::Exists(keys_dir + DIR_SEP + "title.keys"))
87 Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "title.keys", true);
88
74 int option_index = 0; 89 int option_index = 0;
75 bool use_gdbstub = Settings::values.use_gdbstub; 90 bool use_gdbstub = Settings::values.use_gdbstub;
76 u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port); 91 u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port);