Merge pull request #1094 from DarkLordZach/nax0

file_sys: Add support for NAX archives
This commit is contained in:
Mat M 2018-08-24 23:47:46 -04:00 committed by GitHub
commit 6426b0f551
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 821 additions and 97 deletions

View file

@ -20,6 +20,8 @@ add_library(core STATIC
crypto/key_manager.h crypto/key_manager.h
crypto/ctr_encryption_layer.cpp crypto/ctr_encryption_layer.cpp
crypto/ctr_encryption_layer.h crypto/ctr_encryption_layer.h
crypto/xts_encryption_layer.cpp
crypto/xts_encryption_layer.h
file_sys/bis_factory.cpp file_sys/bis_factory.cpp
file_sys/bis_factory.h file_sys/bis_factory.h
file_sys/card_image.cpp file_sys/card_image.cpp
@ -57,6 +59,8 @@ add_library(core STATIC
file_sys/vfs_real.h file_sys/vfs_real.h
file_sys/vfs_vector.cpp file_sys/vfs_vector.cpp
file_sys/vfs_vector.h file_sys/vfs_vector.h
file_sys/xts_archive.cpp
file_sys/xts_archive.h
frontend/emu_window.cpp frontend/emu_window.cpp
frontend/emu_window.h frontend/emu_window.h
frontend/framebuffer_layout.cpp frontend/framebuffer_layout.cpp
@ -347,6 +351,8 @@ add_library(core STATIC
loader/linker.h loader/linker.h
loader/loader.cpp loader/loader.cpp
loader/loader.h loader/loader.h
loader/nax.cpp
loader/nax.h
loader/nca.cpp loader/nca.cpp
loader/nca.h loader/nca.h
loader/nro.cpp loader/nro.cpp

View file

@ -99,10 +99,7 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op
template <typename Key, size_t KeySize> template <typename Key, size_t KeySize>
void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id,
size_t sector_size, Op op) { size_t sector_size, Op op) {
if (size % sector_size > 0) { ASSERT_MSG(size % sector_size == 0, "XTS decryption size must be a multiple of sector size.");
LOG_CRITICAL(Crypto, "Data size must be a multiple of sector size.");
return;
}
for (size_t i = 0; i < size; i += sector_size) { for (size_t i = 0; i < size; i += sector_size) {
SetIV(CalculateNintendoTweak(sector_id++)); SetIV(CalculateNintendoTweak(sector_id++));

View file

@ -20,10 +20,8 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
if (sector_offset == 0) { if (sector_offset == 0) {
UpdateIV(base_offset + offset); UpdateIV(base_offset + offset);
std::vector<u8> raw = base->ReadBytes(length, offset); std::vector<u8> raw = base->ReadBytes(length, offset);
if (raw.size() != length) cipher.Transcode(raw.data(), raw.size(), data, Op::Decrypt);
return Read(data, raw.size(), offset); return raw.size();
cipher.Transcode(raw.data(), length, data, Op::Decrypt);
return length;
} }
// offset does not fall on block boundary (0x10) // 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) { if (length + sector_offset < 0x10) {
std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read)); 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); std::memcpy(data, block.data() + sector_offset, read);
return read + Read(data + read, length - read, offset + read); return read + Read(data + read, length - read, offset + read);

View file

@ -12,11 +12,112 @@
#include "common/file_util.h" #include "common/file_util.h"
#include "common/hex_util.h" #include "common/hex_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h" #include "core/crypto/key_manager.h"
#include "core/settings.h" #include "core/settings.h"
namespace Core::Crypto { 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() { KeyManager::KeyManager() {
// Initialize keys // Initialize keys
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
@ -24,12 +125,15 @@ KeyManager::KeyManager() {
if (Settings::values.use_dev_keys) { if (Settings::values.use_dev_keys) {
dev_mode = true; dev_mode = true;
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false);
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "dev.keys_autogenerated", false);
} else { } else {
dev_mode = false; dev_mode = false;
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", 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, 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) { 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{}; u128 rights_id{};
std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size()); std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size());
Key128 key = Common::HexStringToArray<16>(out[1]); 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 { } else {
std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower); std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower);
if (s128_file_id.find(out[0]) != s128_file_id.end()) { if (s128_file_id.find(out[0]) != s128_file_id.end()) {
const auto index = s128_file_id.at(out[0]); const auto index = s128_file_id.at(out[0]);
Key128 key = Common::HexStringToArray<16>(out[1]); 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()) { } else if (s256_file_id.find(out[0]) != s256_file_id.end()) {
const auto index = s256_file_id.at(out[0]); const auto index = s256_file_id.at(out[0]);
Key256 key = Common::HexStringToArray<32>(out[1]); 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}); 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) { 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; s128_keys[{id, field1, field2}] = key;
} }
void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { 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; s256_keys[{id, field1, field2}] = key;
} }
@ -125,7 +268,16 @@ bool KeyManager::KeyFileExists(bool title) {
FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys"); 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_00", {S128KeyType::Master, 0, 0}},
{"master_key_01", {S128KeyType::Master, 1, 0}}, {"master_key_01", {S128KeyType::Master, 1, 0}},
{"master_key_02", {S128KeyType::Master, 2, 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_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_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}},
{"key_area_key_system_04", {S128KeyType::KeyArea, 4, 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}}, {"header_key", {S256KeyType::Header, 0, 0}},
{"sd_card_save_key", {S256KeyType::SDSave, 0, 0}}, {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}},
{"sd_card_nca_key", {S256KeyType::SDNCA, 0, 0}}, {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}},
}; };
} // namespace Core::Crypto } // namespace Core::Crypto

View file

@ -6,11 +6,13 @@
#include <array> #include <array>
#include <string> #include <string>
#include <string_view>
#include <type_traits> #include <type_traits>
#include <unordered_map>
#include <vector> #include <vector>
#include <boost/container/flat_map.hpp>
#include <fmt/format.h> #include <fmt/format.h>
#include "common/common_types.h" #include "common/common_types.h"
#include "core/loader/loader.h"
namespace Core::Crypto { namespace Core::Crypto {
@ -23,8 +25,7 @@ static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big.");
enum class S256KeyType : u64 { enum class S256KeyType : u64 {
Header, // Header, //
SDSave, // SDKeySource, // f1=SDKeyType
SDNCA, //
}; };
enum class S128KeyType : u64 { enum class S128KeyType : u64 {
@ -36,6 +37,7 @@ enum class S128KeyType : u64 {
KeyArea, // f1=crypto revision f2=type {app, ocean, system} KeyArea, // f1=crypto revision f2=type {app, ocean, system}
SDSeed, // SDSeed, //
Titlekey, // f1=rights id LSB f2=rights id MSB Titlekey, // f1=rights id LSB f2=rights id MSB
Source, // f1=source type, f2= sub id
}; };
enum class KeyAreaKeyType : u8 { enum class KeyAreaKeyType : u8 {
@ -44,6 +46,17 @@ enum class KeyAreaKeyType : u8 {
System, System,
}; };
enum class SourceKeyType : u8 {
SDKEK,
AESKEKGeneration,
AESKeyGeneration,
};
enum class SDKeyType : u8 {
Save,
NCA,
};
template <typename KeyType> template <typename KeyType>
struct KeyIndex { struct KeyIndex {
KeyType type; 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> template <typename KeyType>
bool operator==(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) { 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); 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 { class KeyManager {
public: public:
KeyManager(); KeyManager();
@ -102,16 +93,27 @@ public:
static bool KeyFileExists(bool title); 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: private:
std::unordered_map<KeyIndex<S128KeyType>, Key128> s128_keys; boost::container::flat_map<KeyIndex<S128KeyType>, Key128> s128_keys;
std::unordered_map<KeyIndex<S256KeyType>, Key256> s256_keys; boost::container::flat_map<KeyIndex<S256KeyType>, Key256> s256_keys;
bool dev_mode; bool dev_mode;
void LoadFromFile(const std::string& filename, bool is_title_keys); void LoadFromFile(const std::string& filename, bool is_title_keys);
void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
const std::string& filename, bool title); 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 boost::container::flat_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<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 } // namespace Core::Crypto

View file

@ -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

View file

@ -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

View file

@ -6,19 +6,12 @@
namespace FileSys { 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_) BISFactory::BISFactory(VirtualDir nand_root_)
: nand_root(std::move(nand_root_)), : nand_root(std::move(nand_root_)),
sysnand_cache(std::make_shared<RegisteredCache>( sysnand_cache(std::make_shared<RegisteredCache>(
GetOrCreateDirectory(nand_root, "/system/Contents/registered"))), GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
usrnand_cache(std::make_shared<RegisteredCache>( 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 { std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
return sysnand_cache; return sysnand_cache;

View file

@ -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); partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw);
} }
program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
auto result = AddNCAFromPartition(XCIPartition::Secure); auto result = AddNCAFromPartition(XCIPartition::Secure);
if (result != Loader::ResultStatus::Success) { if (result != Loader::ResultStatus::Success) {
status = result; status = result;
@ -76,6 +78,10 @@ Loader::ResultStatus XCI::GetStatus() const {
return status; return status;
} }
Loader::ResultStatus XCI::GetProgramNCAStatus() const {
return program_nca_status;
}
VirtualDir XCI::GetPartition(XCIPartition partition) const { VirtualDir XCI::GetPartition(XCIPartition partition) const {
return partitions[static_cast<size_t>(partition)]; return partitions[static_cast<size_t>(partition)];
} }
@ -143,6 +149,12 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
if (file->GetExtension() != "nca") if (file->GetExtension() != "nca")
continue; continue;
auto nca = std::make_shared<NCA>(file); 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) { if (nca->GetStatus() == Loader::ResultStatus::Success) {
ncas.push_back(std::move(nca)); ncas.push_back(std::move(nca));
} else { } else {

View file

@ -59,6 +59,7 @@ public:
explicit XCI(VirtualFile file); explicit XCI(VirtualFile file);
Loader::ResultStatus GetStatus() const; Loader::ResultStatus GetStatus() const;
Loader::ResultStatus GetProgramNCAStatus() const;
u8 GetFormatVersion() const; u8 GetFormatVersion() const;
@ -90,6 +91,7 @@ private:
GamecardHeader header{}; GamecardHeader header{};
Loader::ResultStatus status; Loader::ResultStatus status;
Loader::ResultStatus program_nca_status;
std::vector<VirtualDir> partitions; std::vector<VirtualDir> partitions;
std::vector<std::shared_ptr<NCA>> ncas; std::vector<std::shared_ptr<NCA>> ncas;

View file

@ -178,7 +178,7 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
return std::static_pointer_cast<VfsFile>(out); return std::static_pointer_cast<VfsFile>(out);
} }
case NCASectionCryptoType::XTS: case NCASectionCryptoType::XTS:
// TODO(DarkLordZach): Implement XTSEncryptionLayer. // TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs
default: default:
LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}",
static_cast<u8>(s_header.raw.header.crypto_type)); 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); 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) { for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
auto section = sections[i]; auto section = sections[i];
@ -358,6 +362,10 @@ VirtualFile NCA::GetBaseFile() const {
return file; return file;
} }
bool NCA::IsUpdate() const {
return is_update;
}
bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
return false; return false;
} }

View file

@ -93,6 +93,8 @@ public:
VirtualFile GetBaseFile() const; VirtualFile GetBaseFile() const;
bool IsUpdate() const;
protected: protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
@ -111,6 +113,7 @@ private:
NCAHeader header{}; NCAHeader header{};
bool has_rights_id{}; bool has_rights_id{};
bool is_update{};
Loader::ResultStatus status{}; Loader::ResultStatus status{};

View file

@ -254,6 +254,8 @@ RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction
Refresh(); Refresh();
} }
RegisteredCache::~RegisteredCache() = default;
bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const { bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const {
return GetEntryRaw(title_id, type) != nullptr; return GetEntryRaw(title_id, type) != nullptr;
} }
@ -262,6 +264,18 @@ bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
return GetEntryRaw(entry) != nullptr; 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 { VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
const auto id = GetNcaIDFromMetadata(title_id, type); const auto id = GetNcaIDFromMetadata(title_id, type);
if (id == boost::none) if (id == boost::none)

View file

@ -63,12 +63,16 @@ public:
explicit RegisteredCache(VirtualDir dir, explicit RegisteredCache(VirtualDir dir,
RegisteredCacheParsingFunction parsing_function = RegisteredCacheParsingFunction parsing_function =
[](const VirtualFile& file, const NcaID& id) { return file; }); [](const VirtualFile& file, const NcaID& id) { return file; });
~RegisteredCache();
void Refresh(); void Refresh();
bool HasEntry(u64 title_id, ContentRecordType type) const; bool HasEntry(u64 title_id, ContentRecordType type) const;
bool HasEntry(RegisteredCacheEntry entry) 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(u64 title_id, ContentRecordType type) const;
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const; VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;

View file

@ -3,14 +3,27 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <memory> #include <memory>
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/sdmc_factory.h" #include "core/file_sys/sdmc_factory.h"
#include "core/file_sys/xts_archive.h"
namespace FileSys { 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() { ResultVal<VirtualDir> SDMCFactory::Open() {
return MakeResult<VirtualDir>(dir); return MakeResult<VirtualDir>(dir);
} }
std::shared_ptr<RegisteredCache> SDMCFactory::GetSDMCContents() const {
return contents;
}
} // namespace FileSys } // namespace FileSys

View file

@ -4,20 +4,27 @@
#pragma once #pragma once
#include <memory>
#include "core/file_sys/vfs.h" #include "core/file_sys/vfs.h"
#include "core/hle/result.h" #include "core/hle/result.h"
namespace FileSys { namespace FileSys {
class RegisteredCache;
/// File system interface to the SDCard archive /// File system interface to the SDCard archive
class SDMCFactory { class SDMCFactory {
public: public:
explicit SDMCFactory(VirtualDir dir); explicit SDMCFactory(VirtualDir dir);
~SDMCFactory();
ResultVal<VirtualDir> Open(); ResultVal<VirtualDir> Open();
std::shared_ptr<RegisteredCache> GetSDMCContents() const;
private: private:
VirtualDir dir; VirtualDir dir;
std::shared_ptr<RegisteredCache> contents;
}; };
} // namespace FileSys } // namespace FileSys

View file

@ -462,4 +462,11 @@ bool VfsRawCopy(VirtualFile src, VirtualFile dest) {
std::vector<u8> data = src->ReadAllBytes(); std::vector<u8> data = src->ReadAllBytes();
return dest->WriteBytes(data, 0) == data.size(); 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 } // namespace FileSys

View file

@ -318,4 +318,8 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block
// directory of src/dest. // directory of src/dest.
bool VfsRawCopy(VirtualFile src, VirtualFile 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 } // namespace FileSys

View file

@ -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

View file

@ -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

View file

@ -305,17 +305,38 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
} }
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() { std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
LOG_TRACE(Service_FS, "Opening System NAND Contents");
if (bis_factory == nullptr)
return nullptr;
return bis_factory->GetSystemNANDContents(); return bis_factory->GetSystemNANDContents();
} }
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() { std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
LOG_TRACE(Service_FS, "Opening User NAND Contents");
if (bis_factory == nullptr)
return nullptr;
return bis_factory->GetUserNANDContents(); return bis_factory->GetUserNANDContents();
} }
void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) { std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents() {
romfs_factory = nullptr; 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; save_data_factory = nullptr;
sdmc_factory = nullptr; sdmc_factory = nullptr;
}
auto nand_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), auto nand_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
FileSys::Mode::ReadWrite); FileSys::Mode::ReadWrite);
@ -324,16 +345,15 @@ void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
if (bis_factory == nullptr) if (bis_factory == nullptr)
bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory); bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory);
if (save_data_factory == nullptr)
auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory)); save_data_factory = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
save_data_factory = std::move(savedata); if (sdmc_factory == nullptr)
sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
auto sdcard = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
sdmc_factory = std::move(sdcard);
} }
void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs) { 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_LDR>()->InstallAsService(service_manager);
std::make_shared<FSP_PR>()->InstallAsService(service_manager); std::make_shared<FSP_PR>()->InstallAsService(service_manager);
std::make_shared<FSP_SRV>()->InstallAsService(service_manager); std::make_shared<FSP_SRV>()->InstallAsService(service_manager);

View file

@ -46,8 +46,12 @@ ResultVal<FileSys::VirtualDir> OpenSDMC();
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents(); std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); 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); 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 // A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of

View file

@ -11,6 +11,7 @@
#include "core/hle/kernel/process.h" #include "core/hle/kernel/process.h"
#include "core/loader/deconstructed_rom_directory.h" #include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/elf.h" #include "core/loader/elf.h"
#include "core/loader/nax.h"
#include "core/loader/nca.h" #include "core/loader/nca.h"
#include "core/loader/nro.h" #include "core/loader/nro.h"
#include "core/loader/nso.h" #include "core/loader/nso.h"
@ -32,6 +33,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
CHECK_TYPE(NRO) CHECK_TYPE(NRO)
CHECK_TYPE(NCA) CHECK_TYPE(NCA)
CHECK_TYPE(XCI) CHECK_TYPE(XCI)
CHECK_TYPE(NAX)
#undef CHECK_TYPE #undef CHECK_TYPE
@ -73,6 +75,8 @@ std::string GetFileTypeString(FileType type) {
return "NCA"; return "NCA";
case FileType::XCI: case FileType::XCI:
return "XCI"; return "XCI";
case FileType::NAX:
return "NAX";
case FileType::DeconstructedRomDirectory: case FileType::DeconstructedRomDirectory:
return "Directory"; return "Directory";
case FileType::Error: case FileType::Error:
@ -83,7 +87,7 @@ std::string GetFileTypeString(FileType type) {
return "unknown"; return "unknown";
} }
constexpr std::array<const char*, 36> RESULT_MESSAGES{ constexpr std::array<const char*, 49> RESULT_MESSAGES{
"The operation completed successfully.", "The operation completed successfully.",
"The loader requested to load is already loaded.", "The loader requested to load is already loaded.",
"The operation is not implemented.", "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 was a general error loading the NRO into emulated memory.",
"There is no icon available.", "There is no icon available.",
"There is no control data 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) { 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: case FileType::NRO:
return std::make_unique<AppLoader_NRO>(std::move(file)); return std::make_unique<AppLoader_NRO>(std::move(file));
// NX NCA file format. // NX NCA (Nintendo Content Archive) file format.
case FileType::NCA: case FileType::NCA:
return std::make_unique<AppLoader_NCA>(std::move(file)); return std::make_unique<AppLoader_NCA>(std::move(file));
// NX XCI (nX Card Image) file format.
case FileType::XCI: case FileType::XCI:
return std::make_unique<AppLoader_XCI>(std::move(file)); 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. // NX deconstructed ROM directory.
case FileType::DeconstructedRomDirectory: case FileType::DeconstructedRomDirectory:
return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); 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 type = IdentifyFile(file);
FileType filename_type = GuessFromFilename(file->GetName()); 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()); LOG_WARNING(Loader, "File {} has a different type than its extension.", file->GetName());
if (FileType::Unknown == type) if (FileType::Unknown == type)
type = filename_type; type = filename_type;

View file

@ -32,6 +32,7 @@ enum class FileType {
NRO, NRO,
NCA, NCA,
XCI, XCI,
NAX,
DeconstructedRomDirectory, DeconstructedRomDirectory,
}; };
@ -93,6 +94,19 @@ enum class ResultStatus : u16 {
ErrorLoadingNRO, ErrorLoadingNRO,
ErrorNoIcon, ErrorNoIcon,
ErrorNoControl, ErrorNoControl,
ErrorBadNAXHeader,
ErrorIncorrectNAXFileSize,
ErrorNAXKeyHMACFailed,
ErrorNAXValidationHMACFailed,
ErrorNAXKeyDerivationFailed,
ErrorNAXInconvertibleToNCA,
ErrorBadNAXFilePath,
ErrorMissingSDSeed,
ErrorMissingSDKEKSource,
ErrorMissingAESKEKGenerationSource,
ErrorMissingAESKeyGenerationSource,
ErrorMissingSDSaveKeySource,
ErrorMissingSDNCAKeySource,
}; };
std::ostream& operator<<(std::ostream& os, ResultStatus status); std::ostream& operator<<(std::ostream& os, ResultStatus status);

66
src/core/loader/nax.cpp Normal file
View file

@ -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

48
src/core/loader/nax.h Normal file
View file

@ -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

View file

@ -61,11 +61,12 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) {
if (xci->GetStatus() != ResultStatus::Success) if (xci->GetStatus() != ResultStatus::Success)
return xci->GetStatus(); return xci->GetStatus();
if (xci->GetNCAFileByType(FileSys::NCAContentType::Program) == nullptr) { if (xci->GetProgramNCAStatus() != ResultStatus::Success)
if (!Core::Crypto::KeyManager::KeyFileExists(false)) return xci->GetProgramNCAStatus();
const auto nca = xci->GetNCAFileByType(FileSys::NCAContentType::Program);
if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false))
return ResultStatus::ErrorMissingProductionKeyFile; return ResultStatus::ErrorMissingProductionKeyFile;
return ResultStatus::ErrorXCIMissingProgramNCA;
}
auto result = nca_loader->Load(process); auto result = nca_loader->Load(process);
if (result != ResultStatus::Success) if (result != ResultStatus::Success)

View file

@ -126,8 +126,8 @@ void Config::ReadValues() {
qt_config->beginGroup("UIGameList"); qt_config->beginGroup("UIGameList");
UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool(); UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
UISettings::values.icon_size = qt_config->value("icon_size", 64).toUInt(); UISettings::values.icon_size = qt_config->value("icon_size", 64).toUInt();
UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 0).toUInt(); UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 3).toUInt();
UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 3).toUInt(); UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 2).toUInt();
qt_config->endGroup(); qt_config->endGroup();
qt_config->beginGroup("UILayout"); qt_config->beginGroup("UILayout");

View file

@ -426,13 +426,12 @@ static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca,
} }
} }
void GameListWorker::AddInstalledTitlesToGameList() { void GameListWorker::AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache) {
const auto usernand = Service::FileSystem::GetUserNANDContents(); const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
const auto installed_games = usernand->ListEntriesFilter(FileSys::TitleType::Application,
FileSys::ContentRecordType::Program); FileSys::ContentRecordType::Program);
for (const auto& game : installed_games) { for (const auto& game : installed_games) {
const auto& file = usernand->GetEntryRaw(game); const auto& file = cache->GetEntryUnparsed(game);
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file); std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
if (!loader) if (!loader)
continue; continue;
@ -442,8 +441,7 @@ void GameListWorker::AddInstalledTitlesToGameList() {
u64 program_id = 0; u64 program_id = 0;
loader->ReadProgramId(program_id); loader->ReadProgramId(program_id);
const auto& control = const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
usernand->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
if (control != nullptr) if (control != nullptr)
GetMetadataFromControlNCA(control, icon, name); GetMetadataFromControlNCA(control, icon, name);
emit EntryReady({ emit EntryReady({
@ -457,11 +455,11 @@ void GameListWorker::AddInstalledTitlesToGameList() {
}); });
} }
const auto control_data = usernand->ListEntriesFilter(FileSys::TitleType::Application, const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application,
FileSys::ContentRecordType::Control); FileSys::ContentRecordType::Control);
for (const auto& entry : control_data) { for (const auto& entry : control_data) {
const auto nca = usernand->GetEntry(entry); const auto nca = cache->GetEntry(entry);
if (nca != nullptr) if (nca != nullptr)
nca_control_map.insert_or_assign(entry.title_id, nca); nca_control_map.insert_or_assign(entry.title_id, nca);
} }
@ -549,7 +547,9 @@ void GameListWorker::run() {
stop_processing = false; stop_processing = false;
watch_list.append(dir_path); watch_list.append(dir_path);
FillControlMap(dir_path.toStdString()); FillControlMap(dir_path.toStdString());
AddInstalledTitlesToGameList(); AddInstalledTitlesToGameList(Service::FileSystem::GetUserNANDContents());
AddInstalledTitlesToGameList(Service::FileSystem::GetSystemNANDContents());
AddInstalledTitlesToGameList(Service::FileSystem::GetSDMCContents());
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
nca_control_map.clear(); nca_control_map.clear();
emit Finished(watch_list); emit Finished(watch_list);

View file

@ -172,7 +172,7 @@ private:
bool deep_scan; bool deep_scan;
std::atomic_bool stop_processing; std::atomic_bool stop_processing;
void AddInstalledTitlesToGameList(); void AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache);
void FillControlMap(const std::string& dir_path); void FillControlMap(const std::string& dir_path);
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
}; };

View file

@ -133,8 +133,7 @@ GMainWindow::GMainWindow()
show(); show();
// Necessary to load titles from nand in gamelist. // Necessary to load titles from nand in gamelist.
Service::FileSystem::RegisterBIS(std::make_unique<FileSys::BISFactory>(vfs->OpenDirectory( Service::FileSystem::CreateFactories(vfs);
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite)));
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
// Show one-time "callout" messages to the user // Show one-time "callout" messages to the user