diff options
Diffstat (limited to 'src')
53 files changed, 1296 insertions, 230 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.cpp b/src/core/core.cpp index 28038ff6f..07da4c493 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -135,8 +135,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st LOG_CRITICAL(Core, "Failed to determine system mode (Error {})!", static_cast<int>(system_mode.second)); - if (system_mode.second != Loader::ResultStatus::Success) - return ResultStatus::ErrorSystemMode; + return ResultStatus::ErrorSystemMode; } ResultStatus init_result{Init(emu_window)}; @@ -148,14 +147,12 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st } const Loader::ResultStatus load_result{app_loader->Load(current_process)}; - if (Loader::ResultStatus::Success != load_result) { + if (load_result != Loader::ResultStatus::Success) { LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result)); System::Shutdown(); - if (load_result != Loader::ResultStatus::Success) { - return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) + - static_cast<u32>(load_result)); - } + return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) + + static_cast<u32>(load_result)); } status = ResultStatus::Success; return status; 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/am/am.cpp b/src/core/hle/service/am/am.cpp index 78d551a8a..7e3cf6d58 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -18,6 +18,7 @@ #include "core/hle/service/apm/apm.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/nvflinger/nvflinger.h" +#include "core/hle/service/pm/pm.h" #include "core/hle/service/set/set.h" #include "core/settings.h" @@ -309,7 +310,7 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter" {5, &ICommonStateGetter::GetOperationMode, "GetOperationMode"}, {6, &ICommonStateGetter::GetPerformanceMode, "GetPerformanceMode"}, {7, nullptr, "GetCradleStatus"}, - {8, nullptr, "GetBootMode"}, + {8, &ICommonStateGetter::GetBootMode, "GetBootMode"}, {9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"}, {10, nullptr, "RequestToAcquireSleepLock"}, {11, nullptr, "ReleaseSleepLock"}, @@ -334,6 +335,15 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter" event = Kernel::Event::Create(Kernel::ResetType::OneShot, "ICommonStateGetter:Event"); } +void ICommonStateGetter::GetBootMode(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + + rb.Push<u8>(static_cast<u8>(Service::PM::SystemBootMode::Normal)); // Normal boot mode + + LOG_DEBUG(Service_AM, "called"); +} + void ICommonStateGetter::GetEventHandle(Kernel::HLERequestContext& ctx) { event->Signal(); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index b763aff6f..9e8bb4e43 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -116,6 +116,7 @@ private: void GetDefaultDisplayResolutionChangeEvent(Kernel::HLERequestContext& ctx); void GetOperationMode(Kernel::HLERequestContext& ctx); void GetPerformanceMode(Kernel::HLERequestContext& ctx); + void GetBootMode(Kernel::HLERequestContext& ctx); Kernel::SharedPtr<Kernel::Event> event; }; diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 6f9c64263..881c39e31 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -254,7 +254,7 @@ ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) { ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) { ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS"); bis_factory = std::move(factory); - LOG_DEBUG(Service_FS, "Registred BIS"); + LOG_DEBUG(Service_FS, "Registered BIS"); return RESULT_SUCCESS; } @@ -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/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 970942d3f..c0ba330dc 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -17,6 +17,7 @@ #include "core/hle/service/hid/irs.h" #include "core/hle/service/hid/xcd.h" #include "core/hle/service/service.h" +#include "core/settings.h" namespace Service::HID { diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index e298f23a6..88d926808 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -4,8 +4,10 @@ #pragma once +#include <array> +#include "common/bit_field.h" +#include "common/common_types.h" #include "core/hle/service/service.h" -#include "core/settings.h" namespace Service::HID { diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp index e20a25689..6ec35ca60 100644 --- a/src/core/hle/service/pm/pm.cpp +++ b/src/core/hle/service/pm/pm.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/pm/pm.h" #include "core/hle/service/service.h" namespace Service::PM { @@ -10,11 +12,20 @@ class BootMode final : public ServiceFramework<BootMode> { public: explicit BootMode() : ServiceFramework{"pm:bm"} { static const FunctionInfo functions[] = { - {0, nullptr, "GetBootMode"}, + {0, &BootMode::GetBootMode, "GetBootMode"}, {1, nullptr, "SetMaintenanceBoot"}, }; RegisterHandlers(functions); } + +private: + void GetBootMode(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(static_cast<u32>(SystemBootMode::Normal)); // Normal boot mode + + LOG_DEBUG(Service_PM, "called"); + } }; class DebugMonitor final : public ServiceFramework<DebugMonitor> { diff --git a/src/core/hle/service/pm/pm.h b/src/core/hle/service/pm/pm.h index 9fc19fed6..370f2ed72 100644 --- a/src/core/hle/service/pm/pm.h +++ b/src/core/hle/service/pm/pm.h @@ -9,7 +9,7 @@ class ServiceManager; } namespace Service::PM { - +enum class SystemBootMode : u32 { Normal = 0, Maintenance = 1 }; /// Registers all PM services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp index a461e72ec..92b0640e8 100644 --- a/src/core/hle/service/set/set.cpp +++ b/src/core/hle/service/set/set.cpp @@ -32,24 +32,59 @@ constexpr std::array<LanguageCode, 17> available_language_codes = {{ LanguageCode::ZH_HANT, }}; +constexpr size_t pre4_0_0_max_entries = 0xF; +constexpr size_t post4_0_0_max_entries = 0x40; + LanguageCode GetLanguageCodeFromIndex(size_t index) { return available_language_codes.at(index); } -void SET::GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx) { - ctx.WriteBuffer(available_language_codes); +template <size_t size> +static std::array<LanguageCode, size> MakeLanguageCodeSubset() { + std::array<LanguageCode, size> arr; + std::copy_n(available_language_codes.begin(), size, arr.begin()); + return arr; +} +static void PushResponseLanguageCode(Kernel::HLERequestContext& ctx, size_t max_size) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u32>(available_language_codes.size())); + if (available_language_codes.size() > max_size) + rb.Push(static_cast<u32>(max_size)); + else + rb.Push(static_cast<u32>(available_language_codes.size())); +} + +void SET::GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx) { + if (available_language_codes.size() > pre4_0_0_max_entries) + ctx.WriteBuffer(MakeLanguageCodeSubset<pre4_0_0_max_entries>()); + else + ctx.WriteBuffer(available_language_codes); + + PushResponseLanguageCode(ctx, pre4_0_0_max_entries); + + LOG_DEBUG(Service_SET, "called"); +} + +void SET::GetAvailableLanguageCodes2(Kernel::HLERequestContext& ctx) { + if (available_language_codes.size() > post4_0_0_max_entries) + ctx.WriteBuffer(MakeLanguageCodeSubset<post4_0_0_max_entries>()); + else + ctx.WriteBuffer(available_language_codes); + + PushResponseLanguageCode(ctx, post4_0_0_max_entries); LOG_DEBUG(Service_SET, "called"); } void SET::GetAvailableLanguageCodeCount(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u32>(available_language_codes.size())); + PushResponseLanguageCode(ctx, pre4_0_0_max_entries); + + LOG_DEBUG(Service_SET, "called"); +} + +void SET::GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx) { + PushResponseLanguageCode(ctx, post4_0_0_max_entries); LOG_DEBUG(Service_SET, "called"); } @@ -69,8 +104,8 @@ SET::SET() : ServiceFramework("set") { {2, nullptr, "MakeLanguageCode"}, {3, &SET::GetAvailableLanguageCodeCount, "GetAvailableLanguageCodeCount"}, {4, nullptr, "GetRegionCode"}, - {5, &SET::GetAvailableLanguageCodes, "GetAvailableLanguageCodes2"}, - {6, &SET::GetAvailableLanguageCodeCount, "GetAvailableLanguageCodeCount2"}, + {5, &SET::GetAvailableLanguageCodes2, "GetAvailableLanguageCodes2"}, + {6, &SET::GetAvailableLanguageCodeCount2, "GetAvailableLanguageCodeCount2"}, {7, nullptr, "GetKeyCodeMap"}, {8, nullptr, "GetQuestFlag"}, }; diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h index 4232b6162..669e740b7 100644 --- a/src/core/hle/service/set/set.h +++ b/src/core/hle/service/set/set.h @@ -38,7 +38,9 @@ public: private: void GetLanguageCode(Kernel::HLERequestContext& ctx); void GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx); + void GetAvailableLanguageCodes2(Kernel::HLERequestContext& ctx); void GetAvailableLanguageCodeCount(Kernel::HLERequestContext& ctx); + void GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx); }; } // namespace Service::Set 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) diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 68f91cc75..f32a79d7b 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -218,10 +218,6 @@ void Maxwell3D::DrawArrays() { debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr); } - if (debug_context) { - debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr); - } - // Both instance configuration registers can not be set at the same time. ASSERT_MSG(!regs.draw.instance_next || !regs.draw.instance_cont, "Illegal combination of instancing parameters"); @@ -237,6 +233,10 @@ void Maxwell3D::DrawArrays() { const bool is_indexed{regs.index_array.count && !regs.vertex_buffer.count}; rasterizer.AccelerateDrawBatch(is_indexed); + if (debug_context) { + debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr); + } + // TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if // the game is trying to draw indexed or direct mode. This needs to be verified on HW still - // it's possible that it is incorrect and that there is some other register used to specify the diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index d03bc1c0c..92bfda053 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -330,6 +330,17 @@ public: Set = 0x150F, }; + enum class StencilOp : u32 { + Keep = 1, + Zero = 2, + Replace = 3, + Incr = 4, + Decr = 5, + Invert = 6, + IncrWrap = 7, + DecrWrap = 8, + }; + struct Cull { enum class FrontFace : u32 { ClockWise = 0x0900, @@ -508,8 +519,16 @@ public: float clear_color[4]; float clear_depth; + INSERT_PADDING_WORDS(0x3); + s32 clear_stencil; + + INSERT_PADDING_WORDS(0x6C); + + s32 stencil_back_func_ref; + u32 stencil_back_mask; + u32 stencil_back_func_mask; - INSERT_PADDING_WORDS(0x93); + INSERT_PADDING_WORDS(0x20); struct { u32 address_high; @@ -573,16 +592,14 @@ public: u32 enable[NumRenderTargets]; } blend; - struct { - u32 enable; - u32 front_op_fail; - u32 front_op_zfail; - u32 front_op_zpass; - u32 front_func_func; - u32 front_func_ref; - u32 front_func_mask; - u32 front_mask; - } stencil; + u32 stencil_enable; + StencilOp stencil_front_op_fail; + StencilOp stencil_front_op_zfail; + StencilOp stencil_front_op_zpass; + ComparisonOp stencil_front_func_func; + s32 stencil_front_func_ref; + u32 stencil_front_func_mask; + u32 stencil_front_mask; INSERT_PADDING_WORDS(0x3); @@ -626,13 +643,11 @@ public: INSERT_PADDING_WORDS(0x5); - struct { - u32 enable; - u32 back_op_fail; - u32 back_op_zfail; - u32 back_op_zpass; - u32 back_func_func; - } stencil_two_side; + u32 stencil_two_side_enable; + StencilOp stencil_back_op_fail; + StencilOp stencil_back_op_zfail; + StencilOp stencil_back_op_zpass; + ComparisonOp stencil_back_func_func; INSERT_PADDING_WORDS(0x17); @@ -944,6 +959,10 @@ ASSERT_REG_POSITION(viewport, 0x300); ASSERT_REG_POSITION(vertex_buffer, 0x35D); ASSERT_REG_POSITION(clear_color[0], 0x360); ASSERT_REG_POSITION(clear_depth, 0x364); +ASSERT_REG_POSITION(clear_stencil, 0x368); +ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5); +ASSERT_REG_POSITION(stencil_back_mask, 0x3D6); +ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7); ASSERT_REG_POSITION(zeta, 0x3F8); ASSERT_REG_POSITION(vertex_attrib_format[0], 0x458); ASSERT_REG_POSITION(rt_control, 0x487); @@ -955,13 +974,24 @@ ASSERT_REG_POSITION(depth_write_enabled, 0x4BA); ASSERT_REG_POSITION(d3d_cull_mode, 0x4C2); ASSERT_REG_POSITION(depth_test_func, 0x4C3); ASSERT_REG_POSITION(blend, 0x4CF); -ASSERT_REG_POSITION(stencil, 0x4E0); +ASSERT_REG_POSITION(stencil_enable, 0x4E0); +ASSERT_REG_POSITION(stencil_front_op_fail, 0x4E1); +ASSERT_REG_POSITION(stencil_front_op_zfail, 0x4E2); +ASSERT_REG_POSITION(stencil_front_op_zpass, 0x4E3); +ASSERT_REG_POSITION(stencil_front_func_func, 0x4E4); +ASSERT_REG_POSITION(stencil_front_func_ref, 0x4E5); +ASSERT_REG_POSITION(stencil_front_func_mask, 0x4E6); +ASSERT_REG_POSITION(stencil_front_mask, 0x4E7); ASSERT_REG_POSITION(screen_y_control, 0x4EB); ASSERT_REG_POSITION(vb_element_base, 0x50D); ASSERT_REG_POSITION(zeta_enable, 0x54E); ASSERT_REG_POSITION(tsc, 0x557); ASSERT_REG_POSITION(tic, 0x55D); -ASSERT_REG_POSITION(stencil_two_side, 0x565); +ASSERT_REG_POSITION(stencil_two_side_enable, 0x565); +ASSERT_REG_POSITION(stencil_back_op_fail, 0x566); +ASSERT_REG_POSITION(stencil_back_op_zfail, 0x567); +ASSERT_REG_POSITION(stencil_back_op_zpass, 0x568); +ASSERT_REG_POSITION(stencil_back_func_func, 0x569); ASSERT_REG_POSITION(point_coord_replace, 0x581); ASSERT_REG_POSITION(code_address, 0x582); ASSERT_REG_POSITION(draw, 0x585); diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 67194b0e3..7fd622159 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -280,6 +280,19 @@ union Instruction { BitField<56, 1, u64> invert_b; } lop32i; + union { + BitField<28, 8, u64> imm_lut28; + BitField<48, 8, u64> imm_lut48; + + u32 GetImmLut28() const { + return static_cast<u32>(imm_lut28); + } + + u32 GetImmLut48() const { + return static_cast<u32>(imm_lut48); + } + } lop3; + u32 GetImm20_19() const { u32 imm{static_cast<u32>(imm20_19)}; imm <<= 12; @@ -623,6 +636,9 @@ public: IADD_C, IADD_R, IADD_IMM, + IADD3_C, + IADD3_R, + IADD3_IMM, IADD32I, ISCADD_C, // Scale and Add ISCADD_R, @@ -650,6 +666,9 @@ public: LOP_R, LOP_IMM, LOP32I, + LOP3_C, + LOP3_R, + LOP3_IMM, MOV_C, MOV_R, MOV_IMM, @@ -838,6 +857,9 @@ private: INST("0100110000010---", Id::IADD_C, Type::ArithmeticInteger, "IADD_C"), INST("0101110000010---", Id::IADD_R, Type::ArithmeticInteger, "IADD_R"), INST("0011100-00010---", Id::IADD_IMM, Type::ArithmeticInteger, "IADD_IMM"), + INST("010011001100----", Id::IADD3_C, Type::ArithmeticInteger, "IADD3_C"), + INST("010111001100----", Id::IADD3_R, Type::ArithmeticInteger, "IADD3_R"), + INST("0011100-1100----", Id::IADD3_IMM, Type::ArithmeticInteger, "IADD3_IMM"), INST("0001110---------", Id::IADD32I, Type::ArithmeticIntegerImmediate, "IADD32I"), INST("0100110000011---", Id::ISCADD_C, Type::ArithmeticInteger, "ISCADD_C"), INST("0101110000011---", Id::ISCADD_R, Type::ArithmeticInteger, "ISCADD_R"), @@ -872,6 +894,9 @@ private: INST("0101110001000---", Id::LOP_R, Type::ArithmeticInteger, "LOP_R"), INST("0011100001000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"), INST("000001----------", Id::LOP32I, Type::ArithmeticIntegerImmediate, "LOP32I"), + INST("0000001---------", Id::LOP3_C, Type::ArithmeticInteger, "LOP3_C"), + INST("0101101111100---", Id::LOP3_R, Type::ArithmeticInteger, "LOP3_R"), + INST("0011110---------", Id::LOP3_IMM, Type::ArithmeticInteger, "LOP3_IMM"), INST("0100110001001---", Id::SHL_C, Type::Shift, "SHL_C"), INST("0101110001001---", Id::SHL_R, Type::Shift, "SHL_R"), INST("0011100-01001---", Id::SHL_IMM, Type::Shift, "SHL_IMM"), diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index c67eabe65..96851ccb5 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -14,6 +14,7 @@ #include "common/logging/log.h" #include "common/math_util.h" #include "common/microprofile.h" +#include "common/scope_exit.h" #include "core/core.h" #include "core/frontend/emu_window.h" #include "core/hle/kernel/process.h" @@ -181,7 +182,7 @@ static GLShader::ProgramCode GetShaderProgramCode(Maxwell::ShaderProgram program auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); // Fetch program code from memory - GLShader::ProgramCode program_code; + GLShader::ProgramCode program_code(GLShader::MAX_PROGRAM_CODE_LENGTH); auto& shader_config = gpu.regs.shader_config[static_cast<size_t>(program)]; const u64 gpu_address{gpu.regs.code_address.CodeAddress() + shader_config.offset}; const boost::optional<VAddr> cpu_address{gpu.memory_manager.GpuToCpuAddress(gpu_address)}; @@ -315,16 +316,14 @@ std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_c using_color_fb = false; } - // TODO(bunnei): Implement this - const bool has_stencil = false; - + const bool has_stencil = regs.stencil_enable; const bool write_color_fb = state.color_mask.red_enabled == GL_TRUE || state.color_mask.green_enabled == GL_TRUE || state.color_mask.blue_enabled == GL_TRUE || state.color_mask.alpha_enabled == GL_TRUE; const bool write_depth_fb = (state.depth.test_enabled && state.depth.write_mask == GL_TRUE) || - (has_stencil && state.stencil.test_enabled && state.stencil.write_mask != 0); + (has_stencil && (state.stencil.front.write_mask || state.stencil.back.write_mask)); Surface color_surface; Surface depth_surface; @@ -364,41 +363,70 @@ std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_c } void RasterizerOpenGL::Clear() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto prev_state{state}; + SCOPE_EXIT({ prev_state.Apply(); }); + const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; bool use_color_fb = false; bool use_depth_fb = false; - GLbitfield clear_mask = 0; - if (regs.clear_buffers.R && regs.clear_buffers.G && regs.clear_buffers.B && + OpenGLState clear_state; + clear_state.draw.draw_framebuffer = state.draw.draw_framebuffer; + clear_state.color_mask.red_enabled = regs.clear_buffers.R ? GL_TRUE : GL_FALSE; + clear_state.color_mask.green_enabled = regs.clear_buffers.G ? GL_TRUE : GL_FALSE; + clear_state.color_mask.blue_enabled = regs.clear_buffers.B ? GL_TRUE : GL_FALSE; + clear_state.color_mask.alpha_enabled = regs.clear_buffers.A ? GL_TRUE : GL_FALSE; + + GLbitfield clear_mask{}; + if (regs.clear_buffers.R || regs.clear_buffers.G || regs.clear_buffers.B || regs.clear_buffers.A) { - clear_mask |= GL_COLOR_BUFFER_BIT; - use_color_fb = true; + if (regs.clear_buffers.RT == 0) { + // We only support clearing the first color attachment for now + clear_mask |= GL_COLOR_BUFFER_BIT; + use_color_fb = true; + } else { + // TODO(subv): Add support for the other color attachments + LOG_CRITICAL(HW_GPU, "Clear unimplemented for RT {}", regs.clear_buffers.RT); + } } if (regs.clear_buffers.Z) { + ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear Z but buffer is not enabled!"); + use_depth_fb = true; clear_mask |= GL_DEPTH_BUFFER_BIT; - use_depth_fb = regs.zeta_enable != 0; // Always enable the depth write when clearing the depth buffer. The depth write mask is // ignored when clearing the buffer in the Switch, but OpenGL obeys it so we set it to true. - state.depth.test_enabled = true; - state.depth.write_mask = GL_TRUE; - state.depth.test_func = GL_ALWAYS; - state.Apply(); + clear_state.depth.test_enabled = true; + clear_state.depth.test_func = GL_ALWAYS; + } + if (regs.clear_buffers.S) { + ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear stencil but buffer is not enabled!"); + use_depth_fb = true; + clear_mask |= GL_STENCIL_BUFFER_BIT; + clear_state.stencil.test_enabled = true; } - if (clear_mask == 0) + if (!use_color_fb && !use_depth_fb) { + // No color surface nor depth/stencil surface are enabled return; + } + + if (clear_mask == 0) { + // No clear mask is enabled + return; + } ScopeAcquireGLContext acquire_context{emu_window}; auto [dirty_color_surface, dirty_depth_surface] = ConfigureFramebuffers(use_color_fb, use_depth_fb, false); - // TODO(Subv): Support clearing only partial colors. + clear_state.Apply(); + glClearColor(regs.clear_color[0], regs.clear_color[1], regs.clear_color[2], regs.clear_color[3]); glClearDepth(regs.clear_depth); + glClearStencil(regs.clear_stencil); glClear(clear_mask); @@ -451,6 +479,7 @@ void RasterizerOpenGL::DrawArrays() { ConfigureFramebuffers(true, regs.zeta.Address() != 0 && regs.zeta_enable != 0, true); SyncDepthTestState(); + SyncStencilTestState(); SyncBlendState(); SyncLogicOpState(); SyncCullMode(); @@ -841,6 +870,34 @@ void RasterizerOpenGL::SyncDepthTestState() { state.depth.test_func = MaxwellToGL::ComparisonOp(regs.depth_test_func); } +void RasterizerOpenGL::SyncStencilTestState() { + const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + state.stencil.test_enabled = regs.stencil_enable != 0; + + if (!regs.stencil_enable) { + return; + } + + // TODO(bunnei): Verify behavior when this is not set + ASSERT(regs.stencil_two_side_enable); + + state.stencil.front.test_func = MaxwellToGL::ComparisonOp(regs.stencil_front_func_func); + state.stencil.front.test_ref = regs.stencil_front_func_ref; + state.stencil.front.test_mask = regs.stencil_front_func_mask; + state.stencil.front.action_stencil_fail = MaxwellToGL::StencilOp(regs.stencil_front_op_fail); + state.stencil.front.action_depth_fail = MaxwellToGL::StencilOp(regs.stencil_front_op_zfail); + state.stencil.front.action_depth_pass = MaxwellToGL::StencilOp(regs.stencil_front_op_zpass); + state.stencil.front.write_mask = regs.stencil_front_mask; + + state.stencil.back.test_func = MaxwellToGL::ComparisonOp(regs.stencil_back_func_func); + state.stencil.back.test_ref = regs.stencil_back_func_ref; + state.stencil.back.test_mask = regs.stencil_back_func_mask; + state.stencil.back.action_stencil_fail = MaxwellToGL::StencilOp(regs.stencil_back_op_fail); + state.stencil.back.action_depth_fail = MaxwellToGL::StencilOp(regs.stencil_back_op_zfail); + state.stencil.back.action_depth_pass = MaxwellToGL::StencilOp(regs.stencil_back_op_zpass); + state.stencil.back.write_mask = regs.stencil_back_mask; +} + void RasterizerOpenGL::SyncBlendState() { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; @@ -872,7 +929,8 @@ void RasterizerOpenGL::SyncLogicOpState() { if (!state.logic_op.enabled) return; - ASSERT_MSG(regs.blend.enable == 0, "Blending and logic op can't be enabled at the same time."); + ASSERT_MSG(regs.blend.enable[0] == 0, + "Blending and logic op can't be enabled at the same time."); state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation); } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 59b727de0..531b04046 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -141,6 +141,9 @@ private: /// Syncs the depth test state to match the guest state void SyncDepthTestState(); + /// Syncs the stencil test state to match the guest state + void SyncStencilTestState(); + /// Syncs the blend state to match the guest state void SyncBlendState(); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index b1769c99b..83d8d3d94 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -780,17 +780,30 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres } else if (preserve_contents) { // If surface parameters changed and we care about keeping the previous data, recreate // the surface from the old one - return RecreateSurface(surface, params); + UnregisterSurface(surface); + Surface new_surface{RecreateSurface(surface, params)}; + RegisterSurface(new_surface); + return new_surface; } else { // Delete the old surface before creating a new one to prevent collisions. UnregisterSurface(surface); } } + // Try to get a previously reserved surface + surface = TryGetReservedSurface(params); + // No surface found - create a new one - surface = std::make_shared<CachedSurface>(params); - RegisterSurface(surface); - LoadSurface(surface); + if (!surface) { + surface = std::make_shared<CachedSurface>(params); + ReserveSurface(surface); + RegisterSurface(surface); + } + + // Only load surface from memory if we care about the contents + if (preserve_contents) { + LoadSurface(surface); + } return surface; } @@ -799,13 +812,18 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface, const SurfaceParams& new_params) { // Verify surface is compatible for blitting const auto& params{surface->GetSurfaceParams()}; - ASSERT(params.type == new_params.type); - ASSERT_MSG(params.GetCompressionFactor(params.pixel_format) == 1, - "Compressed texture reinterpretation is not supported"); // Create a new surface with the new parameters, and blit the previous surface to it Surface new_surface{std::make_shared<CachedSurface>(new_params)}; + // If format is unchanged, we can do a faster blit without reinterpreting pixel data + if (params.pixel_format == new_params.pixel_format) { + BlitTextures(surface->Texture().handle, params.GetRect(), new_surface->Texture().handle, + new_surface->GetSurfaceParams().GetRect(), params.type, + read_framebuffer.handle, draw_framebuffer.handle); + return new_surface; + } + auto source_format = GetFormatTuple(params.pixel_format, params.component_type); auto dest_format = GetFormatTuple(new_params.pixel_format, new_params.component_type); @@ -818,9 +836,13 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface, glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo.handle); glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB); - glGetTextureImage(surface->Texture().handle, 0, source_format.format, source_format.type, - params.SizeInBytes(), nullptr); - + if (source_format.compressed) { + glGetCompressedTextureImage(surface->Texture().handle, 0, + static_cast<GLsizei>(params.SizeInBytes()), nullptr); + } else { + glGetTextureImage(surface->Texture().handle, 0, source_format.format, source_format.type, + static_cast<GLsizei>(params.SizeInBytes()), nullptr); + } // If the new texture is bigger than the previous one, we need to fill in the rest with data // from the CPU. if (params.SizeInBytes() < new_params.SizeInBytes()) { @@ -846,17 +868,21 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface, const auto& dest_rect{new_params.GetRect()}; glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo.handle); - glTextureSubImage2D( - new_surface->Texture().handle, 0, 0, 0, static_cast<GLsizei>(dest_rect.GetWidth()), - static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format, dest_format.type, nullptr); + if (dest_format.compressed) { + glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + static_cast<GLsizei>(dest_rect.GetWidth()), + static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format, + static_cast<GLsizei>(new_params.SizeInBytes()), nullptr); + } else { + glTextureSubImage2D(new_surface->Texture().handle, 0, 0, 0, + static_cast<GLsizei>(dest_rect.GetWidth()), + static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format, + dest_format.type, nullptr); + } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); pbo.Release(); - // Update cache accordingly - UnregisterSurface(surface); - RegisterSurface(new_surface); - return new_surface; } @@ -931,6 +957,21 @@ void RasterizerCacheOpenGL::UnregisterSurface(const Surface& surface) { surface_cache.erase(search); } +void RasterizerCacheOpenGL::ReserveSurface(const Surface& surface) { + const auto& surface_reserve_key{SurfaceReserveKey::Create(surface->GetSurfaceParams())}; + surface_reserve[surface_reserve_key] = surface; +} + +Surface RasterizerCacheOpenGL::TryGetReservedSurface(const SurfaceParams& params) { + const auto& surface_reserve_key{SurfaceReserveKey::Create(params)}; + auto search{surface_reserve.find(surface_reserve_key)}; + if (search != surface_reserve.end()) { + RegisterSurface(search->second); + return search->second; + } + return {}; +} + template <typename Map, typename Interval> constexpr auto RangeFromInterval(Map& map, const Interval& interval) { return boost::make_iterator_range(map.equal_range(interval)); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index f273152a2..c8c615df2 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -11,6 +11,7 @@ #include <boost/icl/interval_map.hpp> #include "common/common_types.h" +#include "common/hash.h" #include "common/math_util.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_opengl/gl_resource_manager.h" @@ -682,6 +683,27 @@ struct SurfaceParams { u32 cache_height; }; +}; // namespace OpenGL + +/// Hashable variation of SurfaceParams, used for a key in the surface cache +struct SurfaceReserveKey : Common::HashableStruct<OpenGL::SurfaceParams> { + static SurfaceReserveKey Create(const OpenGL::SurfaceParams& params) { + SurfaceReserveKey res; + res.state = params; + return res; + } +}; +namespace std { +template <> +struct hash<SurfaceReserveKey> { + size_t operator()(const SurfaceReserveKey& k) const { + return k.Hash(); + } +}; +} // namespace std + +namespace OpenGL { + class CachedSurface final { public: CachedSurface(const SurfaceParams& params); @@ -752,12 +774,23 @@ private: /// Remove surface from the cache void UnregisterSurface(const Surface& surface); + /// Reserves a unique surface that can be reused later + void ReserveSurface(const Surface& surface); + + /// Tries to get a reserved surface for the specified parameters + Surface TryGetReservedSurface(const SurfaceParams& params); + /// Increase/decrease the number of surface in pages touching the specified region void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta); std::unordered_map<Tegra::GPUVAddr, Surface> surface_cache; PageMap cached_pages; + /// The surface reserve is a "backup" cache, this is where we put unique surfaces that have + /// previously been used. This is to prevent surfaces from being constantly created and + /// destroyed when used with different surface parameters. + std::unordered_map<SurfaceReserveKey, Surface> surface_reserve; + OGLFramebuffer read_framebuffer; OGLFramebuffer draw_framebuffer; }; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index f1e00c93c..94e318966 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -849,6 +849,33 @@ private: } } + void WriteLop3Instruction(Register dest, const std::string& op_a, const std::string& op_b, + const std::string& op_c, const std::string& imm_lut) { + if (dest == Tegra::Shader::Register::ZeroIndex) { + return; + } + + static constexpr std::array<const char*, 32> shift_amounts = { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", + "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", + "22", "23", "24", "25", "26", "27", "28", "29", "30", "31"}; + + std::string result; + result += '('; + + for (size_t i = 0; i < shift_amounts.size(); ++i) { + if (i) + result += '|'; + result += "(((" + imm_lut + " >> (((" + op_c + " >> " + shift_amounts[i] + + ") & 1) | ((" + op_b + " >> " + shift_amounts[i] + ") & 1) << 1 | ((" + op_a + + " >> " + shift_amounts[i] + ") & 1) << 2)) & 1) << " + shift_amounts[i] + ")"; + } + + result += ')'; + + regs.SetRegisterToInteger(dest, true, 0, result, 1, 1); + } + void WriteTexsInstruction(const Instruction& instr, const std::string& coord, const std::string& texture) { // Add an extra scope and declare the texture coords inside to prevent @@ -1297,6 +1324,20 @@ private: instr.alu.lop.pred_result_mode, instr.alu.lop.pred48); break; } + case OpCode::Id::LOP3_C: + case OpCode::Id::LOP3_R: + case OpCode::Id::LOP3_IMM: { + std::string op_c = regs.GetRegisterAsInteger(instr.gpr39); + std::string lut; + if (opcode->GetId() == OpCode::Id::LOP3_R) { + lut = '(' + std::to_string(instr.alu.lop3.GetImmLut28()) + ')'; + } else { + lut = '(' + std::to_string(instr.alu.lop3.GetImmLut48()) + ')'; + } + + WriteLop3Instruction(instr.gpr0, op_a, op_b, op_c, lut); + break; + } case OpCode::Id::IMNMX_C: case OpCode::Id::IMNMX_R: case OpCode::Id::IMNMX_IMM: { diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index 2c636b7f3..4e5a6f130 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -9,14 +9,14 @@ #include <type_traits> #include <utility> #include <vector> +#include <boost/functional/hash.hpp> #include "common/common_types.h" #include "common/hash.h" namespace OpenGL::GLShader { constexpr size_t MAX_PROGRAM_CODE_LENGTH{0x1000}; - -using ProgramCode = std::array<u64, MAX_PROGRAM_CODE_LENGTH>; +using ProgramCode = std::vector<u64>; class ConstBufferEntry { using Maxwell = Tegra::Engines::Maxwell3D::Regs; @@ -115,8 +115,8 @@ struct ShaderEntries { using ProgramResult = std::pair<std::string, ShaderEntries>; struct ShaderSetup { - ShaderSetup(const ProgramCode& program_code) { - program.code = program_code; + explicit ShaderSetup(ProgramCode program_code) { + program.code = std::move(program_code); } struct { @@ -135,8 +135,8 @@ struct ShaderSetup { } /// Used in scenarios where we have a dual vertex shaders - void SetProgramB(const ProgramCode& program_b) { - program.code_b = program_b; + void SetProgramB(ProgramCode program_b) { + program.code_b = std::move(program_b); has_program_b = true; } @@ -146,13 +146,18 @@ struct ShaderSetup { private: u64 GetNewHash() const { + size_t hash = 0; + + const u64 hash_a = Common::ComputeHash64(program.code.data(), program.code.size()); + boost::hash_combine(hash, hash_a); + if (has_program_b) { // Compute hash over dual shader programs - return Common::ComputeHash64(&program, sizeof(program)); - } else { - // Compute hash over a single shader program - return Common::ComputeHash64(&program.code, program.code.size()); + const u64 hash_b = Common::ComputeHash64(program.code_b.data(), program.code_b.size()); + boost::hash_combine(hash, hash_b); } + + return hash; } u64 program_code_hash{}; diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index e1a887d67..60a4defd1 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -27,13 +27,17 @@ OpenGLState::OpenGLState() { color_mask.alpha_enabled = GL_TRUE; stencil.test_enabled = false; - stencil.test_func = GL_ALWAYS; - stencil.test_ref = 0; - stencil.test_mask = 0xFF; - stencil.write_mask = 0xFF; - stencil.action_depth_fail = GL_KEEP; - stencil.action_depth_pass = GL_KEEP; - stencil.action_stencil_fail = GL_KEEP; + auto reset_stencil = [](auto& config) { + config.test_func = GL_ALWAYS; + config.test_ref = 0; + config.test_mask = 0xFFFFFFFF; + config.write_mask = 0xFFFFFFFF; + config.action_depth_fail = GL_KEEP; + config.action_depth_pass = GL_KEEP; + config.action_stencil_fail = GL_KEEP; + }; + reset_stencil(stencil.front); + reset_stencil(stencil.back); blend.enabled = true; blend.rgb_equation = GL_FUNC_ADD; @@ -129,24 +133,23 @@ void OpenGLState::Apply() const { glDisable(GL_STENCIL_TEST); } } - - if (stencil.test_func != cur_state.stencil.test_func || - stencil.test_ref != cur_state.stencil.test_ref || - stencil.test_mask != cur_state.stencil.test_mask) { - glStencilFunc(stencil.test_func, stencil.test_ref, stencil.test_mask); - } - - if (stencil.action_depth_fail != cur_state.stencil.action_depth_fail || - stencil.action_depth_pass != cur_state.stencil.action_depth_pass || - stencil.action_stencil_fail != cur_state.stencil.action_stencil_fail) { - glStencilOp(stencil.action_stencil_fail, stencil.action_depth_fail, - stencil.action_depth_pass); - } - - // Stencil mask - if (stencil.write_mask != cur_state.stencil.write_mask) { - glStencilMask(stencil.write_mask); - } + auto config_stencil = [](GLenum face, const auto& config, const auto& prev_config) { + if (config.test_func != prev_config.test_func || config.test_ref != prev_config.test_ref || + config.test_mask != prev_config.test_mask) { + glStencilFuncSeparate(face, config.test_func, config.test_ref, config.test_mask); + } + if (config.action_depth_fail != prev_config.action_depth_fail || + config.action_depth_pass != prev_config.action_depth_pass || + config.action_stencil_fail != prev_config.action_stencil_fail) { + glStencilOpSeparate(face, config.action_stencil_fail, config.action_depth_fail, + config.action_depth_pass); + } + if (config.write_mask != prev_config.write_mask) { + glStencilMaskSeparate(face, config.write_mask); + } + }; + config_stencil(GL_FRONT, stencil.front, cur_state.stencil.front); + config_stencil(GL_BACK, stencil.back, cur_state.stencil.back); // Blending if (blend.enabled != cur_state.blend.enabled) { diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index 22b0b1e41..46e96a97d 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -58,14 +58,16 @@ public: } color_mask; // GL_COLOR_WRITEMASK struct { - bool test_enabled; // GL_STENCIL_TEST - GLenum test_func; // GL_STENCIL_FUNC - GLint test_ref; // GL_STENCIL_REF - GLuint test_mask; // GL_STENCIL_VALUE_MASK - GLuint write_mask; // GL_STENCIL_WRITEMASK - GLenum action_stencil_fail; // GL_STENCIL_FAIL - GLenum action_depth_fail; // GL_STENCIL_PASS_DEPTH_FAIL - GLenum action_depth_pass; // GL_STENCIL_PASS_DEPTH_PASS + bool test_enabled; // GL_STENCIL_TEST + struct { + GLenum test_func; // GL_STENCIL_FUNC + GLint test_ref; // GL_STENCIL_REF + GLuint test_mask; // GL_STENCIL_VALUE_MASK + GLuint write_mask; // GL_STENCIL_WRITEMASK + GLenum action_stencil_fail; // GL_STENCIL_FAIL + GLenum action_depth_fail; // GL_STENCIL_PASS_DEPTH_FAIL + GLenum action_depth_pass; // GL_STENCIL_PASS_DEPTH_PASS + } front, back; } stencil; struct { diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index 0343759a6..67273e164 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -295,6 +295,30 @@ inline GLenum ComparisonOp(Maxwell::ComparisonOp comparison) { return {}; } +inline GLenum StencilOp(Maxwell::StencilOp stencil) { + switch (stencil) { + case Maxwell::StencilOp::Keep: + return GL_KEEP; + case Maxwell::StencilOp::Zero: + return GL_ZERO; + case Maxwell::StencilOp::Replace: + return GL_REPLACE; + case Maxwell::StencilOp::Incr: + return GL_INCR; + case Maxwell::StencilOp::Decr: + return GL_DECR; + case Maxwell::StencilOp::Invert: + return GL_INVERT; + case Maxwell::StencilOp::IncrWrap: + return GL_INCR_WRAP; + case Maxwell::StencilOp::DecrWrap: + return GL_DECR_WRAP; + } + LOG_CRITICAL(Render_OpenGL, "Unimplemented stencil op={}", static_cast<u32>(stencil)); + UNREACHABLE(); + return {}; +} + inline GLenum FrontFace(Maxwell::Cull::FrontFace front_face) { switch (front_face) { case Maxwell::Cull::FrontFace::ClockWise: diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 76c4cb165..60b6d6d44 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -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"); diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index d5726b8b3..867a3c6f1 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -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, - FileSys::ContentRecordType::Program); +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, - FileSys::ContentRecordType::Control); + 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); diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index c59613769..1d6c85400 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -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); }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index c62360bd4..9ca1626d1 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -91,9 +91,20 @@ void GMainWindow::ShowCallouts() {} const int GMainWindow::max_recent_files_item; +static void InitializeLogging() { + Log::Filter log_filter; + log_filter.ParseFilterString(Settings::values.log_filter); + Log::SetGlobalFilter(log_filter); + + const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); + FileUtil::CreateFullPath(log_dir); + Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); +} + GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr), vfs(std::make_shared<FileSys::RealVfsFilesystem>()) { + InitializeLogging(); debug_context = Tegra::DebugContext::Construct(); @@ -122,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 @@ -545,6 +555,15 @@ void GMainWindow::BootGame(const QString& filename) { } status_bar_update_timer.start(2000); + std::string title_name; + const auto res = Core::System::GetInstance().GetGameName(title_name); + if (res != Loader::ResultStatus::Success) + title_name = FileUtil::GetFilename(filename.toStdString()); + + setWindowTitle(QString("yuzu %1| %4 | %2-%3") + .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc, + QString::fromStdString(title_name))); + render_window->show(); render_window->setFocus(); @@ -576,6 +595,8 @@ void GMainWindow::ShutdownGame() { render_window->hide(); game_list->show(); game_list->setFilterFocus(); + setWindowTitle(QString("yuzu %1| %2-%3") + .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); // Disable status bar updates status_bar_update_timer.stop(); @@ -1173,16 +1194,6 @@ void GMainWindow::UpdateUITheme() { #undef main #endif -static void InitializeLogging() { - Log::Filter log_filter; - log_filter.ParseFilterString(Settings::values.log_filter); - Log::SetGlobalFilter(log_filter); - - const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); - FileUtil::CreateFullPath(log_dir); - Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE)); -} - int main(int argc, char* argv[]) { MicroProfileOnThreadCreate("Frontend"); SCOPE_EXIT({ MicroProfileShutdown(); }); @@ -1200,7 +1211,6 @@ int main(int argc, char* argv[]) { GMainWindow main_window; // After settings have been loaded by GMainWindow, apply the filter - InitializeLogging(); main_window.show(); return app.exec(); } |