36 changed files with 1463 additions and 43 deletions
-
3.gitmodules
-
4externals/CMakeLists.txt
-
1externals/mbedtls
-
12src/common/file_util.cpp
-
2src/common/file_util.h
-
1src/common/logging/backend.cpp
-
1src/common/logging/log.h
-
11src/core/CMakeLists.txt
-
6src/core/crypto/aes_util.cpp
-
118src/core/crypto/aes_util.h
-
56src/core/crypto/ctr_encryption_layer.cpp
-
31src/core/crypto/ctr_encryption_layer.h
-
42src/core/crypto/encryption_layer.cpp
-
30src/core/crypto/encryption_layer.h
-
410src/core/crypto/key_manager.cpp
-
116src/core/crypto/key_manager.h
-
5src/core/crypto/sha_util.cpp
-
20src/core/crypto/sha_util.h
-
150src/core/file_sys/card_image.cpp
-
93src/core/file_sys/card_image.h
-
168src/core/file_sys/content_archive.cpp
-
26src/core/file_sys/content_archive.h
-
21src/core/file_sys/vfs.cpp
-
3src/core/file_sys/vfs.h
-
9src/core/loader/loader.cpp
-
1src/core/loader/loader.h
-
19src/core/loader/nca.cpp
-
2src/core/loader/nca.h
-
67src/core/loader/xci.cpp
-
42src/core/loader/xci.h
-
2src/core/settings.h
-
2src/yuzu/configuration/config.cpp
-
2src/yuzu/game_list.cpp
-
14src/yuzu/main.cpp
-
1src/yuzu_cmd/config.cpp
-
15src/yuzu_cmd/yuzu.cpp
@ -0,0 +1,6 @@ |
|||
// Copyright 2018 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
namespace Crypto { |
|||
} // namespace Crypto
|
|||
@ -0,0 +1,118 @@ |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/assert.h" |
|||
#include "core/file_sys/vfs.h" |
|||
#include "mbedtls/cipher.h" |
|||
|
|||
namespace Crypto { |
|||
|
|||
enum class Mode { |
|||
CTR = MBEDTLS_CIPHER_AES_128_CTR, |
|||
ECB = MBEDTLS_CIPHER_AES_128_ECB, |
|||
XTS = MBEDTLS_CIPHER_AES_128_XTS, |
|||
}; |
|||
|
|||
enum class Op { |
|||
ENCRYPT, |
|||
DECRYPT, |
|||
}; |
|||
|
|||
template <typename Key, size_t KeySize = sizeof(Key)> |
|||
struct AESCipher { |
|||
static_assert(std::is_same_v<Key, std::array<u8, KeySize>>, "Key must be std::array of u8."); |
|||
static_assert(KeySize == 0x10 || KeySize == 0x20, "KeySize must be 128 or 256."); |
|||
|
|||
AESCipher(Key key, Mode mode) { |
|||
mbedtls_cipher_init(&encryption_context); |
|||
mbedtls_cipher_init(&decryption_context); |
|||
|
|||
ASSERT_MSG((mbedtls_cipher_setup( |
|||
&encryption_context, |
|||
mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode))) || |
|||
mbedtls_cipher_setup(&decryption_context, |
|||
mbedtls_cipher_info_from_type( |
|||
static_cast<mbedtls_cipher_type_t>(mode)))) == 0, |
|||
"Failed to initialize mbedtls ciphers."); |
|||
|
|||
ASSERT( |
|||
!mbedtls_cipher_setkey(&encryption_context, key.data(), KeySize * 8, MBEDTLS_ENCRYPT)); |
|||
ASSERT( |
|||
!mbedtls_cipher_setkey(&decryption_context, key.data(), KeySize * 8, MBEDTLS_DECRYPT)); |
|||
//"Failed to set key on mbedtls ciphers."); |
|||
} |
|||
|
|||
~AESCipher() { |
|||
mbedtls_cipher_free(&encryption_context); |
|||
mbedtls_cipher_free(&decryption_context); |
|||
} |
|||
|
|||
void SetIV(std::vector<u8> iv) { |
|||
ASSERT_MSG((mbedtls_cipher_set_iv(&encryption_context, iv.data(), iv.size()) || |
|||
mbedtls_cipher_set_iv(&decryption_context, iv.data(), iv.size())) == 0, |
|||
"Failed to set IV on mbedtls ciphers."); |
|||
} |
|||
|
|||
template <typename Source, typename Dest> |
|||
void Transcode(const Source* src, size_t size, Dest* dest, Op op) { |
|||
size_t written = 0; |
|||
|
|||
const auto context = op == Op::ENCRYPT ? &encryption_context : &decryption_context; |
|||
|
|||
mbedtls_cipher_reset(context); |
|||
|
|||
if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) { |
|||
mbedtls_cipher_update(context, reinterpret_cast<const u8*>(src), size, |
|||
reinterpret_cast<u8*>(dest), &written); |
|||
if (written != size) |
|||
LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.", |
|||
size, written); |
|||
} else { |
|||
const auto block_size = mbedtls_cipher_get_block_size(context); |
|||
|
|||
for (size_t offset = 0; offset < size; offset += block_size) { |
|||
auto length = std::min<size_t>(block_size, size - offset); |
|||
mbedtls_cipher_update(context, reinterpret_cast<const u8*>(src) + offset, length, |
|||
reinterpret_cast<u8*>(dest) + offset, &written); |
|||
if (written != length) |
|||
LOG_WARNING(Crypto, |
|||
"Not all data was decrypted requested={:016X}, actual={:016X}.", |
|||
length, written); |
|||
} |
|||
} |
|||
|
|||
mbedtls_cipher_finish(context, nullptr, nullptr); |
|||
} |
|||
|
|||
template <typename Source, typename Dest> |
|||
void XTSTranscode(const Source* src, size_t size, Dest* dest, size_t sector_id, |
|||
size_t sector_size, Op op) { |
|||
if (size % sector_size > 0) { |
|||
LOG_CRITICAL(Crypto, "Data size must be a multiple of sector size."); |
|||
return; |
|||
} |
|||
|
|||
for (size_t i = 0; i < size; i += sector_size) { |
|||
SetIV(CalculateNintendoTweak(sector_id++)); |
|||
Transcode<u8, u8>(reinterpret_cast<const u8*>(src) + i, sector_size, |
|||
reinterpret_cast<u8*>(dest) + i, op); |
|||
} |
|||
} |
|||
|
|||
private: |
|||
mbedtls_cipher_context_t encryption_context; |
|||
mbedtls_cipher_context_t decryption_context; |
|||
|
|||
static std::vector<u8> CalculateNintendoTweak(size_t sector_id) { |
|||
std::vector<u8> out(0x10); |
|||
for (size_t i = 0xF; i <= 0xF; --i) { |
|||
out[i] = sector_id & 0xFF; |
|||
sector_id >>= 8; |
|||
} |
|||
return out; |
|||
} |
|||
}; |
|||
} // namespace Crypto |
|||
@ -0,0 +1,56 @@ |
|||
// Copyright 2018 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include "common/assert.h"
|
|||
#include "core/crypto/ctr_encryption_layer.h"
|
|||
|
|||
namespace Crypto { |
|||
CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_, size_t base_offset) |
|||
: EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR), |
|||
iv(16, 0) {} |
|||
|
|||
size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const { |
|||
if (length == 0) |
|||
return 0; |
|||
|
|||
const auto sector_offset = offset & 0xF; |
|||
if (sector_offset == 0) { |
|||
UpdateIV(base_offset + offset); |
|||
std::vector<u8> raw = base->ReadBytes(length, offset); |
|||
if (raw.size() != length) |
|||
return Read(data, raw.size(), offset); |
|||
cipher.Transcode(raw.data(), length, data, Op::DECRYPT); |
|||
return length; |
|||
} |
|||
|
|||
// offset does not fall on block boundary (0x10)
|
|||
std::vector<u8> block = base->ReadBytes(0x10, offset - sector_offset); |
|||
UpdateIV(base_offset + offset - sector_offset); |
|||
cipher.Transcode(block.data(), block.size(), block.data(), Op::DECRYPT); |
|||
size_t read = 0x10 - sector_offset; |
|||
|
|||
if (length + sector_offset < 0x10) { |
|||
memcpy_s(data, length, block.data() + sector_offset, std::min<u64>(length, read)); |
|||
return read; |
|||
} |
|||
|
|||
memcpy_s(data, length, block.data() + sector_offset, read); |
|||
return read + Read(data + read, length - read, offset + read); |
|||
} |
|||
|
|||
void CTREncryptionLayer::SetIV(std::vector<u8> iv_) { |
|||
const auto length = std::min<size_t>(iv_.size(), iv.size()); |
|||
for (size_t i = 0; i < length; ++i) |
|||
iv[i] = iv_[i]; |
|||
} |
|||
|
|||
void CTREncryptionLayer::UpdateIV(size_t offset) const { |
|||
offset >>= 4; |
|||
for (size_t i = 0; i < 8; ++i) { |
|||
iv[16 - i - 1] = offset & 0xFF; |
|||
offset >>= 8; |
|||
} |
|||
cipher.SetIV(iv); |
|||
} |
|||
} // namespace Crypto
|
|||
@ -0,0 +1,31 @@ |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include "aes_util.h" |
|||
#include "encryption_layer.h" |
|||
#include "key_manager.h" |
|||
|
|||
namespace Crypto { |
|||
|
|||
// Sits on top of a VirtualFile and provides CTR-mode AES decription. |
|||
struct CTREncryptionLayer : public EncryptionLayer { |
|||
CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, size_t base_offset); |
|||
|
|||
size_t Read(u8* data, size_t length, size_t offset) const override; |
|||
|
|||
void SetIV(std::vector<u8> iv); |
|||
|
|||
private: |
|||
size_t base_offset; |
|||
|
|||
// Must be mutable as operations modify cipher contexts. |
|||
mutable AESCipher<Key128> cipher; |
|||
mutable std::vector<u8> iv; |
|||
|
|||
void UpdateIV(size_t offset) const; |
|||
}; |
|||
|
|||
} // namespace Crypto |
|||
@ -0,0 +1,42 @@ |
|||
// Copyright 2018 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include "core/crypto/encryption_layer.h"
|
|||
|
|||
namespace Crypto { |
|||
|
|||
EncryptionLayer::EncryptionLayer(FileSys::VirtualFile base_) : base(std::move(base_)) {} |
|||
|
|||
std::string EncryptionLayer::GetName() const { |
|||
return base->GetName(); |
|||
} |
|||
|
|||
size_t EncryptionLayer::GetSize() const { |
|||
return base->GetSize(); |
|||
} |
|||
|
|||
bool EncryptionLayer::Resize(size_t new_size) { |
|||
return false; |
|||
} |
|||
|
|||
std::shared_ptr<FileSys::VfsDirectory> EncryptionLayer::GetContainingDirectory() const { |
|||
return base->GetContainingDirectory(); |
|||
} |
|||
|
|||
bool EncryptionLayer::IsWritable() const { |
|||
return false; |
|||
} |
|||
|
|||
bool EncryptionLayer::IsReadable() const { |
|||
return true; |
|||
} |
|||
|
|||
size_t EncryptionLayer::Write(const u8* data, size_t length, size_t offset) { |
|||
return 0; |
|||
} |
|||
|
|||
bool EncryptionLayer::Rename(std::string_view name) { |
|||
return base->Rename(name); |
|||
} |
|||
} // namespace Crypto
|
|||
@ -0,0 +1,30 @@ |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
#include "core/file_sys/vfs.h" |
|||
|
|||
namespace Crypto { |
|||
|
|||
// Basically non-functional class that implements all of the methods that are irrelevant to an |
|||
// EncryptionLayer. Reduces duplicate code. |
|||
struct EncryptionLayer : public FileSys::VfsFile { |
|||
explicit EncryptionLayer(FileSys::VirtualFile base); |
|||
|
|||
size_t Read(u8* data, size_t length, size_t offset) const override = 0; |
|||
|
|||
std::string GetName() const override; |
|||
size_t GetSize() const override; |
|||
bool Resize(size_t new_size) override; |
|||
std::shared_ptr<FileSys::VfsDirectory> GetContainingDirectory() const override; |
|||
bool IsWritable() const override; |
|||
bool IsReadable() const override; |
|||
size_t Write(const u8* data, size_t length, size_t offset) override; |
|||
bool Rename(std::string_view name) override; |
|||
|
|||
protected: |
|||
FileSys::VirtualFile base; |
|||
}; |
|||
|
|||
} // namespace Crypto |
|||
@ -0,0 +1,410 @@ |
|||
// Copyright 2018 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <fstream>
|
|||
#include <locale>
|
|||
#include <sstream>
|
|||
#include "common/assert.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "core/crypto/key_manager.h"
|
|||
#include "mbedtls/sha256.h"
|
|||
|
|||
namespace Crypto { |
|||
KeyManager keys = {}; |
|||
|
|||
std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> KeyManager::s128_hash_prod = { |
|||
{{S128KeyType::MASTER, 0, 0}, |
|||
"0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32}, |
|||
{{S128KeyType::MASTER, 1, 0}, |
|||
"4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32}, |
|||
{{S128KeyType::MASTER, 2, 0}, |
|||
"79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32}, |
|||
{{S128KeyType::MASTER, 3, 0}, |
|||
"4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32}, |
|||
{{S128KeyType::MASTER, 4, 0}, |
|||
"75ff1d95d26113550ee6fcc20acb58e97edeb3a2ff52543ed5aec63bdcc3da50"_array32}, |
|||
{{S128KeyType::PACKAGE1, 0, 0}, |
|||
"4543CD1B7CAD7EE0466A3DE2086A0EF923805DCEA6C741541CDDB14F54F97B40"_array32}, |
|||
{{S128KeyType::PACKAGE1, 1, 0}, |
|||
"4A11DA019D26470C9B805F1721364830DC0096DD66EAC453B0D14455E5AF5CF8"_array32}, |
|||
{{S128KeyType::PACKAGE1, 2, 0}, |
|||
"CCA867360B3318246FBF0B8A86473176ED486DFE229772B941A02E84D50A3155"_array32}, |
|||
{{S128KeyType::PACKAGE1, 3, 0}, |
|||
"E65C383CDF526DFFAA77682868EBFA9535EE60D8075C961BBC1EDE5FBF7E3C5F"_array32}, |
|||
{{S128KeyType::PACKAGE1, 4, 0}, |
|||
"28ae73d6ae8f7206fca549e27097714e599df1208e57099416ff429b71370162"_array32}, |
|||
{{S128KeyType::PACKAGE2, 0, 0}, |
|||
"94D6F38B9D0456644E21DFF4707D092B70179B82D1AA2F5B6A76B8F9ED948264"_array32}, |
|||
{{S128KeyType::PACKAGE2, 1, 0}, |
|||
"7794F24FA879D378FEFDC8776B949B88AD89386410BE9025D463C619F1530509"_array32}, |
|||
{{S128KeyType::PACKAGE2, 2, 0}, |
|||
"5304BDDE6AC8E462961B5DB6E328B1816D245D36D6574BB78938B74D4418AF35"_array32}, |
|||
{{S128KeyType::PACKAGE2, 3, 0}, |
|||
"BE1E52C4345A979DDD4924375B91C902052C2E1CF8FBF2FAA42E8F26D5125B60"_array32}, |
|||
{{S128KeyType::PACKAGE2, 4, 0}, |
|||
"631b45d349ab8f76a050fe59512966fb8dbaf0755ef5b6903048bf036cfa611e"_array32}, |
|||
{{S128KeyType::TITLEKEK, 0, 0}, |
|||
"C2FA30CAC6AE1680466CB54750C24550E8652B3B6F38C30B49DADF067B5935E9"_array32}, |
|||
{{S128KeyType::TITLEKEK, 1, 0}, |
|||
"0D6B8F3746AD910D36438A859C11E8BE4310112425D63751D09B5043B87DE598"_array32}, |
|||
{{S128KeyType::TITLEKEK, 2, 0}, |
|||
"D09E18D3DB6BC7393536896F728528736FBEFCDD15C09D9D612FDE5C7BDCD821"_array32}, |
|||
{{S128KeyType::TITLEKEK, 3, 0}, |
|||
"47C6F9F7E99BB1F56DCDC93CDBD340EA82DCCD74DD8F3535ADA20ECF79D438ED"_array32}, |
|||
{{S128KeyType::TITLEKEK, 4, 0}, |
|||
"128610de8424cb29e08f9ee9a81c9e6ffd3c6662854aad0c8f937e0bcedc4d88"_array32}, |
|||
{{S128KeyType::ETICKET_RSA_KEK, 0, 0}, |
|||
"46cccf288286e31c931379de9efa288c95c9a15e40b00a4c563a8be244ece515"_array32}, |
|||
{{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Application)}, |
|||
"592957F44FE5DB5EC6B095F568910E31A226D3B7FE42D64CFB9CE4051E90AEB6"_array32}, |
|||
{{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Application)}, |
|||
"C2252A0FBF9D339ABC3D681351D00452F926E7CA0C6CA85F659078DE3FA647F3"_array32}, |
|||
{{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Application)}, |
|||
"7C7722824B2F7C4938C40F3EA93E16CB69D3285EB133490EF8ECCD2C4B52DF41"_array32}, |
|||
{{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Application)}, |
|||
"AFBB8EBFB2094F1CF71E330826AE06D64414FCA128C464618DF30EED92E62BE6"_array32}, |
|||
{{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Application)}, |
|||
"5dc10eb81918da3f2fa90f69c8542511963656cfb31fb7c779581df8faf1f2f5"_array32}, |
|||
{{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}, |
|||
"AA2C65F0E27F730807A13F2ED5B99BE5183165B87C50B6ED48F5CAC2840687EB"_array32}, |
|||
{{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}, |
|||
"860185F2313A14F7006A029CB21A52750E7718C1E94FFB98C0AE2207D1A60165"_array32}, |
|||
{{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}, |
|||
"7283FB1EFBD42438DADF363FDB776ED355C98737A2AAE75D0E9283CE1C12A2E4"_array32}, |
|||
{{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}, |
|||
"9881C2D3AB70B14C8AA12016FC73ADAD93C6AD9FB59A9ECAD312B6F89E2413EC"_array32}, |
|||
{{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}, |
|||
"eaa6a8d242b89e174928fa9549a0f66ec1562e2576fac896f438a2b3c1fb6005"_array32}, |
|||
{{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::System)}, |
|||
"194CF6BD14554DA8D457E14CBFE04E55C8FB8CA52E0AFB3D7CB7084AE435B801"_array32}, |
|||
{{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::System)}, |
|||
"CE1DB7BB6E5962384889DB7A396AFD614F82F69DC38A33D2DEAF47F3E4B964B7"_array32}, |
|||
{{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::System)}, |
|||
"42238DE5685DEF4FDE7BE42C0097CEB92447006386D6B5D5AAA2C9AFD2E28422"_array32}, |
|||
{{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::System)}, |
|||
"1F6847F268E9D9C5D1AD4D7E226A63B833BF02071446957A962EF065521879C1"_array32}, |
|||
{{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::System)}, |
|||
"644007f9913c3602399d4d75cc34faeb7f1faad18b23e34187b16fdc45f4980f"_array32}, |
|||
}; |
|||
|
|||
std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> KeyManager::s256_hash_prod = { |
|||
{{S256KeyType::HEADER, 0, 0}, |
|||
"8E03DE24818D96CE4F2A09B43AF979E679974F7570713A61EED8B314864A11D5"_array32}, |
|||
{{S256KeyType::SD_SAVE, 0, 0}, |
|||
"13020ee72d0f8b8f9112dc738b829fdb017102499a7c2259b52aeefc0a273f5c"_array32}, |
|||
{{S256KeyType::SD_NCA, 0, 0}, |
|||
"8a1c05b4f88bae5b04d77f632e6acfc8893c4a05fd701f53585daafc996b532a"_array32}, |
|||
}; |
|||
|
|||
// TODO(DarkLordZach): Find missing hashes for dev keys.
|
|||
|
|||
std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> KeyManager::s128_hash_dev = { |
|||
{{S128KeyType::MASTER, 0, 0}, |
|||
"779dd8b533a2fb670f27b308cb8d0151c4a107568b817429172b7f80aa592c25"_array32}, |
|||
{{S128KeyType::MASTER, 1, 0}, |
|||
"0175c8bc49771576f75527be719098db4ebaf77707206749415663aa3a9ea9cc"_array32}, |
|||
{{S128KeyType::MASTER, 2, 0}, |
|||
"4f0b4d724e5a8787268157c7ce0767c26d2e2021832aa7020f306d6e260eea42"_array32}, |
|||
{{S128KeyType::MASTER, 3, 0}, |
|||
"7b5a29586c1f84f66fbfabb94518fc45408bb8e5445253d063dda7cfef2a818c"_array32}, |
|||
{{S128KeyType::MASTER, 4, 0}, |
|||
"87a61dbb05a8755de7fe069562aab38ebfb266c9eb835f09fa62dacc89c98341"_array32}, |
|||
{{S128KeyType::PACKAGE1, 0, 0}, |
|||
"166510bc63ae50391ebe4ee4ff90ca31cd0e2dd0ff6be839a2f573ec146cc23a"_array32}, |
|||
{{S128KeyType::PACKAGE1, 1, 0}, |
|||
"f74cd01b86743139c920ec54a8116c669eea805a0be1583e13fc5bc8de68645b"_array32}, |
|||
{{S128KeyType::PACKAGE1, 2, 0}, |
|||
"d0cdecd513bb6aa3d9dc6244c977dc8a5a7ea157d0a8747d79e7581146e1f768"_array32}, |
|||
{{S128KeyType::PACKAGE1, 3, 0}, |
|||
"aa39394d626b3b79f5b7ccc07378b5996b6d09bf0eb6771b0b40c9077fbfde8c"_array32}, |
|||
{{S128KeyType::PACKAGE1, 4, 0}, |
|||
"8f4754b8988c0e673fc2bbea0534cdd6075c815c9270754ae980aef3e4f0a508"_array32}, |
|||
{{S128KeyType::PACKAGE2, 0, 0}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::PACKAGE2, 1, 0}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::PACKAGE2, 2, 0}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::PACKAGE2, 3, 0}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::PACKAGE2, 4, 0}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::TITLEKEK, 0, 0}, |
|||
"C2FA30CAC6AE1680466CB54750C24550E8652B3B6F38C30B49DADF067B5935E9"_array32}, |
|||
{{S128KeyType::TITLEKEK, 1, 0}, |
|||
"0D6B8F3746AD910D36438A859C11E8BE4310112425D63751D09B5043B87DE598"_array32}, |
|||
{{S128KeyType::TITLEKEK, 2, 0}, |
|||
"D09E18D3DB6BC7393536896F728528736FBEFCDD15C09D9D612FDE5C7BDCD821"_array32}, |
|||
{{S128KeyType::TITLEKEK, 3, 0}, |
|||
"47C6F9F7E99BB1F56DCDC93CDBD340EA82DCCD74DD8F3535ADA20ECF79D438ED"_array32}, |
|||
{{S128KeyType::TITLEKEK, 4, 0}, |
|||
"128610de8424cb29e08f9ee9a81c9e6ffd3c6662854aad0c8f937e0bcedc4d88"_array32}, |
|||
{{S128KeyType::ETICKET_RSA_KEK, 0, 0}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Application)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Application)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Application)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Application)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Application)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::System)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::System)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::System)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::System)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::System)}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
}; |
|||
|
|||
std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> KeyManager::s256_hash_dev = { |
|||
{{S256KeyType::HEADER, 0, 0}, |
|||
"ecde86a76e37ac4fd7591d3aa55c00cc77d8595fc27968052ec18a177d939060"_array32}, |
|||
{{S256KeyType::SD_SAVE, 0, 0}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
{{S256KeyType::SD_NCA, 0, 0}, |
|||
"0000000000000000000000000000000000000000000000000000000000000000"_array32}, |
|||
}; |
|||
|
|||
std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { |
|||
{"master_key_00", {S128KeyType::MASTER, 0, 0}}, |
|||
{"master_key_01", {S128KeyType::MASTER, 1, 0}}, |
|||
{"master_key_02", {S128KeyType::MASTER, 2, 0}}, |
|||
{"master_key_03", {S128KeyType::MASTER, 3, 0}}, |
|||
{"master_key_04", {S128KeyType::MASTER, 4, 0}}, |
|||
{"package1_key_00", {S128KeyType::PACKAGE1, 0, 0}}, |
|||
{"package1_key_01", {S128KeyType::PACKAGE1, 1, 0}}, |
|||
{"package1_key_02", {S128KeyType::PACKAGE1, 2, 0}}, |
|||
{"package1_key_03", {S128KeyType::PACKAGE1, 3, 0}}, |
|||
{"package1_key_04", {S128KeyType::PACKAGE1, 4, 0}}, |
|||
{"package2_key_00", {S128KeyType::PACKAGE2, 0, 0}}, |
|||
{"package2_key_01", {S128KeyType::PACKAGE2, 1, 0}}, |
|||
{"package2_key_02", {S128KeyType::PACKAGE2, 2, 0}}, |
|||
{"package2_key_03", {S128KeyType::PACKAGE2, 3, 0}}, |
|||
{"package2_key_04", {S128KeyType::PACKAGE2, 4, 0}}, |
|||
{"titlekek_00", {S128KeyType::TITLEKEK, 0, 0}}, |
|||
{"titlekek_01", {S128KeyType::TITLEKEK, 1, 0}}, |
|||
{"titlekek_02", {S128KeyType::TITLEKEK, 2, 0}}, |
|||
{"titlekek_03", {S128KeyType::TITLEKEK, 3, 0}}, |
|||
{"titlekek_04", {S128KeyType::TITLEKEK, 4, 0}}, |
|||
{"eticket_rsa_kek", {S128KeyType::ETICKET_RSA_KEK, 0, 0}}, |
|||
{"key_area_key_application_00", |
|||
{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Application)}}, |
|||
{"key_area_key_application_01", |
|||
{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Application)}}, |
|||
{"key_area_key_application_02", |
|||
{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Application)}}, |
|||
{"key_area_key_application_03", |
|||
{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Application)}}, |
|||
{"key_area_key_application_04", |
|||
{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Application)}}, |
|||
{"key_area_key_ocean_00", {S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}}, |
|||
{"key_area_key_ocean_01", {S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}}, |
|||
{"key_area_key_ocean_02", {S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}}, |
|||
{"key_area_key_ocean_03", {S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}}, |
|||
{"key_area_key_ocean_04", {S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}}, |
|||
{"key_area_key_system_00", |
|||
{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::System)}}, |
|||
{"key_area_key_system_01", |
|||
{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::System)}}, |
|||
{"key_area_key_system_02", |
|||
{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::System)}}, |
|||
{"key_area_key_system_03", |
|||
{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::System)}}, |
|||
{"key_area_key_system_04", |
|||
{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::System)}}, |
|||
}; |
|||
|
|||
std::unordered_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = { |
|||
{"header_key", {S256KeyType::HEADER, 0, 0}}, |
|||
{"sd_card_save_key", {S256KeyType::SD_SAVE, 0, 0}}, |
|||
{"sd_card_nca_key", {S256KeyType::SD_NCA, 0, 0}}, |
|||
}; |
|||
|
|||
static u8 ToHexNibble(char c1) { |
|||
if (c1 >= 65 && c1 <= 70) |
|||
return c1 - 55; |
|||
if (c1 >= 97 && c1 <= 102) |
|||
return c1 - 87; |
|||
if (c1 >= 48 && c1 <= 57) |
|||
return c1 - 48; |
|||
throw std::logic_error("Invalid hex digit"); |
|||
} |
|||
|
|||
template <size_t Size> |
|||
static std::array<u8, Size> HexStringToArray(std::string_view str) { |
|||
std::array<u8, Size> out{}; |
|||
for (size_t i = 0; i < 2 * Size; i += 2) { |
|||
auto d1 = str[i]; |
|||
auto d2 = str[i + 1]; |
|||
out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2); |
|||
} |
|||
return out; |
|||
} |
|||
|
|||
std::array<u8, 16> operator""_array16(const char* str, size_t len) { |
|||
if (len != 32) |
|||
throw std::logic_error("Not of correct size."); |
|||
return HexStringToArray<16>(str); |
|||
} |
|||
|
|||
std::array<u8, 32> operator""_array32(const char* str, size_t len) { |
|||
if (len != 64) |
|||
throw std::logic_error("Not of correct size."); |
|||
return HexStringToArray<32>(str); |
|||
} |
|||
|
|||
void KeyManager::SetValidationMode(bool dev) { |
|||
dev_mode = dev; |
|||
} |
|||
|
|||
void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) { |
|||
const auto filename = std::string(filename_); |
|||
std::ifstream file(filename); |
|||
if (!file.is_open()) |
|||
return; |
|||
|
|||
std::string line; |
|||
while (std::getline(file, line)) { |
|||
std::vector<std::string> out; |
|||
std::stringstream stream(line); |
|||
std::string item; |
|||
while (std::getline(stream, item, '=')) |
|||
out.push_back(std::move(item)); |
|||
|
|||
if (out.size() != 2) |
|||
continue; |
|||
|
|||
out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end()); |
|||
out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end()); |
|||
|
|||
if (is_title_keys) { |
|||
auto rights_id_raw = HexStringToArray<16>(out[0]); |
|||
u128 rights_id = *reinterpret_cast<std::array<u64, 2>*>(&rights_id_raw); |
|||
Key128 key = HexStringToArray<16>(out[1]); |
|||
SetKey(S128KeyType::TITLEKEY, key, rights_id[1], rights_id[0]); |
|||
} else { |
|||
std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower); |
|||
if (s128_file_id.find(out[0]) != s128_file_id.end()) { |
|||
const auto index = s128_file_id[out[0]]; |
|||
Key128 key = HexStringToArray<16>(out[1]); |
|||
SetKey(index.type, key, index.field1, index.field2); |
|||
} else if (s256_file_id.find(out[0]) != s256_file_id.end()) { |
|||
const auto index = s256_file_id[out[0]]; |
|||
Key256 key = HexStringToArray<32>(out[1]); |
|||
SetKey(index.type, key, index.field1, index.field2); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) { |
|||
return s128_keys.find({id, field1, field2}) != s128_keys.end(); |
|||
} |
|||
|
|||
bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) { |
|||
return s256_keys.find({id, field1, field2}) != s256_keys.end(); |
|||
} |
|||
|
|||
Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) { |
|||
if (!HasKey(id, field1, field2)) |
|||
return {}; |
|||
return s128_keys[{id, field1, field2}]; |
|||
} |
|||
|
|||
Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) { |
|||
if (!HasKey(id, field1, field2)) |
|||
return {}; |
|||
return s256_keys[{id, field1, field2}]; |
|||
} |
|||
|
|||
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { |
|||
s128_keys[{id, field1, field2}] = key; |
|||
} |
|||
|
|||
void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { |
|||
s256_keys[{id, field1, field2}] = key; |
|||
} |
|||
|
|||
bool KeyManager::ValidateKey(S128KeyType key, u64 field1, u64 field2) { |
|||
auto& hash = dev_mode ? s128_hash_dev : s128_hash_prod; |
|||
|
|||
KeyIndex<S128KeyType> id = {key, field1, field2}; |
|||
if (key == S128KeyType::SD_SEED || key == S128KeyType::TITLEKEY || |
|||
hash.find(id) == hash.end()) { |
|||
LOG_WARNING(Crypto, "Could not validate [{}]", id.DebugInfo()); |
|||
return true; |
|||
} |
|||
|
|||
if (!HasKey(key, field1, field2)) { |
|||
LOG_CRITICAL(Crypto, |
|||
"System has requested validation of [{}], but user has not added it. Add this " |
|||
"key to use functionality.", |
|||
id.DebugInfo()); |
|||
return false; |
|||
} |
|||
|
|||
SHA256Hash key_hash{}; |
|||
const auto a_key = GetKey(key, field1, field2); |
|||
mbedtls_sha256(a_key.data(), a_key.size(), key_hash.data(), 0); |
|||
if (key_hash != hash[id]) { |
|||
LOG_CRITICAL(Crypto, |
|||
"The hash of the provided key for [{}] does not match the one on file. This " |
|||
"means you probably have an incorrect key. If you believe this to be in " |
|||
"error, contact the yuzu devs.", |
|||
id.DebugInfo()); |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
bool KeyManager::ValidateKey(S256KeyType key, u64 field1, u64 field2) { |
|||
auto& hash = dev_mode ? s256_hash_dev : s256_hash_prod; |
|||
|
|||
KeyIndex<S256KeyType> id = {key, field1, field2}; |
|||
if (hash.find(id) == hash.end()) { |
|||
LOG_ERROR(Crypto, "Could not validate [{}]", id.DebugInfo()); |
|||
return true; |
|||
} |
|||
|
|||
if (!HasKey(key, field1, field2)) { |
|||
LOG_ERROR(Crypto, |
|||
"System has requested validation of [{}], but user has not added it. Add this " |
|||
"key to use functionality.", |
|||
id.DebugInfo()); |
|||
return false; |
|||
} |
|||
|
|||
SHA256Hash key_hash{}; |
|||
const auto a_key = GetKey(key, field1, field2); |
|||
mbedtls_sha256(a_key.data(), a_key.size(), key_hash.data(), 0); |
|||
if (key_hash != hash[id]) { |
|||
LOG_CRITICAL(Crypto, |
|||
"The hash of the provided key for [{}] does not match the one on file. This " |
|||
"means you probably have an incorrect key. If you believe this to be in " |
|||
"error, contact the yuzu devs.", |
|||
id.DebugInfo()); |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} // namespace Crypto
|
|||
@ -0,0 +1,116 @@ |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
#include <array> |
|||
#include <unordered_map> |
|||
#include <vector> |
|||
#include "common/common_types.h" |
|||
|
|||
namespace Crypto { |
|||
|
|||
typedef std::array<u8, 0x10> Key128; |
|||
typedef std::array<u8, 0x20> Key256; |
|||
typedef std::array<u8, 0x20> SHA256Hash; |
|||
|
|||
static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); |
|||
static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big."); |
|||
|
|||
enum class S256KeyType : u64 { |
|||
HEADER, // |
|||
SD_SAVE, // |
|||
SD_NCA, // |
|||
}; |
|||
|
|||
enum class S128KeyType : u64 { |
|||
MASTER, // f1=crypto revision |
|||
PACKAGE1, // f1=crypto revision |
|||
PACKAGE2, // f1=crypto revision |
|||
TITLEKEK, // f1=crypto revision |
|||
ETICKET_RSA_KEK, // |
|||
KEY_AREA, // f1=crypto revision f2=type {app, ocean, system} |
|||
SD_SEED, // |
|||
TITLEKEY, // f1=rights id LSB f2=rights id MSB |
|||
}; |
|||
|
|||
enum class KeyAreaKeyType : u8 { |
|||
Application, |
|||
Ocean, |
|||
System, |
|||
}; |
|||
|
|||
template <typename KeyType> |
|||
struct KeyIndex { |
|||
KeyType type; |
|||
u64 field1; |
|||
u64 field2; |
|||
|
|||
std::string DebugInfo() { |
|||
u8 key_size = 16; |
|||
if (std::is_same_v<KeyType, S256KeyType>) |
|||
key_size = 32; |
|||
return fmt::format("key_size={:02X}, key={:02X}, field1={:016X}, field2={:016X}", key_size, |
|||
static_cast<u8>(type), field1, field2); |
|||
} |
|||
}; |
|||
|
|||
// The following two (== and hash) are so KeyIndex can be a key in unordered_map |
|||
|
|||
template <typename KeyType> |
|||
bool operator==(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) { |
|||
return lhs.type == rhs.type && lhs.field1 == rhs.field1 && lhs.field2 == rhs.field2; |
|||
} |
|||
|
|||
} // namespace Crypto |
|||
|
|||
namespace std { |
|||
template <typename KeyType> |
|||
struct hash<Crypto::KeyIndex<KeyType>> { |
|||
size_t operator()(const Crypto::KeyIndex<KeyType>& k) const { |
|||
using std::hash; |
|||
|
|||
return ((hash<u64>()(static_cast<u64>(k.type)) ^ (hash<u64>()(k.field1) << 1)) >> 1) ^ |
|||
(hash<u64>()(k.field2) << 1); |
|||
} |
|||
}; |
|||
} // namespace std |
|||
|
|||
namespace Crypto { |
|||
|
|||
std::array<u8, 0x10> operator"" _array16(const char* str, size_t len); |
|||
std::array<u8, 0x20> operator"" _array32(const char* str, size_t len); |
|||
|
|||
struct KeyManager { |
|||
void SetValidationMode(bool dev); |
|||
void LoadFromFile(std::string_view filename, bool is_title_keys); |
|||
|
|||
bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0); |
|||
bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0); |
|||
|
|||
Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0); |
|||
Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0); |
|||
|
|||
void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); |
|||
void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); |
|||
|
|||
bool ValidateKey(S128KeyType key, u64 field1 = 0, u64 field2 = 0); |
|||
bool ValidateKey(S256KeyType key, u64 field1 = 0, u64 field2 = 0); |
|||
|
|||
private: |
|||
std::unordered_map<KeyIndex<S128KeyType>, Key128> s128_keys; |
|||
std::unordered_map<KeyIndex<S256KeyType>, Key256> s256_keys; |
|||
|
|||
bool dev_mode = false; |
|||
|
|||
static std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> s128_hash_prod; |
|||
static std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> s256_hash_prod; |
|||
static std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> s128_hash_dev; |
|||
static std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> s256_hash_dev; |
|||
static std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id; |
|||
static std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id; |
|||
}; |
|||
|
|||
extern KeyManager keys; |
|||
|
|||
} // namespace Crypto |
|||
@ -0,0 +1,5 @@ |
|||
// Copyright 2018 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
namespace Crypto {} // namespace Crypto
|
|||
@ -0,0 +1,20 @@ |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/assert.h" |
|||
#include "core/file_sys/vfs.h" |
|||
#include "key_manager.h" |
|||
#include "mbedtls/cipher.h" |
|||
|
|||
namespace Crypto { |
|||
typedef std::array<u8, 0x20> SHA256Hash; |
|||
|
|||
inline SHA256Hash operator"" _HASH(const char* data, size_t len) { |
|||
if (len != 0x40) |
|||
return {}; |
|||
} |
|||
|
|||
} // namespace Crypto |
|||
@ -0,0 +1,150 @@ |
|||
// Copyright 2018 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <array>
|
|||
#include <string>
|
|||
#include <core/loader/loader.h>
|
|||
#include "core/file_sys/card_image.h"
|
|||
#include "core/file_sys/partition_filesystem.h"
|
|||
#include "core/file_sys/vfs_offset.h"
|
|||
|
|||
namespace FileSys { |
|||
|
|||
XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { |
|||
if (file->ReadObject(&header) != sizeof(GamecardHeader)) { |
|||
status = Loader::ResultStatus::ErrorInvalidFormat; |
|||
return; |
|||
} |
|||
|
|||
if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) { |
|||
status = Loader::ResultStatus::ErrorInvalidFormat; |
|||
return; |
|||
} |
|||
|
|||
PartitionFilesystem main_hfs( |
|||
std::make_shared<OffsetVfsFile>(file, header.hfs_size, header.hfs_offset)); |
|||
|
|||
if (main_hfs.GetStatus() != Loader::ResultStatus::Success) { |
|||
status = main_hfs.GetStatus(); |
|||
return; |
|||
} |
|||
|
|||
const static std::array<std::string, 0x4> partition_names = {"update", "normal", "secure", |
|||
"logo"}; |
|||
|
|||
for (XCIPartition partition : |
|||
{XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) { |
|||
auto raw = main_hfs.GetFile(partition_names[static_cast<size_t>(partition)]); |
|||
if (raw != nullptr) |
|||
partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw); |
|||
} |
|||
|
|||
auto result = AddNCAFromPartition(XCIPartition::Secure); |
|||
if (result != Loader::ResultStatus::Success) { |
|||
status = result; |
|||
return; |
|||
} |
|||
result = AddNCAFromPartition(XCIPartition::Update); |
|||
if (result != Loader::ResultStatus::Success) { |
|||
status = result; |
|||
return; |
|||
} |
|||
|
|||
result = AddNCAFromPartition(XCIPartition::Normal); |
|||
if (result != Loader::ResultStatus::Success) { |
|||
status = result; |
|||
return; |
|||
} |
|||
|
|||
if (GetFormatVersion() >= 0x2) { |
|||
result = AddNCAFromPartition(XCIPartition::Logo); |
|||
if (result != Loader::ResultStatus::Success) { |
|||
status = result; |
|||
return; |
|||
} |
|||
} |
|||
|
|||
status = Loader::ResultStatus::Success; |
|||
} |
|||
|
|||
Loader::ResultStatus XCI::GetStatus() const { |
|||
return status; |
|||
} |
|||
|
|||
VirtualDir XCI::GetPartition(XCIPartition partition) const { |
|||
return partitions[static_cast<size_t>(partition)]; |
|||
} |
|||
|
|||
VirtualDir XCI::GetSecurePartition() const { |
|||
return GetPartition(XCIPartition::Secure); |
|||
} |
|||
|
|||
VirtualDir XCI::GetNormalPartition() const { |
|||
return GetPartition(XCIPartition::Normal); |
|||
} |
|||
|
|||
VirtualDir XCI::GetUpdatePartition() const { |
|||
return GetPartition(XCIPartition::Update); |
|||
} |
|||
|
|||
VirtualDir XCI::GetLogoPartition() const { |
|||
return GetPartition(XCIPartition::Logo); |
|||
} |
|||
|
|||
std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const { |
|||
for (const auto& nca : ncas) { |
|||
if (nca->GetType() == type) |
|||
return nca; |
|||
} |
|||
|
|||
return nullptr; |
|||
} |
|||
|
|||
VirtualFile XCI::GetNCAFileByType(NCAContentType type) const { |
|||
auto nca = GetNCAByType(type); |
|||
if (nca != nullptr) |
|||
return nca->GetBaseFile(); |
|||
return nullptr; |
|||
} |
|||
|
|||
std::vector<std::shared_ptr<VfsFile>> XCI::GetFiles() const { |
|||
return {}; |
|||
} |
|||
|
|||
std::vector<std::shared_ptr<VfsDirectory>> XCI::GetSubdirectories() const { |
|||
return std::vector<std::shared_ptr<VfsDirectory>>(); |
|||
} |
|||
|
|||
std::string XCI::GetName() const { |
|||
return file->GetName(); |
|||
} |
|||
|
|||
std::shared_ptr<VfsDirectory> XCI::GetParentDirectory() const { |
|||
return file->GetContainingDirectory(); |
|||
} |
|||
|
|||
bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { |
|||
return false; |
|||
} |
|||
|
|||
Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { |
|||
if (partitions[static_cast<size_t>(part)] == nullptr) { |
|||
return Loader::ResultStatus::ErrorInvalidFormat; |
|||
} |
|||
|
|||
for (VirtualFile file : partitions[static_cast<size_t>(part)]->GetFiles()) { |
|||
if (file->GetExtension() != "nca") |
|||
continue; |
|||
auto nca = std::make_shared<NCA>(file); |
|||
if (nca->GetStatus() == Loader::ResultStatus::Success) |
|||
ncas.push_back(std::move(nca)); |
|||
} |
|||
|
|||
return Loader::ResultStatus::Success; |
|||
} |
|||
|
|||
u8 XCI::GetFormatVersion() const { |
|||
return GetLogoPartition() == nullptr ? 0x1 : 0x2; |
|||
} |
|||
} // namespace FileSys
|
|||
@ -0,0 +1,93 @@ |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
#include "common/swap.h" |
|||
#include "core/file_sys/content_archive.h" |
|||
#include "core/file_sys/vfs.h" |
|||
|
|||
namespace FileSys { |
|||
|
|||
enum class GamecardSize : u8 { |
|||
S_1GB = 0xFA, |
|||
S_2GB = 0xF8, |
|||
S_4GB = 0xF0, |
|||
S_8GB = 0xE0, |
|||
S_16GB = 0xE1, |
|||
S_32GB = 0xE2, |
|||
}; |
|||
|
|||
struct GamecardInfo { |
|||
std::array<u8, 0x70> data; |
|||
}; |
|||
static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size."); |
|||
|
|||
struct GamecardHeader { |
|||
std::array<u8, 0x100> signature; |
|||
u32_le magic; |
|||
u32_le secure_area_start; |
|||
u32_le backup_area_start; |
|||
u8 kek_index; |
|||
GamecardSize size; |
|||
u8 header_version; |
|||
u8 flags; |
|||
u64_le package_id; |
|||
u64_le valid_data_end; |
|||
u128 info_iv; |
|||
u64_le hfs_offset; |
|||
u64_le hfs_size; |
|||
std::array<u8, 0x20> hfs_header_hash; |
|||
std::array<u8, 0x20> initial_data_hash; |
|||
u32_le secure_mode_flag; |
|||
u32_le title_key_flag; |
|||
u32_le key_flag; |
|||
u32_le normal_area_end; |
|||
GamecardInfo info; |
|||
}; |
|||
static_assert(sizeof(GamecardHeader) == 0x200, "GamecardHeader has incorrect size."); |
|||
|
|||
enum class XCIPartition : u8 { Update, Normal, Secure, Logo }; |
|||
|
|||
class XCI : public ReadOnlyVfsDirectory { |
|||
public: |
|||
explicit XCI(VirtualFile file); |
|||
|
|||
Loader::ResultStatus GetStatus() const; |
|||
|
|||
u8 GetFormatVersion() const; |
|||
|
|||
VirtualDir GetPartition(XCIPartition partition) const; |
|||
VirtualDir GetSecurePartition() const; |
|||
VirtualDir GetNormalPartition() const; |
|||
VirtualDir GetUpdatePartition() const; |
|||
VirtualDir GetLogoPartition() const; |
|||
|
|||
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const; |
|||
VirtualFile GetNCAFileByType(NCAContentType type) const; |
|||
|
|||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; |
|||
|
|||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; |
|||
|
|||
std::string GetName() const override; |
|||
|
|||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override; |
|||
|
|||
protected: |
|||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; |
|||
|
|||
private: |
|||
Loader::ResultStatus AddNCAFromPartition(XCIPartition part); |
|||
|
|||
VirtualFile file; |
|||
GamecardHeader header{}; |
|||
|
|||
Loader::ResultStatus status; |
|||
|
|||
std::vector<VirtualDir> partitions; |
|||
std::vector<std::shared_ptr<NCA>> ncas; |
|||
}; |
|||
} // namespace FileSys |
|||
@ -0,0 +1,67 @@ |
|||
// Copyright 2018 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <vector>
|
|||
|
|||
#include "common/file_util.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "common/string_util.h"
|
|||
#include "common/swap.h"
|
|||
#include "core/core.h"
|
|||
#include "core/file_sys/content_archive.h"
|
|||
#include "core/file_sys/control_metadata.h"
|
|||
#include "core/file_sys/program_metadata.h"
|
|||
#include "core/file_sys/romfs.h"
|
|||
#include "core/gdbstub/gdbstub.h"
|
|||
#include "core/hle/kernel/process.h"
|
|||
#include "core/hle/kernel/resource_limit.h"
|
|||
#include "core/hle/service/filesystem/filesystem.h"
|
|||
#include "core/loader/nso.h"
|
|||
#include "core/loader/xci.h"
|
|||
#include "core/memory.h"
|
|||
|
|||
namespace Loader { |
|||
|
|||
AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) |
|||
: AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)), |
|||
nca_loader(std::make_unique<AppLoader_NCA>( |
|||
xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {} |
|||
|
|||
FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& file) { |
|||
FileSys::XCI xci(file); |
|||
|
|||
if (xci.GetStatus() == ResultStatus::Success && |
|||
xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr && |
|||
AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) == |
|||
FileType::NCA) |
|||
return FileType::XCI; |
|||
|
|||
return FileType::Error; |
|||
} |
|||
|
|||
ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) { |
|||
if (is_loaded) { |
|||
return ResultStatus::ErrorAlreadyLoaded; |
|||
} |
|||
|
|||
auto result = nca_loader->Load(process); |
|||
if (result != ResultStatus::Success) |
|||
return result; |
|||
|
|||
is_loaded = true; |
|||
|
|||
return ResultStatus::Success; |
|||
} |
|||
|
|||
ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& dir) { |
|||
return nca_loader->ReadRomFS(dir); |
|||
} |
|||
|
|||
ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) { |
|||
return nca_loader->ReadProgramId(out_program_id); |
|||
} |
|||
|
|||
AppLoader_XCI::~AppLoader_XCI() = default; |
|||
|
|||
} // namespace Loader
|
|||
@ -0,0 +1,42 @@ |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include "core/file_sys/card_image.h" |
|||
#include "core/loader/nca.h" |
|||
|
|||
namespace Loader { |
|||
|
|||
/// Loads an XCI file |
|||
class AppLoader_XCI final : public AppLoader { |
|||
public: |
|||
explicit AppLoader_XCI(FileSys::VirtualFile file); |
|||
|
|||
/** |
|||
* Returns the type of the file |
|||
* @param file std::shared_ptr<VfsFile> open file |
|||
* @return FileType found, or FileType::Error if this loader doesn't know it |
|||
*/ |
|||
static FileType IdentifyType(const FileSys::VirtualFile& file); |
|||
|
|||
FileType GetFileType() override { |
|||
return IdentifyType(file); |
|||
} |
|||
|
|||
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; |
|||
|
|||
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; |
|||
ResultStatus ReadProgramId(u64& out_program_id) override; |
|||
|
|||
~AppLoader_XCI(); |
|||
|
|||
private: |
|||
FileSys::ProgramMetadata metadata; |
|||
|
|||
std::unique_ptr<FileSys::XCI> xci; |
|||
std::unique_ptr<AppLoader_NCA> nca_loader; |
|||
}; |
|||
|
|||
} // namespace Loader |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue