diff options
Diffstat (limited to 'src/core')
28 files changed, 813 insertions, 81 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 31a7bf6fd..a74270a0f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -20,6 +20,8 @@ add_library(core STATIC crypto/key_manager.h crypto/ctr_encryption_layer.cpp crypto/ctr_encryption_layer.h + crypto/xts_encryption_layer.cpp + crypto/xts_encryption_layer.h file_sys/bis_factory.cpp file_sys/bis_factory.h file_sys/card_image.cpp @@ -57,6 +59,8 @@ add_library(core STATIC file_sys/vfs_real.h file_sys/vfs_vector.cpp file_sys/vfs_vector.h + file_sys/xts_archive.cpp + file_sys/xts_archive.h frontend/emu_window.cpp frontend/emu_window.h frontend/framebuffer_layout.cpp @@ -347,6 +351,8 @@ add_library(core STATIC loader/linker.h loader/loader.cpp loader/loader.h + loader/nax.cpp + loader/nax.h loader/nca.cpp loader/nca.h loader/nro.cpp diff --git a/src/core/core.h b/src/core/core.h index 790e23cae..7188dabdc 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -187,6 +187,13 @@ public: return current_process; } + /// Gets the name of the current game + Loader::ResultStatus GetGameName(std::string& out) const { + if (app_loader == nullptr) + return Loader::ResultStatus::ErrorNotInitialized; + return app_loader->ReadTitle(out); + } + PerfStats perf_stats; FrameLimiter frame_limiter; diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp index a9876c83e..72e4bed67 100644 --- a/src/core/crypto/aes_util.cpp +++ b/src/core/crypto/aes_util.cpp @@ -99,10 +99,7 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op template <typename Key, size_t KeySize> void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* 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; - } + ASSERT_MSG(size % sector_size == 0, "XTS decryption size must be a multiple of sector size."); for (size_t i = 0; i < size; i += sector_size) { SetIV(CalculateNintendoTweak(sector_id++)); @@ -112,4 +109,4 @@ void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest, template class AESCipher<Key128>; template class AESCipher<Key256>; -} // namespace Core::Crypto
\ No newline at end of file +} // namespace Core::Crypto diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp index 106db02b3..3ea60dbd0 100644 --- a/src/core/crypto/ctr_encryption_layer.cpp +++ b/src/core/crypto/ctr_encryption_layer.cpp @@ -20,10 +20,8 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const { 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; + cipher.Transcode(raw.data(), raw.size(), data, Op::Decrypt); + return raw.size(); } // offset does not fall on block boundary (0x10) @@ -34,7 +32,7 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const { if (length + sector_offset < 0x10) { std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read)); - return read; + return std::min<u64>(length, read); } std::memcpy(data, block.data() + sector_offset, read); return read + Read(data + read, length - read, offset + read); diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index db8b22c85..0b6c07de8 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -12,11 +12,112 @@ #include "common/file_util.h" #include "common/hex_util.h" #include "common/logging/log.h" +#include "core/crypto/aes_util.h" #include "core/crypto/key_manager.h" #include "core/settings.h" namespace Core::Crypto { +Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { + Key128 out{}; + + AESCipher<Key128> cipher1(master, Mode::ECB); + cipher1.Transcode(kek_seed.data(), kek_seed.size(), out.data(), Op::Decrypt); + AESCipher<Key128> cipher2(out, Mode::ECB); + cipher2.Transcode(source.data(), source.size(), out.data(), Op::Decrypt); + + if (key_seed != Key128{}) { + AESCipher<Key128> cipher3(out, Mode::ECB); + cipher3.Transcode(key_seed.data(), key_seed.size(), out.data(), Op::Decrypt); + } + + return out; +} + +boost::optional<Key128> DeriveSDSeed() { + const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + "/system/save/8000000000000043", + "rb+"); + if (!save_43.IsOpen()) + return boost::none; + const FileUtil::IOFile sd_private( + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+"); + if (!sd_private.IsOpen()) + return boost::none; + + sd_private.Seek(0, SEEK_SET); + std::array<u8, 0x10> private_seed{}; + if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != 0x10) + return boost::none; + + std::array<u8, 0x10> buffer{}; + size_t offset = 0; + for (; offset + 0x10 < save_43.GetSize(); ++offset) { + save_43.Seek(offset, SEEK_SET); + save_43.ReadBytes(buffer.data(), buffer.size()); + if (buffer == private_seed) + break; + } + + if (offset + 0x10 >= save_43.GetSize()) + return boost::none; + + Key128 seed{}; + save_43.Seek(offset + 0x10, SEEK_SET); + save_43.ReadBytes(seed.data(), seed.size()); + return seed; +} + +Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys) { + if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK))) + return Loader::ResultStatus::ErrorMissingSDKEKSource; + if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration))) + return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource; + if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration))) + return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource; + + const auto sd_kek_source = + keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK)); + const auto aes_kek_gen = + keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration)); + const auto aes_key_gen = + keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)); + const auto master_00 = keys.GetKey(S128KeyType::Master); + const auto sd_kek = + GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen); + + if (!keys.HasKey(S128KeyType::SDSeed)) + return Loader::ResultStatus::ErrorMissingSDSeed; + const auto sd_seed = keys.GetKey(S128KeyType::SDSeed); + + if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save))) + return Loader::ResultStatus::ErrorMissingSDSaveKeySource; + if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA))) + return Loader::ResultStatus::ErrorMissingSDNCAKeySource; + + std::array<Key256, 2> sd_key_sources{ + keys.GetKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save)), + keys.GetKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA)), + }; + + // Combine sources and seed + for (auto& source : sd_key_sources) { + for (size_t i = 0; i < source.size(); ++i) + source[i] ^= sd_seed[i & 0xF]; + } + + AESCipher<Key128> cipher(sd_kek, Mode::ECB); + // The transform manipulates sd_keys as part of the Transcode, so the return/output is + // unnecessary. This does not alter sd_keys_sources. + std::transform(sd_key_sources.begin(), sd_key_sources.end(), sd_keys.begin(), + sd_key_sources.begin(), [&cipher](const Key256& source, Key256& out) { + cipher.Transcode(source.data(), source.size(), out.data(), Op::Decrypt); + return source; ///< Return unaltered source to satisfy output requirement. + }); + + return Loader::ResultStatus::Success; +} + KeyManager::KeyManager() { // Initialize keys const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); @@ -24,12 +125,15 @@ KeyManager::KeyManager() { if (Settings::values.use_dev_keys) { dev_mode = true; AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); + AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "dev.keys_autogenerated", false); } else { dev_mode = false; AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false); + AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "prod.keys_autogenerated", false); } AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true); + AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true); } void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { @@ -56,17 +160,17 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { u128 rights_id{}; std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size()); Key128 key = Common::HexStringToArray<16>(out[1]); - SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); + s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key; } 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.at(out[0]); Key128 key = Common::HexStringToArray<16>(out[1]); - SetKey(index.type, key, index.field1, index.field2); + s128_keys[{index.type, index.field1, index.field2}] = key; } else if (s256_file_id.find(out[0]) != s256_file_id.end()) { const auto index = s256_file_id.at(out[0]); Key256 key = Common::HexStringToArray<32>(out[1]); - SetKey(index.type, key, index.field1, index.field2); + s256_keys[{index.type, index.field1, index.field2}] = key; } } } @@ -100,11 +204,50 @@ Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const { return s256_keys.at({id, field1, field2}); } +template <size_t Size> +void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname, + const std::array<u8, Size>& key) { + const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); + std::string filename = "title.keys_autogenerated"; + if (!title_key) + filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated"; + const auto add_info_text = !FileUtil::Exists(yuzu_keys_dir + DIR_SEP + filename); + FileUtil::CreateFullPath(yuzu_keys_dir + DIR_SEP + filename); + std::ofstream file(yuzu_keys_dir + DIR_SEP + filename, std::ios::app); + if (!file.is_open()) + return; + if (add_info_text) { + file + << "# This file is autogenerated by Yuzu\n" + << "# It serves to store keys that were automatically generated from the normal keys\n" + << "# If you are experiencing issues involving keys, it may help to delete this file\n"; + } + + file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key)); + AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, title_key); +} + void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { + const auto iter = std::find_if( + s128_file_id.begin(), s128_file_id.end(), + [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) { + return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == + std::tie(id, field1, field2); + }); + if (iter != s128_file_id.end()) + WriteKeyToFile(id == S128KeyType::Titlekey, iter->first, key); s128_keys[{id, field1, field2}] = key; } void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { + const auto iter = std::find_if( + s256_file_id.begin(), s256_file_id.end(), + [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) { + return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == + std::tie(id, field1, field2); + }); + if (iter != s256_file_id.end()) + WriteKeyToFile(false, iter->first, key); s256_keys[{id, field1, field2}] = key; } @@ -125,7 +268,16 @@ bool KeyManager::KeyFileExists(bool title) { FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys"); } -const std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { +void KeyManager::DeriveSDSeedLazy() { + if (HasKey(S128KeyType::SDSeed)) + return; + + const auto res = DeriveSDSeed(); + if (res != boost::none) + SetKey(S128KeyType::SDSeed, res.get()); +} + +const boost::container::flat_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}}, @@ -167,11 +319,17 @@ const std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_fi {"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}}, {"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}}, {"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}}, + {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK), 0}}, + {"aes_kek_generation_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration), 0}}, + {"aes_key_generation_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}}, + {"sd_seed", {S128KeyType::SDSeed, 0, 0}}, }; -const std::unordered_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = { +const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = { {"header_key", {S256KeyType::Header, 0, 0}}, - {"sd_card_save_key", {S256KeyType::SDSave, 0, 0}}, - {"sd_card_nca_key", {S256KeyType::SDNCA, 0, 0}}, + {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}}, + {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}}, }; } // namespace Core::Crypto diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 0c62d4421..7ca3e6cbc 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -6,11 +6,13 @@ #include <array> #include <string> +#include <string_view> #include <type_traits> -#include <unordered_map> #include <vector> +#include <boost/container/flat_map.hpp> #include <fmt/format.h> #include "common/common_types.h" +#include "core/loader/loader.h" namespace Core::Crypto { @@ -22,9 +24,8 @@ 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, // - SDSave, // - SDNCA, // + Header, // + SDKeySource, // f1=SDKeyType }; enum class S128KeyType : u64 { @@ -36,6 +37,7 @@ enum class S128KeyType : u64 { KeyArea, // f1=crypto revision f2=type {app, ocean, system} SDSeed, // Titlekey, // f1=rights id LSB f2=rights id MSB + Source, // f1=source type, f2= sub id }; enum class KeyAreaKeyType : u8 { @@ -44,6 +46,17 @@ enum class KeyAreaKeyType : u8 { System, }; +enum class SourceKeyType : u8 { + SDKEK, + AESKEKGeneration, + AESKeyGeneration, +}; + +enum class SDKeyType : u8 { + Save, + NCA, +}; + template <typename KeyType> struct KeyIndex { KeyType type; @@ -59,34 +72,12 @@ struct KeyIndex { } }; -// The following two (== and hash) are so KeyIndex can be a key in unordered_map - +// boost flat_map requires operator< for O(log(n)) lookups. template <typename KeyType> -bool operator==(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) { - return std::tie(lhs.type, lhs.field1, lhs.field2) == std::tie(rhs.type, rhs.field1, rhs.field2); +bool operator<(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) { + return std::tie(lhs.type, lhs.field1, lhs.field2) < std::tie(rhs.type, rhs.field1, rhs.field2); } -template <typename KeyType> -bool operator!=(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) { - return !operator==(lhs, rhs); -} - -} // namespace Core::Crypto - -namespace std { -template <typename KeyType> -struct hash<Core::Crypto::KeyIndex<KeyType>> { - size_t operator()(const Core::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 Core::Crypto { - class KeyManager { public: KeyManager(); @@ -102,16 +93,27 @@ public: static bool KeyFileExists(bool title); + // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system save + // 8*43 and the private file to exist. + void DeriveSDSeedLazy(); + private: - std::unordered_map<KeyIndex<S128KeyType>, Key128> s128_keys; - std::unordered_map<KeyIndex<S256KeyType>, Key256> s256_keys; + boost::container::flat_map<KeyIndex<S128KeyType>, Key128> s128_keys; + boost::container::flat_map<KeyIndex<S256KeyType>, Key256> s256_keys; bool dev_mode; void LoadFromFile(const std::string& filename, bool is_title_keys); void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, const std::string& filename, bool title); + template <size_t Size> + void WriteKeyToFile(bool title_key, std::string_view keyname, const std::array<u8, Size>& key); - static const std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id; - static const std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id; + static const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> s128_file_id; + static const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> s256_file_id; }; + +Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed); +boost::optional<Key128> DeriveSDSeed(); +Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys); + } // namespace Core::Crypto diff --git a/src/core/crypto/xts_encryption_layer.cpp b/src/core/crypto/xts_encryption_layer.cpp new file mode 100644 index 000000000..c10832cfe --- /dev/null +++ b/src/core/crypto/xts_encryption_layer.cpp @@ -0,0 +1,58 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cstring> +#include "common/assert.h" +#include "core/crypto/xts_encryption_layer.h" + +namespace Core::Crypto { + +constexpr u64 XTS_SECTOR_SIZE = 0x4000; + +XTSEncryptionLayer::XTSEncryptionLayer(FileSys::VirtualFile base_, Key256 key_) + : EncryptionLayer(std::move(base_)), cipher(key_, Mode::XTS) {} + +size_t XTSEncryptionLayer::Read(u8* data, size_t length, size_t offset) const { + if (length == 0) + return 0; + + const auto sector_offset = offset & 0x3FFF; + if (sector_offset == 0) { + if (length % XTS_SECTOR_SIZE == 0) { + std::vector<u8> raw = base->ReadBytes(length, offset); + cipher.XTSTranscode(raw.data(), raw.size(), data, offset / XTS_SECTOR_SIZE, + XTS_SECTOR_SIZE, Op::Decrypt); + return raw.size(); + } + if (length > XTS_SECTOR_SIZE) { + const auto rem = length % XTS_SECTOR_SIZE; + const auto read = length - rem; + return Read(data, read, offset) + Read(data + read, rem, offset + read); + } + std::vector<u8> buffer = base->ReadBytes(XTS_SECTOR_SIZE, offset); + if (buffer.size() < XTS_SECTOR_SIZE) + buffer.resize(XTS_SECTOR_SIZE); + cipher.XTSTranscode(buffer.data(), buffer.size(), buffer.data(), offset / XTS_SECTOR_SIZE, + XTS_SECTOR_SIZE, Op::Decrypt); + std::memcpy(data, buffer.data(), std::min(buffer.size(), length)); + return std::min(buffer.size(), length); + } + + // offset does not fall on block boundary (0x4000) + std::vector<u8> block = base->ReadBytes(0x4000, offset - sector_offset); + if (block.size() < XTS_SECTOR_SIZE) + block.resize(XTS_SECTOR_SIZE); + cipher.XTSTranscode(block.data(), block.size(), block.data(), + (offset - sector_offset) / XTS_SECTOR_SIZE, XTS_SECTOR_SIZE, Op::Decrypt); + const size_t read = XTS_SECTOR_SIZE - sector_offset; + + if (length + sector_offset < XTS_SECTOR_SIZE) { + std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read)); + return std::min<u64>(length, read); + } + std::memcpy(data, block.data() + sector_offset, read); + return read + Read(data + read, length - read, offset + read); +} +} // namespace Core::Crypto diff --git a/src/core/crypto/xts_encryption_layer.h b/src/core/crypto/xts_encryption_layer.h new file mode 100644 index 000000000..7a1f1dc64 --- /dev/null +++ b/src/core/crypto/xts_encryption_layer.h @@ -0,0 +1,25 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/crypto/aes_util.h" +#include "core/crypto/encryption_layer.h" +#include "core/crypto/key_manager.h" + +namespace Core::Crypto { + +// Sits on top of a VirtualFile and provides XTS-mode AES decription. +class XTSEncryptionLayer : public EncryptionLayer { +public: + XTSEncryptionLayer(FileSys::VirtualFile base, Key256 key); + + size_t Read(u8* data, size_t length, size_t offset) const override; + +private: + // Must be mutable as operations modify cipher contexts. + mutable AESCipher<Key256> cipher; +}; + +} // namespace Core::Crypto diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index ae4e33800..08a7cea5a 100644 --- a/src/core/file_sys/bis_factory.cpp +++ b/src/core/file_sys/bis_factory.cpp @@ -6,19 +6,12 @@ namespace FileSys { -static VirtualDir GetOrCreateDirectory(const VirtualDir& dir, std::string_view path) { - const auto res = dir->GetDirectoryRelative(path); - if (res == nullptr) - return dir->CreateDirectoryRelative(path); - return res; -} - BISFactory::BISFactory(VirtualDir nand_root_) : nand_root(std::move(nand_root_)), sysnand_cache(std::make_shared<RegisteredCache>( - GetOrCreateDirectory(nand_root, "/system/Contents/registered"))), + GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))), usrnand_cache(std::make_shared<RegisteredCache>( - GetOrCreateDirectory(nand_root, "/user/Contents/registered"))) {} + GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {} std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const { return sysnand_cache; diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 1d7c7fb10..d61a2ebe1 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -43,6 +43,8 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw); } + program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA; + auto result = AddNCAFromPartition(XCIPartition::Secure); if (result != Loader::ResultStatus::Success) { status = result; @@ -76,6 +78,10 @@ Loader::ResultStatus XCI::GetStatus() const { return status; } +Loader::ResultStatus XCI::GetProgramNCAStatus() const { + return program_nca_status; +} + VirtualDir XCI::GetPartition(XCIPartition partition) const { return partitions[static_cast<size_t>(partition)]; } @@ -143,6 +149,12 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { if (file->GetExtension() != "nca") continue; auto nca = std::make_shared<NCA>(file); + // TODO(DarkLordZach): Add proper Rev1+ Support + if (nca->IsUpdate()) + continue; + if (nca->GetType() == NCAContentType::Program) { + program_nca_status = nca->GetStatus(); + } if (nca->GetStatus() == Loader::ResultStatus::Success) { ncas.push_back(std::move(nca)); } else { diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index a03d5264e..54ab828d1 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -59,6 +59,7 @@ public: explicit XCI(VirtualFile file); Loader::ResultStatus GetStatus() const; + Loader::ResultStatus GetProgramNCAStatus() const; u8 GetFormatVersion() const; @@ -90,6 +91,7 @@ private: GamecardHeader header{}; Loader::ResultStatus status; + Loader::ResultStatus program_nca_status; std::vector<VirtualDir> partitions; std::vector<std::shared_ptr<NCA>> ncas; diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 47afcad9b..e8b5d6ece 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -178,7 +178,7 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting return std::static_pointer_cast<VfsFile>(out); } case NCASectionCryptoType::XTS: - // TODO(DarkLordZach): Implement XTSEncryptionLayer. + // TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs default: LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", static_cast<u8>(s_header.raw.header.crypto_type)); @@ -258,6 +258,10 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) { file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); } + is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) { + return header.raw.header.crypto_type == NCASectionCryptoType::BKTR; + }) != sections.end(); + for (std::ptrdiff_t i = 0; i < number_sections; ++i) { auto section = sections[i]; @@ -358,6 +362,10 @@ VirtualFile NCA::GetBaseFile() const { return file; } +bool NCA::IsUpdate() const { + return is_update; +} + bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { return false; } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 4b74c54ec..b961cfde7 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -93,6 +93,8 @@ public: VirtualFile GetBaseFile() const; + bool IsUpdate() const; + protected: bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; @@ -111,6 +113,7 @@ private: NCAHeader header{}; bool has_rights_id{}; + bool is_update{}; Loader::ResultStatus status{}; diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index e90dc6695..a02efc71e 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -254,6 +254,8 @@ RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction Refresh(); } +RegisteredCache::~RegisteredCache() = default; + bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const { return GetEntryRaw(title_id, type) != nullptr; } @@ -262,6 +264,18 @@ bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const { return GetEntryRaw(entry) != nullptr; } +VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType type) const { + const auto id = GetNcaIDFromMetadata(title_id, type); + if (id == boost::none) + return nullptr; + + return GetFileAtID(id.get()); +} + +VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const { + return GetEntryUnparsed(entry.title_id, entry.type); +} + VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const { const auto id = GetNcaIDFromMetadata(title_id, type); if (id == boost::none) diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index a7c51a59c..7b8955dfa 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -63,12 +63,16 @@ public: explicit RegisteredCache(VirtualDir dir, RegisteredCacheParsingFunction parsing_function = [](const VirtualFile& file, const NcaID& id) { return file; }); + ~RegisteredCache(); void Refresh(); bool HasEntry(u64 title_id, ContentRecordType type) const; bool HasEntry(RegisteredCacheEntry entry) const; + VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const; + VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const; + VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const; VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const; diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index d1acf379f..d66a9c9a4 100644 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp @@ -3,14 +3,27 @@ // Refer to the license.txt file included. #include <memory> +#include "core/file_sys/registered_cache.h" #include "core/file_sys/sdmc_factory.h" +#include "core/file_sys/xts_archive.h" namespace FileSys { -SDMCFactory::SDMCFactory(VirtualDir dir) : dir(std::move(dir)) {} +SDMCFactory::SDMCFactory(VirtualDir dir_) + : dir(std::move(dir_)), contents(std::make_shared<RegisteredCache>( + GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"), + [](const VirtualFile& file, const NcaID& id) { + return std::make_shared<NAX>(file, id)->GetDecrypted(); + })) {} + +SDMCFactory::~SDMCFactory() = default; ResultVal<VirtualDir> SDMCFactory::Open() { return MakeResult<VirtualDir>(dir); } +std::shared_ptr<RegisteredCache> SDMCFactory::GetSDMCContents() const { + return contents; +} + } // namespace FileSys diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h index 245060690..ea12149de 100644 --- a/src/core/file_sys/sdmc_factory.h +++ b/src/core/file_sys/sdmc_factory.h @@ -4,20 +4,27 @@ #pragma once +#include <memory> #include "core/file_sys/vfs.h" #include "core/hle/result.h" namespace FileSys { +class RegisteredCache; + /// File system interface to the SDCard archive class SDMCFactory { public: explicit SDMCFactory(VirtualDir dir); + ~SDMCFactory(); ResultVal<VirtualDir> Open(); + std::shared_ptr<RegisteredCache> GetSDMCContents() const; private: VirtualDir dir; + + std::shared_ptr<RegisteredCache> contents; }; } // namespace FileSys diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index b915b4c11..146c839f4 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -462,4 +462,11 @@ bool VfsRawCopy(VirtualFile src, VirtualFile dest) { std::vector<u8> data = src->ReadAllBytes(); return dest->WriteBytes(data, 0) == data.size(); } + +VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) { + const auto res = rel->GetDirectoryRelative(path); + if (res == nullptr) + return rel->CreateDirectoryRelative(path); + return res; +} } // namespace FileSys diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index 22db08b59..5142a3e86 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -318,4 +318,8 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block // directory of src/dest. bool VfsRawCopy(VirtualFile src, VirtualFile dest); +// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not +// it attempts to create it and returns the new dir or nullptr on failure. +VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path); + } // namespace FileSys diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp new file mode 100644 index 000000000..552835738 --- /dev/null +++ b/src/core/file_sys/xts_archive.cpp @@ -0,0 +1,169 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <array> +#include <cstring> +#include <regex> +#include <string> +#include <mbedtls/md.h> +#include <mbedtls/sha256.h> +#include "common/assert.h" +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "core/crypto/aes_util.h" +#include "core/crypto/xts_encryption_layer.h" +#include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/xts_archive.h" +#include "core/loader/loader.h" + +namespace FileSys { + +constexpr u64 NAX_HEADER_PADDING_SIZE = 0x4000; + +template <typename SourceData, typename SourceKey, typename Destination> +static bool CalculateHMAC256(Destination* out, const SourceKey* key, size_t key_length, + const SourceData* data, size_t data_length) { + mbedtls_md_context_t context; + mbedtls_md_init(&context); + + const auto key_f = reinterpret_cast<const u8*>(key); + const std::vector<u8> key_v(key_f, key_f + key_length); + + if (mbedtls_md_setup(&context, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1) || + mbedtls_md_hmac_starts(&context, reinterpret_cast<const u8*>(key), key_length) || + mbedtls_md_hmac_update(&context, reinterpret_cast<const u8*>(data), data_length) || + mbedtls_md_hmac_finish(&context, reinterpret_cast<u8*>(out))) { + mbedtls_md_free(&context); + return false; + } + + mbedtls_md_free(&context); + return true; +} + +NAX::NAX(VirtualFile file_) : file(std::move(file_)), header(std::make_unique<NAXHeader>()) { + std::string path = FileUtil::SanitizePath(file->GetFullPath()); + static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca", + std::regex_constants::ECMAScript | + std::regex_constants::icase); + std::smatch match; + if (!std::regex_search(path, match, nax_path_regex)) { + status = Loader::ResultStatus::ErrorBadNAXFilePath; + return; + } + + std::string two_dir = match[1]; + std::string nca_id = match[2]; + std::transform(two_dir.begin(), two_dir.end(), two_dir.begin(), ::toupper); + std::transform(nca_id.begin(), nca_id.end(), nca_id.begin(), ::tolower); + + status = Parse(fmt::format("/registered/{}/{}.nca", two_dir, nca_id)); +} + +NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id) + : file(std::move(file_)), header(std::make_unique<NAXHeader>()) { + Core::Crypto::SHA256Hash hash{}; + mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0); + status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0], + Common::HexArrayToString(nca_id, false))); +} + +Loader::ResultStatus NAX::Parse(std::string_view path) { + if (file->ReadObject(header.get()) != sizeof(NAXHeader)) + return Loader::ResultStatus::ErrorBadNAXHeader; + + if (header->magic != Common::MakeMagic('N', 'A', 'X', '0')) + return Loader::ResultStatus::ErrorBadNAXHeader; + + if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size) + return Loader::ResultStatus::ErrorIncorrectNAXFileSize; + + keys.DeriveSDSeedLazy(); + std::array<Core::Crypto::Key256, 2> sd_keys{}; + const auto sd_keys_res = Core::Crypto::DeriveSDKeys(sd_keys, keys); + if (sd_keys_res != Loader::ResultStatus::Success) { + return sd_keys_res; + } + + const auto enc_keys = header->key_area; + + size_t i = 0; + for (; i < sd_keys.size(); ++i) { + std::array<Core::Crypto::Key128, 2> nax_keys{}; + if (!CalculateHMAC256(nax_keys.data(), sd_keys[i].data(), 0x10, std::string(path).c_str(), + path.size())) { + return Loader::ResultStatus::ErrorNAXKeyHMACFailed; + } + + for (size_t j = 0; j < nax_keys.size(); ++j) { + Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(nax_keys[j], + Core::Crypto::Mode::ECB); + cipher.Transcode(enc_keys[j].data(), 0x10, header->key_area[j].data(), + Core::Crypto::Op::Decrypt); + } + + Core::Crypto::SHA256Hash validation{}; + if (!CalculateHMAC256(validation.data(), &header->magic, 0x60, sd_keys[i].data() + 0x10, + 0x10)) { + return Loader::ResultStatus::ErrorNAXValidationHMACFailed; + } + if (header->hmac == validation) + break; + } + + if (i == 2) { + return Loader::ResultStatus::ErrorNAXKeyDerivationFailed; + } + + type = static_cast<NAXContentType>(i); + + Core::Crypto::Key256 final_key{}; + std::memcpy(final_key.data(), &header->key_area, final_key.size()); + const auto enc_file = + std::make_shared<OffsetVfsFile>(file, header->file_size, NAX_HEADER_PADDING_SIZE); + dec_file = std::make_shared<Core::Crypto::XTSEncryptionLayer>(enc_file, final_key); + + return Loader::ResultStatus::Success; +} + +Loader::ResultStatus NAX::GetStatus() const { + return status; +} + +VirtualFile NAX::GetDecrypted() const { + return dec_file; +} + +std::shared_ptr<NCA> NAX::AsNCA() const { + if (type == NAXContentType::NCA) + return std::make_shared<NCA>(GetDecrypted()); + return nullptr; +} + +NAXContentType NAX::GetContentType() const { + return type; +} + +std::vector<std::shared_ptr<VfsFile>> NAX::GetFiles() const { + return {dec_file}; +} + +std::vector<std::shared_ptr<VfsDirectory>> NAX::GetSubdirectories() const { + return {}; +} + +std::string NAX::GetName() const { + return file->GetName(); +} + +std::shared_ptr<VfsDirectory> NAX::GetParentDirectory() const { + return file->GetContainingDirectory(); +} + +bool NAX::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { + return false; +} +} // namespace FileSys diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h new file mode 100644 index 000000000..55d2154a6 --- /dev/null +++ b/src/core/file_sys/xts_archive.h @@ -0,0 +1,69 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <vector> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/crypto/key_manager.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/vfs.h" +#include "core/loader/loader.h" + +namespace FileSys { + +struct NAXHeader { + std::array<u8, 0x20> hmac; + u64_le magic; + std::array<Core::Crypto::Key128, 2> key_area; + u64_le file_size; + INSERT_PADDING_BYTES(0x30); +}; +static_assert(sizeof(NAXHeader) == 0x80, "NAXHeader has incorrect size."); + +enum class NAXContentType : u8 { + Save = 0, + NCA = 1, +}; + +class NAX : public ReadOnlyVfsDirectory { +public: + explicit NAX(VirtualFile file); + explicit NAX(VirtualFile file, std::array<u8, 0x10> nca_id); + + Loader::ResultStatus GetStatus() const; + + VirtualFile GetDecrypted() const; + + std::shared_ptr<NCA> AsNCA() const; + + NAXContentType GetContentType() 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 Parse(std::string_view path); + + std::unique_ptr<NAXHeader> header; + + VirtualFile file; + Loader::ResultStatus status; + NAXContentType type; + + VirtualFile dec_file; + + Core::Crypto::KeyManager keys; +}; +} // namespace FileSys diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index fa81c01cf..881c39e31 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -305,17 +305,38 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() { } std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() { + LOG_TRACE(Service_FS, "Opening System NAND Contents"); + + if (bis_factory == nullptr) + return nullptr; + return bis_factory->GetSystemNANDContents(); } std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() { + LOG_TRACE(Service_FS, "Opening User NAND Contents"); + + if (bis_factory == nullptr) + return nullptr; + return bis_factory->GetUserNANDContents(); } -void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) { - romfs_factory = nullptr; - save_data_factory = nullptr; - sdmc_factory = nullptr; +std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents() { + LOG_TRACE(Service_FS, "Opening SDMC Contents"); + + if (sdmc_factory == nullptr) + return nullptr; + + return sdmc_factory->GetSDMCContents(); +} + +void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) { + if (overwrite) { + bis_factory = nullptr; + save_data_factory = nullptr; + sdmc_factory = nullptr; + } auto nand_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite); @@ -324,16 +345,15 @@ void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) { if (bis_factory == nullptr) bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory); - - auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory)); - save_data_factory = std::move(savedata); - - auto sdcard = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory)); - sdmc_factory = std::move(sdcard); + if (save_data_factory == nullptr) + save_data_factory = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory)); + if (sdmc_factory == nullptr) + sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory)); } void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs) { - RegisterFileSystems(vfs); + romfs_factory = nullptr; + CreateFactories(vfs, false); std::make_shared<FSP_LDR>()->InstallAsService(service_manager); std::make_shared<FSP_PR>()->InstallAsService(service_manager); std::make_shared<FSP_SRV>()->InstallAsService(service_manager); diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index df78be44a..d88a66825 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -46,8 +46,12 @@ ResultVal<FileSys::VirtualDir> OpenSDMC(); std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents(); std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); +std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents(); + +// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function +// above is called. +void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true); -/// Registers all Filesystem services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs); // A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 70ef5d240..c13fb49b8 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -11,6 +11,7 @@ #include "core/hle/kernel/process.h" #include "core/loader/deconstructed_rom_directory.h" #include "core/loader/elf.h" +#include "core/loader/nax.h" #include "core/loader/nca.h" #include "core/loader/nro.h" #include "core/loader/nso.h" @@ -32,6 +33,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) { CHECK_TYPE(NRO) CHECK_TYPE(NCA) CHECK_TYPE(XCI) + CHECK_TYPE(NAX) #undef CHECK_TYPE @@ -73,6 +75,8 @@ std::string GetFileTypeString(FileType type) { return "NCA"; case FileType::XCI: return "XCI"; + case FileType::NAX: + return "NAX"; case FileType::DeconstructedRomDirectory: return "Directory"; case FileType::Error: @@ -83,7 +87,7 @@ std::string GetFileTypeString(FileType type) { return "unknown"; } -constexpr std::array<const char*, 36> RESULT_MESSAGES{ +constexpr std::array<const char*, 49> RESULT_MESSAGES{ "The operation completed successfully.", "The loader requested to load is already loaded.", "The operation is not implemented.", @@ -120,6 +124,19 @@ constexpr std::array<const char*, 36> RESULT_MESSAGES{ "There was a general error loading the NRO into emulated memory.", "There is no icon available.", "There is no control data available.", + "The NAX file has a bad header.", + "The NAX file has incorrect size as determined by the header.", + "The HMAC to generated the NAX decryption keys failed.", + "The HMAC to validate the NAX decryption keys failed.", + "The NAX key derivation failed.", + "The NAX file cannot be interpreted as an NCA file.", + "The NAX file has an incorrect path.", + "The SD seed could not be found or derived.", + "The SD KEK Source could not be found.", + "The AES KEK Generation Source could not be found.", + "The AES Key Generation Source could not be found.", + "The SD Save Key Source could not be found.", + "The SD NCA Key Source could not be found.", }; std::ostream& operator<<(std::ostream& os, ResultStatus status) { @@ -150,13 +167,18 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT case FileType::NRO: return std::make_unique<AppLoader_NRO>(std::move(file)); - // NX NCA file format. + // NX NCA (Nintendo Content Archive) file format. case FileType::NCA: return std::make_unique<AppLoader_NCA>(std::move(file)); + // NX XCI (nX Card Image) file format. case FileType::XCI: return std::make_unique<AppLoader_XCI>(std::move(file)); + // NX NAX (NintendoAesXts) file format. + case FileType::NAX: + return std::make_unique<AppLoader_NAX>(std::move(file)); + // NX deconstructed ROM directory. case FileType::DeconstructedRomDirectory: return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); @@ -170,7 +192,8 @@ std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file) { FileType type = IdentifyFile(file); FileType filename_type = GuessFromFilename(file->GetName()); - if (type != filename_type) { + // Special case: 00 is either a NCA or NAX. + if (type != filename_type && !(file->GetName() == "00" && type == FileType::NAX)) { LOG_WARNING(Loader, "File {} has a different type than its extension.", file->GetName()); if (FileType::Unknown == type) type = filename_type; diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index b74cfbf8a..885fee84c 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -32,6 +32,7 @@ enum class FileType { NRO, NCA, XCI, + NAX, DeconstructedRomDirectory, }; @@ -93,6 +94,19 @@ enum class ResultStatus : u16 { ErrorLoadingNRO, ErrorNoIcon, ErrorNoControl, + ErrorBadNAXHeader, + ErrorIncorrectNAXFileSize, + ErrorNAXKeyHMACFailed, + ErrorNAXValidationHMACFailed, + ErrorNAXKeyDerivationFailed, + ErrorNAXInconvertibleToNCA, + ErrorBadNAXFilePath, + ErrorMissingSDSeed, + ErrorMissingSDKEKSource, + ErrorMissingAESKEKGenerationSource, + ErrorMissingAESKeyGenerationSource, + ErrorMissingSDSaveKeySource, + ErrorMissingSDNCAKeySource, }; std::ostream& operator<<(std::ostream& os, ResultStatus status); diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp new file mode 100644 index 000000000..b46d81c02 --- /dev/null +++ b/src/core/loader/nax.cpp @@ -0,0 +1,66 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/romfs.h" +#include "core/file_sys/xts_archive.h" +#include "core/hle/kernel/process.h" +#include "core/loader/nax.h" +#include "core/loader/nca.h" + +namespace Loader { + +AppLoader_NAX::AppLoader_NAX(FileSys::VirtualFile file) + : AppLoader(file), nax(std::make_unique<FileSys::NAX>(file)), + nca_loader(std::make_unique<AppLoader_NCA>(nax->GetDecrypted())) {} + +AppLoader_NAX::~AppLoader_NAX() = default; + +FileType AppLoader_NAX::IdentifyType(const FileSys::VirtualFile& file) { + FileSys::NAX nax(file); + + if (nax.GetStatus() == ResultStatus::Success && nax.AsNCA() != nullptr && + nax.AsNCA()->GetStatus() == ResultStatus::Success) { + return FileType::NAX; + } + + return FileType::Error; +} + +ResultStatus AppLoader_NAX::Load(Kernel::SharedPtr<Kernel::Process>& process) { + if (is_loaded) { + return ResultStatus::ErrorAlreadyLoaded; + } + + if (nax->GetStatus() != ResultStatus::Success) + return nax->GetStatus(); + + const auto nca = nax->AsNCA(); + if (nca == nullptr) { + if (!Core::Crypto::KeyManager::KeyFileExists(false)) + return ResultStatus::ErrorMissingProductionKeyFile; + return ResultStatus::ErrorNAXInconvertibleToNCA; + } + + if (nca->GetStatus() != ResultStatus::Success) + return nca->GetStatus(); + + const auto result = nca_loader->Load(process); + if (result != ResultStatus::Success) + return result; + + is_loaded = true; + + return ResultStatus::Success; +} + +ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) { + return nca_loader->ReadRomFS(dir); +} + +ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { + return nca_loader->ReadProgramId(out_program_id); +} +} // namespace Loader diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h new file mode 100644 index 000000000..4dbae2918 --- /dev/null +++ b/src/core/loader/nax.h @@ -0,0 +1,48 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "common/common_types.h" +#include "core/loader/loader.h" + +namespace FileSys { + +class NAX; + +} // namespace FileSys + +namespace Loader { + +class AppLoader_NCA; + +/// Loads a NAX file +class AppLoader_NAX final : public AppLoader { +public: + explicit AppLoader_NAX(FileSys::VirtualFile file); + ~AppLoader_NAX() override; + + /** + * 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; + +private: + std::unique_ptr<FileSys::NAX> nax; + std::unique_ptr<AppLoader_NCA> nca_loader; +}; + +} // namespace Loader diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 4c4979545..9dc4d1f35 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -61,11 +61,12 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) { if (xci->GetStatus() != ResultStatus::Success) return xci->GetStatus(); - if (xci->GetNCAFileByType(FileSys::NCAContentType::Program) == nullptr) { - if (!Core::Crypto::KeyManager::KeyFileExists(false)) - return ResultStatus::ErrorMissingProductionKeyFile; - return ResultStatus::ErrorXCIMissingProgramNCA; - } + if (xci->GetProgramNCAStatus() != ResultStatus::Success) + return xci->GetProgramNCAStatus(); + + const auto nca = xci->GetNCAFileByType(FileSys::NCAContentType::Program); + if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false)) + return ResultStatus::ErrorMissingProductionKeyFile; auto result = nca_loader->Load(process); if (result != ResultStatus::Success) |