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