summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/CMakeLists.txt6
-rw-r--r--src/core/core.cpp11
-rw-r--r--src/core/core.h7
-rw-r--r--src/core/crypto/aes_util.cpp7
-rw-r--r--src/core/crypto/ctr_encryption_layer.cpp8
-rw-r--r--src/core/crypto/key_manager.cpp172
-rw-r--r--src/core/crypto/key_manager.h68
-rw-r--r--src/core/crypto/xts_encryption_layer.cpp58
-rw-r--r--src/core/crypto/xts_encryption_layer.h25
-rw-r--r--src/core/file_sys/bis_factory.cpp11
-rw-r--r--src/core/file_sys/card_image.cpp12
-rw-r--r--src/core/file_sys/card_image.h2
-rw-r--r--src/core/file_sys/content_archive.cpp10
-rw-r--r--src/core/file_sys/content_archive.h3
-rw-r--r--src/core/file_sys/registered_cache.cpp14
-rw-r--r--src/core/file_sys/registered_cache.h4
-rw-r--r--src/core/file_sys/sdmc_factory.cpp15
-rw-r--r--src/core/file_sys/sdmc_factory.h7
-rw-r--r--src/core/file_sys/vfs.cpp7
-rw-r--r--src/core/file_sys/vfs.h4
-rw-r--r--src/core/file_sys/xts_archive.cpp169
-rw-r--r--src/core/file_sys/xts_archive.h69
-rw-r--r--src/core/hle/service/am/am.cpp12
-rw-r--r--src/core/hle/service/am/am.h1
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp44
-rw-r--r--src/core/hle/service/filesystem/filesystem.h6
-rw-r--r--src/core/hle/service/hid/hid.cpp1
-rw-r--r--src/core/hle/service/hid/hid.h4
-rw-r--r--src/core/hle/service/pm/pm.cpp13
-rw-r--r--src/core/hle/service/pm/pm.h2
-rw-r--r--src/core/hle/service/set/set.cpp51
-rw-r--r--src/core/hle/service/set/set.h2
-rw-r--r--src/core/loader/loader.cpp29
-rw-r--r--src/core/loader/loader.h14
-rw-r--r--src/core/loader/nax.cpp66
-rw-r--r--src/core/loader/nax.h48
-rw-r--r--src/core/loader/xci.cpp11
-rw-r--r--src/video_core/engines/maxwell_3d.cpp8
-rw-r--r--src/video_core/engines/maxwell_3d.h70
-rw-r--r--src/video_core/engines/shader_bytecode.h25
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp94
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h3
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp75
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.h33
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp41
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.h25
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp53
-rw-r--r--src/video_core/renderer_opengl/gl_state.h18
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h24
-rw-r--r--src/yuzu/configuration/config.cpp4
-rw-r--r--src/yuzu/game_list.cpp22
-rw-r--r--src/yuzu/game_list_p.h2
-rw-r--r--src/yuzu/main.cpp36
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();
}