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

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>
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++));

View file

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

View file

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

View file

@ -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 {
@ -23,8 +25,7 @@ static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big.");
enum class S256KeyType : u64 {
Header, //
SDSave, //
SDNCA, //
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

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 {
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;

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);
}
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 {

View file

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

View file

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

View file

@ -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{};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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() {
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;
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);

View file

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

View file

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

View file

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

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)
return xci->GetStatus();
if (xci->GetNCAFileByType(FileSys::NCAContentType::Program) == nullptr) {
if (!Core::Crypto::KeyManager::KeyFileExists(false))
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;
return ResultStatus::ErrorXCIMissingProgramNCA;
}
auto result = nca_loader->Load(process);
if (result != ResultStatus::Success)

View file

@ -126,8 +126,8 @@ void Config::ReadValues() {
qt_config->beginGroup("UIGameList");
UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
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_2_text_id = qt_config->value("row_2_text_id", 3).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", 2).toUInt();
qt_config->endGroup();
qt_config->beginGroup("UILayout");

View file

@ -426,13 +426,12 @@ static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca,
}
}
void GameListWorker::AddInstalledTitlesToGameList() {
const auto usernand = Service::FileSystem::GetUserNANDContents();
const auto installed_games = usernand->ListEntriesFilter(FileSys::TitleType::Application,
void GameListWorker::AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache) {
const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
FileSys::ContentRecordType::Program);
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);
if (!loader)
continue;
@ -442,8 +441,7 @@ void GameListWorker::AddInstalledTitlesToGameList() {
u64 program_id = 0;
loader->ReadProgramId(program_id);
const auto& control =
usernand->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
if (control != nullptr)
GetMetadataFromControlNCA(control, icon, name);
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);
for (const auto& entry : control_data) {
const auto nca = usernand->GetEntry(entry);
const auto nca = cache->GetEntry(entry);
if (nca != nullptr)
nca_control_map.insert_or_assign(entry.title_id, nca);
}
@ -549,7 +547,9 @@ void GameListWorker::run() {
stop_processing = false;
watch_list.append(dir_path);
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);
nca_control_map.clear();
emit Finished(watch_list);

View file

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

View file

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