diff options
Diffstat (limited to '')
114 files changed, 4115 insertions, 1156 deletions
diff --git a/.travis/clang-format/script.sh b/.travis/clang-format/script.sh index 0f6e8e6e4..3eff6322f 100755 --- a/.travis/clang-format/script.sh +++ b/.travis/clang-format/script.sh @@ -1,6 +1,6 @@ #!/bin/bash -ex -if grep -nr '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .travis* dist/*.desktop \ +if grep -nrI '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .travis* dist/*.desktop \ dist/*.svg dist/*.xml; then echo Trailing whitespace found, aborting exit 1 diff --git a/externals/mbedtls b/externals/mbedtls -Subproject d409b75a4cf75a5b358b352c75826ddbca44db5 +Subproject a280e602f3a4ae001d3a83cbc3e6e04c99c2227 diff --git a/src/common/web_result.h b/src/common/web_result.h index 969926674..8bfa2141d 100644 --- a/src/common/web_result.h +++ b/src/common/web_result.h @@ -5,6 +5,7 @@ #pragma once #include <string> +#include "common/common_types.h" namespace Common { struct WebResult { diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1a9ec459f..4755ec822 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -18,6 +18,8 @@ add_library(core STATIC crypto/encryption_layer.h crypto/key_manager.cpp crypto/key_manager.h + crypto/partition_data_manager.cpp + crypto/partition_data_manager.h crypto/ctr_encryption_layer.cpp crypto/ctr_encryption_layer.h crypto/xts_encryption_layer.cpp @@ -70,6 +72,7 @@ add_library(core STATIC file_sys/vfs_real.cpp file_sys/vfs_real.h file_sys/vfs_static.h + file_sys/vfs_types.h file_sys/vfs_vector.cpp file_sys/vfs_vector.h file_sys/xts_archive.cpp @@ -415,8 +418,8 @@ create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives) if (ENABLE_WEB_SERVICE) - add_definitions(-DENABLE_WEB_SERVICE) - target_link_libraries(core PUBLIC json-headers web_service) + target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE) + target_link_libraries(core PRIVATE web_service) endif() if (ARCHITECTURE_x86_64) diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 7e978cf7a..4d2491870 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -129,7 +129,7 @@ public: }; std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const { - auto& current_process = Core::CurrentProcess(); + auto* current_process = Core::CurrentProcess(); auto** const page_table = current_process->VMManager().page_table.pointers.data(); Dynarmic::A64::UserConfig config; @@ -144,7 +144,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const { // Multi-process state config.processor_id = core_index; - config.global_monitor = &exclusive_monitor->monitor; + config.global_monitor = &exclusive_monitor.monitor; // System registers config.tpidrro_el0 = &cb->tpidrro_el0; @@ -171,10 +171,9 @@ void ARM_Dynarmic::Step() { cb->InterpreterFallback(jit->GetPC(), 1); } -ARM_Dynarmic::ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, - std::size_t core_index) +ARM_Dynarmic::ARM_Dynarmic(ExclusiveMonitor& exclusive_monitor, std::size_t core_index) : cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), core_index{core_index}, - exclusive_monitor{std::dynamic_pointer_cast<DynarmicExclusiveMonitor>(exclusive_monitor)} { + exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} { ThreadContext ctx{}; inner_unicorn.SaveContext(ctx); PageTableChanged(); diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h index 4ee92ee27..512bf8ce9 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.h +++ b/src/core/arm/dynarmic/arm_dynarmic.h @@ -23,7 +23,7 @@ class DynarmicExclusiveMonitor; class ARM_Dynarmic final : public ARM_Interface { public: - ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, std::size_t core_index); + ARM_Dynarmic(ExclusiveMonitor& exclusive_monitor, std::size_t core_index); ~ARM_Dynarmic(); void MapBackingMemory(VAddr address, std::size_t size, u8* memory, @@ -62,7 +62,7 @@ private: ARM_Unicorn inner_unicorn; std::size_t core_index; - std::shared_ptr<DynarmicExclusiveMonitor> exclusive_monitor; + DynarmicExclusiveMonitor& exclusive_monitor; Memory::PageTable* current_page_table = nullptr; }; diff --git a/src/core/core.cpp b/src/core/core.cpp index b6acfb3e4..3c57a62ec 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -71,9 +71,9 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, } /// Runs a CPU core while the system is powered on -void RunCpuCore(std::shared_ptr<Cpu> cpu_state) { +void RunCpuCore(Cpu& cpu_state) { while (Core::System::GetInstance().IsPoweredOn()) { - cpu_state->RunLoop(true); + cpu_state.RunLoop(true); } } } // Anonymous namespace @@ -95,7 +95,7 @@ struct System::Impl { status = ResultStatus::Success; // Update thread_to_cpu in case Core 0 is run from a different host thread - thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0]; + thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0].get(); if (GDBStub::IsServerEnabled()) { GDBStub::HandlePacket(); @@ -136,18 +136,19 @@ struct System::Impl { if (virtual_filesystem == nullptr) virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>(); - kernel.MakeCurrentProcess(Kernel::Process::Create(kernel, "main")); + auto main_process = Kernel::Process::Create(kernel, "main"); + kernel.MakeCurrentProcess(main_process.get()); - cpu_barrier = std::make_shared<CpuBarrier>(); + cpu_barrier = std::make_unique<CpuBarrier>(); cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size()); for (std::size_t index = 0; index < cpu_cores.size(); ++index) { - cpu_cores[index] = std::make_shared<Cpu>(cpu_exclusive_monitor, cpu_barrier, index); + cpu_cores[index] = std::make_unique<Cpu>(*cpu_exclusive_monitor, *cpu_barrier, index); } telemetry_session = std::make_unique<Core::TelemetrySession>(); service_manager = std::make_shared<Service::SM::ServiceManager>(); - Service::Init(service_manager, virtual_filesystem); + Service::Init(service_manager, *virtual_filesystem); GDBStub::Init(); renderer = VideoCore::CreateRenderer(emu_window); @@ -159,12 +160,12 @@ struct System::Impl { // Create threads for CPU cores 1-3, and build thread_to_cpu map // CPU core 0 is run on the main thread - thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0]; + thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0].get(); if (Settings::values.use_multi_core) { for (std::size_t index = 0; index < cpu_core_threads.size(); ++index) { cpu_core_threads[index] = - std::make_unique<std::thread>(RunCpuCore, cpu_cores[index + 1]); - thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1]; + std::make_unique<std::thread>(RunCpuCore, std::ref(*cpu_cores[index + 1])); + thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1].get(); } } @@ -244,6 +245,7 @@ struct System::Impl { for (auto& cpu_core : cpu_cores) { cpu_core.reset(); } + cpu_exclusive_monitor.reset(); cpu_barrier.reset(); // Shutdown kernel and core timing @@ -281,9 +283,9 @@ struct System::Impl { std::unique_ptr<VideoCore::RendererBase> renderer; std::unique_ptr<Tegra::GPU> gpu_core; std::shared_ptr<Tegra::DebugContext> debug_context; - std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor; - std::shared_ptr<CpuBarrier> cpu_barrier; - std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores; + std::unique_ptr<ExclusiveMonitor> cpu_exclusive_monitor; + std::unique_ptr<CpuBarrier> cpu_barrier; + std::array<std::unique_ptr<Cpu>, NUM_CPU_CORES> cpu_cores; std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads; std::size_t active_core{}; ///< Active core, only used in single thread mode @@ -297,7 +299,7 @@ struct System::Impl { std::string status_details = ""; /// Map of guest threads to CPU cores - std::map<std::thread::id, std::shared_ptr<Cpu>> thread_to_cpu; + std::map<std::thread::id, Cpu*> thread_to_cpu; Core::PerfStats perf_stats; Core::FrameLimiter frame_limiter; @@ -353,19 +355,22 @@ std::size_t System::CurrentCoreIndex() { } Kernel::Scheduler& System::CurrentScheduler() { - return *CurrentCpuCore().Scheduler(); + return CurrentCpuCore().Scheduler(); } -const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(std::size_t core_index) { - ASSERT(core_index < NUM_CPU_CORES); - return impl->cpu_cores[core_index]->Scheduler(); +Kernel::Scheduler& System::Scheduler(std::size_t core_index) { + return CpuCore(core_index).Scheduler(); +} + +const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const { + return CpuCore(core_index).Scheduler(); } -Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() { +Kernel::Process* System::CurrentProcess() { return impl->kernel.CurrentProcess(); } -const Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() const { +const Kernel::Process* System::CurrentProcess() const { return impl->kernel.CurrentProcess(); } @@ -379,6 +384,11 @@ Cpu& System::CpuCore(std::size_t core_index) { return *impl->cpu_cores[core_index]; } +const Cpu& System::CpuCore(std::size_t core_index) const { + ASSERT(core_index < NUM_CPU_CORES); + return *impl->cpu_cores[core_index]; +} + ExclusiveMonitor& System::Monitor() { return *impl->cpu_exclusive_monitor; } diff --git a/src/core/core.h b/src/core/core.h index f9a3e97e3..173be45f8 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -156,6 +156,9 @@ public: /// Gets a CPU interface to the CPU core with the specified index Cpu& CpuCore(std::size_t core_index); + /// Gets a CPU interface to the CPU core with the specified index + const Cpu& CpuCore(std::size_t core_index) const; + /// Gets the exclusive monitor ExclusiveMonitor& Monitor(); @@ -172,13 +175,16 @@ public: const VideoCore::RendererBase& Renderer() const; /// Gets the scheduler for the CPU core with the specified index - const std::shared_ptr<Kernel::Scheduler>& Scheduler(std::size_t core_index); + Kernel::Scheduler& Scheduler(std::size_t core_index); + + /// Gets the scheduler for the CPU core with the specified index + const Kernel::Scheduler& Scheduler(std::size_t core_index) const; - /// Provides a reference to the current process - Kernel::SharedPtr<Kernel::Process>& CurrentProcess(); + /// Provides a pointer to the current process + Kernel::Process* CurrentProcess(); - /// Provides a constant reference to the current process. - const Kernel::SharedPtr<Kernel::Process>& CurrentProcess() const; + /// Provides a constant pointer to the current process. + const Kernel::Process* CurrentProcess() const; /// Provides a reference to the kernel instance. Kernel::KernelCore& Kernel(); @@ -246,7 +252,7 @@ inline TelemetrySession& Telemetry() { return System::GetInstance().TelemetrySession(); } -inline Kernel::SharedPtr<Kernel::Process>& CurrentProcess() { +inline Kernel::Process* CurrentProcess() { return System::GetInstance().CurrentProcess(); } diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp index 265f8ed9c..fffda8a99 100644 --- a/src/core/core_cpu.cpp +++ b/src/core/core_cpu.cpp @@ -49,10 +49,8 @@ bool CpuBarrier::Rendezvous() { return false; } -Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, - std::shared_ptr<CpuBarrier> cpu_barrier, std::size_t core_index) - : cpu_barrier{std::move(cpu_barrier)}, core_index{core_index} { - +Cpu::Cpu(ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, std::size_t core_index) + : cpu_barrier{cpu_barrier}, core_index{core_index} { if (Settings::values.use_cpu_jit) { #ifdef ARCHITECTURE_x86_64 arm_interface = std::make_unique<ARM_Dynarmic>(exclusive_monitor, core_index); @@ -64,15 +62,15 @@ Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, arm_interface = std::make_unique<ARM_Unicorn>(); } - scheduler = std::make_shared<Kernel::Scheduler>(*arm_interface); + scheduler = std::make_unique<Kernel::Scheduler>(*arm_interface); } Cpu::~Cpu() = default; -std::shared_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_cores) { +std::unique_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_cores) { if (Settings::values.use_cpu_jit) { #ifdef ARCHITECTURE_x86_64 - return std::make_shared<DynarmicExclusiveMonitor>(num_cores); + return std::make_unique<DynarmicExclusiveMonitor>(num_cores); #else return nullptr; // TODO(merry): Passthrough exclusive monitor #endif @@ -83,7 +81,7 @@ std::shared_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_core void Cpu::RunLoop(bool tight_loop) { // Wait for all other CPU cores to complete the previous slice, such that they run in lock-step - if (!cpu_barrier->Rendezvous()) { + if (!cpu_barrier.Rendezvous()) { // If rendezvous failed, session has been killed return; } diff --git a/src/core/core_cpu.h b/src/core/core_cpu.h index ee7e04abc..1d2bdc6cd 100644 --- a/src/core/core_cpu.h +++ b/src/core/core_cpu.h @@ -41,8 +41,7 @@ private: class Cpu { public: - Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, - std::shared_ptr<CpuBarrier> cpu_barrier, std::size_t core_index); + Cpu(ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, std::size_t core_index); ~Cpu(); void RunLoop(bool tight_loop = true); @@ -59,8 +58,12 @@ public: return *arm_interface; } - const std::shared_ptr<Kernel::Scheduler>& Scheduler() const { - return scheduler; + Kernel::Scheduler& Scheduler() { + return *scheduler; + } + + const Kernel::Scheduler& Scheduler() const { + return *scheduler; } bool IsMainCore() const { @@ -71,14 +74,14 @@ public: return core_index; } - static std::shared_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores); + static std::unique_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores); private: void Reschedule(); std::unique_ptr<ARM_Interface> arm_interface; - std::shared_ptr<CpuBarrier> cpu_barrier; - std::shared_ptr<Kernel::Scheduler> scheduler; + CpuBarrier& cpu_barrier; + std::unique_ptr<Kernel::Scheduler> scheduler; std::atomic<bool> reschedule_pending = false; std::size_t core_index; diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index bf3a70944..fd0786068 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -4,23 +4,56 @@ #include <algorithm> #include <array> +#include <bitset> +#include <cctype> #include <fstream> #include <locale> +#include <map> #include <sstream> #include <string_view> #include <tuple> #include <vector> +#include <mbedtls/bignum.h> +#include <mbedtls/cipher.h> +#include <mbedtls/cmac.h> +#include <mbedtls/sha256.h> +#include "common/common_funcs.h" #include "common/common_paths.h" #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/crypto/partition_data_manager.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/registered_cache.h" +#include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" #include "core/settings.h" namespace Core::Crypto { +constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; + +using namespace Common; + +const std::array<SHA256Hash, 2> eticket_source_hashes{ + "B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"_array32, // eticket_rsa_kek_source + "E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"_array32, // eticket_rsa_kekek_source +}; + +const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{ + {{S128KeyType::Master, 0}, "master_key_"}, + {{S128KeyType::Package1, 0}, "package1_key_"}, + {{S128KeyType::Package2, 0}, "package2_key_"}, + {{S128KeyType::Titlekek, 0}, "titlekek_"}, + {{S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob)}, "keyblob_key_source_"}, + {{S128KeyType::Keyblob, 0}, "keyblob_key_"}, + {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"}, +}; + Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { Key128 out{}; @@ -37,57 +70,136 @@ Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, K return out; } +Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source) { + AESCipher<Key128> sbk_cipher(sbk, Mode::ECB); + AESCipher<Key128> tsec_cipher(tsec, Mode::ECB); + tsec_cipher.Transcode(source.data(), source.size(), source.data(), Op::Decrypt); + sbk_cipher.Transcode(source.data(), source.size(), source.data(), Op::Decrypt); + return source; +} + +Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master_source) { + Key128 master_root; + std::memcpy(master_root.data(), keyblob.data(), sizeof(Key128)); + + AESCipher<Key128> master_cipher(master_root, Mode::ECB); + + Key128 master{}; + master_cipher.Transcode(master_source.data(), master_source.size(), master.data(), Op::Decrypt); + return master; +} + +std::array<u8, 144> DecryptKeyblob(const std::array<u8, 176>& encrypted_keyblob, + const Key128& key) { + std::array<u8, 0x90> keyblob; + AESCipher<Key128> cipher(key, Mode::CTR); + cipher.SetIV(std::vector<u8>(encrypted_keyblob.data() + 0x10, encrypted_keyblob.data() + 0x20)); + cipher.Transcode(encrypted_keyblob.data() + 0x20, keyblob.size(), keyblob.data(), Op::Decrypt); + return keyblob; +} + +void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) { + const auto kek_generation_source = + GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)); + const auto key_generation_source = + GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)); + + if (HasKey(S128KeyType::Master, crypto_revision)) { + for (auto kak_type : + {KeyAreaKeyType::Application, KeyAreaKeyType::Ocean, KeyAreaKeyType::System}) { + if (HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), + static_cast<u64>(kak_type))) { + const auto source = + GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), + static_cast<u64>(kak_type)); + const auto kek = + GenerateKeyEncryptionKey(source, GetKey(S128KeyType::Master, crypto_revision), + kek_generation_source, key_generation_source); + SetKey(S128KeyType::KeyArea, kek, crypto_revision, static_cast<u64>(kak_type)); + } + } + + AESCipher<Key128> master_cipher(GetKey(S128KeyType::Master, crypto_revision), Mode::ECB); + for (auto key_type : {SourceKeyType::Titlekek, SourceKeyType::Package2}) { + if (HasKey(S128KeyType::Source, static_cast<u64>(key_type))) { + Key128 key{}; + master_cipher.Transcode( + GetKey(S128KeyType::Source, static_cast<u64>(key_type)).data(), key.size(), + key.data(), Op::Decrypt); + SetKey(key_type == SourceKeyType::Titlekek ? S128KeyType::Titlekek + : S128KeyType::Package2, + key, crypto_revision); + } + } + } +} + +Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { + AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB); + Key128 mac_key{}; + mac_cipher.Transcode(mac_source.data(), mac_key.size(), mac_key.data(), Op::Decrypt); + return mac_key; +} + 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) + if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) { return boost::none; + } std::array<u8, 0x10> buffer{}; std::size_t offset = 0; for (; offset + 0x10 < save_43.GetSize(); ++offset) { - save_43.Seek(offset, SEEK_SET); + if (!save_43.Seek(offset, SEEK_SET)) { + return boost::none; + } + save_43.ReadBytes(buffer.data(), buffer.size()); - if (buffer == private_seed) + if (buffer == private_seed) { break; + } } - if (offset + 0x10 >= save_43.GetSize()) + if (!save_43.Seek(offset + 0x10, SEEK_SET)) { return boost::none; + } Key128 seed{}; - save_43.Seek(offset + 0x10, SEEK_SET); - save_43.ReadBytes(seed.data(), seed.size()); + if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) { + return boost::none; + } return seed; } -Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys) { - if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK))) +Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, 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))) + 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)); + keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek)); const auto aes_kek_gen = - keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration)); + 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); + keys.SetKey(S128KeyType::SDKek, sd_kek); if (!keys.HasKey(S128KeyType::SDSeed)) return Loader::ResultStatus::ErrorMissingSDSeed; @@ -118,9 +230,147 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManag return source; ///< Return unaltered source to satisfy output requirement. }); + keys.SetKey(S256KeyType::SDKey, sd_keys[0], static_cast<u64>(SDKeyType::Save)); + keys.SetKey(S256KeyType::SDKey, sd_keys[1], static_cast<u64>(SDKeyType::NCA)); + return Loader::ResultStatus::Success; } +std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { + if (!ticket_save.IsOpen()) + return {}; + + std::vector<u8> buffer(ticket_save.GetSize()); + if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) { + return {}; + } + + std::vector<TicketRaw> out; + u32 magic{}; + for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { + if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && + buffer[offset + 3] == 0x0) { + out.emplace_back(); + auto& next = out.back(); + std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw)); + offset += next.size(); + } + } + + return out; +} + +template <size_t size> +static std::array<u8, size> operator^(const std::array<u8, size>& lhs, + const std::array<u8, size>& rhs) { + std::array<u8, size> out{}; + std::transform(lhs.begin(), lhs.end(), rhs.begin(), out.begin(), std::bit_xor<>()); + return out; +} + +template <size_t target_size, size_t in_size> +static std::array<u8, target_size> MGF1(const std::array<u8, in_size>& seed) { + // Avoids truncation overflow within the loop below. + static_assert(target_size <= 0xFF); + + std::array<u8, in_size + 4> seed_exp{}; + std::memcpy(seed_exp.data(), seed.data(), in_size); + + std::vector<u8> out; + size_t i = 0; + while (out.size() < target_size) { + out.resize(out.size() + 0x20); + seed_exp[in_size + 3] = static_cast<u8>(i); + mbedtls_sha256(seed_exp.data(), seed_exp.size(), out.data() + out.size() - 0x20, 0); + ++i; + } + + std::array<u8, target_size> target; + std::memcpy(target.data(), out.data(), target_size); + return target; +} + +template <size_t size> +static boost::optional<u64> FindTicketOffset(const std::array<u8, size>& data) { + u64 offset = 0; + for (size_t i = 0x20; i < data.size() - 0x10; ++i) { + if (data[i] == 0x1) { + offset = i + 1; + break; + } else if (data[i] != 0x0) { + return boost::none; + } + } + + return offset; +} + +boost::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, + const RSAKeyPair<2048>& key) { + u32 cert_authority; + std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority)); + if (cert_authority == 0) + return boost::none; + if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) { + LOG_INFO(Crypto, + "Attempting to parse ticket with non-standard certificate authority {:08X}.", + cert_authority); + } + + Key128 rights_id; + std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128)); + + if (rights_id == Key128{}) + return boost::none; + + Key128 key_temp{}; + + if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) { + std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size()); + return std::make_pair(rights_id, key_temp); + } + + mbedtls_mpi D; // RSA Private Exponent + mbedtls_mpi N; // RSA Modulus + mbedtls_mpi S; // Input + mbedtls_mpi M; // Output + + mbedtls_mpi_init(&D); + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&S); + mbedtls_mpi_init(&M); + + mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); + mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); + mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100); + + mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); + + std::array<u8, 0x100> rsa_step; + mbedtls_mpi_write_binary(&M, rsa_step.data(), rsa_step.size()); + + u8 m_0 = rsa_step[0]; + std::array<u8, 0x20> m_1; + std::memcpy(m_1.data(), rsa_step.data() + 0x01, m_1.size()); + std::array<u8, 0xDF> m_2; + std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size()); + + if (m_0 != 0) + return boost::none; + + m_1 = m_1 ^ MGF1<0x20>(m_2); + m_2 = m_2 ^ MGF1<0xDF>(m_1); + + const auto offset = FindTicketOffset(m_2); + if (offset == boost::none) + return boost::none; + ASSERT(offset.get() > 0); + + std::memcpy(key_temp.data(), m_2.data() + offset.get(), key_temp.size()); + + return std::make_pair(rights_id, key_temp); +} + KeyManager::KeyManager() { // Initialize keys const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); @@ -137,6 +387,15 @@ KeyManager::KeyManager() { AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true); AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true); + AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "console.keys", false); + AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "console.keys_autogenerated", false); +} + +static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) { + if (base.size() < begin + length) + return false; + return std::all_of(base.begin() + begin, base.begin() + begin + length, + [](u8 c) { return std::isdigit(c); }); } void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { @@ -158,6 +417,9 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end()); out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end()); + if (out[0].compare(0, 1, "#") == 0) + continue; + if (is_title_keys) { auto rights_id_raw = Common::HexStringToArray<16>(out[0]); u128 rights_id{}; @@ -174,6 +436,50 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) { const auto index = s256_file_id.at(out[0]); Key256 key = Common::HexStringToArray<32>(out[1]); s256_keys[{index.type, index.field1, index.field2}] = key; + } else if (out[0].compare(0, 8, "keyblob_") == 0 && + out[0].compare(0, 9, "keyblob_k") != 0) { + if (!ValidCryptoRevisionString(out[0], 8, 2)) + continue; + + const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16); + keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); + } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { + if (!ValidCryptoRevisionString(out[0], 18, 2)) + continue; + + const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); + encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); + } else { + for (const auto& kv : KEYS_VARIABLE_LENGTH) { + if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) + continue; + if (out[0].compare(0, kv.second.size(), kv.second) == 0) { + const auto index = + std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16); + const auto sub = kv.first.second; + if (sub == 0) { + s128_keys[{kv.first.first, index, 0}] = + Common::HexStringToArray<16>(out[1]); + } else { + s128_keys[{kv.first.first, kv.first.second, index}] = + Common::HexStringToArray<16>(out[1]); + } + + break; + } + } + + static constexpr std::array<const char*, 3> kak_names = { + "key_area_key_application_", "key_area_key_ocean_", "key_area_key_system_"}; + for (size_t j = 0; j < kak_names.size(); ++j) { + const auto& match = kak_names[j]; + if (out[0].compare(0, std::strlen(match), match) == 0) { + const auto index = + std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16); + s128_keys[{S128KeyType::KeyArea, index, j}] = + Common::HexStringToArray<16>(out[1]); + } + } } } } @@ -187,6 +493,28 @@ void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string& LoadFromFile(dir2 + DIR_SEP + filename, title); } +bool KeyManager::BaseDeriveNecessary() const { + const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) { + return !HasKey(key_type, index1, index2); + }; + + if (check_key_existence(S256KeyType::Header)) + return true; + + for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) { + if (check_key_existence(S128KeyType::Master, i) || + check_key_existence(S128KeyType::KeyArea, i, + static_cast<u64>(KeyAreaKeyType::Application)) || + check_key_existence(S128KeyType::KeyArea, i, static_cast<u64>(KeyAreaKeyType::Ocean)) || + check_key_existence(S128KeyType::KeyArea, i, + static_cast<u64>(KeyAreaKeyType::System)) || + check_key_existence(S128KeyType::Titlekek, i)) + return true; + } + + return false; +} + bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const { return s128_keys.find({id, field1, field2}) != s128_keys.end(); } @@ -207,13 +535,30 @@ Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const { return s256_keys.at({id, field1, field2}); } -template <std::size_t Size> -void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname, +Key256 KeyManager::GetBISKey(u8 partition_id) const { + Key256 out{}; + + for (const auto& bis_type : {BISKeyType::Crypto, BISKeyType::Tweak}) { + if (HasKey(S128KeyType::BIS, partition_id, static_cast<u64>(bis_type))) { + std::memcpy( + out.data() + sizeof(Key128) * static_cast<u64>(bis_type), + s128_keys.at({S128KeyType::BIS, partition_id, static_cast<u64>(bis_type)}).data(), + sizeof(Key128)); + } + } + + return out; +} + +template <size_t Size> +void KeyManager::WriteKeyToFile(KeyCategory category, 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) + if (category == KeyCategory::Standard) filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated"; + else if (category == KeyCategory::Console) + filename = "console.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); @@ -227,7 +572,7 @@ void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname, } file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key)); - AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, title_key); + AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title); } void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { @@ -237,8 +582,15 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { Key128 rights_id; std::memcpy(rights_id.data(), &field2, sizeof(u64)); std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64)); - WriteKeyToFile(true, Common::HexArrayToString(rights_id), key); + WriteKeyToFile(KeyCategory::Title, Common::HexArrayToString(rights_id), key); } + + auto category = KeyCategory::Standard; + if (id == S128KeyType::Keyblob || id == S128KeyType::KeyblobMAC || id == S128KeyType::TSEC || + id == S128KeyType::SecureBoot || id == S128KeyType::SDSeed || id == S128KeyType::BIS) { + category = KeyCategory::Console; + } + const auto iter2 = std::find_if( s128_file_id.begin(), s128_file_id.end(), [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) { @@ -246,7 +598,30 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { std::tie(id, field1, field2); }); if (iter2 != s128_file_id.end()) - WriteKeyToFile(false, iter2->first, key); + WriteKeyToFile(category, iter2->first, key); + + // Variable cases + if (id == S128KeyType::KeyArea) { + static constexpr std::array<const char*, 3> kak_names = {"key_area_key_application_{:02X}", + "key_area_key_ocean_{:02X}", + "key_area_key_system_{:02X}"}; + WriteKeyToFile(category, fmt::format(kak_names.at(field2), field1), key); + } else if (id == S128KeyType::Master) { + WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key); + } else if (id == S128KeyType::Package1) { + WriteKeyToFile(category, fmt::format("package1_key_{:02X}", field1), key); + } else if (id == S128KeyType::Package2) { + WriteKeyToFile(category, fmt::format("package2_key_{:02X}", field1), key); + } else if (id == S128KeyType::Titlekek) { + WriteKeyToFile(category, fmt::format("titlekek_{:02X}", field1), key); + } else if (id == S128KeyType::Keyblob) { + WriteKeyToFile(category, fmt::format("keyblob_key_{:02X}", field1), key); + } else if (id == S128KeyType::KeyblobMAC) { + WriteKeyToFile(category, fmt::format("keyblob_mac_key_{:02X}", field1), key); + } else if (id == S128KeyType::Source && field1 == static_cast<u64>(SourceKeyType::Keyblob)) { + WriteKeyToFile(category, fmt::format("keyblob_key_source_{:02X}", field2), key); + } + s128_keys[{id, field1, field2}] = key; } @@ -260,7 +635,7 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { std::tie(id, field1, field2); }); if (iter != s256_file_id.end()) - WriteKeyToFile(false, iter->first, key); + WriteKeyToFile(KeyCategory::Standard, iter->first, key); s256_keys[{id, field1, field2}] = key; } @@ -290,59 +665,388 @@ void KeyManager::DeriveSDSeedLazy() { SetKey(S128KeyType::SDSeed, res.get()); } +static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) { + Key128 out{}; + + mbedtls_cipher_cmac(mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB), key.data(), + key.size() * 8, source, size, out.data()); + return out; +} + +void KeyManager::DeriveBase() { + if (!BaseDeriveNecessary()) + return; + + if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC)) + return; + + const auto has_bis = [this](u64 id) { + return HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Crypto)) && + HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Tweak)); + }; + + const auto copy_bis = [this](u64 id_from, u64 id_to) { + SetKey(S128KeyType::BIS, + GetKey(S128KeyType::BIS, id_from, static_cast<u64>(BISKeyType::Crypto)), id_to, + static_cast<u64>(BISKeyType::Crypto)); + + SetKey(S128KeyType::BIS, + GetKey(S128KeyType::BIS, id_from, static_cast<u64>(BISKeyType::Tweak)), id_to, + static_cast<u64>(BISKeyType::Tweak)); + }; + + if (has_bis(2) && !has_bis(3)) + copy_bis(2, 3); + else if (has_bis(3) && !has_bis(2)) + copy_bis(3, 2); + + std::bitset<32> revisions(0xFFFFFFFF); + for (size_t i = 0; i < revisions.size(); ++i) { + if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob), i) || + encrypted_keyblobs[i] == std::array<u8, 0xB0>{}) { + revisions.reset(i); + } + } + + if (!revisions.any()) + return; + + const auto sbk = GetKey(S128KeyType::SecureBoot); + const auto tsec = GetKey(S128KeyType::TSEC); + const auto master_source = GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master)); + + for (size_t i = 0; i < revisions.size(); ++i) { + if (!revisions[i]) + continue; + + // Derive keyblob key + const auto key = DeriveKeyblobKey( + sbk, tsec, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob), i)); + + SetKey(S128KeyType::Keyblob, key, i); + + // Derive keyblob MAC key + if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC))) + continue; + + const auto mac_key = DeriveKeyblobMACKey( + key, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC))); + SetKey(S128KeyType::KeyblobMAC, mac_key, i); + + Key128 cmac = CalculateCMAC(encrypted_keyblobs[i].data() + 0x10, 0xA0, mac_key); + if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0) + continue; + + // Decrypt keyblob + if (keyblobs[i] == std::array<u8, 0x90>{}) { + keyblobs[i] = DecryptKeyblob(encrypted_keyblobs[i], key); + WriteKeyToFile<0x90>(KeyCategory::Console, fmt::format("keyblob_{:02X}", i), + keyblobs[i]); + } + + Key128 package1; + std::memcpy(package1.data(), keyblobs[i].data() + 0x80, sizeof(Key128)); + SetKey(S128KeyType::Package1, package1, i); + + // Derive master key + if (HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master))) { + SetKey(S128KeyType::Master, + DeriveMasterKey(keyblobs[i], GetKey(S128KeyType::Source, + static_cast<u64>(SourceKeyType::Master))), + i); + } + } + + revisions.set(); + for (size_t i = 0; i < revisions.size(); ++i) { + if (!HasKey(S128KeyType::Master, i)) + revisions.reset(i); + } + + if (!revisions.any()) + return; + + for (size_t i = 0; i < revisions.size(); ++i) { + if (!revisions[i]) + continue; + + // Derive general purpose keys + DeriveGeneralPurposeKeys(i); + } + + if (HasKey(S128KeyType::Master, 0) && + HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)) && + HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)) && + HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek)) && + HasKey(S256KeyType::HeaderSource)) { + const auto header_kek = GenerateKeyEncryptionKey( + GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek)), + GetKey(S128KeyType::Master, 0), + GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)), + GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration))); + SetKey(S128KeyType::HeaderKek, header_kek); + + AESCipher<Key128> header_cipher(header_kek, Mode::ECB); + Key256 out = GetKey(S256KeyType::HeaderSource); + header_cipher.Transcode(out.data(), out.size(), out.data(), Op::Decrypt); + SetKey(S256KeyType::Header, out); + } +} + +void KeyManager::DeriveETicket(PartitionDataManager& data) { + // ETicket keys + const auto es = Service::FileSystem::GetUnionContents()->GetEntry( + 0x0100000000000033, FileSys::ContentRecordType::Program); + + if (es == nullptr) + return; + + const auto exefs = es->GetExeFS(); + if (exefs == nullptr) + return; + + const auto main = exefs->GetFile("main"); + if (main == nullptr) + return; + + const auto bytes = main->ReadAllBytes(); + + const auto eticket_kek = FindKeyFromHex16(bytes, eticket_source_hashes[0]); + const auto eticket_kekek = FindKeyFromHex16(bytes, eticket_source_hashes[1]); + + const auto seed3 = data.GetRSAKekSeed3(); + const auto mask0 = data.GetRSAKekMask0(); + + if (eticket_kek != Key128{}) + SetKey(S128KeyType::Source, eticket_kek, static_cast<size_t>(SourceKeyType::ETicketKek)); + if (eticket_kekek != Key128{}) { + SetKey(S128KeyType::Source, eticket_kekek, + static_cast<size_t>(SourceKeyType::ETicketKekek)); + } + if (seed3 != Key128{}) + SetKey(S128KeyType::RSAKek, seed3, static_cast<size_t>(RSAKekType::Seed3)); + if (mask0 != Key128{}) + SetKey(S128KeyType::RSAKek, mask0, static_cast<size_t>(RSAKekType::Mask0)); + if (eticket_kek == Key128{} || eticket_kekek == Key128{} || seed3 == Key128{} || + mask0 == Key128{}) { + return; + } + + Key128 rsa_oaep_kek{}; + std::transform(seed3.begin(), seed3.end(), mask0.begin(), rsa_oaep_kek.begin(), + std::bit_xor<>()); + + if (rsa_oaep_kek == Key128{}) + return; + + SetKey(S128KeyType::Source, rsa_oaep_kek, + static_cast<u64>(SourceKeyType::RSAOaepKekGeneration)); + + Key128 temp_kek{}; + Key128 temp_kekek{}; + Key128 eticket_final{}; + + // Derive ETicket RSA Kek + AESCipher<Key128> es_master(GetKey(S128KeyType::Master), Mode::ECB); + es_master.Transcode(rsa_oaep_kek.data(), rsa_oaep_kek.size(), temp_kek.data(), Op::Decrypt); + AESCipher<Key128> es_kekek(temp_kek, Mode::ECB); + es_kekek.Transcode(eticket_kekek.data(), eticket_kekek.size(), temp_kekek.data(), Op::Decrypt); + AESCipher<Key128> es_kek(temp_kekek, Mode::ECB); + es_kek.Transcode(eticket_kek.data(), eticket_kek.size(), eticket_final.data(), Op::Decrypt); + + if (eticket_final == Key128{}) + return; + + SetKey(S128KeyType::ETicketRSAKek, eticket_final); + + // Titlekeys + data.DecryptProdInfo(GetBISKey(0)); + + const auto eticket_extended_kek = data.GetETicketExtendedKek(); + + std::vector<u8> extended_iv(0x10); + std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size()); + std::array<u8, 0x230> extended_dec{}; + AESCipher<Key128> rsa_1(eticket_final, Mode::CTR); + rsa_1.SetIV(extended_iv); + rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, + extended_dec.data(), Op::Decrypt); + + RSAKeyPair<2048> rsa_key{}; + std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); + std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); + std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); + + const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + "/system/save/80000000000000e1", + "rb+"); + const FileUtil::IOFile save2(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + "/system/save/80000000000000e2", + "rb+"); + + const auto blob2 = GetTicketblob(save2); + auto res = GetTicketblob(save1); + res.insert(res.end(), blob2.begin(), blob2.end()); + + for (const auto& raw : res) { + const auto pair = ParseTicket(raw, rsa_key); + if (pair == boost::none) + continue; + const auto& [rid, key] = pair.value(); + u128 rights_id; + std::memcpy(rights_id.data(), rid.data(), rid.size()); + SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); + } +} + +void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { + if (key == Key128{}) + return; + SetKey(id, key, field1, field2); +} + +void KeyManager::SetKeyWrapped(S256KeyType id, Key256 key, u64 field1, u64 field2) { + if (key == Key256{}) + return; + SetKey(id, key, field1, field2); +} + +void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { + if (!BaseDeriveNecessary()) + return; + + if (!data.HasBoot0()) + return; + + for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) { + if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{}) + continue; + encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i); + WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i), + encrypted_keyblobs[i]); + } + + SetKeyWrapped(S128KeyType::Source, data.GetPackage2KeySource(), + static_cast<u64>(SourceKeyType::Package2)); + SetKeyWrapped(S128KeyType::Source, data.GetAESKekGenerationSource(), + static_cast<u64>(SourceKeyType::AESKekGeneration)); + SetKeyWrapped(S128KeyType::Source, data.GetTitlekekSource(), + static_cast<u64>(SourceKeyType::Titlekek)); + SetKeyWrapped(S128KeyType::Source, data.GetMasterKeySource(), + static_cast<u64>(SourceKeyType::Master)); + SetKeyWrapped(S128KeyType::Source, data.GetKeyblobMACKeySource(), + static_cast<u64>(SourceKeyType::KeyblobMAC)); + + for (size_t i = 0; i < PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH; ++i) { + SetKeyWrapped(S128KeyType::Source, data.GetKeyblobKeySource(i), + static_cast<u64>(SourceKeyType::Keyblob), i); + } + + if (data.HasFuses()) + SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey()); + + DeriveBase(); + + Key128 latest_master{}; + for (s8 i = 0x1F; i >= 0; --i) { + if (GetKey(S128KeyType::Master, static_cast<u8>(i)) != Key128{}) { + latest_master = GetKey(S128KeyType::Master, static_cast<u8>(i)); + break; + } + } + + const auto masters = data.GetTZMasterKeys(latest_master); + for (size_t i = 0; i < masters.size(); ++i) { + if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i)) + SetKey(S128KeyType::Master, masters[i], i); + } + + DeriveBase(); + + if (!data.HasPackage2()) + return; + + std::array<Key128, 0x20> package2_keys{}; + for (size_t i = 0; i < package2_keys.size(); ++i) { + if (HasKey(S128KeyType::Package2, i)) + package2_keys[i] = GetKey(S128KeyType::Package2, i); + } + data.DecryptPackage2(package2_keys, Package2Type::NormalMain); + + SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyApplicationSource(), + static_cast<u64>(SourceKeyType::KeyAreaKey), + static_cast<u64>(KeyAreaKeyType::Application)); + SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyOceanSource(), + static_cast<u64>(SourceKeyType::KeyAreaKey), + static_cast<u64>(KeyAreaKeyType::Ocean)); + SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeySystemSource(), + static_cast<u64>(SourceKeyType::KeyAreaKey), + static_cast<u64>(KeyAreaKeyType::System)); + SetKeyWrapped(S128KeyType::Source, data.GetSDKekSource(), + static_cast<u64>(SourceKeyType::SDKek)); + SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDSaveKeySource(), + static_cast<u64>(SDKeyType::Save)); + SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDNCAKeySource(), + static_cast<u64>(SDKeyType::NCA)); + SetKeyWrapped(S128KeyType::Source, data.GetHeaderKekSource(), + static_cast<u64>(SourceKeyType::HeaderKek)); + SetKeyWrapped(S256KeyType::HeaderSource, data.GetHeaderKeySource()); + SetKeyWrapped(S128KeyType::Source, data.GetAESKeyGenerationSource(), + static_cast<u64>(SourceKeyType::AESKeyGeneration)); + + DeriveBase(); +} + 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}}, - {"master_key_03", {S128KeyType::Master, 3, 0}}, - {"master_key_04", {S128KeyType::Master, 4, 0}}, - {"package1_key_00", {S128KeyType::Package1, 0, 0}}, - {"package1_key_01", {S128KeyType::Package1, 1, 0}}, - {"package1_key_02", {S128KeyType::Package1, 2, 0}}, - {"package1_key_03", {S128KeyType::Package1, 3, 0}}, - {"package1_key_04", {S128KeyType::Package1, 4, 0}}, - {"package2_key_00", {S128KeyType::Package2, 0, 0}}, - {"package2_key_01", {S128KeyType::Package2, 1, 0}}, - {"package2_key_02", {S128KeyType::Package2, 2, 0}}, - {"package2_key_03", {S128KeyType::Package2, 3, 0}}, - {"package2_key_04", {S128KeyType::Package2, 4, 0}}, - {"titlekek_00", {S128KeyType::Titlekek, 0, 0}}, - {"titlekek_01", {S128KeyType::Titlekek, 1, 0}}, - {"titlekek_02", {S128KeyType::Titlekek, 2, 0}}, - {"titlekek_03", {S128KeyType::Titlekek, 3, 0}}, - {"titlekek_04", {S128KeyType::Titlekek, 4, 0}}, {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, - {"key_area_key_application_00", - {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Application)}}, - {"key_area_key_application_01", - {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Application)}}, - {"key_area_key_application_02", - {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Application)}}, - {"key_area_key_application_03", - {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Application)}}, - {"key_area_key_application_04", - {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Application)}}, - {"key_area_key_ocean_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}}, - {"key_area_key_ocean_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}}, - {"key_area_key_ocean_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}}, - {"key_area_key_ocean_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}}, - {"key_area_key_ocean_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}}, - {"key_area_key_system_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::System)}}, - {"key_area_key_system_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::System)}}, - {"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}}, - {"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}}, - {"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}}, - {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK), 0}}, + {"eticket_rsa_kek_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}}, + {"eticket_rsa_kekek_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}}, + {"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}}, + {"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}}, + {"rsa_oaep_kek_generation_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}}, + {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}}, {"aes_kek_generation_source", - {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration), 0}}, + {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}}, {"aes_key_generation_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}}, + {"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}}, + {"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}}, + {"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}}, + {"key_area_key_application_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), + static_cast<u64>(KeyAreaKeyType::Application)}}, + {"key_area_key_ocean_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), + static_cast<u64>(KeyAreaKeyType::Ocean)}}, + {"key_area_key_system_source", + {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey), + static_cast<u64>(KeyAreaKeyType::System)}}, + {"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}}, + {"keyblob_mac_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)}}, + {"tsec_key", {S128KeyType::TSEC, 0, 0}}, + {"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}}, {"sd_seed", {S128KeyType::SDSeed, 0, 0}}, + {"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}}, + {"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}}, + {"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}}, + {"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}}, + {"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}}, + {"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}}, + {"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}}, + {"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}}, + {"header_kek", {S128KeyType::HeaderKek, 0, 0}}, + {"sd_card_kek", {S128KeyType::SDKek, 0, 0}}, }; const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = { {"header_key", {S256KeyType::Header, 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}}, + {"header_key_source", {S256KeyType::HeaderSource, 0, 0}}, + {"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}}, + {"sd_card_nca_key", {S256KeyType::SDKey, 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 978eec8dc..cccb3c0ae 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -5,11 +5,18 @@ #pragma once #include <array> +#include <map> #include <string> #include <boost/container/flat_map.hpp> #include <boost/optional.hpp> #include <fmt/format.h> #include "common/common_types.h" +#include "core/crypto/partition_data_manager.h" +#include "core/file_sys/vfs_types.h" + +namespace FileUtil { +class IOFile; +} namespace Loader { enum class ResultStatus : u16; @@ -22,13 +29,30 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180; using Key128 = std::array<u8, 0x10>; using Key256 = std::array<u8, 0x20>; using SHA256Hash = std::array<u8, 0x20>; +using TicketRaw = std::array<u8, 0x400>; static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); -static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big."); +static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big."); + +template <size_t bit_size, size_t byte_size = (bit_size >> 3)> +struct RSAKeyPair { + std::array<u8, byte_size> encryption_key; + std::array<u8, byte_size> decryption_key; + std::array<u8, byte_size> modulus; + std::array<u8, 4> exponent; +}; + +enum class KeyCategory : u8 { + Standard, + Title, + Console, +}; enum class S256KeyType : u64 { - Header, // - SDKeySource, // f1=SDKeyType + SDKey, // f1=SDKeyType + Header, // + SDKeySource, // f1=SDKeyType + HeaderSource, // }; enum class S128KeyType : u64 { @@ -41,6 +65,14 @@ enum class S128KeyType : u64 { SDSeed, // Titlekey, // f1=rights id LSB f2=rights id MSB Source, // f1=source type, f2= sub id + Keyblob, // f1=crypto revision + KeyblobMAC, // f1=crypto revision + TSEC, // + SecureBoot, // + BIS, // f1=partition (0-3), f2=type {crypt, tweak} + HeaderKek, // + SDKek, // + RSAKek, // }; enum class KeyAreaKeyType : u8 { @@ -50,9 +82,19 @@ enum class KeyAreaKeyType : u8 { }; enum class SourceKeyType : u8 { - SDKEK, - AESKEKGeneration, - AESKeyGeneration, + SDKek, // + AESKekGeneration, // + AESKeyGeneration, // + RSAOaepKekGeneration, // + Master, // + Keyblob, // f2=crypto revision + KeyAreaKey, // f2=KeyAreaKeyType + Titlekek, // + Package2, // + HeaderKek, // + KeyblobMAC, // + ETicketKek, // + ETicketKekek, // }; enum class SDKeyType : u8 { @@ -60,6 +102,16 @@ enum class SDKeyType : u8 { NCA, }; +enum class BISKeyType : u8 { + Crypto, + Tweak, +}; + +enum class RSAKekType : u8 { + Mask0, + Seed3, +}; + template <typename KeyType> struct KeyIndex { KeyType type; @@ -91,6 +143,8 @@ public: Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const; Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const; + Key256 GetBISKey(u8 partition_id) const; + void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); @@ -100,23 +154,51 @@ public: // 8*43 and the private file to exist. void DeriveSDSeedLazy(); + bool BaseDeriveNecessary() const; + void DeriveBase(); + void DeriveETicket(PartitionDataManager& data); + + void PopulateFromPartitionData(PartitionDataManager& data); + private: - boost::container::flat_map<KeyIndex<S128KeyType>, Key128> s128_keys; - boost::container::flat_map<KeyIndex<S256KeyType>, Key256> s256_keys; + std::map<KeyIndex<S128KeyType>, Key128> s128_keys; + std::map<KeyIndex<S256KeyType>, Key256> s256_keys; + + std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; + std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; 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 <std::size_t Size> - void WriteKeyToFile(bool title_key, std::string_view keyname, const std::array<u8, Size>& key); + template <size_t Size> + void WriteKeyToFile(KeyCategory category, std::string_view keyname, + const std::array<u8, Size>& key); + + void DeriveGeneralPurposeKeys(std::size_t crypto_revision); + + void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); + void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); 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); +Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source); +Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source); +Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master_source); +std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblob, + const Key128& key); + boost::optional<Key128> DeriveSDSeed(); -Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys); +Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys); + +std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save); + +// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset +// 0x140-0x144 is zero) +boost::optional<std::pair<Key128, Key128>> ParseTicket( + const TicketRaw& ticket, const RSAKeyPair<2048>& eticket_extended_key); } // namespace Core::Crypto diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp new file mode 100644 index 000000000..25cee1f3a --- /dev/null +++ b/src/core/crypto/partition_data_manager.cpp @@ -0,0 +1,593 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// NOTE TO FUTURE MAINTAINERS: +// When a new version of switch cryptography is released, +// hash the new keyblob source and master key and add the hashes to +// the arrays below. + +#include <algorithm> +#include <array> +#include <cctype> +#include <cstring> +#include <mbedtls/sha256.h> +#include "common/assert.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/hex_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "common/swap.h" +#include "core/crypto/key_manager.h" +#include "core/crypto/partition_data_manager.h" +#include "core/crypto/xts_encryption_layer.h" +#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_offset.h" + +using namespace Common; + +namespace Core::Crypto { + +struct Package2Header { + std::array<u8, 0x100> signature; + Key128 header_ctr; + std::array<Key128, 4> section_ctr; + u32_le magic; + u32_le base_offset; + INSERT_PADDING_BYTES(4); + u8 version_max; + u8 version_min; + INSERT_PADDING_BYTES(2); + std::array<u32_le, 4> section_size; + std::array<u32_le, 4> section_offset; + std::array<SHA256Hash, 4> section_hash; +}; +static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size."); + +struct INIHeader { + u32_le magic; + u32_le size; + u32_le process_count; + INSERT_PADDING_BYTES(4); +}; +static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size."); + +struct SectionHeader { + u32_le offset; + u32_le size_decompressed; + u32_le size_compressed; + u32_le attribute; +}; +static_assert(sizeof(SectionHeader) == 0x10, "SectionHeader has incorrect size."); + +struct KIPHeader { + u32_le magic; + std::array<char, 12> name; + u64_le title_id; + u32_le category; + u8 priority; + u8 core; + INSERT_PADDING_BYTES(1); + u8 flags; + std::array<SectionHeader, 6> sections; + std::array<u32, 0x20> capabilities; +}; +static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size."); + +const std::array<SHA256Hash, 0x10> source_hashes{ + "B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source + "7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source + "21E2DF100FC9E094DB51B47B9B1D6E94ED379DB8B547955BEF8FE08D8DD35603"_array32, // package2_key_source + "FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // aes_kek_generation_source + "FBD10056999EDC7ACDB96098E47E2C3606230270D23281E671F0F389FC5BC585"_array32, // aes_key_generation_source + "C48B619827986C7F4E3081D59DB2B460C84312650E9A8E6B458E53E8CBCA4E87"_array32, // titlekek_source + "04AD66143C726B2A139FB6B21128B46F56C553B2B3887110304298D8D0092D9E"_array32, // key_area_key_application_source + "FD434000C8FF2B26F8E9A9D2D2C12F6BE5773CBB9DC86300E1BD99F8EA33A417"_array32, // key_area_key_ocean_source + "1F17B1FD51AD1C2379B58F152CA4912EC2106441E51722F38700D5937A1162F7"_array32, // key_area_key_system_source + "6B2ED877C2C52334AC51E59ABFA7EC457F4A7D01E46291E9F2EAA45F011D24B7"_array32, // sd_card_kek_source + "D482743563D3EA5DCDC3B74E97C9AC8A342164FA041A1DC80F17F6D31E4BC01C"_array32, // sd_card_save_key_source + "2E751CECF7D93A2B957BD5FFCB082FD038CC2853219DD3092C6DAB9838F5A7CC"_array32, // sd_card_nca_key_source + "1888CAED5551B3EDE01499E87CE0D86827F80820EFB275921055AA4E2ABDFFC2"_array32, // header_kek_source + "8F783E46852DF6BE0BA4E19273C4ADBAEE16380043E1B8C418C4089A8BD64AA6"_array32, // header_key_source + "D1757E52F1AE55FA882EC690BC6F954AC46A83DC22F277F8806BD55577C6EED7"_array32, // rsa_kek_seed3 + "FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // rsa_kek_mask0 +}; + +const std::array<SHA256Hash, 0x20> keyblob_source_hashes{ + "8A06FE274AC491436791FDB388BCDD3AB9943BD4DEF8094418CDAC150FD73786"_array32, // keyblob_key_source_00 + "2D5CAEB2521FEF70B47E17D6D0F11F8CE2C1E442A979AD8035832C4E9FBCCC4B"_array32, // keyblob_key_source_01 + "61C5005E713BAE780641683AF43E5F5C0E03671117F702F401282847D2FC6064"_array32, // keyblob_key_source_02 + "8E9795928E1C4428E1B78F0BE724D7294D6934689C11B190943923B9D5B85903"_array32, // keyblob_key_source_03 + "95FA33AF95AFF9D9B61D164655B32710ED8D615D46C7D6CC3CC70481B686B402"_array32, // keyblob_key_source_04 + "3F5BE7B3C8B1ABD8C10B4B703D44766BA08730562C172A4FE0D6B866B3E2DB3E"_array32, // keyblob_key_source_05 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_06 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_07 + + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_08 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_09 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0A + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0B + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0C + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0D + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0E + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0F + + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_10 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_11 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_12 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_13 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_14 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_15 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_16 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_17 + + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_18 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_19 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1A + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1B + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1C + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1D + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1E + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1F +}; + +const std::array<SHA256Hash, 0x20> master_key_hashes{ + "0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32, // master_key_00 + "4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32, // master_key_01 + "79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32, // master_key_02 + "4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32, // master_key_03 + "75FF1D95D26113550EE6FCC20ACB58E97EDEB3A2FF52543ED5AEC63BDCC3DA50"_array32, // master_key_04 + "EBE2BCD6704673EC0F88A187BB2AD9F1CC82B718C389425941BDC194DC46B0DD"_array32, // master_key_05 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_06 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_07 + + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_08 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_09 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0A + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0B + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0C + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0D + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0E + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0F + + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_10 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_11 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_12 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_13 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_14 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_15 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_16 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_17 + + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_18 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_19 + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1A + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1B + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1C + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1D + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1E + "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F +}; + +static std::vector<u8> DecompressBLZ(const std::vector<u8>& in) { + const auto data_size = in.size() - 0xC; + + u32 compressed_size{}; + u32 init_index{}; + u32 additional_size{}; + std::memcpy(&compressed_size, in.data() + data_size, sizeof(u32)); + std::memcpy(&init_index, in.data() + data_size + 0x4, sizeof(u32)); + std::memcpy(&additional_size, in.data() + data_size + 0x8, sizeof(u32)); + + std::vector<u8> out(in.size() + additional_size); + + if (compressed_size == in.size()) + std::memcpy(out.data(), in.data() + in.size() - compressed_size, compressed_size); + else + std::memcpy(out.data(), in.data(), compressed_size); + + auto index = in.size() - init_index; + auto out_index = out.size(); + + while (out_index > 0) { + --index; + auto control = in[index]; + for (size_t i = 0; i < 8; ++i) { + if ((control & 0x80) > 0) { + ASSERT(index >= 2); + index -= 2; + u64 segment_offset = in[index] | in[index + 1] << 8; + u64 segment_size = ((segment_offset >> 12) & 0xF) + 3; + segment_offset &= 0xFFF; + segment_offset += 3; + + if (out_index < segment_size) + segment_size = out_index; + + ASSERT(out_index >= segment_size); + + out_index -= segment_size; + + for (size_t j = 0; j < segment_size; ++j) { + ASSERT(out_index + j + segment_offset < out.size()); + out[out_index + j] = out[out_index + j + segment_offset]; + } + } else { + ASSERT(out_index >= 1); + --out_index; + --index; + out[out_index] = in[index]; + } + + control <<= 1; + if (out_index == 0) + return out; + } + } + + return out; +} + +static u8 CalculateMaxKeyblobSourceHash() { + for (s8 i = 0x1F; i >= 0; --i) { + if (keyblob_source_hashes[i] != SHA256Hash{}) + return static_cast<u8>(i + 1); + } + + return 0; +} + +const u8 PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH = CalculateMaxKeyblobSourceHash(); + +template <size_t key_size = 0x10> +std::array<u8, key_size> FindKeyFromHex(const std::vector<u8>& binary, + const std::array<u8, 0x20>& hash) { + if (binary.size() < key_size) + return {}; + + std::array<u8, 0x20> temp{}; + for (size_t i = 0; i < binary.size() - key_size; ++i) { + mbedtls_sha256(binary.data() + i, key_size, temp.data(), 0); + + if (temp != hash) + continue; + + std::array<u8, key_size> out{}; + std::memcpy(out.data(), binary.data() + i, key_size); + return out; + } + + return {}; +} + +std::array<u8, 16> FindKeyFromHex16(const std::vector<u8>& binary, std::array<u8, 32> hash) { + return FindKeyFromHex<0x10>(binary, hash); +} + +static std::array<Key128, 0x20> FindEncryptedMasterKeyFromHex(const std::vector<u8>& binary, + const Key128& key) { + if (binary.size() < 0x10) + return {}; + + SHA256Hash temp{}; + Key128 dec_temp{}; + std::array<Key128, 0x20> out{}; + AESCipher<Key128> cipher(key, Mode::ECB); + for (size_t i = 0; i < binary.size() - 0x10; ++i) { + cipher.Transcode(binary.data() + i, dec_temp.size(), dec_temp.data(), Op::Decrypt); + mbedtls_sha256(dec_temp.data(), dec_temp.size(), temp.data(), 0); + + for (size_t k = 0; k < out.size(); ++k) { + if (temp == master_key_hashes[k]) { + out[k] = dec_temp; + break; + } + } + } + + return out; +} + +FileSys::VirtualFile FindFileInDirWithNames(const FileSys::VirtualDir& dir, + const std::string& name) { + auto upper = name; + std::transform(upper.begin(), upper.end(), upper.begin(), [](u8 c) { return std::toupper(c); }); + for (const auto& fname : {name, name + ".bin", upper, upper + ".BIN"}) { + if (dir->GetFile(fname) != nullptr) + return dir->GetFile(fname); + } + + return nullptr; +} + +PartitionDataManager::PartitionDataManager(const FileSys::VirtualDir& sysdata_dir) + : boot0(FindFileInDirWithNames(sysdata_dir, "BOOT0")), + fuses(FindFileInDirWithNames(sysdata_dir, "fuses")), + kfuses(FindFileInDirWithNames(sysdata_dir, "kfuses")), + package2({ + FindFileInDirWithNames(sysdata_dir, "BCPKG2-1-Normal-Main"), + FindFileInDirWithNames(sysdata_dir, "BCPKG2-2-Normal-Sub"), + FindFileInDirWithNames(sysdata_dir, "BCPKG2-3-SafeMode-Main"), + FindFileInDirWithNames(sysdata_dir, "BCPKG2-4-SafeMode-Sub"), + FindFileInDirWithNames(sysdata_dir, "BCPKG2-5-Repair-Main"), + FindFileInDirWithNames(sysdata_dir, "BCPKG2-6-Repair-Sub"), + }), + prodinfo(FindFileInDirWithNames(sysdata_dir, "PRODINFO")), + secure_monitor(FindFileInDirWithNames(sysdata_dir, "secmon")), + package1_decrypted(FindFileInDirWithNames(sysdata_dir, "pkg1_decr")), + secure_monitor_bytes(secure_monitor == nullptr ? std::vector<u8>{} + : secure_monitor->ReadAllBytes()), + package1_decrypted_bytes(package1_decrypted == nullptr ? std::vector<u8>{} + : package1_decrypted->ReadAllBytes()) { +} + +PartitionDataManager::~PartitionDataManager() = default; + +bool PartitionDataManager::HasBoot0() const { + return boot0 != nullptr; +} + +FileSys::VirtualFile PartitionDataManager::GetBoot0Raw() const { + return boot0; +} + +PartitionDataManager::EncryptedKeyBlob PartitionDataManager::GetEncryptedKeyblob( + std::size_t index) const { + if (HasBoot0() && index < NUM_ENCRYPTED_KEYBLOBS) + return GetEncryptedKeyblobs()[index]; + return {}; +} + +PartitionDataManager::EncryptedKeyBlobs PartitionDataManager::GetEncryptedKeyblobs() const { + if (!HasBoot0()) + return {}; + + EncryptedKeyBlobs out{}; + for (size_t i = 0; i < out.size(); ++i) + boot0->Read(out[i].data(), out[i].size(), 0x180000 + i * 0x200); + return out; +} + +std::vector<u8> PartitionDataManager::GetSecureMonitor() const { + return secure_monitor_bytes; +} + +std::array<u8, 16> PartitionDataManager::GetPackage2KeySource() const { + return FindKeyFromHex(secure_monitor_bytes, source_hashes[2]); +} + +std::array<u8, 16> PartitionDataManager::GetAESKekGenerationSource() const { + return FindKeyFromHex(secure_monitor_bytes, source_hashes[3]); +} + +std::array<u8, 16> PartitionDataManager::GetTitlekekSource() const { + return FindKeyFromHex(secure_monitor_bytes, source_hashes[5]); +} + +std::array<std::array<u8, 16>, 32> PartitionDataManager::GetTZMasterKeys( + std::array<u8, 0x10> master_key) const { + return FindEncryptedMasterKeyFromHex(secure_monitor_bytes, master_key); +} + +std::array<u8, 16> PartitionDataManager::GetRSAKekSeed3() const { + return FindKeyFromHex(secure_monitor_bytes, source_hashes[14]); +} + +std::array<u8, 16> PartitionDataManager::GetRSAKekMask0() const { + return FindKeyFromHex(secure_monitor_bytes, source_hashes[15]); +} + +std::vector<u8> PartitionDataManager::GetPackage1Decrypted() const { + return package1_decrypted_bytes; +} + +std::array<u8, 16> PartitionDataManager::GetMasterKeySource() const { + return FindKeyFromHex(package1_decrypted_bytes, source_hashes[1]); +} + +std::array<u8, 16> PartitionDataManager::GetKeyblobMACKeySource() const { + return FindKeyFromHex(package1_decrypted_bytes, source_hashes[0]); +} + +std::array<u8, 16> PartitionDataManager::GetKeyblobKeySource(std::size_t revision) const { + if (keyblob_source_hashes[revision] == SHA256Hash{}) { + LOG_WARNING(Crypto, + "No keyblob source hash for crypto revision {:02X}! Cannot derive keys...", + revision); + } + return FindKeyFromHex(package1_decrypted_bytes, keyblob_source_hashes[revision]); +} + +bool PartitionDataManager::HasFuses() const { + return fuses != nullptr; +} + +FileSys::VirtualFile PartitionDataManager::GetFusesRaw() const { + return fuses; +} + +std::array<u8, 16> PartitionDataManager::GetSecureBootKey() const { + if (!HasFuses()) + return {}; + Key128 out{}; + fuses->Read(out.data(), out.size(), 0xA4); + return out; +} + +bool PartitionDataManager::HasKFuses() const { + return kfuses != nullptr; +} + +FileSys::VirtualFile PartitionDataManager::GetKFusesRaw() const { + return kfuses; +} + +bool PartitionDataManager::HasPackage2(Package2Type type) const { + return package2.at(static_cast<size_t>(type)) != nullptr; +} + +FileSys::VirtualFile PartitionDataManager::GetPackage2Raw(Package2Type type) const { + return package2.at(static_cast<size_t>(type)); +} + +bool AttemptDecrypt(const std::array<u8, 16>& key, Package2Header& header) { + + const std::vector<u8> iv(header.header_ctr.begin(), header.header_ctr.end()); + Package2Header temp = header; + AESCipher<Key128> cipher(key, Mode::CTR); + cipher.SetIV(iv); + cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - 0x100, &temp.header_ctr, + Op::Decrypt); + if (temp.magic == Common::MakeMagic('P', 'K', '2', '1')) { + header = temp; + return true; + } + + return false; +} + +void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& package2_keys, + Package2Type type) { + FileSys::VirtualFile file = std::make_shared<FileSys::OffsetVfsFile>( + package2[static_cast<size_t>(type)], + package2[static_cast<size_t>(type)]->GetSize() - 0x4000, 0x4000); + + Package2Header header{}; + if (file->ReadObject(&header) != sizeof(Package2Header)) + return; + + std::size_t revision = 0xFF; + if (header.magic != Common::MakeMagic('P', 'K', '2', '1')) { + for (std::size_t i = 0; i < package2_keys.size(); ++i) { + if (AttemptDecrypt(package2_keys[i], header)) { + revision = i; + } + } + } + + if (header.magic != Common::MakeMagic('P', 'K', '2', '1')) + return; + + const auto a = std::make_shared<FileSys::OffsetVfsFile>( + file, header.section_size[1], header.section_size[0] + sizeof(Package2Header)); + + auto c = a->ReadAllBytes(); + + AESCipher<Key128> cipher(package2_keys[revision], Mode::CTR); + cipher.SetIV({header.section_ctr[1].begin(), header.section_ctr[1].end()}); + cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt); + + INIHeader ini; + std::memcpy(&ini, c.data(), sizeof(INIHeader)); + if (ini.magic != Common::MakeMagic('I', 'N', 'I', '1')) + return; + + u64 offset = sizeof(INIHeader); + for (size_t i = 0; i < ini.process_count; ++i) { + KIPHeader kip; + std::memcpy(&kip, c.data() + offset, sizeof(KIPHeader)); + if (kip.magic != Common::MakeMagic('K', 'I', 'P', '1')) + return; + + const auto name = + Common::StringFromFixedZeroTerminatedBuffer(kip.name.data(), kip.name.size()); + + if (name != "FS" && name != "spl") { + offset += sizeof(KIPHeader) + kip.sections[0].size_compressed + + kip.sections[1].size_compressed + kip.sections[2].size_compressed; + continue; + } + + const u64 initial_offset = sizeof(KIPHeader) + offset; + const auto text_begin = c.cbegin() + initial_offset; + const auto text_end = text_begin + kip.sections[0].size_compressed; + const std::vector<u8> text = DecompressBLZ({text_begin, text_end}); + + const auto rodata_end = text_end + kip.sections[1].size_compressed; + const std::vector<u8> rodata = DecompressBLZ({text_end, rodata_end}); + + const auto data_end = rodata_end + kip.sections[2].size_compressed; + const std::vector<u8> data = DecompressBLZ({rodata_end, data_end}); + + std::vector<u8> out; + out.reserve(text.size() + rodata.size() + data.size()); + out.insert(out.end(), text.begin(), text.end()); + out.insert(out.end(), rodata.begin(), rodata.end()); + out.insert(out.end(), data.begin(), data.end()); + + offset += sizeof(KIPHeader) + out.size(); + + if (name == "FS") + package2_fs[static_cast<size_t>(type)] = std::move(out); + else if (name == "spl") + package2_spl[static_cast<size_t>(type)] = std::move(out); + } +} + +const std::vector<u8>& PartitionDataManager::GetPackage2FSDecompressed(Package2Type type) const { + return package2_fs.at(static_cast<size_t>(type)); +} + +std::array<u8, 16> PartitionDataManager::GetKeyAreaKeyApplicationSource(Package2Type type) const { + return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[6]); +} + +std::array<u8, 16> PartitionDataManager::GetKeyAreaKeyOceanSource(Package2Type type) const { + return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[7]); +} + +std::array<u8, 16> PartitionDataManager::GetKeyAreaKeySystemSource(Package2Type type) const { + return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[8]); +} + +std::array<u8, 16> PartitionDataManager::GetSDKekSource(Package2Type type) const { + return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[9]); +} + +std::array<u8, 32> PartitionDataManager::GetSDSaveKeySource(Package2Type type) const { + return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[10]); +} + +std::array<u8, 32> PartitionDataManager::GetSDNCAKeySource(Package2Type type) const { + return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[11]); +} + +std::array<u8, 16> PartitionDataManager::GetHeaderKekSource(Package2Type type) const { + return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[12]); +} + +std::array<u8, 32> PartitionDataManager::GetHeaderKeySource(Package2Type type) const { + return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[13]); +} + +const std::vector<u8>& PartitionDataManager::GetPackage2SPLDecompressed(Package2Type type) const { + return package2_spl.at(static_cast<size_t>(type)); +} + +std::array<u8, 16> PartitionDataManager::GetAESKeyGenerationSource(Package2Type type) const { + return FindKeyFromHex(package2_spl.at(static_cast<size_t>(type)), source_hashes[4]); +} + +bool PartitionDataManager::HasProdInfo() const { + return prodinfo != nullptr; +} + +FileSys::VirtualFile PartitionDataManager::GetProdInfoRaw() const { + return prodinfo; +} + +void PartitionDataManager::DecryptProdInfo(std::array<u8, 0x20> bis_key) { + if (prodinfo == nullptr) + return; + + prodinfo_decrypted = std::make_shared<XTSEncryptionLayer>(prodinfo, bis_key); +} + +std::array<u8, 576> PartitionDataManager::GetETicketExtendedKek() const { + std::array<u8, 0x240> out{}; + if (prodinfo_decrypted != nullptr) + prodinfo_decrypted->Read(out.data(), out.size(), 0x3890); + return out; +} +} // namespace Core::Crypto diff --git a/src/core/crypto/partition_data_manager.h b/src/core/crypto/partition_data_manager.h new file mode 100644 index 000000000..0ad007c72 --- /dev/null +++ b/src/core/crypto/partition_data_manager.h @@ -0,0 +1,109 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <vector> +#include "common/common_types.h" +#include "core/file_sys/vfs_types.h" + +namespace Core::Crypto { + +enum class Package2Type { + NormalMain, + NormalSub, + SafeModeMain, + SafeModeSub, + RepairMain, + RepairSub, +}; + +class PartitionDataManager { +public: + static const u8 MAX_KEYBLOB_SOURCE_HASH; + static constexpr std::size_t NUM_ENCRYPTED_KEYBLOBS = 32; + static constexpr std::size_t ENCRYPTED_KEYBLOB_SIZE = 0xB0; + + using EncryptedKeyBlob = std::array<u8, ENCRYPTED_KEYBLOB_SIZE>; + using EncryptedKeyBlobs = std::array<EncryptedKeyBlob, NUM_ENCRYPTED_KEYBLOBS>; + + explicit PartitionDataManager(const FileSys::VirtualDir& sysdata_dir); + ~PartitionDataManager(); + + // BOOT0 + bool HasBoot0() const; + FileSys::VirtualFile GetBoot0Raw() const; + EncryptedKeyBlob GetEncryptedKeyblob(std::size_t index) const; + EncryptedKeyBlobs GetEncryptedKeyblobs() const; + std::vector<u8> GetSecureMonitor() const; + std::array<u8, 0x10> GetPackage2KeySource() const; + std::array<u8, 0x10> GetAESKekGenerationSource() const; + std::array<u8, 0x10> GetTitlekekSource() const; + std::array<std::array<u8, 0x10>, 0x20> GetTZMasterKeys(std::array<u8, 0x10> master_key) const; + std::array<u8, 0x10> GetRSAKekSeed3() const; + std::array<u8, 0x10> GetRSAKekMask0() const; + std::vector<u8> GetPackage1Decrypted() const; + std::array<u8, 0x10> GetMasterKeySource() const; + std::array<u8, 0x10> GetKeyblobMACKeySource() const; + std::array<u8, 0x10> GetKeyblobKeySource(std::size_t revision) const; + + // Fuses + bool HasFuses() const; + FileSys::VirtualFile GetFusesRaw() const; + std::array<u8, 0x10> GetSecureBootKey() const; + + // K-Fuses + bool HasKFuses() const; + FileSys::VirtualFile GetKFusesRaw() const; + + // Package2 + bool HasPackage2(Package2Type type = Package2Type::NormalMain) const; + FileSys::VirtualFile GetPackage2Raw(Package2Type type = Package2Type::NormalMain) const; + void DecryptPackage2(const std::array<std::array<u8, 16>, 0x20>& package2_keys, + Package2Type type); + const std::vector<u8>& GetPackage2FSDecompressed( + Package2Type type = Package2Type::NormalMain) const; + std::array<u8, 0x10> GetKeyAreaKeyApplicationSource( + Package2Type type = Package2Type::NormalMain) const; + std::array<u8, 0x10> GetKeyAreaKeyOceanSource( + Package2Type type = Package2Type::NormalMain) const; + std::array<u8, 0x10> GetKeyAreaKeySystemSource( + Package2Type type = Package2Type::NormalMain) const; + std::array<u8, 0x10> GetSDKekSource(Package2Type type = Package2Type::NormalMain) const; + std::array<u8, 0x20> GetSDSaveKeySource(Package2Type type = Package2Type::NormalMain) const; + std::array<u8, 0x20> GetSDNCAKeySource(Package2Type type = Package2Type::NormalMain) const; + std::array<u8, 0x10> GetHeaderKekSource(Package2Type type = Package2Type::NormalMain) const; + std::array<u8, 0x20> GetHeaderKeySource(Package2Type type = Package2Type::NormalMain) const; + const std::vector<u8>& GetPackage2SPLDecompressed( + Package2Type type = Package2Type::NormalMain) const; + std::array<u8, 0x10> GetAESKeyGenerationSource( + Package2Type type = Package2Type::NormalMain) const; + + // PRODINFO + bool HasProdInfo() const; + FileSys::VirtualFile GetProdInfoRaw() const; + void DecryptProdInfo(std::array<u8, 0x20> bis_key); + std::array<u8, 0x240> GetETicketExtendedKek() const; + +private: + FileSys::VirtualFile boot0; + FileSys::VirtualFile fuses; + FileSys::VirtualFile kfuses; + std::array<FileSys::VirtualFile, 6> package2; + FileSys::VirtualFile prodinfo; + FileSys::VirtualFile secure_monitor; + FileSys::VirtualFile package1_decrypted; + + // Processed + std::array<FileSys::VirtualFile, 6> package2_decrypted; + FileSys::VirtualFile prodinfo_decrypted; + std::vector<u8> secure_monitor_bytes; + std::vector<u8> package1_decrypted_bytes; + std::array<std::vector<u8>, 6> package2_fs; + std::array<std::vector<u8>, 6> package2_spl; +}; + +std::array<u8, 0x10> FindKeyFromHex16(const std::vector<u8>& binary, std::array<u8, 0x20> hash); + +} // namespace Core::Crypto diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index 6102ef476..76a2b7e86 100644 --- a/src/core/file_sys/bis_factory.cpp +++ b/src/core/file_sys/bis_factory.cpp @@ -10,19 +10,19 @@ namespace FileSys { BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_) : nand_root(std::move(nand_root_)), load_root(std::move(load_root_)), - sysnand_cache(std::make_shared<RegisteredCache>( + sysnand_cache(std::make_unique<RegisteredCache>( GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))), - usrnand_cache(std::make_shared<RegisteredCache>( + usrnand_cache(std::make_unique<RegisteredCache>( GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {} BISFactory::~BISFactory() = default; -std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const { - return sysnand_cache; +RegisteredCache* BISFactory::GetSystemNANDContents() const { + return sysnand_cache.get(); } -std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const { - return usrnand_cache; +RegisteredCache* BISFactory::GetUserNANDContents() const { + return usrnand_cache.get(); } VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const { diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h index c352e0925..364d309bd 100644 --- a/src/core/file_sys/bis_factory.h +++ b/src/core/file_sys/bis_factory.h @@ -20,8 +20,8 @@ public: explicit BISFactory(VirtualDir nand_root, VirtualDir load_root); ~BISFactory(); - std::shared_ptr<RegisteredCache> GetSystemNANDContents() const; - std::shared_ptr<RegisteredCache> GetUserNANDContents() const; + RegisteredCache* GetSystemNANDContents() const; + RegisteredCache* GetUserNANDContents() const; VirtualDir GetModificationLoadRoot(u64 title_id) const; @@ -29,8 +29,8 @@ private: VirtualDir nand_root; VirtualDir load_root; - std::shared_ptr<RegisteredCache> sysnand_cache; - std::shared_ptr<RegisteredCache> usrnand_cache; + std::unique_ptr<RegisteredCache> sysnand_cache; + std::unique_ptr<RegisteredCache> usrnand_cache; }; } // namespace FileSys diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index aa1b3c17d..6dcec7816 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -133,7 +133,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType ty static_cast<u8>(type)); u128 out_128{}; memcpy(out_128.data(), out.data(), 16); - LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", + LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", master_key_id, header.key_index, out_128[1], out_128[0]); return out; diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index 5b1177a03..a012c2be9 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -17,11 +17,13 @@ const std::array<const char*, 15> LANGUAGE_NAMES = { }; std::string LanguageEntry::GetApplicationName() const { - return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(), 0x200); + return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(), + application_name.size()); } std::string LanguageEntry::GetDeveloperName() const { - return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100); + return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), + developer_name.size()); } NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) { @@ -56,7 +58,12 @@ u64 NACP::GetTitleId() const { return raw->title_id; } +u64 NACP::GetDLCBaseTitleId() const { + return raw->dlc_base_title_id; +} + std::string NACP::GetVersionString() const { - return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(), 0x10); + return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(), + raw->version_string.size()); } } // namespace FileSys diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index 43d6f0719..141f7e056 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -79,6 +79,7 @@ public: std::string GetApplicationName(Language language = Language::Default) const; std::string GetDeveloperName(Language language = Language::Default) const; u64 GetTitleId() const; + u64 GetDLCBaseTitleId() const; std::string GetVersionString() const; private: diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index 6c072d0a3..554eae9bc 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp @@ -107,12 +107,12 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { return nullptr; if (real_offset + rle_size > in_data.size()) - rle_size = in_data.size() - real_offset; + rle_size = static_cast<u16>(in_data.size() - real_offset); std::memset(in_data.data() + real_offset, data.get(), rle_size); } else { // Standard Patch auto read = data_size; if (real_offset + read > in_data.size()) - read = in_data.size() - real_offset; + read = static_cast<u16>(in_data.size() - real_offset); if (ips->Read(in_data.data() + real_offset, read, offset) != data_size) return nullptr; offset += data_size; @@ -265,7 +265,7 @@ void IPSwitchCompiler::Parse() { if (patch_line.length() < 11) break; auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16); - offset += offset_shift; + offset += static_cast<unsigned long>(offset_shift); std::vector<u8> replace; // 9 - first char of replacement val diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index b14d7cb0a..0117cb0bf 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -214,8 +214,14 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, VirtualFile update_raw) const { - LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id, - static_cast<u8>(type)); + const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", + title_id, static_cast<u8>(type)) + .c_str(); + + if (type == ContentRecordType::Program) + LOG_INFO(Loader, log_string); + else + LOG_DEBUG(Loader, log_string); if (romfs == nullptr) return romfs; @@ -345,23 +351,22 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam return out; } -std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { - const auto& installed{Service::FileSystem::GetUnionContents()}; +std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { + const auto installed{Service::FileSystem::GetUnionContents()}; const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control); if (base_control_nca == nullptr) return {}; - return ParseControlNCA(base_control_nca); + return ParseControlNCA(*base_control_nca); } -std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA( - const std::shared_ptr<NCA>& nca) const { - const auto base_romfs = nca->GetRomFS(); +std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const { + const auto base_romfs = nca.GetRomFS(); if (base_romfs == nullptr) return {}; - const auto romfs = PatchRomFS(base_romfs, nca->GetBaseIVFCOffset(), ContentRecordType::Control); + const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control); if (romfs == nullptr) return {}; @@ -373,7 +378,7 @@ std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA( if (nacp_file == nullptr) nacp_file = extracted->GetFile("Control.nacp"); - const auto nacp = nacp_file == nullptr ? nullptr : std::make_shared<NACP>(nacp_file); + auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file); VirtualFile icon_file; for (const auto& language : FileSys::LANGUAGE_NAMES) { @@ -382,6 +387,6 @@ std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA( break; } - return {nacp, icon_file}; + return {std::move(nacp), icon_file}; } } // namespace FileSys diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index eb6fc4607..7d168837f 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -57,11 +57,10 @@ public: // Given title_id of the program, attempts to get the control data of the update and parse it, // falling back to the base control data. - std::pair<std::shared_ptr<NACP>, VirtualFile> GetControlMetadata() const; + std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const; // Version of GetControlMetadata that takes an arbitrary NCA - std::pair<std::shared_ptr<NACP>, VirtualFile> ParseControlNCA( - const std::shared_ptr<NCA>& nca) const; + std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const; private: u64 title_id; diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index e9b040689..1febb398e 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -308,14 +308,14 @@ VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const { return GetEntryRaw(entry.title_id, entry.type); } -std::shared_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const { +std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const { const auto raw = GetEntryRaw(title_id, type); if (raw == nullptr) return nullptr; - return std::make_shared<NCA>(raw); + return std::make_unique<NCA>(raw); } -std::shared_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const { +std::unique_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const { return GetEntry(entry.title_id, entry.type); } @@ -516,7 +516,7 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { }) != yuzu_meta.end(); } -RegisteredCacheUnion::RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches) +RegisteredCacheUnion::RegisteredCacheUnion(std::vector<RegisteredCache*> caches) : caches(std::move(caches)) {} void RegisteredCacheUnion::Refresh() { @@ -572,14 +572,14 @@ VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const return GetEntryRaw(entry.title_id, entry.type); } -std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const { +std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const { const auto raw = GetEntryRaw(title_id, type); if (raw == nullptr) return nullptr; - return std::make_shared<NCA>(raw); + return std::make_unique<NCA>(raw); } -std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const { +std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const { return GetEntry(entry.title_id, entry.type); } diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index c0cd59fc5..5ddacba47 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -88,8 +88,8 @@ public: VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const; VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const; - std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const; - std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const; + std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const; + std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const; std::vector<RegisteredCacheEntry> ListEntries() const; // If a parameter is not boost::none, it will be filtered for from all entries. @@ -142,7 +142,7 @@ private: // Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface. class RegisteredCacheUnion { public: - explicit RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches); + explicit RegisteredCacheUnion(std::vector<RegisteredCache*> caches); void Refresh(); @@ -157,8 +157,8 @@ public: VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const; VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const; - std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const; - std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const; + std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const; + std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const; std::vector<RegisteredCacheEntry> ListEntries() const; // If a parameter is not boost::none, it will be filtered for from all entries. @@ -168,7 +168,7 @@ public: boost::optional<u64> title_id = boost::none) const; private: - std::vector<std::shared_ptr<RegisteredCache>> caches; + std::vector<RegisteredCache*> caches; }; } // namespace FileSys diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index 47f2ab9e0..ef1aaebbb 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -51,6 +51,13 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescr meta.title_id); } + if (meta.type == SaveDataType::DeviceSaveData && meta.user_id != u128{0, 0}) { + LOG_WARNING(Service_FS, + "Possibly incorrect SaveDataDescriptor, type is DeviceSaveData but user_id is " + "non-zero ({:016X}{:016X})", + meta.user_id[1], meta.user_id[0]); + } + std::string save_directory = GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id); @@ -92,6 +99,9 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ case SaveDataSpaceId::NandUser: out = "/user/"; break; + case SaveDataSpaceId::TemporaryStorage: + out = "/temp/"; + break; default: ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space)); } @@ -100,10 +110,11 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ case SaveDataType::SystemSaveData: return fmt::format("{}save/{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]); case SaveDataType::SaveData: + case SaveDataType::DeviceSaveData: return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0], title_id); case SaveDataType::TemporaryStorage: - return fmt::format("{}temp/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0], + return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0], title_id); default: ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type)); diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index d66a9c9a4..bd3a57058 100644 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp @@ -10,10 +10,10 @@ namespace FileSys { SDMCFactory::SDMCFactory(VirtualDir dir_) - : dir(std::move(dir_)), contents(std::make_shared<RegisteredCache>( + : dir(std::move(dir_)), contents(std::make_unique<RegisteredCache>( GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"), [](const VirtualFile& file, const NcaID& id) { - return std::make_shared<NAX>(file, id)->GetDecrypted(); + return NAX{file, id}.GetDecrypted(); })) {} SDMCFactory::~SDMCFactory() = default; @@ -22,8 +22,8 @@ ResultVal<VirtualDir> SDMCFactory::Open() { return MakeResult<VirtualDir>(dir); } -std::shared_ptr<RegisteredCache> SDMCFactory::GetSDMCContents() const { - return contents; +RegisteredCache* SDMCFactory::GetSDMCContents() const { + return contents.get(); } } // namespace FileSys diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h index ea12149de..42794ba5b 100644 --- a/src/core/file_sys/sdmc_factory.h +++ b/src/core/file_sys/sdmc_factory.h @@ -19,12 +19,12 @@ public: ~SDMCFactory(); ResultVal<VirtualDir> Open(); - std::shared_ptr<RegisteredCache> GetSDMCContents() const; + RegisteredCache* GetSDMCContents() const; private: VirtualDir dir; - std::shared_ptr<RegisteredCache> contents; + std::unique_ptr<RegisteredCache> contents; }; } // namespace FileSys diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index 270291631..7f0d520ca 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -12,20 +12,12 @@ #include <vector> #include <boost/optional.hpp> #include "common/common_types.h" +#include "core/file_sys/vfs_types.h" namespace FileSys { -class VfsDirectory; -class VfsFile; -class VfsFilesystem; - enum class Mode : u32; -// Convenience typedefs to use Vfs* interfaces -using VirtualFilesystem = std::shared_ptr<VfsFilesystem>; -using VirtualDir = std::shared_ptr<VfsDirectory>; -using VirtualFile = std::shared_ptr<VfsFile>; - // An enumeration representing what can be at the end of a path in a VfsFilesystem enum class VfsEntryType { None, diff --git a/src/core/file_sys/vfs_types.h b/src/core/file_sys/vfs_types.h new file mode 100644 index 000000000..6215ed7af --- /dev/null +++ b/src/core/file_sys/vfs_types.h @@ -0,0 +1,21 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +namespace FileSys { + +class VfsDirectory; +class VfsFile; +class VfsFilesystem; + +// Declarations for Vfs* pointer types + +using VirtualDir = std::shared_ptr<VfsDirectory>; +using VirtualFile = std::shared_ptr<VfsFile>; +using VirtualFilesystem = std::shared_ptr<VfsFilesystem>; + +} // namespace FileSys diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index e961ef121..bdcc889e0 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -207,7 +207,7 @@ void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) { static Kernel::Thread* FindThreadById(int id) { for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) { - const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList(); + const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList(); for (auto& thread : threads) { if (thread->GetThreadID() == static_cast<u32>(id)) { current_core = core; @@ -597,7 +597,7 @@ static void HandleQuery() { } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) { std::string val = "m"; for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) { - const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList(); + const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList(); for (const auto& thread : threads) { val += fmt::format("{:x}", thread->GetThreadID()); val += ","; @@ -612,7 +612,7 @@ static void HandleQuery() { buffer += "l<?xml version=\"1.0\"?>"; buffer += "<threads>"; for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) { - const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList(); + const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList(); for (const auto& thread : threads) { buffer += fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*", diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp index ebf193930..57157beb4 100644 --- a/src/core/hle/kernel/address_arbiter.cpp +++ b/src/core/hle/kernel/address_arbiter.cpp @@ -39,7 +39,7 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address) std::vector<SharedPtr<Thread>>& waiting_threads, VAddr arb_addr) { const auto& scheduler = Core::System::GetInstance().Scheduler(core_index); - const auto& thread_list = scheduler->GetThreadList(); + const auto& thread_list = scheduler.GetThreadList(); for (const auto& thread : thread_list) { if (thread->GetArbiterWaitAddress() == arb_addr) diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h index e5fa67ae8..885259618 100644 --- a/src/core/hle/kernel/errors.h +++ b/src/core/hle/kernel/errors.h @@ -22,6 +22,7 @@ enum { HandleTableFull = 105, InvalidMemoryState = 106, InvalidMemoryPermissions = 108, + InvalidMemoryRange = 110, InvalidThreadPriority = 112, InvalidProcessorId = 113, InvalidHandle = 114, @@ -56,6 +57,7 @@ constexpr ResultCode ERR_INVALID_ADDRESS(ErrorModule::Kernel, ErrCodes::InvalidA constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::InvalidMemoryState); constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS(ErrorModule::Kernel, ErrCodes::InvalidMemoryPermissions); +constexpr ResultCode ERR_INVALID_MEMORY_RANGE(ErrorModule::Kernel, ErrCodes::InvalidMemoryRange); constexpr ResultCode ERR_INVALID_HANDLE(ErrorModule::Kernel, ErrCodes::InvalidHandle); constexpr ResultCode ERR_INVALID_PROCESSOR_ID(ErrorModule::Kernel, ErrCodes::InvalidProcessorId); constexpr ResultCode ERR_INVALID_SIZE(ErrorModule::Kernel, ErrCodes::InvalidSize); diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 98eb74298..bd680adfe 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -116,7 +116,7 @@ struct KernelCore::Impl { next_thread_id = 1; process_list.clear(); - current_process.reset(); + current_process = nullptr; handle_table.Clear(); resource_limits.fill(nullptr); @@ -207,7 +207,7 @@ struct KernelCore::Impl { // Lists all processes that exist in the current session. std::vector<SharedPtr<Process>> process_list; - SharedPtr<Process> current_process; + Process* current_process = nullptr; Kernel::HandleTable handle_table; std::array<SharedPtr<ResourceLimit>, 4> resource_limits; @@ -266,15 +266,15 @@ void KernelCore::AppendNewProcess(SharedPtr<Process> process) { impl->process_list.push_back(std::move(process)); } -void KernelCore::MakeCurrentProcess(SharedPtr<Process> process) { - impl->current_process = std::move(process); +void KernelCore::MakeCurrentProcess(Process* process) { + impl->current_process = process; } -SharedPtr<Process>& KernelCore::CurrentProcess() { +Process* KernelCore::CurrentProcess() { return impl->current_process; } -const SharedPtr<Process>& KernelCore::CurrentProcess() const { +const Process* KernelCore::CurrentProcess() const { return impl->current_process; } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index c0771ecf0..41554821f 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -66,13 +66,13 @@ public: void AppendNewProcess(SharedPtr<Process> process); /// Makes the given process the new current process. - void MakeCurrentProcess(SharedPtr<Process> process); + void MakeCurrentProcess(Process* process); - /// Retrieves a reference to the current process. - SharedPtr<Process>& CurrentProcess(); + /// Retrieves a pointer to the current process. + Process* CurrentProcess(); - /// Retrieves a const reference to the current process. - const SharedPtr<Process>& CurrentProcess() const; + /// Retrieves a const pointer to the current process. + const Process* CurrentProcess() const; /// Adds a port to the named port table void AddNamedPort(std::string name, SharedPtr<ClientPort> port); diff --git a/src/core/hle/kernel/object.cpp b/src/core/hle/kernel/object.cpp index d51562d92..d87a62bb9 100644 --- a/src/core/hle/kernel/object.cpp +++ b/src/core/hle/kernel/object.cpp @@ -25,7 +25,6 @@ bool Object::IsWaitable() const { case HandleType::Process: case HandleType::AddressArbiter: case HandleType::ResourceLimit: - case HandleType::CodeSet: case HandleType::ClientPort: case HandleType::ClientSession: return false; diff --git a/src/core/hle/kernel/object.h b/src/core/hle/kernel/object.h index 9eb72315c..c9f4d0bb3 100644 --- a/src/core/hle/kernel/object.h +++ b/src/core/hle/kernel/object.h @@ -26,7 +26,6 @@ enum class HandleType : u32 { AddressArbiter, Timer, ResourceLimit, - CodeSet, ClientPort, ServerPort, ClientSession, diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index fb0027a71..073dd5a7d 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -20,13 +20,7 @@ namespace Kernel { -SharedPtr<CodeSet> CodeSet::Create(KernelCore& kernel, std::string name) { - SharedPtr<CodeSet> codeset(new CodeSet(kernel)); - codeset->name = std::move(name); - return codeset; -} - -CodeSet::CodeSet(KernelCore& kernel) : Object{kernel} {} +CodeSet::CodeSet() = default; CodeSet::~CodeSet() = default; SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) { @@ -159,11 +153,11 @@ void Process::PrepareForTermination() { } }; - auto& system = Core::System::GetInstance(); - stop_threads(system.Scheduler(0)->GetThreadList()); - stop_threads(system.Scheduler(1)->GetThreadList()); - stop_threads(system.Scheduler(2)->GetThreadList()); - stop_threads(system.Scheduler(3)->GetThreadList()); + const auto& system = Core::System::GetInstance(); + stop_threads(system.Scheduler(0).GetThreadList()); + stop_threads(system.Scheduler(1).GetThreadList()); + stop_threads(system.Scheduler(2).GetThreadList()); + stop_threads(system.Scheduler(3).GetThreadList()); } /** @@ -224,20 +218,20 @@ void Process::FreeTLSSlot(VAddr tls_address) { tls_slots[tls_page].reset(tls_slot); } -void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) { +void Process::LoadModule(CodeSet module_, VAddr base_addr) { const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions, MemoryState memory_state) { - auto vma = vm_manager - .MapMemoryBlock(segment.addr + base_addr, module_->memory, segment.offset, - segment.size, memory_state) - .Unwrap(); + const auto vma = vm_manager + .MapMemoryBlock(segment.addr + base_addr, module_.memory, + segment.offset, segment.size, memory_state) + .Unwrap(); vm_manager.Reprotect(vma, permissions); }; // Map CodeSet segments - MapSegment(module_->CodeSegment(), VMAPermission::ReadExecute, MemoryState::CodeStatic); - MapSegment(module_->RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable); - MapSegment(module_->DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable); + MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::CodeStatic); + MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable); + MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable); } ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission perms) { diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 590e0c73d..f2816943a 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -24,6 +24,7 @@ class ProgramMetadata; namespace Kernel { class KernelCore; +class ResourceLimit; struct AddressMapping { // Address and size must be page-aligned @@ -57,30 +58,33 @@ union ProcessFlags { BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000). }; -enum class ProcessStatus { Created, Running, Exited }; - -class ResourceLimit; +/** + * Indicates the status of a Process instance. + * + * @note These match the values as used by kernel, + * so new entries should only be added if RE + * shows that a new value has been introduced. + */ +enum class ProcessStatus { + Created, + CreatedWithDebuggerAttached, + Running, + WaitingForDebuggerToAttach, + DebuggerAttached, + Exiting, + Exited, + DebugBreak, +}; -struct CodeSet final : public Object { +struct CodeSet final { struct Segment { std::size_t offset = 0; VAddr addr = 0; u32 size = 0; }; - static SharedPtr<CodeSet> Create(KernelCore& kernel, std::string name); - - std::string GetTypeName() const override { - return "CodeSet"; - } - std::string GetName() const override { - return name; - } - - static const HandleType HANDLE_TYPE = HandleType::CodeSet; - HandleType GetHandleType() const override { - return HANDLE_TYPE; - } + explicit CodeSet(); + ~CodeSet(); Segment& CodeSegment() { return segments[0]; @@ -109,14 +113,7 @@ struct CodeSet final : public Object { std::shared_ptr<std::vector<u8>> memory; std::array<Segment, 3> segments; - VAddr entrypoint; - - /// Name of the process - std::string name; - -private: - explicit CodeSet(KernelCore& kernel); - ~CodeSet() override; + VAddr entrypoint = 0; }; class Process final : public Object { @@ -219,7 +216,7 @@ public: */ void PrepareForTermination(); - void LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr); + void LoadModule(CodeSet module_, VAddr base_addr); /////////////////////////////////////////////////////////////////////////////////////////////// // Memory Management diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp index cfd6e1bad..1342c597e 100644 --- a/src/core/hle/kernel/scheduler.cpp +++ b/src/core/hle/kernel/scheduler.cpp @@ -9,7 +9,7 @@ #include "common/logging/log.h" #include "core/arm/arm_interface.h" #include "core/core.h" -#include "core/core_timing.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/scheduler.h" @@ -78,16 +78,16 @@ void Scheduler::SwitchContext(Thread* new_thread) { // Cancel any outstanding wakeup events for this thread new_thread->CancelWakeupTimer(); - auto previous_process = Core::CurrentProcess(); + auto* const previous_process = Core::CurrentProcess(); current_thread = new_thread; ready_queue.remove(new_thread->GetPriority(), new_thread); new_thread->SetStatus(ThreadStatus::Running); - const auto thread_owner_process = current_thread->GetOwnerProcess(); + auto* const thread_owner_process = current_thread->GetOwnerProcess(); if (previous_process != thread_owner_process) { - Core::CurrentProcess() = thread_owner_process; + Core::System::GetInstance().Kernel().MakeCurrentProcess(thread_owner_process); SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table); } diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index b488b508d..d08b84bde 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -39,6 +39,73 @@ namespace { constexpr bool Is4KBAligned(VAddr address) { return (address & 0xFFF) == 0; } + +// Checks if address + size is greater than the given address +// This can return false if the size causes an overflow of a 64-bit type +// or if the given size is zero. +constexpr bool IsValidAddressRange(VAddr address, u64 size) { + return address + size > address; +} + +// Checks if a given address range lies within a larger address range. +constexpr bool IsInsideAddressRange(VAddr address, u64 size, VAddr address_range_begin, + VAddr address_range_end) { + const VAddr end_address = address + size - 1; + return address_range_begin <= address && end_address <= address_range_end - 1; +} + +bool IsInsideAddressSpace(const VMManager& vm, VAddr address, u64 size) { + return IsInsideAddressRange(address, size, vm.GetAddressSpaceBaseAddress(), + vm.GetAddressSpaceEndAddress()); +} + +bool IsInsideNewMapRegion(const VMManager& vm, VAddr address, u64 size) { + return IsInsideAddressRange(address, size, vm.GetNewMapRegionBaseAddress(), + vm.GetNewMapRegionEndAddress()); +} + +// Helper function that performs the common sanity checks for svcMapMemory +// and svcUnmapMemory. This is doable, as both functions perform their sanitizing +// in the same order. +ResultCode MapUnmapMemorySanityChecks(const VMManager& vm_manager, VAddr dst_addr, VAddr src_addr, + u64 size) { + if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) { + return ERR_INVALID_ADDRESS; + } + + if (size == 0 || !Is4KBAligned(size)) { + return ERR_INVALID_SIZE; + } + + if (!IsValidAddressRange(dst_addr, size)) { + return ERR_INVALID_ADDRESS_STATE; + } + + if (!IsValidAddressRange(src_addr, size)) { + return ERR_INVALID_ADDRESS_STATE; + } + + if (!IsInsideAddressSpace(vm_manager, src_addr, size)) { + return ERR_INVALID_ADDRESS_STATE; + } + + if (!IsInsideNewMapRegion(vm_manager, dst_addr, size)) { + return ERR_INVALID_MEMORY_RANGE; + } + + const VAddr dst_end_address = dst_addr + size; + if (dst_end_address > vm_manager.GetHeapRegionBaseAddress() && + vm_manager.GetHeapRegionEndAddress() > dst_addr) { + return ERR_INVALID_MEMORY_RANGE; + } + + if (dst_end_address > vm_manager.GetMapRegionBaseAddress() && + vm_manager.GetMapRegionEndAddress() > dst_addr) { + return ERR_INVALID_MEMORY_RANGE; + } + + return RESULT_SUCCESS; +} } // Anonymous namespace /// Set the process heap to a given Size. It can both extend and shrink the heap. @@ -69,15 +136,15 @@ static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) { LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, src_addr, size); - if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) { - return ERR_INVALID_ADDRESS; - } + auto* const current_process = Core::CurrentProcess(); + const auto& vm_manager = current_process->VMManager(); - if (size == 0 || !Is4KBAligned(size)) { - return ERR_INVALID_SIZE; + const auto result = MapUnmapMemorySanityChecks(vm_manager, dst_addr, src_addr, size); + if (result != RESULT_SUCCESS) { + return result; } - return Core::CurrentProcess()->MirrorMemory(dst_addr, src_addr, size); + return current_process->MirrorMemory(dst_addr, src_addr, size); } /// Unmaps a region that was previously mapped with svcMapMemory @@ -85,15 +152,15 @@ static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) { LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, src_addr, size); - if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) { - return ERR_INVALID_ADDRESS; - } + auto* const current_process = Core::CurrentProcess(); + const auto& vm_manager = current_process->VMManager(); - if (size == 0 || !Is4KBAligned(size)) { - return ERR_INVALID_SIZE; + const auto result = MapUnmapMemorySanityChecks(vm_manager, dst_addr, src_addr, size); + if (result != RESULT_SUCCESS) { + return result; } - return Core::CurrentProcess()->UnmapMemory(dst_addr, src_addr, size); + return current_process->UnmapMemory(dst_addr, src_addr, size); } /// Connect to an OS service given the port name, returns the handle to the port to out @@ -303,15 +370,15 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) { struct BreakReason { union { - u64 raw; - BitField<31, 1, u64> dont_kill_application; + u32 raw; + BitField<31, 1, u32> signal_debugger; }; }; /// Break program execution -static void Break(u64 reason, u64 info1, u64 info2) { +static void Break(u32 reason, u64 info1, u64 info2) { BreakReason break_reason{reason}; - if (break_reason.dont_kill_application) { + if (break_reason.signal_debugger) { LOG_ERROR( Debug_Emulated, "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", @@ -322,6 +389,12 @@ static void Break(u64 reason, u64 info1, u64 info2) { "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", reason, info1, info2); ASSERT(false); + + Core::CurrentProcess()->PrepareForTermination(); + + // Kill the current thread + GetCurrentThread()->Stop(); + Core::System::GetInstance().PrepareReschedule(); } } @@ -341,7 +414,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id, info_sub_id, handle); - const auto& current_process = Core::CurrentProcess(); + const auto* current_process = Core::CurrentProcess(); const auto& vm_manager = current_process->VMManager(); switch (static_cast<GetInfoType>(info_id)) { @@ -375,25 +448,12 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) case GetInfoType::RandomEntropy: *result = 0; break; - case GetInfoType::AddressSpaceBaseAddr: - *result = vm_manager.GetCodeRegionBaseAddress(); + case GetInfoType::ASLRRegionBaseAddr: + *result = vm_manager.GetASLRRegionBaseAddress(); break; - case GetInfoType::AddressSpaceSize: { - const u64 width = vm_manager.GetAddressSpaceWidth(); - - switch (width) { - case 32: - *result = 0xFFE00000; - break; - case 36: - *result = 0xFF8000000; - break; - case 39: - *result = 0x7FF8000000; - break; - } + case GetInfoType::ASLRRegionSize: + *result = vm_manager.GetASLRRegionSize(); break; - } case GetInfoType::NewMapRegionBaseAddr: *result = vm_manager.GetNewMapRegionBaseAddress(); break; @@ -439,7 +499,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) { return ERR_INVALID_HANDLE; } - const auto current_process = Core::CurrentProcess(); + const auto* current_process = Core::CurrentProcess(); if (thread->GetOwnerProcess() != current_process) { return ERR_INVALID_HANDLE; } @@ -531,7 +591,7 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s return ERR_INVALID_HANDLE; } - return shared_memory->Map(Core::CurrentProcess().get(), addr, permissions_type, + return shared_memory->Map(Core::CurrentProcess(), addr, permissions_type, MemoryPermission::DontCare); } @@ -550,7 +610,7 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 auto& kernel = Core::System::GetInstance().Kernel(); auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle); - return shared_memory->Unmap(Core::CurrentProcess().get(), addr); + return shared_memory->Unmap(Core::CurrentProcess(), addr); } /// Query process memory @@ -588,7 +648,7 @@ static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAdd /// Exits the current process static void ExitProcess() { - auto& current_process = Core::CurrentProcess(); + auto* current_process = Core::CurrentProcess(); LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID()); ASSERT_MSG(current_process->GetStatus() == ProcessStatus::Running, @@ -636,7 +696,7 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V auto& kernel = Core::System::GetInstance().Kernel(); CASCADE_RESULT(SharedPtr<Thread> thread, Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top, - Core::CurrentProcess())); + *Core::CurrentProcess())); const auto new_guest_handle = kernel.HandleTable().Create(thread); if (new_guest_handle.Failed()) { return new_guest_handle.Code(); @@ -736,7 +796,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target std::vector<SharedPtr<Thread>>& waiting_threads, VAddr condvar_addr) { const auto& scheduler = Core::System::GetInstance().Scheduler(core_index); - const auto& thread_list = scheduler->GetThreadList(); + const auto& thread_list = scheduler.GetThreadList(); for (const auto& thread : thread_list) { if (thread->GetCondVarWaitAddress() == condvar_addr) @@ -1025,6 +1085,29 @@ static ResultCode ClearEvent(Handle handle) { return RESULT_SUCCESS; } +static ResultCode GetProcessInfo(u64* out, Handle process_handle, u32 type) { + LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type); + + // This function currently only allows retrieving a process' status. + enum class InfoType { + Status, + }; + + const auto& kernel = Core::System::GetInstance().Kernel(); + const auto process = kernel.HandleTable().Get<Process>(process_handle); + if (!process) { + return ERR_INVALID_HANDLE; + } + + const auto info_type = static_cast<InfoType>(type); + if (info_type != InfoType::Status) { + return ERR_INVALID_ENUM_VALUE; + } + + *out = static_cast<u64>(process->GetStatus()); + return RESULT_SUCCESS; +} + namespace { struct FunctionDef { using Func = void(); @@ -1160,7 +1243,7 @@ static const FunctionDef SVC_Table[] = { {0x79, nullptr, "CreateProcess"}, {0x7A, nullptr, "StartProcess"}, {0x7B, nullptr, "TerminateProcess"}, - {0x7C, nullptr, "GetProcessInfo"}, + {0x7C, SvcWrap<GetProcessInfo>, "GetProcessInfo"}, {0x7D, nullptr, "CreateResourceLimit"}, {0x7E, nullptr, "SetResourceLimitLimitValue"}, {0x7F, nullptr, "CallSecureMonitor"}, diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h index 70148c4fe..554a5e328 100644 --- a/src/core/hle/kernel/svc.h +++ b/src/core/hle/kernel/svc.h @@ -41,8 +41,8 @@ enum class GetInfoType : u64 { RandomEntropy = 11, PerformanceCounter = 0xF0000002, // 2.0.0+ - AddressSpaceBaseAddr = 12, - AddressSpaceSize = 13, + ASLRRegionBaseAddr = 12, + ASLRRegionSize = 13, NewMapRegionBaseAddr = 14, NewMapRegionSize = 15, // 3.0.0+ diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index 22712e64f..b09753c80 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -35,18 +35,18 @@ void SvcWrap() { template <ResultCode func(u32)> void SvcWrap() { - FuncReturn(func((u32)Param(0)).raw); + FuncReturn(func(static_cast<u32>(Param(0))).raw); } template <ResultCode func(u32, u32)> void SvcWrap() { - FuncReturn(func((u32)Param(0), (u32)Param(1)).raw); + FuncReturn(func(static_cast<u32>(Param(0)), static_cast<u32>(Param(1))).raw); } template <ResultCode func(u32*, u32)> void SvcWrap() { u32 param_1 = 0; - u32 retval = func(¶m_1, (u32)Param(1)).raw; + u32 retval = func(¶m_1, static_cast<u32>(Param(1))).raw; Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } @@ -61,7 +61,7 @@ void SvcWrap() { template <ResultCode func(u64, s32)> void SvcWrap() { - FuncReturn(func(Param(0), (s32)Param(1)).raw); + FuncReturn(func(Param(0), static_cast<s32>(Param(1))).raw); } template <ResultCode func(u64, u32)> @@ -77,21 +77,29 @@ void SvcWrap() { FuncReturn(retval); } +template <ResultCode func(u64*, u32, u32)> +void SvcWrap() { + u64 param_1 = 0; + u32 retval = func(¶m_1, static_cast<u32>(Param(1)), static_cast<u32>(Param(2))).raw; + Core::CurrentArmInterface().SetReg(1, param_1); + FuncReturn(retval); +} + template <ResultCode func(u32, u64)> void SvcWrap() { - FuncReturn(func((u32)(Param(0) & 0xFFFFFFFF), Param(1)).raw); + FuncReturn(func(static_cast<u32>(Param(0)), Param(1)).raw); } template <ResultCode func(u32, u32, u64)> void SvcWrap() { - FuncReturn(func((u32)(Param(0) & 0xFFFFFFFF), (u32)(Param(1) & 0xFFFFFFFF), Param(2)).raw); + FuncReturn(func(static_cast<u32>(Param(0)), static_cast<u32>(Param(1)), Param(2)).raw); } template <ResultCode func(u32, u32*, u64*)> void SvcWrap() { u32 param_1 = 0; u64 param_2 = 0; - ResultCode retval = func((u32)(Param(2) & 0xFFFFFFFF), ¶m_1, ¶m_2); + ResultCode retval = func(static_cast<u32>(Param(2)), ¶m_1, ¶m_2); Core::CurrentArmInterface().SetReg(1, param_1); Core::CurrentArmInterface().SetReg(2, param_2); FuncReturn(retval.raw); @@ -100,12 +108,12 @@ void SvcWrap() { template <ResultCode func(u64, u64, u32, u32)> void SvcWrap() { FuncReturn( - func(Param(0), Param(1), (u32)(Param(3) & 0xFFFFFFFF), (u32)(Param(3) & 0xFFFFFFFF)).raw); + func(Param(0), Param(1), static_cast<u32>(Param(3)), static_cast<u32>(Param(3))).raw); } template <ResultCode func(u32, u64, u32)> void SvcWrap() { - FuncReturn(func((u32)Param(0), Param(1), (u32)Param(2)).raw); + FuncReturn(func(static_cast<u32>(Param(0)), Param(1), static_cast<u32>(Param(2))).raw); } template <ResultCode func(u64, u64, u64)> @@ -115,25 +123,28 @@ void SvcWrap() { template <ResultCode func(u32, u64, u64, u32)> void SvcWrap() { - FuncReturn(func((u32)Param(0), Param(1), Param(2), (u32)Param(3)).raw); + FuncReturn( + func(static_cast<u32>(Param(0)), Param(1), Param(2), static_cast<u32>(Param(3))).raw); } template <ResultCode func(u32, u64, u64)> void SvcWrap() { - FuncReturn(func((u32)Param(0), Param(1), Param(2)).raw); + FuncReturn(func(static_cast<u32>(Param(0)), Param(1), Param(2)).raw); } template <ResultCode func(u32*, u64, u64, s64)> void SvcWrap() { u32 param_1 = 0; - ResultCode retval = func(¶m_1, Param(1), (u32)(Param(2) & 0xFFFFFFFF), (s64)Param(3)); + ResultCode retval = + func(¶m_1, Param(1), static_cast<u32>(Param(2)), static_cast<s64>(Param(3))); Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval.raw); } template <ResultCode func(u64, u64, u32, s64)> void SvcWrap() { - FuncReturn(func(Param(0), Param(1), (u32)Param(2), (s64)Param(3)).raw); + FuncReturn( + func(Param(0), Param(1), static_cast<u32>(Param(2)), static_cast<s64>(Param(3))).raw); } template <ResultCode func(u64*, u64, u64, u64)> @@ -147,9 +158,9 @@ void SvcWrap() { template <ResultCode func(u32*, u64, u64, u64, u32, s32)> void SvcWrap() { u32 param_1 = 0; - u32 retval = - func(¶m_1, Param(1), Param(2), Param(3), (u32)Param(4), (s32)(Param(5) & 0xFFFFFFFF)) - .raw; + u32 retval = func(¶m_1, Param(1), Param(2), Param(3), static_cast<u32>(Param(4)), + static_cast<s32>(Param(5))) + .raw; Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } @@ -172,7 +183,7 @@ void SvcWrap() { template <ResultCode func(u32*, u64, u64, u32)> void SvcWrap() { u32 param_1 = 0; - u32 retval = func(¶m_1, Param(1), Param(2), (u32)(Param(3) & 0xFFFFFFFF)).raw; + u32 retval = func(¶m_1, Param(1), Param(2), static_cast<u32>(Param(3))).raw; Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } @@ -181,22 +192,22 @@ template <ResultCode func(Handle*, u64, u32, u32)> void SvcWrap() { u32 param_1 = 0; u32 retval = - func(¶m_1, Param(1), (u32)(Param(2) & 0xFFFFFFFF), (u32)(Param(3) & 0xFFFFFFFF)).raw; + func(¶m_1, Param(1), static_cast<u32>(Param(2)), static_cast<u32>(Param(3))).raw; Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } template <ResultCode func(u64, u32, s32, s64)> void SvcWrap() { - FuncReturn( - func(Param(0), (u32)(Param(1) & 0xFFFFFFFF), (s32)(Param(2) & 0xFFFFFFFF), (s64)Param(3)) - .raw); + FuncReturn(func(Param(0), static_cast<u32>(Param(1)), static_cast<s32>(Param(2)), + static_cast<s64>(Param(3))) + .raw); } template <ResultCode func(u64, u32, s32, s32)> void SvcWrap() { - FuncReturn(func(Param(0), (u32)(Param(1) & 0xFFFFFFFF), (s32)(Param(2) & 0xFFFFFFFF), - (s32)(Param(3) & 0xFFFFFFFF)) + FuncReturn(func(Param(0), static_cast<u32>(Param(1)), static_cast<s32>(Param(2)), + static_cast<s32>(Param(3))) .raw); } @@ -226,7 +237,7 @@ void SvcWrap() { template <void func(s64)> void SvcWrap() { - func((s64)Param(0)); + func(static_cast<s64>(Param(0))); } template <void func(u64, u64 len)> @@ -239,4 +250,9 @@ void SvcWrap() { func(Param(0), Param(1), Param(2)); } +template <void func(u32, u64, u64)> +void SvcWrap() { + func(static_cast<u32>(Param(0)), Param(1), Param(2)); +} + } // namespace Kernel diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 8e514cf9a..35ec98c1a 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -97,7 +97,7 @@ void Thread::CancelWakeupTimer() { static boost::optional<s32> GetNextProcessorId(u64 mask) { for (s32 index = 0; index < Core::NUM_CPU_CORES; ++index) { if (mask & (1ULL << index)) { - if (!Core::System::GetInstance().Scheduler(index)->GetCurrentThread()) { + if (!Core::System::GetInstance().Scheduler(index).GetCurrentThread()) { // Core is enabled and not running any threads, use this one return index; } @@ -147,14 +147,14 @@ void Thread::ResumeFromWait() { new_processor_id = processor_id; } if (ideal_core != -1 && - Core::System::GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) { + Core::System::GetInstance().Scheduler(ideal_core).GetCurrentThread() == nullptr) { new_processor_id = ideal_core; } ASSERT(*new_processor_id < 4); // Add thread to new core's scheduler - auto& next_scheduler = Core::System::GetInstance().Scheduler(*new_processor_id); + auto* next_scheduler = &Core::System::GetInstance().Scheduler(*new_processor_id); if (*new_processor_id != processor_id) { // Remove thread from previous core's scheduler @@ -169,7 +169,7 @@ void Thread::ResumeFromWait() { next_scheduler->ScheduleThread(this, current_priority); // Change thread's scheduler - scheduler = next_scheduler.get(); + scheduler = next_scheduler; Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule(); } @@ -183,18 +183,15 @@ void Thread::ResumeFromWait() { */ static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, VAddr stack_top, VAddr entry_point, u64 arg) { - memset(&context, 0, sizeof(Core::ARM_Interface::ThreadContext)); - + context = {}; context.cpu_registers[0] = arg; context.pc = entry_point; context.sp = stack_top; - context.pstate = 0; - context.fpcr = 0; } ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name, VAddr entry_point, u32 priority, u64 arg, s32 processor_id, - VAddr stack_top, SharedPtr<Process> owner_process) { + VAddr stack_top, Process& owner_process) { // Check if priority is in ranged. Lowest priority -> highest priority id. if (priority > THREADPRIO_LOWEST) { LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority); @@ -208,7 +205,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name // TODO(yuriks): Other checks, returning 0xD9001BEA - if (!Memory::IsValidVirtualAddress(*owner_process, entry_point)) { + if (!Memory::IsValidVirtualAddress(owner_process, entry_point)) { LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point); // TODO (bunnei): Find the correct error code to use here return ResultCode(-1); @@ -232,8 +229,8 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name thread->wait_handle = 0; thread->name = std::move(name); thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap(); - thread->owner_process = owner_process; - thread->scheduler = Core::System::GetInstance().Scheduler(processor_id).get(); + thread->owner_process = &owner_process; + thread->scheduler = &Core::System::GetInstance().Scheduler(processor_id); thread->scheduler->AddThread(thread, priority); thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread); @@ -264,7 +261,7 @@ SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 pri // Initialize new "main" thread const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress(); auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0, THREADPROCESSORID_0, - stack_top, &owner_process); + stack_top, owner_process); SharedPtr<Thread> thread = std::move(thread_res).Unwrap(); @@ -378,14 +375,14 @@ void Thread::ChangeCore(u32 core, u64 mask) { new_processor_id = processor_id; } if (ideal_core != -1 && - Core::System::GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) { + Core::System::GetInstance().Scheduler(ideal_core).GetCurrentThread() == nullptr) { new_processor_id = ideal_core; } ASSERT(*new_processor_id < 4); // Add thread to new core's scheduler - auto& next_scheduler = Core::System::GetInstance().Scheduler(*new_processor_id); + auto* next_scheduler = &Core::System::GetInstance().Scheduler(*new_processor_id); if (*new_processor_id != processor_id) { // Remove thread from previous core's scheduler @@ -400,7 +397,7 @@ void Thread::ChangeCore(u32 core, u64 mask) { next_scheduler->ScheduleThread(this, current_priority); // Change thread's scheduler - scheduler = next_scheduler.get(); + scheduler = next_scheduler; Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule(); } diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index c6ffbd28c..f4d7bd235 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -89,7 +89,7 @@ public: static ResultVal<SharedPtr<Thread>> Create(KernelCore& kernel, std::string name, VAddr entry_point, u32 priority, u64 arg, s32 processor_id, VAddr stack_top, - SharedPtr<Process> owner_process); + Process& owner_process); std::string GetName() const override { return name; @@ -262,11 +262,11 @@ public: return processor_id; } - SharedPtr<Process>& GetOwnerProcess() { + Process* GetOwnerProcess() { return owner_process; } - const SharedPtr<Process>& GetOwnerProcess() const { + const Process* GetOwnerProcess() const { return owner_process; } @@ -386,7 +386,7 @@ private: u64 tpidr_el0 = 0; ///< TPIDR_EL0 read/write system register. /// Process that owns this thread - SharedPtr<Process> owner_process; + Process* owner_process; /// Objects that the thread is waiting on, in the same order as they were /// passed to WaitSynchronization1/N. diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index e412309fd..1e28ccbda 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -393,30 +393,35 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty switch (type) { case FileSys::ProgramAddressSpaceType::Is32Bit: + case FileSys::ProgramAddressSpaceType::Is32BitNoMap: address_space_width = 32; code_region_base = 0x200000; code_region_end = code_region_base + 0x3FE00000; - map_region_size = 0x40000000; - heap_region_size = 0x40000000; + aslr_region_base = 0x200000; + aslr_region_end = aslr_region_base + 0xFFE00000; + if (type == FileSys::ProgramAddressSpaceType::Is32Bit) { + map_region_size = 0x40000000; + heap_region_size = 0x40000000; + } else { + map_region_size = 0; + heap_region_size = 0x80000000; + } break; case FileSys::ProgramAddressSpaceType::Is36Bit: address_space_width = 36; code_region_base = 0x8000000; code_region_end = code_region_base + 0x78000000; + aslr_region_base = 0x8000000; + aslr_region_end = aslr_region_base + 0xFF8000000; map_region_size = 0x180000000; heap_region_size = 0x180000000; break; - case FileSys::ProgramAddressSpaceType::Is32BitNoMap: - address_space_width = 32; - code_region_base = 0x200000; - code_region_end = code_region_base + 0x3FE00000; - map_region_size = 0; - heap_region_size = 0x80000000; - break; case FileSys::ProgramAddressSpaceType::Is39Bit: address_space_width = 39; code_region_base = 0x8000000; code_region_end = code_region_base + 0x80000000; + aslr_region_base = 0x8000000; + aslr_region_end = aslr_region_base + 0x7FF8000000; map_region_size = 0x1000000000; heap_region_size = 0x180000000; new_map_region_size = 0x80000000; @@ -490,6 +495,18 @@ u64 VMManager::GetAddressSpaceWidth() const { return address_space_width; } +VAddr VMManager::GetASLRRegionBaseAddress() const { + return aslr_region_base; +} + +VAddr VMManager::GetASLRRegionEndAddress() const { + return aslr_region_end; +} + +u64 VMManager::GetASLRRegionSize() const { + return aslr_region_end - aslr_region_base; +} + VAddr VMManager::GetCodeRegionBaseAddress() const { return code_region_base; } diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index 015559a64..4accde6b3 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -205,6 +205,15 @@ public: /// Gets the address space width in bits. u64 GetAddressSpaceWidth() const; + /// Gets the base address of the ASLR region. + VAddr GetASLRRegionBaseAddress() const; + + /// Gets the end address of the ASLR region. + VAddr GetASLRRegionEndAddress() const; + + /// Gets the size of the ASLR region + u64 GetASLRRegionSize() const; + /// Gets the base address of the code region. VAddr GetCodeRegionBaseAddress() const; @@ -306,6 +315,9 @@ private: VAddr address_space_base = 0; VAddr address_space_end = 0; + VAddr aslr_region_base = 0; + VAddr aslr_region_end = 0; + VAddr code_region_base = 0; VAddr code_region_end = 0; diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 0ecfb5af1..518161bf7 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -7,8 +7,10 @@ #include <vector> #include "common/logging/log.h" #include "core/file_sys/content_archive.h" +#include "core/file_sys/control_metadata.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/process.h" @@ -19,7 +21,7 @@ namespace Service::AOC { constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; -constexpr u64 DLC_BASE_TO_AOC_ID_MASK = 0x1000; +constexpr u64 DLC_BASE_TO_AOC_ID = 0x1000; static bool CheckAOCTitleIDMatchesBase(u64 base, u64 aoc) { return (aoc & DLC_BASE_TITLE_ID_MASK) == base; @@ -97,14 +99,24 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { ctx.WriteBuffer(out); - IPC::ResponseBuilder rb{ctx, 2}; + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); + rb.Push(count); } void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push(Core::System::GetInstance().CurrentProcess()->GetTitleID() | DLC_BASE_TO_AOC_ID_MASK); + const auto title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); + FileSys::PatchManager pm{title_id}; + + const auto res = pm.GetControlMetadata(); + if (res.first == nullptr) { + rb.Push(title_id + DLC_BASE_TO_AOC_ID); + return; + } + + rb.Push(res.first->GetDLCBaseTitleId()); } void AOC_U::PrepareAddOnContent(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index fc6067e59..7168c6a10 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -2,8 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <chrono> #include <cstring> #include <memory> +#include <optional> #include <vector> #include <opus.h> @@ -33,7 +35,8 @@ public: {1, nullptr, "SetContext"}, {2, nullptr, "DecodeInterleavedForMultiStream"}, {3, nullptr, "SetContextForMultiStream"}, - {4, nullptr, "Unknown4"}, + {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerformance, + "DecodeInterleavedWithPerformance"}, {5, nullptr, "Unknown5"}, {6, nullptr, "Unknown6"}, {7, nullptr, "Unknown7"}, @@ -59,8 +62,31 @@ private: ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16)); } - bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input, - std::vector<opus_int16>& output) { + void DecodeInterleavedWithPerformance(Kernel::HLERequestContext& ctx) { + u32 consumed = 0; + u32 sample_count = 0; + u64 performance = 0; + std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16)); + if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples, + performance)) { + IPC::ResponseBuilder rb{ctx, 2}; + // TODO(ogniK): Use correct error code + rb.Push(ResultCode(-1)); + return; + } + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(consumed); + rb.Push<u64>(performance); + rb.Push<u32>(sample_count); + ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16)); + } + + bool Decoder_DecodeInterleaved( + u32& consumed, u32& sample_count, const std::vector<u8>& input, + std::vector<opus_int16>& output, + std::optional<std::reference_wrapper<u64>> performance_time = std::nullopt) { + const auto start_time = std::chrono::high_resolution_clock::now(); std::size_t raw_output_sz = output.size() * sizeof(opus_int16); if (sizeof(OpusHeader) > input.size()) return false; @@ -80,8 +106,13 @@ private: (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)), 0); if (out_sample_count < 0) return false; + const auto end_time = std::chrono::high_resolution_clock::now() - start_time; sample_count = out_sample_count; consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz); + if (performance_time.has_value()) { + performance_time->get() = + std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count(); + } return true; } diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 439e62d27..e32a7c48e 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -319,13 +319,12 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() { return sdmc_factory->Open(); } -std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents() { - return std::make_shared<FileSys::RegisteredCacheUnion>( - std::vector<std::shared_ptr<FileSys::RegisteredCache>>{ - GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()}); +std::unique_ptr<FileSys::RegisteredCacheUnion> GetUnionContents() { + return std::make_unique<FileSys::RegisteredCacheUnion>(std::vector<FileSys::RegisteredCache*>{ + GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()}); } -std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() { +FileSys::RegisteredCache* GetSystemNANDContents() { LOG_TRACE(Service_FS, "Opening System NAND Contents"); if (bis_factory == nullptr) @@ -334,7 +333,7 @@ std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() { return bis_factory->GetSystemNANDContents(); } -std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() { +FileSys::RegisteredCache* GetUserNANDContents() { LOG_TRACE(Service_FS, "Opening User NAND Contents"); if (bis_factory == nullptr) @@ -343,7 +342,7 @@ std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() { return bis_factory->GetUserNANDContents(); } -std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents() { +FileSys::RegisteredCache* GetSDMCContents() { LOG_TRACE(Service_FS, "Opening SDMC Contents"); if (sdmc_factory == nullptr) @@ -361,19 +360,19 @@ FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) { return bis_factory->GetModificationLoadRoot(title_id); } -void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) { +void CreateFactories(FileSys::VfsFilesystem& 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); - auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), - FileSys::Mode::ReadWrite); - auto load_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), - FileSys::Mode::ReadWrite); + auto nand_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), + FileSys::Mode::ReadWrite); + auto sd_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), + FileSys::Mode::ReadWrite); + auto load_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), + FileSys::Mode::ReadWrite); if (bis_factory == nullptr) bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory, load_directory); @@ -383,7 +382,7 @@ void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) { sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory)); } -void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs) { +void InstallInterfaces(SM::ServiceManager& service_manager, FileSys::VfsFilesystem& vfs) { romfs_factory = nullptr; CreateFactories(vfs, false); std::make_shared<FSP_LDR>()->InstallAsService(service_manager); diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 53b01bb01..6ca5c5636 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -47,19 +47,19 @@ ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, FileSys::SaveDataDescriptor save_struct); ResultVal<FileSys::VirtualDir> OpenSDMC(); -std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents(); +std::unique_ptr<FileSys::RegisteredCacheUnion> GetUnionContents(); -std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents(); -std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); -std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents(); +FileSys::RegisteredCache* GetSystemNANDContents(); +FileSys::RegisteredCache* GetUserNANDContents(); +FileSys::RegisteredCache* GetSDMCContents(); FileSys::VirtualDir GetModificationLoadRoot(u64 title_id); // 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); +void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true); -void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs); +void InstallInterfaces(SM::ServiceManager& service_manager, FileSys::VfsFilesystem& vfs); // A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of // pointers and booleans. This makes using a VfsDirectory with switch services much easier and diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp index 4b2f758a8..44accecb7 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/pl_u.cpp @@ -161,7 +161,7 @@ PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} { }; RegisterHandlers(functions); // Attempt to load shared font data from disk - const auto nand = FileSystem::GetSystemNANDContents(); + const auto* nand = FileSystem::GetSystemNANDContents(); std::size_t offset = 0; // Rebuild shared fonts from data ncas if (nand->HasEntry(static_cast<u64>(FontArchives::Standard), diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index 7555bbe7d..c41ef7058 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -15,6 +15,11 @@ #include "video_core/renderer_base.h" namespace Service::Nvidia::Devices { +namespace NvErrCodes { +enum { + InvalidNmapHandle = -22, +}; +} nvhost_as_gpu::nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {} nvhost_as_gpu::~nvhost_as_gpu() = default; @@ -79,14 +84,16 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) std::memcpy(entries.data(), input.data(), input.size()); auto& gpu = Core::System::GetInstance().GPU(); - for (const auto& entry : entries) { LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}", entry.offset, entry.nvmap_handle, entry.pages); Tegra::GPUVAddr offset = static_cast<Tegra::GPUVAddr>(entry.offset) << 0x10; - auto object = nvmap_dev->GetObject(entry.nvmap_handle); - ASSERT(object); + if (!object) { + LOG_CRITICAL(Service_NVDRV, "nvmap {} is an invalid handle!", entry.nvmap_handle); + std::memcpy(output.data(), entries.data(), output.size()); + return static_cast<u32>(NvErrCodes::InvalidNmapHandle); + } ASSERT(object->status == nvmap::Object::Status::Allocated); @@ -167,10 +174,11 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou auto& system_instance = Core::System::GetInstance(); // Remove this memory region from the rasterizer cache. - system_instance.Renderer().Rasterizer().FlushAndInvalidateRegion(params.offset, - itr->second.size); - auto& gpu = system_instance.GPU(); + auto cpu_addr = gpu.MemoryManager().GpuToCpuAddress(params.offset); + ASSERT(cpu_addr); + system_instance.Renderer().Rasterizer().FlushAndInvalidateRegion(*cpu_addr, itr->second.size); + params.offset = gpu.MemoryManager().UnmapBuffer(params.offset, itr->second.size); buffer_mappings.erase(itr->second.offset); diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index a2287cc1b..43651d8a6 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -11,6 +11,13 @@ namespace Service::Nvidia::Devices { +namespace NvErrCodes { +enum { + OperationNotPermitted = -1, + InvalidValue = -22, +}; +} + nvmap::nvmap() = default; nvmap::~nvmap() = default; @@ -44,7 +51,11 @@ u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& o u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) { IocCreateParams params; std::memcpy(¶ms, input.data(), sizeof(params)); + LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size); + if (!params.size) { + return static_cast<u32>(NvErrCodes::InvalidValue); + } // Create a new nvmap object and obtain a handle to it. auto object = std::make_shared<Object>(); object->id = next_id++; @@ -55,8 +66,6 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) { u32 handle = next_handle++; handles[handle] = std::move(object); - LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size); - params.handle = handle; std::memcpy(output.data(), ¶ms, sizeof(params)); @@ -66,9 +75,29 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) { u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) { IocAllocParams params; std::memcpy(¶ms, input.data(), sizeof(params)); + LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr); + + if (!params.handle) { + return static_cast<u32>(NvErrCodes::InvalidValue); + } + + if ((params.align - 1) & params.align) { + return static_cast<u32>(NvErrCodes::InvalidValue); + } + + const u32 min_alignment = 0x1000; + if (params.align < min_alignment) { + params.align = min_alignment; + } auto object = GetObject(params.handle); - ASSERT(object); + if (!object) { + return static_cast<u32>(NvErrCodes::InvalidValue); + } + + if (object->status == Object::Status::Allocated) { + return static_cast<u32>(NvErrCodes::OperationNotPermitted); + } object->flags = params.flags; object->align = params.align; @@ -76,8 +105,6 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) { object->addr = params.addr; object->status = Object::Status::Allocated; - LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr); - std::memcpy(output.data(), ¶ms, sizeof(params)); return 0; } @@ -88,8 +115,14 @@ u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) { LOG_WARNING(Service_NVDRV, "called"); + if (!params.handle) { + return static_cast<u32>(NvErrCodes::InvalidValue); + } + auto object = GetObject(params.handle); - ASSERT(object); + if (!object) { + return static_cast<u32>(NvErrCodes::OperationNotPermitted); + } params.id = object->id; @@ -105,7 +138,14 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) { auto itr = std::find_if(handles.begin(), handles.end(), [&](const auto& entry) { return entry.second->id == params.id; }); - ASSERT(itr != handles.end()); + if (itr == handles.end()) { + return static_cast<u32>(NvErrCodes::InvalidValue); + } + + auto& object = itr->second; + if (object->status != Object::Status::Allocated) { + return static_cast<u32>(NvErrCodes::InvalidValue); + } itr->second->refcount++; @@ -125,8 +165,13 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) { LOG_WARNING(Service_NVDRV, "(STUBBED) called type={}", params.param); auto object = GetObject(params.handle); - ASSERT(object); - ASSERT(object->status == Object::Status::Allocated); + if (!object) { + return static_cast<u32>(NvErrCodes::InvalidValue); + } + + if (object->status != Object::Status::Allocated) { + return static_cast<u32>(NvErrCodes::OperationNotPermitted); + } switch (static_cast<ParamTypes>(params.param)) { case ParamTypes::Size: @@ -163,9 +208,12 @@ u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); auto itr = handles.find(params.handle); - ASSERT(itr != handles.end()); - - ASSERT(itr->second->refcount > 0); + if (itr == handles.end()) { + return static_cast<u32>(NvErrCodes::InvalidValue); + } + if (!itr->second->refcount) { + return static_cast<u32>(NvErrCodes::InvalidValue); + } itr->second->refcount--; diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 62f049660..a225cb4cb 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -197,7 +197,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co // Module interface /// Initialize ServiceManager -void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesystem& rfs) { +void Init(std::shared_ptr<SM::ServiceManager>& sm, FileSys::VfsFilesystem& vfs) { // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it // here and pass it into the respective InstallInterfaces functions. auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(); @@ -220,7 +220,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesys EUPLD::InstallInterfaces(*sm); Fatal::InstallInterfaces(*sm); FGM::InstallInterfaces(*sm); - FileSystem::InstallInterfaces(*sm, rfs); + FileSystem::InstallInterfaces(*sm, vfs); Friend::InstallInterfaces(*sm); GRC::InstallInterfaces(*sm); HID::InstallInterfaces(*sm); diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 2fc57a82e..98483ecf1 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -180,8 +180,7 @@ private: }; /// Initialize ServiceManager -void Init(std::shared_ptr<SM::ServiceManager>& sm, - const std::shared_ptr<FileSys::VfsFilesystem>& vfs); +void Init(std::shared_ptr<SM::ServiceManager>& sm, FileSys::VfsFilesystem& vfs); /// Shutdown ServiceManager void Shutdown(); diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index bbc02abcc..184537daa 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -968,6 +968,54 @@ private: rb.PushCopyObjects(vsync_event); } + enum class ConvertedScaleMode : u64 { + None = 0, // VI seems to name this as "Unknown" but lots of games pass it, assume it's no + // scaling/default + Freeze = 1, + ScaleToWindow = 2, + Crop = 3, + NoCrop = 4, + }; + + // This struct is different, currently it's 1:1 but this might change in the future. + enum class NintendoScaleMode : u32 { + None = 0, + Freeze = 1, + ScaleToWindow = 2, + Crop = 3, + NoCrop = 4, + }; + + void ConvertScalingMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto mode = rp.PopEnum<NintendoScaleMode>(); + LOG_DEBUG(Service_VI, "called mode={}", static_cast<u32>(mode)); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + switch (mode) { + case NintendoScaleMode::None: + rb.PushEnum(ConvertedScaleMode::None); + break; + case NintendoScaleMode::Freeze: + rb.PushEnum(ConvertedScaleMode::Freeze); + break; + case NintendoScaleMode::ScaleToWindow: + rb.PushEnum(ConvertedScaleMode::ScaleToWindow); + break; + case NintendoScaleMode::Crop: + rb.PushEnum(ConvertedScaleMode::Crop); + break; + case NintendoScaleMode::NoCrop: + rb.PushEnum(ConvertedScaleMode::NoCrop); + break; + default: + UNIMPLEMENTED_MSG("Unknown scaling mode {}", static_cast<u32>(mode)); + rb.PushEnum(ConvertedScaleMode::None); + break; + } + } + std::shared_ptr<NVFlinger::NVFlinger> nv_flinger; }; @@ -991,7 +1039,7 @@ IApplicationDisplayService::IApplicationDisplayService( {2030, &IApplicationDisplayService::CreateStrayLayer, "CreateStrayLayer"}, {2031, &IApplicationDisplayService::DestroyStrayLayer, "DestroyStrayLayer"}, {2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"}, - {2102, nullptr, "ConvertScalingMode"}, + {2102, &IApplicationDisplayService::ConvertScalingMode, "ConvertScalingMode"}, {2450, nullptr, "GetIndirectLayerImageMap"}, {2451, nullptr, "GetIndirectLayerImageCropMap"}, {2460, nullptr, "GetIndirectLayerImageRequiredMemoryInfo"}, diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 951fd8257..8518dddcb 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -139,14 +139,22 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process) for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) { const FileSys::VirtualFile module_file = dir->GetFile(module); - if (module_file != nullptr) { - const VAddr load_addr = next_load_addr; - next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr, - std::strcmp(module, "rtld") == 0, pm); - LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); - // Register module with GDBStub - GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false); + if (module_file == nullptr) { + continue; } + + const VAddr load_addr = next_load_addr; + const bool should_pass_arguments = std::strcmp(module, "rtld") == 0; + const auto tentative_next_load_addr = + AppLoader_NSO::LoadModule(*module_file, load_addr, should_pass_arguments, pm); + if (!tentative_next_load_addr) { + return ResultStatus::ErrorLoadingNSO; + } + + next_load_addr = *tentative_next_load_addr; + LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); + // Register module with GDBStub + GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false); } process.Run(base_address, metadata.GetMainThreadPriority(), metadata.GetMainThreadStackSize()); diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index e67b49fc9..6057c7f26 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -9,16 +9,11 @@ #include "common/common_types.h" #include "common/file_util.h" #include "common/logging/log.h" -#include "core/core.h" -#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/loader/elf.h" #include "core/memory.h" -using Kernel::CodeSet; -using Kernel::SharedPtr; - //////////////////////////////////////////////////////////////////////////////////////////////////// // ELF Header Constants @@ -211,7 +206,7 @@ public: u32 GetFlags() const { return (u32)(header->e_flags); } - SharedPtr<CodeSet> LoadInto(VAddr vaddr); + Kernel::CodeSet LoadInto(VAddr vaddr); int GetNumSegments() const { return (int)(header->e_phnum); @@ -274,7 +269,7 @@ const char* ElfReader::GetSectionName(int section) const { return nullptr; } -SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) { +Kernel::CodeSet ElfReader::LoadInto(VAddr vaddr) { LOG_DEBUG(Loader, "String section: {}", header->e_shstrndx); // Should we relocate? @@ -302,8 +297,7 @@ SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) { std::vector<u8> program_image(total_image_size); std::size_t current_image_position = 0; - auto& kernel = Core::System::GetInstance().Kernel(); - SharedPtr<CodeSet> codeset = CodeSet::Create(kernel, ""); + Kernel::CodeSet codeset; for (unsigned int i = 0; i < header->e_phnum; ++i) { const Elf32_Phdr* p = &segments[i]; @@ -311,14 +305,14 @@ SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) { p->p_vaddr, p->p_filesz, p->p_memsz); if (p->p_type == PT_LOAD) { - CodeSet::Segment* codeset_segment; + Kernel::CodeSet::Segment* codeset_segment; u32 permission_flags = p->p_flags & (PF_R | PF_W | PF_X); if (permission_flags == (PF_R | PF_X)) { - codeset_segment = &codeset->CodeSegment(); + codeset_segment = &codeset.CodeSegment(); } else if (permission_flags == (PF_R)) { - codeset_segment = &codeset->RODataSegment(); + codeset_segment = &codeset.RODataSegment(); } else if (permission_flags == (PF_R | PF_W)) { - codeset_segment = &codeset->DataSegment(); + codeset_segment = &codeset.DataSegment(); } else { LOG_ERROR(Loader, "Unexpected ELF PT_LOAD segment id {} with flags {:X}", i, p->p_flags); @@ -345,8 +339,8 @@ SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) { } } - codeset->entrypoint = base_addr + header->e_entry; - codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); + codeset.entrypoint = base_addr + header->e_entry; + codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image)); LOG_DEBUG(Loader, "Done loading."); @@ -397,11 +391,11 @@ ResultStatus AppLoader_ELF::Load(Kernel::Process& process) { const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); ElfReader elf_reader(&buffer[0]); - SharedPtr<CodeSet> codeset = elf_reader.LoadInto(base_address); - codeset->name = file->GetName(); + Kernel::CodeSet codeset = elf_reader.LoadInto(base_address); + const VAddr entry_point = codeset.entrypoint; - process.LoadModule(codeset, codeset->entrypoint); - process.Run(codeset->entrypoint, 48, Memory::DEFAULT_STACK_SIZE); + process.LoadModule(std::move(codeset), entry_point); + process.Run(entry_point, 48, Memory::DEFAULT_STACK_SIZE); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 91659ec17..9cd0b0ccd 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -93,7 +93,7 @@ std::string GetFileTypeString(FileType type) { return "unknown"; } -constexpr std::array<const char*, 59> RESULT_MESSAGES{ +constexpr std::array<const char*, 60> RESULT_MESSAGES{ "The operation completed successfully.", "The loader requested to load is already loaded.", "The operation is not implemented.", @@ -128,6 +128,7 @@ constexpr std::array<const char*, 59> RESULT_MESSAGES{ "The RomFS could not be found.", "The ELF file has incorrect size as determined by the header.", "There was a general error loading the NRO into emulated memory.", + "There was a general error loading the NSO into emulated memory.", "There is no icon available.", "There is no control data available.", "The NAX file has a bad header.", diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 0e0333db5..e562b3a04 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -90,6 +90,7 @@ enum class ResultStatus : u16 { ErrorNoRomFS, ErrorIncorrectELFFileSize, ErrorLoadingNRO, + ErrorLoadingNSO, ErrorNoIcon, ErrorNoControl, ErrorBadNAXHeader, diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 25dd3f04e..243b499f2 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -14,7 +14,6 @@ #include "core/file_sys/control_metadata.h" #include "core/file_sys/vfs_offset.h" #include "core/gdbstub/gdbstub.h" -#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/loader/nro.h" @@ -128,10 +127,10 @@ static constexpr u32 PageAlignSize(u32 size) { return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; } -bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) { +bool AppLoader_NRO::LoadNro(const FileSys::VfsFile& file, VAddr load_base) { // Read NSO header NroHeader nro_header{}; - if (sizeof(NroHeader) != file->ReadObject(&nro_header)) { + if (sizeof(NroHeader) != file.ReadObject(&nro_header)) { return {}; } if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) { @@ -139,22 +138,21 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) { } // Build program image - auto& kernel = Core::System::GetInstance().Kernel(); - Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(kernel, ""); - std::vector<u8> program_image = file->ReadBytes(PageAlignSize(nro_header.file_size)); + std::vector<u8> program_image = file.ReadBytes(PageAlignSize(nro_header.file_size)); if (program_image.size() != PageAlignSize(nro_header.file_size)) { return {}; } + Kernel::CodeSet codeset; for (std::size_t i = 0; i < nro_header.segments.size(); ++i) { - codeset->segments[i].addr = nro_header.segments[i].offset; - codeset->segments[i].offset = nro_header.segments[i].offset; - codeset->segments[i].size = PageAlignSize(nro_header.segments[i].size); + codeset.segments[i].addr = nro_header.segments[i].offset; + codeset.segments[i].offset = nro_header.segments[i].offset; + codeset.segments[i].size = PageAlignSize(nro_header.segments[i].size); } if (!Settings::values.program_args.empty()) { const auto arg_data = Settings::values.program_args; - codeset->DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE; + codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE; NSOArgumentHeader args_header{ NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}}; const auto end_offset = program_image.size(); @@ -176,16 +174,15 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) { // Resize program image to include .bss section and page align each section bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset); } - codeset->DataSegment().size += bss_size; + codeset.DataSegment().size += bss_size; program_image.resize(static_cast<u32>(program_image.size()) + bss_size); // Load codeset for current process - codeset->name = file->GetName(); - codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); - Core::CurrentProcess()->LoadModule(codeset, load_base); + codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image)); + Core::CurrentProcess()->LoadModule(std::move(codeset), load_base); // Register module with GDBStub - GDBStub::RegisterModule(codeset->name, load_base, load_base); + GDBStub::RegisterModule(file.GetName(), load_base, load_base); return true; } @@ -198,7 +195,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::Process& process) { // Load NRO const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); - if (!LoadNro(file, base_address)) { + if (!LoadNro(*file, base_address)) { return ResultStatus::ErrorLoadingNRO; } diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h index 04b46119a..50ee5a78a 100644 --- a/src/core/loader/nro.h +++ b/src/core/loader/nro.h @@ -41,7 +41,7 @@ public: bool IsRomFSUpdatable() const override; private: - bool LoadNro(FileSys::VirtualFile file, VAddr load_base); + bool LoadNro(const FileSys::VfsFile& file, VAddr load_base); std::vector<u8> icon_data; std::unique_ptr<FileSys::NACP> nacp; diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 28c6dd9b7..68efca5c0 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -12,7 +12,6 @@ #include "core/core.h" #include "core/file_sys/patch_manager.h" #include "core/gdbstub/gdbstub.h" -#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/vm_manager.h" #include "core/loader/nso.h" @@ -94,42 +93,38 @@ static constexpr u32 PageAlignSize(u32 size) { return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; } -VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base, - bool should_pass_arguments, - boost::optional<FileSys::PatchManager> pm) { - if (file == nullptr) - return {}; - - if (file->GetSize() < sizeof(NsoHeader)) +std::optional<VAddr> AppLoader_NSO::LoadModule(const FileSys::VfsFile& file, VAddr load_base, + bool should_pass_arguments, + std::optional<FileSys::PatchManager> pm) { + if (file.GetSize() < sizeof(NsoHeader)) return {}; NsoHeader nso_header{}; - if (sizeof(NsoHeader) != file->ReadObject(&nso_header)) + if (sizeof(NsoHeader) != file.ReadObject(&nso_header)) return {}; if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) return {}; // Build program image - auto& kernel = Core::System::GetInstance().Kernel(); - Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(kernel, ""); + Kernel::CodeSet codeset; std::vector<u8> program_image; for (std::size_t i = 0; i < nso_header.segments.size(); ++i) { std::vector<u8> data = - file->ReadBytes(nso_header.segments_compressed_size[i], nso_header.segments[i].offset); + file.ReadBytes(nso_header.segments_compressed_size[i], nso_header.segments[i].offset); if (nso_header.IsSegmentCompressed(i)) { data = DecompressSegment(data, nso_header.segments[i]); } program_image.resize(nso_header.segments[i].location); program_image.insert(program_image.end(), data.begin(), data.end()); - codeset->segments[i].addr = nso_header.segments[i].location; - codeset->segments[i].offset = nso_header.segments[i].location; - codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size())); + codeset.segments[i].addr = nso_header.segments[i].location; + codeset.segments[i].offset = nso_header.segments[i].location; + codeset.segments[i].size = PageAlignSize(static_cast<u32>(data.size())); } if (should_pass_arguments && !Settings::values.program_args.empty()) { const auto arg_data = Settings::values.program_args; - codeset->DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE; + codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE; NSOArgumentHeader args_header{ NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}}; const auto end_offset = program_image.size(); @@ -154,12 +149,12 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base, // Resize program image to include .bss section and page align each section bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset); } - codeset->DataSegment().size += bss_size; + codeset.DataSegment().size += bss_size; const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)}; program_image.resize(image_size); // Apply patches if necessary - if (pm != boost::none && pm->HasNSOPatch(nso_header.build_id)) { + if (pm && pm->HasNSOPatch(nso_header.build_id)) { std::vector<u8> pi_header(program_image.size() + 0x100); std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader)); std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size()); @@ -170,12 +165,11 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base, } // Load codeset for current process - codeset->name = file->GetName(); - codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); - Core::CurrentProcess()->LoadModule(codeset, load_base); + codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image)); + Core::CurrentProcess()->LoadModule(std::move(codeset), load_base); // Register module with GDBStub - GDBStub::RegisterModule(codeset->name, load_base, load_base); + GDBStub::RegisterModule(file.GetName(), load_base, load_base); return load_base + image_size; } @@ -187,7 +181,9 @@ ResultStatus AppLoader_NSO::Load(Kernel::Process& process) { // Load module const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress(); - LoadModule(file, base_address, true); + if (!LoadModule(*file, base_address, true)) { + return ResultStatus::ErrorLoadingNSO; + } LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address); process.Run(base_address, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 70ab3b718..433306139 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -4,6 +4,7 @@ #pragma once +#include <optional> #include "common/common_types.h" #include "core/file_sys/patch_manager.h" #include "core/loader/linker.h" @@ -36,8 +37,9 @@ public: return IdentifyType(file); } - static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base, bool should_pass_arguments, - boost::optional<FileSys::PatchManager> pm = boost::none); + static std::optional<VAddr> LoadModule(const FileSys::VfsFile& file, VAddr load_base, + bool should_pass_arguments, + std::optional<FileSys::PatchManager> pm = {}); ResultStatus Load(Kernel::Process& process) override; }; diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 5534ce01c..13e57848d 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -35,7 +35,7 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file) return; std::tie(nacp_file, icon_file) = - FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(control_nca); + FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca); } AppLoader_NSP::~AppLoader_NSP() = default; diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index b006594a6..db91cd01e 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -49,7 +49,7 @@ private: std::unique_ptr<AppLoader> secondary_loader; FileSys::VirtualFile icon_file; - std::shared_ptr<FileSys::NACP> nacp_file; + std::unique_ptr<FileSys::NACP> nacp_file; u64 title_id; }; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index ee5452eb9..7a619acb4 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -30,7 +30,7 @@ AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) return; std::tie(nacp_file, icon_file) = - FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(control_nca); + FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(*control_nca); } AppLoader_XCI::~AppLoader_XCI() = default; diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 770ed1437..46f8dfc9e 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -49,7 +49,7 @@ private: std::unique_ptr<AppLoader_NCA> nca_loader; FileSys::VirtualFile icon_file; - std::shared_ptr<FileSys::NACP> nacp_file; + std::unique_ptr<FileSys::NACP> nacp_file; }; } // namespace Loader diff --git a/src/core/settings.h b/src/core/settings.h index 83b9a04c8..8f2da01c8 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -136,7 +136,7 @@ struct Values { float resolution_factor; bool use_frame_limit; u16 frame_limit; - bool use_accurate_framebuffers; + bool use_accurate_gpu_emulation; float bg_red; float bg_green; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 7b04792b5..0de13edd3 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -163,8 +163,8 @@ TelemetrySession::TelemetrySession() { AddField(Telemetry::FieldType::UserConfig, "Renderer_UseFrameLimit", Settings::values.use_frame_limit); AddField(Telemetry::FieldType::UserConfig, "Renderer_FrameLimit", Settings::values.frame_limit); - AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAccurateFramebuffers", - Settings::values.use_accurate_framebuffers); + AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAccurateGpuEmulation", + Settings::values.use_accurate_gpu_emulation); AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode", Settings::values.use_docked_mode); } diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp index c0a57e71f..37e15bad0 100644 --- a/src/tests/core/arm/arm_test_common.cpp +++ b/src/tests/core/arm/arm_test_common.cpp @@ -15,7 +15,8 @@ namespace ArmTests { TestEnvironment::TestEnvironment(bool mutable_memory_) : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) { - Core::CurrentProcess() = Kernel::Process::Create(kernel, ""); + auto process = Kernel::Process::Create(kernel, ""); + kernel.MakeCurrentProcess(process.get()); page_table = &Core::CurrentProcess()->VMManager().page_table; std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr); diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp index 912e785b9..597b279b9 100644 --- a/src/video_core/engines/fermi_2d.cpp +++ b/src/video_core/engines/fermi_2d.cpp @@ -62,14 +62,16 @@ void Fermi2D::HandleSurfaceCopy() { u8* dst_buffer = Memory::GetPointer(dest_cpu); if (!regs.src.linear && regs.dst.linear) { // If the input is tiled and the output is linear, deswizzle the input and copy it over. - Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel, - dst_bytes_per_pixel, src_buffer, dst_buffer, true, - regs.src.BlockHeight()); + Texture::CopySwizzledData(regs.src.width, regs.src.height, regs.src.depth, + src_bytes_per_pixel, dst_bytes_per_pixel, src_buffer, + dst_buffer, true, regs.src.BlockHeight(), + regs.src.BlockDepth()); } else { // If the input is linear and the output is tiled, swizzle the input and copy it over. - Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel, - dst_bytes_per_pixel, dst_buffer, src_buffer, false, - regs.dst.BlockHeight()); + Texture::CopySwizzledData(regs.src.width, regs.src.height, regs.src.depth, + src_bytes_per_pixel, dst_bytes_per_pixel, dst_buffer, + src_buffer, false, regs.dst.BlockHeight(), + regs.dst.BlockDepth()); } } } diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h index 81d15c62a..2a6e8bbbb 100644 --- a/src/video_core/engines/fermi_2d.h +++ b/src/video_core/engines/fermi_2d.h @@ -36,9 +36,9 @@ public: RenderTargetFormat format; BitField<0, 1, u32> linear; union { - BitField<0, 4, u32> block_depth; + BitField<0, 4, u32> block_width; BitField<4, 4, u32> block_height; - BitField<8, 4, u32> block_width; + BitField<8, 4, u32> block_depth; }; u32 depth; u32 layer; @@ -53,10 +53,20 @@ public: address_low); } + u32 BlockWidth() const { + // The block width is stored in log2 format. + return 1 << block_width; + } + u32 BlockHeight() const { // The block height is stored in log2 format. return 1 << block_height; } + + u32 BlockDepth() const { + // The block depth is stored in log2 format. + return 1 << block_depth; + } }; static_assert(sizeof(Surface) == 0x28, "Surface has incorrect size"); diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 4290da33f..c8d1b6478 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -347,6 +347,16 @@ public: DecrWrap = 8, }; + enum class MemoryLayout : u32 { + Linear = 0, + BlockLinear = 1, + }; + + enum class InvMemoryLayout : u32 { + BlockLinear = 0, + Linear = 1, + }; + struct Cull { enum class FrontFace : u32 { ClockWise = 0x0900, @@ -432,7 +442,12 @@ public: u32 width; u32 height; Tegra::RenderTargetFormat format; - u32 block_dimensions; + union { + BitField<0, 3, u32> block_width; + BitField<4, 3, u32> block_height; + BitField<8, 3, u32> block_depth; + BitField<12, 1, InvMemoryLayout> type; + } memory_layout; u32 array_mode; u32 layer_stride; u32 base_layer; @@ -532,7 +547,21 @@ public: INSERT_PADDING_WORDS(0x3); s32 clear_stencil; - INSERT_PADDING_WORDS(0x6C); + INSERT_PADDING_WORDS(0x17); + + struct { + u32 enable; + union { + BitField<0, 16, u32> min_x; + BitField<16, 16, u32> max_x; + }; + union { + BitField<0, 16, u32> min_y; + BitField<16, 16, u32> max_y; + }; + } scissor_test; + + INSERT_PADDING_WORDS(0x52); s32 stencil_back_func_ref; u32 stencil_back_mask; @@ -548,7 +577,12 @@ public: u32 address_high; u32 address_low; Tegra::DepthFormat format; - u32 block_dimensions; + union { + BitField<0, 4, u32> block_width; + BitField<4, 4, u32> block_height; + BitField<8, 4, u32> block_depth; + BitField<20, 1, InvMemoryLayout> type; + } memory_layout; u32 layer_stride; GPUVAddr Address() const { @@ -1002,6 +1036,7 @@ 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(scissor_test, 0x380); ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5); ASSERT_REG_POSITION(stencil_back_mask, 0x3D6); ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7); diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index aa7481b8c..bf2a21bb6 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -68,12 +68,14 @@ void MaxwellDMA::HandleCopy() { if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) { // If the input is tiled and the output is linear, deswizzle the input and copy it over. - Texture::CopySwizzledData(regs.src_params.size_x, regs.src_params.size_y, 1, 1, src_buffer, - dst_buffer, true, regs.src_params.BlockHeight()); + Texture::CopySwizzledData(regs.src_params.size_x, regs.src_params.size_y, + regs.src_params.size_z, 1, 1, src_buffer, dst_buffer, true, + regs.src_params.BlockHeight(), regs.src_params.BlockDepth()); } else { // If the input is linear and the output is tiled, swizzle the input and copy it over. - Texture::CopySwizzledData(regs.dst_params.size_x, regs.dst_params.size_y, 1, 1, dst_buffer, - src_buffer, false, regs.dst_params.BlockHeight()); + Texture::CopySwizzledData(regs.dst_params.size_x, regs.dst_params.size_y, + regs.dst_params.size_z, 1, 1, dst_buffer, src_buffer, false, + regs.dst_params.BlockHeight(), regs.dst_params.BlockDepth()); } } diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h index 311ccb616..df19e02e2 100644 --- a/src/video_core/engines/maxwell_dma.h +++ b/src/video_core/engines/maxwell_dma.h @@ -43,6 +43,10 @@ public: u32 BlockHeight() const { return 1 << block_height; } + + u32 BlockDepth() const { + return 1 << block_depth; + } }; static_assert(sizeof(Parameters) == 24, "Parameters has wrong size"); diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index b1f137b9c..f356f9a03 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -214,6 +214,18 @@ enum class IMinMaxExchange : u64 { XHi = 3, }; +enum class VmadType : u64 { + Size16_Low = 0, + Size16_High = 1, + Size32 = 2, + Invalid = 3, +}; + +enum class VmadShr : u64 { + Shr7 = 1, + Shr15 = 2, +}; + enum class XmadMode : u64 { None = 0, CLo = 1, @@ -255,7 +267,7 @@ enum class ControlCode : u64 { GTU = 12, NEU = 13, GEU = 14, - // + T = 15, OFF = 16, LO = 17, SFF = 18, @@ -314,6 +326,15 @@ enum class TextureMiscMode : u64 { PTP, }; +enum class IsberdMode : u64 { + None = 0, + Patch = 1, + Prim = 2, + Attr = 3, +}; + +enum class IsberdShift : u64 { None = 0, U16 = 1, B32 = 2 }; + enum class IpaInterpMode : u64 { Linear = 0, Perspective = 1, @@ -340,6 +361,87 @@ struct IpaMode { } }; +enum class SystemVariable : u64 { + LaneId = 0x00, + VirtCfg = 0x02, + VirtId = 0x03, + Pm0 = 0x04, + Pm1 = 0x05, + Pm2 = 0x06, + Pm3 = 0x07, + Pm4 = 0x08, + Pm5 = 0x09, + Pm6 = 0x0a, + Pm7 = 0x0b, + OrderingTicket = 0x0f, + PrimType = 0x10, + InvocationId = 0x11, + Ydirection = 0x12, + ThreadKill = 0x13, + ShaderType = 0x14, + DirectBeWriteAddressLow = 0x15, + DirectBeWriteAddressHigh = 0x16, + DirectBeWriteEnabled = 0x17, + MachineId0 = 0x18, + MachineId1 = 0x19, + MachineId2 = 0x1a, + MachineId3 = 0x1b, + Affinity = 0x1c, + InvocationInfo = 0x1d, + WscaleFactorXY = 0x1e, + WscaleFactorZ = 0x1f, + Tid = 0x20, + TidX = 0x21, + TidY = 0x22, + TidZ = 0x23, + CtaParam = 0x24, + CtaIdX = 0x25, + CtaIdY = 0x26, + CtaIdZ = 0x27, + NtId = 0x28, + CirQueueIncrMinusOne = 0x29, + Nlatc = 0x2a, + SmSpaVersion = 0x2c, + MultiPassShaderInfo = 0x2d, + LwinHi = 0x2e, + SwinHi = 0x2f, + SwinLo = 0x30, + SwinSz = 0x31, + SmemSz = 0x32, + SmemBanks = 0x33, + LwinLo = 0x34, + LwinSz = 0x35, + LmemLosz = 0x36, + LmemHioff = 0x37, + EqMask = 0x38, + LtMask = 0x39, + LeMask = 0x3a, + GtMask = 0x3b, + GeMask = 0x3c, + RegAlloc = 0x3d, + CtxAddr = 0x3e, // .fmask = F_SM50 + BarrierAlloc = 0x3e, // .fmask = F_SM60 + GlobalErrorStatus = 0x40, + WarpErrorStatus = 0x42, + WarpErrorStatusClear = 0x43, + PmHi0 = 0x48, + PmHi1 = 0x49, + PmHi2 = 0x4a, + PmHi3 = 0x4b, + PmHi4 = 0x4c, + PmHi5 = 0x4d, + PmHi6 = 0x4e, + PmHi7 = 0x4f, + ClockLo = 0x50, + ClockHi = 0x51, + GlobalTimerLo = 0x52, + GlobalTimerHi = 0x53, + HwTaskId = 0x60, + CircularQueueEntryIndex = 0x61, + CircularQueueEntryAddressLow = 0x62, + CircularQueueEntryAddressHigh = 0x63, +}; + union Instruction { Instruction& operator=(const Instruction& instr) { value = instr.value; @@ -362,6 +464,7 @@ union Instruction { BitField<48, 16, u64> opcode; union { + BitField<20, 16, u64> imm20_16; BitField<20, 19, u64> imm20_19; BitField<20, 32, s64> imm20_32; BitField<45, 1, u64> negate_b; @@ -403,6 +506,10 @@ union Instruction { } } lop3; + u16 GetImm20_16() const { + return static_cast<u16>(imm20_16); + } + u32 GetImm20_19() const { u32 imm{static_cast<u32>(imm20_19)}; imm <<= 12; @@ -915,6 +1022,35 @@ union Instruction { } bra; union { + BitField<39, 1, u64> emit; // EmitVertex + BitField<40, 1, u64> cut; // EndPrimitive + } out; + + union { + BitField<31, 1, u64> skew; + BitField<32, 1, u64> o; + BitField<33, 2, IsberdMode> mode; + BitField<47, 2, IsberdShift> shift; + } isberd; + + union { + BitField<48, 1, u64> signed_a; + BitField<38, 1, u64> is_byte_chunk_a; + BitField<36, 2, VmadType> type_a; + BitField<36, 2, u64> byte_height_a; + + BitField<49, 1, u64> signed_b; + BitField<50, 1, u64> use_register_b; + BitField<30, 1, u64> is_byte_chunk_b; + BitField<28, 2, VmadType> type_b; + BitField<28, 2, u64> byte_height_b; + + BitField<51, 2, VmadShr> shr; + BitField<55, 1, u64> saturate; // Saturates the result (a * b + c) + BitField<47, 1, u64> cc; + } vmad; + + union { BitField<20, 16, u64> imm20_16; BitField<36, 1, u64> product_shift_left; BitField<37, 1, u64> merge_37; @@ -936,6 +1072,10 @@ union Instruction { BitField<36, 5, u64> index; } cbuf36; + // Unsure about the size of this one. + // It's always used with a gpr0, so any size should be fine. + BitField<20, 8, SystemVariable> sys20; + BitField<47, 1, u64> generates_cc; BitField<61, 1, u64> is_b_imm; BitField<60, 1, u64> is_b_gpr; @@ -975,6 +1115,9 @@ public: TMML, // Texture Mip Map Level EXIT, IPA, + OUT_R, // Emit vertex/primitive + ISBERD, + VMAD, FFMA_IMM, // Fused Multiply and Add FFMA_CR, FFMA_RC, @@ -1034,6 +1177,7 @@ public: MOV_C, MOV_R, MOV_IMM, + MOV_SYS, MOV32_IMM, SHL_C, SHL_R, @@ -1209,6 +1353,9 @@ private: INST("1101111101011---", Id::TMML, Type::Memory, "TMML"), INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"), INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), + INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"), + INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"), + INST("01011111--------", Id::VMAD, Type::Trivial, "VMAD"), INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"), INST("010010011-------", Id::FFMA_CR, Type::Ffma, "FFMA_CR"), INST("010100011-------", Id::FFMA_RC, Type::Ffma, "FFMA_RC"), @@ -1255,6 +1402,7 @@ private: INST("0100110010011---", Id::MOV_C, Type::Arithmetic, "MOV_C"), INST("0101110010011---", Id::MOV_R, Type::Arithmetic, "MOV_R"), INST("0011100-10011---", Id::MOV_IMM, Type::Arithmetic, "MOV_IMM"), + INST("1111000011001---", Id::MOV_SYS, Type::Trivial, "MOV_SYS"), INST("000000010000----", Id::MOV32_IMM, Type::ArithmeticImmediate, "MOV32_IMM"), INST("0100110001100---", Id::FMNMX_C, Type::Arithmetic, "FMNMX_C"), INST("0101110001100---", Id::FMNMX_R, Type::Arithmetic, "FMNMX_R"), diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index ca923d17d..022d4ab74 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -87,6 +87,16 @@ GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) { return gpu_addr; } +GPUVAddr MemoryManager::GetRegionEnd(GPUVAddr region_start) const { + for (const auto& region : mapped_regions) { + const GPUVAddr region_end{region.gpu_addr + region.size}; + if (region_start >= region.gpu_addr && region_start < region_end) { + return region_end; + } + } + return {}; +} + boost::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) { GPUVAddr gpu_addr = 0; u64 free_space = 0; diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h index 86765e72a..caf80093f 100644 --- a/src/video_core/memory_manager.h +++ b/src/video_core/memory_manager.h @@ -26,6 +26,7 @@ public: GPUVAddr MapBufferEx(VAddr cpu_addr, u64 size); GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size); GPUVAddr UnmapBuffer(GPUVAddr gpu_addr, u64 size); + GPUVAddr GetRegionEnd(GPUVAddr region_start) const; boost::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr); std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const; diff --git a/src/video_core/rasterizer_cache.h b/src/video_core/rasterizer_cache.h index 083b283b0..0a3b3951e 100644 --- a/src/video_core/rasterizer_cache.h +++ b/src/video_core/rasterizer_cache.h @@ -11,32 +11,77 @@ #include "common/common_types.h" #include "core/core.h" +#include "core/settings.h" #include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" +class RasterizerCacheObject { +public: + /// Gets the address of the shader in guest memory, required for cache management + virtual VAddr GetAddr() const = 0; + + /// Gets the size of the shader in guest memory, required for cache management + virtual std::size_t GetSizeInBytes() const = 0; + + /// Wriets any cached resources back to memory + virtual void Flush() = 0; + + /// Sets whether the cached object should be considered registered + void SetIsRegistered(bool registered) { + is_registered = registered; + } + + /// Returns true if the cached object is registered + bool IsRegistered() const { + return is_registered; + } + + /// Returns true if the cached object is dirty + bool IsDirty() const { + return is_dirty; + } + + /// Returns ticks from when this cached object was last modified + u64 GetLastModifiedTicks() const { + return last_modified_ticks; + } + + /// Marks an object as recently modified, used to specify whether it is clean or dirty + template <class T> + void MarkAsModified(bool dirty, T& cache) { + is_dirty = dirty; + last_modified_ticks = cache.GetModifiedTicks(); + } + +private: + bool is_registered{}; ///< Whether the object is currently registered with the cache + bool is_dirty{}; ///< Whether the object is dirty (out of sync with guest memory) + u64 last_modified_ticks{}; ///< When the object was last modified, used for in-order flushing +}; + template <class T> class RasterizerCache : NonCopyable { + friend class RasterizerCacheObject; + public: + /// Write any cached resources overlapping the specified region back to memory + void FlushRegion(Tegra::GPUVAddr addr, size_t size) { + const auto& objects{GetSortedObjectsFromRegion(addr, size)}; + for (auto& object : objects) { + FlushObject(object); + } + } + /// Mark the specified region as being invalidated void InvalidateRegion(VAddr addr, u64 size) { - if (size == 0) - return; - - const ObjectInterval interval{addr, addr + size}; - for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) { - for (auto& cached_object : pair.second) { - if (!cached_object) - continue; - - remove_objects.emplace(cached_object); + const auto& objects{GetSortedObjectsFromRegion(addr, size)}; + for (auto& object : objects) { + if (!object->IsRegistered()) { + // Skip duplicates + continue; } + Unregister(object); } - - for (auto& remove_object : remove_objects) { - Unregister(remove_object); - } - - remove_objects.clear(); } /// Invalidates everything in the cache @@ -62,6 +107,7 @@ protected: /// Register an object into the cache void Register(const T& object) { + object->SetIsRegistered(true); object_cache.add({GetInterval(object), ObjectSet{object}}); auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer(); rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1); @@ -69,12 +115,57 @@ protected: /// Unregisters an object from the cache void Unregister(const T& object) { + object->SetIsRegistered(false); auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer(); rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1); + + // Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit + if (Settings::values.use_accurate_gpu_emulation) { + FlushObject(object); + } + object_cache.subtract({GetInterval(object), ObjectSet{object}}); } + /// Returns a ticks counter used for tracking when cached objects were last modified + u64 GetModifiedTicks() { + return ++modified_ticks; + } + private: + /// Returns a list of cached objects from the specified memory region, ordered by access time + std::vector<T> GetSortedObjectsFromRegion(VAddr addr, u64 size) { + if (size == 0) { + return {}; + } + + std::vector<T> objects; + const ObjectInterval interval{addr, addr + size}; + for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) { + for (auto& cached_object : pair.second) { + if (!cached_object) { + continue; + } + objects.push_back(cached_object); + } + } + + std::sort(objects.begin(), objects.end(), [](const T& a, const T& b) -> bool { + return a->GetLastModifiedTicks() < b->GetLastModifiedTicks(); + }); + + return objects; + } + + /// Flushes the specified object, updating appropriate cache state as needed + void FlushObject(const T& object) { + if (!object->IsDirty()) { + return; + } + object->Flush(); + object->MarkAsModified(false, *this); + } + using ObjectSet = std::set<T>; using ObjectCache = boost::icl::interval_map<VAddr, ObjectSet>; using ObjectInterval = typename ObjectCache::interval_type; @@ -84,6 +175,6 @@ private: object->GetAddr() + object->GetSizeInBytes()); } - ObjectCache object_cache; - ObjectSet remove_objects; + ObjectCache object_cache; ///< Cache of objects + u64 modified_ticks{}; ///< Counter of cache state ticks, used for in-order flushing }; diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index 965976334..be29dc8be 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -15,15 +15,18 @@ namespace OpenGL { -struct CachedBufferEntry final { - VAddr GetAddr() const { +struct CachedBufferEntry final : public RasterizerCacheObject { + VAddr GetAddr() const override { return addr; } - std::size_t GetSizeInBytes() const { + std::size_t GetSizeInBytes() const override { return size; } + // We do not have to flush this cache as things in it are never modified by us. + void Flush() override {} + VAddr addr; std::size_t size; GLintptr offset; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 209bdf181..468253033 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -255,7 +255,7 @@ DrawParameters RasterizerOpenGL::SetupDraw() { return params; } -void RasterizerOpenGL::SetupShaders() { +void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { MICROPROFILE_SCOPE(OpenGL_Shader); const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); @@ -270,6 +270,11 @@ void RasterizerOpenGL::SetupShaders() { // Skip stages that are not enabled if (!gpu.regs.IsShaderConfigEnabled(index)) { + switch (program) { + case Maxwell::ShaderProgram::Geometry: + shader_program_manager->UseTrivialGeometryShader(); + break; + } continue; } @@ -281,18 +286,26 @@ void RasterizerOpenGL::SetupShaders() { &ubo, sizeof(ubo), static_cast<std::size_t>(uniform_buffer_alignment)); // Bind the buffer - glBindBufferRange(GL_UNIFORM_BUFFER, stage, buffer_cache.GetHandle(), offset, sizeof(ubo)); + glBindBufferRange(GL_UNIFORM_BUFFER, static_cast<GLuint>(stage), buffer_cache.GetHandle(), + offset, static_cast<GLsizeiptr>(sizeof(ubo))); Shader shader{shader_cache.GetStageProgram(program)}; switch (program) { case Maxwell::ShaderProgram::VertexA: case Maxwell::ShaderProgram::VertexB: { - shader_program_manager->UseProgrammableVertexShader(shader->GetProgramHandle()); + shader_program_manager->UseProgrammableVertexShader( + shader->GetProgramHandle(primitive_mode)); + break; + } + case Maxwell::ShaderProgram::Geometry: { + shader_program_manager->UseProgrammableGeometryShader( + shader->GetProgramHandle(primitive_mode)); break; } case Maxwell::ShaderProgram::Fragment: { - shader_program_manager->UseProgrammableFragmentShader(shader->GetProgramHandle()); + shader_program_manager->UseProgrammableFragmentShader( + shader->GetProgramHandle(primitive_mode)); break; } default: @@ -302,12 +315,13 @@ void RasterizerOpenGL::SetupShaders() { } // Configure the const buffers for this shader stage. - current_constbuffer_bindpoint = SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage), - shader, current_constbuffer_bindpoint); + current_constbuffer_bindpoint = + SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage), shader, primitive_mode, + current_constbuffer_bindpoint); // Configure the textures for this shader stage. current_texture_bindpoint = SetupTextures(static_cast<Maxwell::ShaderStage>(stage), shader, - current_texture_bindpoint); + primitive_mode, current_texture_bindpoint); // When VertexA is enabled, we have dual vertex shaders if (program == Maxwell::ShaderProgram::VertexA) { @@ -317,8 +331,6 @@ void RasterizerOpenGL::SetupShaders() { } state.Apply(); - - shader_program_manager->UseTrivialGeometryShader(); } std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const { @@ -412,6 +424,13 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep // Used when just a single color attachment is enabled, e.g. for clearing a color buffer Surface color_surface = res_cache.GetColorBufferSurface(*single_color_target, preserve_contents); + + if (color_surface) { + // Assume that a surface will be written to if it is used as a framebuffer, even if + // the shader doesn't actually write to it. + color_surface->MarkAsModified(true, res_cache); + } + glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target), GL_TEXTURE_2D, @@ -422,6 +441,13 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep std::array<GLenum, Maxwell::NumRenderTargets> buffers; for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) { Surface color_surface = res_cache.GetColorBufferSurface(index, preserve_contents); + + if (color_surface) { + // Assume that a surface will be written to if it is used as a framebuffer, even + // if the shader doesn't actually write to it. + color_surface->MarkAsModified(true, res_cache); + } + buffers[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index); glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index), @@ -441,6 +467,10 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep } if (depth_surface) { + // Assume that a surface will be written to if it is used as a framebuffer, even if + // the shader doesn't actually write to it. + depth_surface->MarkAsModified(true, res_cache); + if (regs.stencil_enable) { // Attach both depth and stencil glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, @@ -541,6 +571,7 @@ void RasterizerOpenGL::DrawArrays() { SyncLogicOpState(); SyncCullMode(); SyncAlphaTest(); + SyncScissorTest(); SyncTransformFeedback(); SyncPointState(); @@ -580,7 +611,7 @@ void RasterizerOpenGL::DrawArrays() { SetupVertexArrays(); DrawParameters params = SetupDraw(); - SetupShaders(); + SetupShaders(params.primitive_mode); buffer_cache.Unmap(); @@ -604,7 +635,14 @@ void RasterizerOpenGL::DrawArrays() { void RasterizerOpenGL::FlushAll() {} -void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {} +void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) { + MICROPROFILE_SCOPE(OpenGL_CacheManagement); + + if (Settings::values.use_accurate_gpu_emulation) { + // Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit + res_cache.FlushRegion(addr, size); + } +} void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) { MICROPROFILE_SCOPE(OpenGL_CacheManagement); @@ -614,6 +652,7 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) { } void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) { + FlushRegion(addr, size); InvalidateRegion(addr, size); } @@ -719,7 +758,7 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr } u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shader, - u32 current_bindpoint) { + GLenum primitive_mode, u32 current_bindpoint) { MICROPROFILE_SCOPE(OpenGL_UBO); const auto& gpu = Core::System::GetInstance().GPU(); const auto& maxwell3d = gpu.Maxwell3D(); @@ -771,7 +810,7 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad buffer.address, size, static_cast<std::size_t>(uniform_buffer_alignment)); // Now configure the bindpoint of the buffer inside the shader - glUniformBlockBinding(shader->GetProgramHandle(), + glUniformBlockBinding(shader->GetProgramHandle(primitive_mode), shader->GetProgramResourceIndex(used_buffer), current_bindpoint + bindpoint); @@ -787,7 +826,8 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad return current_bindpoint + static_cast<u32>(entries.size()); } -u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, u32 current_unit) { +u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, + GLenum primitive_mode, u32 current_unit) { MICROPROFILE_SCOPE(OpenGL_Texture); const auto& gpu = Core::System::GetInstance().GPU(); const auto& maxwell3d = gpu.Maxwell3D(); @@ -802,8 +842,8 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, // Bind the uniform to the sampler. - glProgramUniform1i(shader->GetProgramHandle(), shader->GetUniformLocation(entry), - current_bindpoint); + glProgramUniform1i(shader->GetProgramHandle(primitive_mode), + shader->GetUniformLocation(entry), current_bindpoint); const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset()); @@ -972,6 +1012,22 @@ void RasterizerOpenGL::SyncAlphaTest() { } } +void RasterizerOpenGL::SyncScissorTest() { + const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + + state.scissor.enabled = (regs.scissor_test.enable != 0); + // TODO(Blinkhawk): Figure if the hardware supports scissor testing per viewport and how it's + // implemented. + if (regs.scissor_test.enable != 0) { + const u32 width = regs.scissor_test.max_x - regs.scissor_test.min_x; + const u32 height = regs.scissor_test.max_y - regs.scissor_test.min_y; + state.scissor.x = regs.scissor_test.min_x; + state.scissor.y = regs.scissor_test.min_y; + state.scissor.width = width; + state.scissor.height = height; + } +} + void RasterizerOpenGL::SyncTransformFeedback() { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 0dab2018b..b1f7ccc7e 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -120,7 +120,7 @@ private: * @returns The next available bindpoint for use in the next shader stage. */ u32 SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader, - u32 current_bindpoint); + GLenum primitive_mode, u32 current_bindpoint); /* * Configures the current textures to use for the draw command. @@ -130,7 +130,7 @@ private: * @returns The next available bindpoint for use in the next shader stage. */ u32 SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader, - u32 current_unit); + GLenum primitive_mode, u32 current_unit); /// Syncs the viewport to match the guest state void SyncViewport(); @@ -165,6 +165,9 @@ private: /// Syncs the alpha test state to match the guest state void SyncAlphaTest(); + /// Syncs the scissor test state to match the guest state + void SyncScissorTest(); + /// Syncs the transform feedback state to match the guest state void SyncTransformFeedback(); @@ -207,7 +210,7 @@ private: DrawParameters SetupDraw(); - void SetupShaders(); + void SetupShaders(GLenum primitive_mode); enum class AccelDraw { Disabled, Arrays, Indexed }; AccelDraw accelerate_draw = AccelDraw::Disabled; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 56ff83eff..1cb77aaf2 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -34,18 +34,57 @@ struct FormatTuple { bool compressed; }; -static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { - auto& gpu{Core::System::GetInstance().GPU()}; - const auto cpu_addr{gpu.MemoryManager().GpuToCpuAddress(gpu_addr)}; - return cpu_addr ? *cpu_addr : 0; +static bool IsPixelFormatASTC(PixelFormat format) { + switch (format) { + case PixelFormat::ASTC_2D_4X4: + case PixelFormat::ASTC_2D_5X4: + case PixelFormat::ASTC_2D_8X8: + case PixelFormat::ASTC_2D_8X5: + return true; + default: + return false; + } +} + +static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) { + switch (format) { + case PixelFormat::ASTC_2D_4X4: + return {4, 4}; + case PixelFormat::ASTC_2D_5X4: + return {5, 4}; + case PixelFormat::ASTC_2D_8X8: + return {8, 8}; + case PixelFormat::ASTC_2D_8X5: + return {8, 5}; + default: + LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format)); + UNREACHABLE(); + } +} + +void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) { + auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; + const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr_)}; + + addr = cpu_addr ? *cpu_addr : 0; + gpu_addr = gpu_addr_; + size_in_bytes = SizeInBytesRaw(); + + if (IsPixelFormatASTC(pixel_format)) { + // ASTC is uncompressed in software, in emulated as RGBA8 + size_in_bytes_gl = width * height * depth * 4; + } else { + size_in_bytes_gl = SizeInBytesGL(); + } } /*static*/ SurfaceParams SurfaceParams::CreateForTexture( const Tegra::Texture::FullTextureInfo& config, const GLShader::SamplerEntry& entry) { SurfaceParams params{}; - params.addr = TryGetCpuAddr(config.tic.Address()); params.is_tiled = config.tic.IsTiled(); + params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, + params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value()); params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); @@ -85,20 +124,23 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { break; } - params.size_in_bytes_total = params.SizeInBytesTotal(); - params.size_in_bytes_2d = params.SizeInBytes2D(); params.max_mip_level = config.tic.max_mip_level + 1; params.rt = {}; + params.InitCacheParameters(config.tic.Address()); + return params; } /*static*/ SurfaceParams SurfaceParams::CreateForFramebuffer(std::size_t index) { const auto& config{Core::System::GetInstance().GPU().Maxwell3D().regs.rt[index]}; SurfaceParams params{}; - params.addr = TryGetCpuAddr(config.Address()); - params.is_tiled = true; - params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight; + + params.is_tiled = + config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; + params.block_width = 1 << config.memory_layout.block_width; + params.block_height = 1 << config.memory_layout.block_height; + params.block_depth = 1 << config.memory_layout.block_depth; params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); params.component_type = ComponentTypeFromRenderTarget(config.format); params.type = GetFormatType(params.pixel_format); @@ -107,8 +149,6 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { params.unaligned_height = config.height; params.target = SurfaceTarget::Texture2D; params.depth = 1; - params.size_in_bytes_total = params.SizeInBytesTotal(); - params.size_in_bytes_2d = params.SizeInBytes2D(); params.max_mip_level = 0; // Render target specific parameters, not used for caching @@ -117,16 +157,21 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { params.rt.layer_stride = config.layer_stride; params.rt.base_layer = config.base_layer; + params.InitCacheParameters(config.Address()); + return params; } -/*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer(u32 zeta_width, u32 zeta_height, - Tegra::GPUVAddr zeta_address, - Tegra::DepthFormat format) { +/*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer( + u32 zeta_width, u32 zeta_height, Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format, + u32 block_width, u32 block_height, u32 block_depth, + Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) { SurfaceParams params{}; - params.addr = TryGetCpuAddr(zeta_address); - params.is_tiled = true; - params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight; + + params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; + params.block_width = 1 << std::min(block_width, 5U); + params.block_height = 1 << std::min(block_height, 5U); + params.block_depth = 1 << std::min(block_depth, 5U); params.pixel_format = PixelFormatFromDepthFormat(format); params.component_type = ComponentTypeFromDepthFormat(format); params.type = GetFormatType(params.pixel_format); @@ -135,20 +180,22 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { params.unaligned_height = zeta_height; params.target = SurfaceTarget::Texture2D; params.depth = 1; - params.size_in_bytes_total = params.SizeInBytesTotal(); - params.size_in_bytes_2d = params.SizeInBytes2D(); params.max_mip_level = 0; params.rt = {}; + params.InitCacheParameters(zeta_address); + return params; } /*static*/ SurfaceParams SurfaceParams::CreateForFermiCopySurface( const Tegra::Engines::Fermi2D::Regs::Surface& config) { SurfaceParams params{}; - params.addr = TryGetCpuAddr(config.Address()); + params.is_tiled = !config.linear; - params.block_height = params.is_tiled ? config.BlockHeight() : 0, + params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0, + params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0, + params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0, params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); params.component_type = ComponentTypeFromRenderTarget(config.format); params.type = GetFormatType(params.pixel_format); @@ -157,11 +204,11 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { params.unaligned_height = config.height; params.target = SurfaceTarget::Texture2D; params.depth = 1; - params.size_in_bytes_total = params.SizeInBytesTotal(); - params.size_in_bytes_2d = params.SizeInBytes2D(); params.max_mip_level = 0; params.rt = {}; + params.InitCacheParameters(config.Address()); + return params; } @@ -221,6 +268,8 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RG32UI {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // R32UI {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8 + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5 + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4 // Depth formats {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F @@ -264,28 +313,6 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType return format; } -static bool IsPixelFormatASTC(PixelFormat format) { - switch (format) { - case PixelFormat::ASTC_2D_4X4: - case PixelFormat::ASTC_2D_8X8: - return true; - default: - return false; - } -} - -static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) { - switch (format) { - case PixelFormat::ASTC_2D_4X4: - return {4, 4}; - case PixelFormat::ASTC_2D_8X8: - return {8, 8}; - default: - LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format)); - UNREACHABLE(); - } -} - MathUtil::Rectangle<u32> SurfaceParams::GetRect() const { u32 actual_height{unaligned_height}; if (IsPixelFormatASTC(pixel_format)) { @@ -313,29 +340,27 @@ static bool IsFormatBCn(PixelFormat format) { } template <bool morton_to_gl, PixelFormat format> -void MortonCopy(u32 stride, u32 block_height, u32 height, u8* gl_buffer, std::size_t gl_buffer_size, - VAddr addr) { - constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT; - constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format); +void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth, u8* gl_buffer, + std::size_t gl_buffer_size, VAddr addr) { + constexpr u32 bytes_per_pixel = SurfaceParams::GetBytesPerPixel(format); + + // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual + // pixel values. + const u32 tile_size{IsFormatBCn(format) ? 4U : 1U}; if (morton_to_gl) { - // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual - // pixel values. - const u32 tile_size{IsFormatBCn(format) ? 4U : 1U}; const std::vector<u8> data = Tegra::Texture::UnswizzleTexture( - addr, tile_size, bytes_per_pixel, stride, height, block_height); + addr, tile_size, bytes_per_pixel, stride, height, depth, block_height, block_depth); const std::size_t size_to_copy{std::min(gl_buffer_size, data.size())}; memcpy(gl_buffer, data.data(), size_to_copy); } else { - // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should - // check the configuration for this and perform more generic un/swizzle - LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!"); - VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel, - Memory::GetPointer(addr), gl_buffer, morton_to_gl); + Tegra::Texture::CopySwizzledData(stride / tile_size, height / tile_size, depth, + bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(addr), + gl_buffer, false, block_height, block_depth); } } -static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr), +static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, VAddr), SurfaceParams::MaxPixelFormat> morton_to_gl_fns = { // clang-format off @@ -385,6 +410,8 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr), MortonCopy<true, PixelFormat::RG32UI>, MortonCopy<true, PixelFormat::R32UI>, MortonCopy<true, PixelFormat::ASTC_2D_8X8>, + MortonCopy<true, PixelFormat::ASTC_2D_8X5>, + MortonCopy<true, PixelFormat::ASTC_2D_5X4>, MortonCopy<true, PixelFormat::Z32F>, MortonCopy<true, PixelFormat::Z16>, MortonCopy<true, PixelFormat::Z24S8>, @@ -393,7 +420,7 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr), // clang-format on }; -static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr), +static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, VAddr), SurfaceParams::MaxPixelFormat> gl_to_morton_fns = { // clang-format off @@ -410,17 +437,16 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr), MortonCopy<false, PixelFormat::RGBA16UI>, MortonCopy<false, PixelFormat::R11FG11FB10F>, MortonCopy<false, PixelFormat::RGBA32UI>, - // TODO(Subv): Swizzling DXT1/DXT23/DXT45/DXN1/DXN2/BC7U/BC6H_UF16/BC6H_SF16/ASTC_2D_4X4 - // formats are not supported - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, + MortonCopy<false, PixelFormat::DXT1>, + MortonCopy<false, PixelFormat::DXT23>, + MortonCopy<false, PixelFormat::DXT45>, + MortonCopy<false, PixelFormat::DXN1>, + MortonCopy<false, PixelFormat::DXN2UNORM>, + MortonCopy<false, PixelFormat::DXN2SNORM>, + MortonCopy<false, PixelFormat::BC7U>, + MortonCopy<false, PixelFormat::BC6H_UF16>, + MortonCopy<false, PixelFormat::BC6H_SF16>, + // TODO(Subv): Swizzling ASTC formats are not supported nullptr, MortonCopy<false, PixelFormat::G8R8U>, MortonCopy<false, PixelFormat::G8R8S>, @@ -445,6 +471,8 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr), MortonCopy<false, PixelFormat::RG32UI>, MortonCopy<false, PixelFormat::R32UI>, nullptr, + nullptr, + nullptr, MortonCopy<false, PixelFormat::Z32F>, MortonCopy<false, PixelFormat::Z16>, MortonCopy<false, PixelFormat::Z24S8>, @@ -604,22 +632,21 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface, auto source_format = GetFormatTuple(src_params.pixel_format, src_params.component_type); auto dest_format = GetFormatTuple(dst_params.pixel_format, dst_params.component_type); - std::size_t buffer_size = - std::max(src_params.size_in_bytes_total, dst_params.size_in_bytes_total); + std::size_t buffer_size = std::max(src_params.size_in_bytes, dst_params.size_in_bytes); glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle); glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB); if (source_format.compressed) { glGetCompressedTextureImage(src_surface->Texture().handle, src_attachment, - static_cast<GLsizei>(src_params.size_in_bytes_total), nullptr); + static_cast<GLsizei>(src_params.size_in_bytes), nullptr); } else { glGetTextureImage(src_surface->Texture().handle, src_attachment, source_format.format, - source_format.type, static_cast<GLsizei>(src_params.size_in_bytes_total), + source_format.type, static_cast<GLsizei>(src_params.size_in_bytes), nullptr); } // If the new texture is bigger than the previous one, we need to fill in the rest with data // from the CPU. - if (src_params.size_in_bytes_total < dst_params.size_in_bytes_total) { + if (src_params.size_in_bytes < dst_params.size_in_bytes) { // Upload the rest of the memory. if (dst_params.is_tiled) { // TODO(Subv): We might have to de-tile the subtexture and re-tile it with the rest @@ -629,12 +656,12 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface, LOG_DEBUG(HW_GPU, "Trying to upload extra texture data from the CPU during " "reinterpretation but the texture is tiled."); } - std::size_t remaining_size = - dst_params.size_in_bytes_total - src_params.size_in_bytes_total; + std::size_t remaining_size = dst_params.size_in_bytes - src_params.size_in_bytes; std::vector<u8> data(remaining_size); - Memory::ReadBlock(dst_params.addr + src_params.size_in_bytes_total, data.data(), - data.size()); - glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes_total, remaining_size, + std::memcpy(data.data(), Memory::GetPointer(dst_params.addr + src_params.size_in_bytes), + data.size()); + + glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes, remaining_size, data.data()); } @@ -680,7 +707,8 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface, } CachedSurface::CachedSurface(const SurfaceParams& params) - : params(params), gl_target(SurfaceTargetToGL(params.target)) { + : params(params), gl_target(SurfaceTargetToGL(params.target)), + cached_size_in_bytes(params.size_in_bytes) { texture.Create(); const auto& rect{params.GetRect()}; @@ -730,9 +758,21 @@ CachedSurface::CachedSurface(const SurfaceParams& params) VideoCore::LabelGLObject(GL_TEXTURE, texture.handle, params.addr, SurfaceParams::SurfaceTargetName(params.target)); + + // Clamp size to mapped GPU memory region + // TODO(bunnei): Super Mario Odyssey maps a 0x40000 byte region and then uses it for a 0x80000 + // R32F render buffer. We do not yet know if this is a game bug or something else, but this + // check is necessary to prevent flushing from overwriting unmapped memory. + + auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; + const u64 max_size{memory_manager.GetRegionEnd(params.gpu_addr) - params.gpu_addr}; + if (cached_size_in_bytes > max_size) { + LOG_ERROR(HW_GPU, "Surface size {} exceeds region size {}", params.size_in_bytes, max_size); + cached_size_in_bytes = max_size; + } } -static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) { +static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height, bool reverse) { union S8Z24 { BitField<0, 24, u32> z24; BitField<24, 8, u32> s8; @@ -745,22 +785,29 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) { }; static_assert(sizeof(Z24S8) == 4, "Z24S8 is incorrect size"); - S8Z24 input_pixel{}; - Z24S8 output_pixel{}; - constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::S8Z24)}; + S8Z24 s8z24_pixel{}; + Z24S8 z24s8_pixel{}; + constexpr auto bpp{SurfaceParams::GetBytesPerPixel(PixelFormat::S8Z24)}; for (std::size_t y = 0; y < height; ++y) { for (std::size_t x = 0; x < width; ++x) { const std::size_t offset{bpp * (y * width + x)}; - std::memcpy(&input_pixel, &data[offset], sizeof(S8Z24)); - output_pixel.s8.Assign(input_pixel.s8); - output_pixel.z24.Assign(input_pixel.z24); - std::memcpy(&data[offset], &output_pixel, sizeof(Z24S8)); + if (reverse) { + std::memcpy(&z24s8_pixel, &data[offset], sizeof(Z24S8)); + s8z24_pixel.s8.Assign(z24s8_pixel.s8); + s8z24_pixel.z24.Assign(z24s8_pixel.z24); + std::memcpy(&data[offset], &s8z24_pixel, sizeof(S8Z24)); + } else { + std::memcpy(&s8z24_pixel, &data[offset], sizeof(S8Z24)); + z24s8_pixel.s8.Assign(s8z24_pixel.s8); + z24s8_pixel.z24.Assign(s8z24_pixel.z24); + std::memcpy(&data[offset], &z24s8_pixel, sizeof(Z24S8)); + } } } } static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) { - constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8U)}; + constexpr auto bpp{SurfaceParams::GetBytesPerPixel(PixelFormat::G8R8U)}; for (std::size_t y = 0; y < height; ++y) { for (std::size_t x = 0; x < width; ++x) { const std::size_t offset{bpp * (y * width + x)}; @@ -780,7 +827,9 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma u32 width, u32 height) { switch (pixel_format) { case PixelFormat::ASTC_2D_4X4: - case PixelFormat::ASTC_2D_8X8: { + case PixelFormat::ASTC_2D_8X8: + case PixelFormat::ASTC_2D_8X5: + case PixelFormat::ASTC_2D_5X4: { // Convert ASTC pixel formats to RGBA8, as most desktop GPUs do not support ASTC. u32 block_width{}; u32 block_height{}; @@ -790,7 +839,7 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma } case PixelFormat::S8Z24: // Convert the S8Z24 depth format to Z24S8, as OpenGL does not support S8Z24. - ConvertS8Z24ToZ24S8(data, width, height); + ConvertS8Z24ToZ24S8(data, width, height, false); break; case PixelFormat::G8R8U: @@ -801,49 +850,54 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma } } +/** + * Helper function to perform software conversion (as needed) when flushing a buffer from OpenGL to + * Switch memory. This is for Maxwell pixel formats that cannot be represented as-is in OpenGL or + * with typical desktop GPUs. + */ +static void ConvertFormatAsNeeded_FlushGLBuffer(std::vector<u8>& data, PixelFormat pixel_format, + u32 width, u32 height) { + switch (pixel_format) { + case PixelFormat::G8R8U: + case PixelFormat::G8R8S: + case PixelFormat::ASTC_2D_4X4: + case PixelFormat::ASTC_2D_8X8: { + LOG_CRITICAL(HW_GPU, "Conversion of format {} after texture flushing is not implemented", + static_cast<u32>(pixel_format)); + UNREACHABLE(); + break; + } + case PixelFormat::S8Z24: + // Convert the Z24S8 depth format to S8Z24, as OpenGL does not support S8Z24. + ConvertS8Z24ToZ24S8(data, width, height, true); + break; + } +} + MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192)); void CachedSurface::LoadGLBuffer() { - ASSERT(params.type != SurfaceType::Fill); - - const u8* const texture_src_data = Memory::GetPointer(params.addr); - - ASSERT(texture_src_data); - - const u32 bytes_per_pixel = GetGLBytesPerPixel(params.pixel_format); - const u32 copy_size = params.width * params.height * bytes_per_pixel; - const std::size_t total_size = copy_size * params.depth; - MICROPROFILE_SCOPE(OpenGL_SurfaceLoad); + gl_buffer.resize(params.size_in_bytes_gl); if (params.is_tiled) { - gl_buffer.resize(total_size); + u32 depth = params.depth; + u32 block_depth = params.block_depth; - // TODO(bunnei): This only unswizzles and copies a 2D texture - we do not yet know how to do - // this for 3D textures, etc. - switch (params.target) { - case SurfaceParams::SurfaceTarget::Texture2D: - // Pass impl. to the fallback code below - break; - case SurfaceParams::SurfaceTarget::Texture2DArray: - case SurfaceParams::SurfaceTarget::TextureCubemap: - for (std::size_t index = 0; index < params.depth; ++index) { - const std::size_t offset{index * copy_size}; - morton_to_gl_fns[static_cast<std::size_t>(params.pixel_format)]( - params.width, params.block_height, params.height, gl_buffer.data() + offset, - copy_size, params.addr + offset); - } - break; - default: - LOG_CRITICAL(HW_GPU, "Unimplemented tiled load for target={}", - static_cast<u32>(params.target)); - UNREACHABLE(); + ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}", + params.block_width, static_cast<u32>(params.target)); + + if (params.target == SurfaceParams::SurfaceTarget::Texture2D) { + // TODO(Blinkhawk): Eliminate this condition once all texture types are implemented. + depth = 1U; + block_depth = 1U; } morton_to_gl_fns[static_cast<std::size_t>(params.pixel_format)]( - params.width, params.block_height, params.height, gl_buffer.data(), copy_size, - params.addr); + params.width, params.block_height, params.height, block_depth, depth, gl_buffer.data(), + gl_buffer.size(), params.addr); } else { - const u8* const texture_src_data_end{texture_src_data + total_size}; + const auto texture_src_data{Memory::GetPointer(params.addr)}; + const auto texture_src_data_end{texture_src_data + params.size_in_bytes_gl}; gl_buffer.assign(texture_src_data, texture_src_data_end); } @@ -852,7 +906,44 @@ void CachedSurface::LoadGLBuffer() { MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64)); void CachedSurface::FlushGLBuffer() { - ASSERT_MSG(false, "Unimplemented"); + MICROPROFILE_SCOPE(OpenGL_SurfaceFlush); + + ASSERT_MSG(!IsPixelFormatASTC(params.pixel_format), "Unimplemented"); + + // OpenGL temporary buffer needs to be big enough to store raw texture size + gl_buffer.resize(GetSizeInBytes()); + + const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type); + // Ensure no bad interactions with GL_UNPACK_ALIGNMENT + ASSERT(params.width * SurfaceParams::GetBytesPerPixel(params.pixel_format) % 4 == 0); + glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width)); + ASSERT(!tuple.compressed); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + glGetTextureImage(texture.handle, 0, tuple.format, tuple.type, gl_buffer.size(), + gl_buffer.data()); + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + ConvertFormatAsNeeded_FlushGLBuffer(gl_buffer, params.pixel_format, params.width, + params.height); + ASSERT(params.type != SurfaceType::Fill); + const u8* const texture_src_data = Memory::GetPointer(params.addr); + ASSERT(texture_src_data); + if (params.is_tiled) { + u32 depth = params.depth; + u32 block_depth = params.block_depth; + + ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}", + params.block_width, static_cast<u32>(params.target)); + + if (params.target == SurfaceParams::SurfaceTarget::Texture2D) { + // TODO(Blinkhawk): Eliminate this condition once all texture types are implemented. + depth = 1U; + } + gl_to_morton_fns[static_cast<size_t>(params.pixel_format)]( + params.width, params.block_height, params.height, block_depth, depth, gl_buffer.data(), + gl_buffer.size(), GetAddr()); + } else { + std::memcpy(Memory::GetPointer(GetAddr()), gl_buffer.data(), GetSizeInBytes()); + } } MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192)); @@ -862,9 +953,6 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle MICROPROFILE_SCOPE(OpenGL_TextureUL); - ASSERT(gl_buffer.size() == static_cast<std::size_t>(params.width) * params.height * - GetGLBytesPerPixel(params.pixel_format) * params.depth); - const auto& rect{params.GetRect()}; // Load data from memory to the surface @@ -873,7 +961,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle std::size_t buffer_offset = static_cast<std::size_t>(static_cast<std::size_t>(y0) * params.width + static_cast<std::size_t>(x0)) * - GetGLBytesPerPixel(params.pixel_format); + SurfaceParams::GetBytesPerPixel(params.pixel_format); const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type); const GLuint target_tex = texture.handle; @@ -889,7 +977,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle cur_state.Apply(); // Ensure no bad interactions with GL_UNPACK_ALIGNMENT - ASSERT(params.width * GetGLBytesPerPixel(params.pixel_format) % 4 == 0); + ASSERT(params.width * SurfaceParams::GetBytesPerPixel(params.pixel_format) % 4 == 0); glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.width)); glActiveTexture(GL_TEXTURE0); @@ -899,7 +987,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle glCompressedTexImage2D( SurfaceTargetToGL(params.target), 0, tuple.internal_format, static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), 0, - static_cast<GLsizei>(params.size_in_bytes_2d), &gl_buffer[buffer_offset]); + static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]); break; case SurfaceParams::SurfaceTarget::Texture3D: case SurfaceParams::SurfaceTarget::Texture2DArray: @@ -907,16 +995,16 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle SurfaceTargetToGL(params.target), 0, tuple.internal_format, static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), static_cast<GLsizei>(params.depth), 0, - static_cast<GLsizei>(params.size_in_bytes_total), &gl_buffer[buffer_offset]); + static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]); break; case SurfaceParams::SurfaceTarget::TextureCubemap: for (std::size_t face = 0; face < params.depth; ++face) { glCompressedTexImage2D(static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), 0, tuple.internal_format, static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), 0, - static_cast<GLsizei>(params.size_in_bytes_2d), + static_cast<GLsizei>(params.SizeInBytesCubeFaceGL()), &gl_buffer[buffer_offset]); - buffer_offset += params.size_in_bytes_2d; + buffer_offset += params.SizeInBytesCubeFace(); } break; default: @@ -926,7 +1014,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle glCompressedTexImage2D( GL_TEXTURE_2D, 0, tuple.internal_format, static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), 0, - static_cast<GLsizei>(params.size_in_bytes_2d), &gl_buffer[buffer_offset]); + static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]); } } else { @@ -955,7 +1043,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle y0, static_cast<GLsizei>(rect.GetWidth()), static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type, &gl_buffer[buffer_offset]); - buffer_offset += params.size_in_bytes_2d; + buffer_offset += params.SizeInBytesCubeFace(); } break; default: @@ -989,7 +1077,9 @@ Surface RasterizerCacheOpenGL::GetDepthBufferSurface(bool preserve_contents) { } SurfaceParams depth_params{SurfaceParams::CreateForDepthBuffer( - regs.zeta_width, regs.zeta_height, regs.zeta.Address(), regs.zeta.format)}; + regs.zeta_width, regs.zeta_height, regs.zeta.Address(), regs.zeta.format, + regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height, + regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)}; return GetSurface(depth_params, preserve_contents); } @@ -1015,10 +1105,7 @@ Surface RasterizerCacheOpenGL::GetColorBufferSurface(std::size_t index, bool pre void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) { surface->LoadGLBuffer(); surface->UploadGLTexture(read_framebuffer.handle, draw_framebuffer.handle); -} - -void RasterizerCacheOpenGL::FlushSurface(const Surface& surface) { - surface->FlushGLBuffer(); + surface->MarkAsModified(false, *this); } Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) { @@ -1087,6 +1174,14 @@ void RasterizerCacheOpenGL::FermiCopySurface( FastCopySurface(GetSurface(src_params, true), GetSurface(dst_params, false)); } +void RasterizerCacheOpenGL::AccurateCopySurface(const Surface& src_surface, + const Surface& dst_surface) { + const auto& src_params{src_surface->GetSurfaceParams()}; + const auto& dst_params{dst_surface->GetSurfaceParams()}; + FlushRegion(src_params.addr, dst_params.size_in_bytes); + LoadSurface(dst_surface); +} + Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, const SurfaceParams& new_params) { // Verify surface is compatible for blitting @@ -1095,6 +1190,12 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, // Get a new surface with the new parameters, and blit the previous surface to it Surface new_surface{GetUncachedSurface(new_params)}; + // With use_accurate_gpu_emulation enabled, do an accurate surface copy + if (Settings::values.use_accurate_gpu_emulation) { + AccurateCopySurface(old_surface, new_surface); + return new_surface; + } + // For compatible surfaces, we can just do fast glCopyImageSubData based copy if (old_params.target == new_params.target && old_params.type == new_params.type && old_params.depth == new_params.depth && old_params.depth == 1 && @@ -1106,11 +1207,10 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, // If the format is the same, just do a framebuffer blit. This is significantly faster than // using PBOs. The is also likely less accurate, as textures will be converted rather than - // reinterpreted. When use_accurate_framebuffers setting is enabled, perform a more accurate + // reinterpreted. When use_accurate_gpu_emulation setting is enabled, perform a more accurate // surface copy, where pixels are reinterpreted as a new format (without conversion). This // code path uses OpenGL PBOs and is quite slow. - const bool is_blit{old_params.pixel_format == new_params.pixel_format || - !Settings::values.use_accurate_framebuffers}; + const bool is_blit{old_params.pixel_format == new_params.pixel_format}; switch (new_params.target) { case SurfaceParams::SurfaceTarget::Texture2D: diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 0b4940b3c..7c1cb72d0 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -18,6 +18,7 @@ #include "video_core/rasterizer_cache.h" #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_shader_gen.h" +#include "video_core/textures/decoders.h" #include "video_core/textures/texture.h" namespace OpenGL { @@ -74,19 +75,21 @@ struct SurfaceParams { RG32UI = 43, R32UI = 44, ASTC_2D_8X8 = 45, + ASTC_2D_8X5 = 46, + ASTC_2D_5X4 = 47, MaxColorFormat, // Depth formats - Z32F = 46, - Z16 = 47, + Z32F = 48, + Z16 = 49, MaxDepthFormat, // DepthStencil formats - Z24S8 = 48, - S8Z24 = 49, - Z32FS8 = 50, + Z24S8 = 50, + S8Z24 = 51, + Z32FS8 = 52, MaxDepthStencilFormat, @@ -220,6 +223,8 @@ struct SurfaceParams { 1, // RG32UI 1, // R32UI 4, // ASTC_2D_8X8 + 4, // ASTC_2D_8X5 + 4, // ASTC_2D_5X4 1, // Z32F 1, // Z16 1, // Z24S8 @@ -282,6 +287,8 @@ struct SurfaceParams { 64, // RG32UI 32, // R32UI 16, // ASTC_2D_8X8 + 32, // ASTC_2D_8X5 + 32, // ASTC_2D_5X4 32, // Z32F 16, // Z16 32, // Z24S8 @@ -553,8 +560,12 @@ struct SurfaceParams { return PixelFormat::BC6H_SF16; case Tegra::Texture::TextureFormat::ASTC_2D_4X4: return PixelFormat::ASTC_2D_4X4; + case Tegra::Texture::TextureFormat::ASTC_2D_5X4: + return PixelFormat::ASTC_2D_5X4; case Tegra::Texture::TextureFormat::ASTC_2D_8X8: return PixelFormat::ASTC_2D_8X8; + case Tegra::Texture::TextureFormat::ASTC_2D_8X5: + return PixelFormat::ASTC_2D_8X5; case Tegra::Texture::TextureFormat::R16_G16: switch (component_type) { case Tegra::Texture::ComponentType::FLOAT: @@ -691,21 +702,42 @@ struct SurfaceParams { return SurfaceType::Invalid; } + /// Returns the sizer in bytes of the specified pixel format + static constexpr u32 GetBytesPerPixel(PixelFormat pixel_format) { + if (pixel_format == SurfaceParams::PixelFormat::Invalid) { + return 0; + } + return GetFormatBpp(pixel_format) / CHAR_BIT; + } + /// Returns the rectangle corresponding to this surface MathUtil::Rectangle<u32> GetRect() const; - /// Returns the size of this surface as a 2D texture in bytes, adjusted for compression - std::size_t SizeInBytes2D() const { + /// Returns the total size of this surface in bytes, adjusted for compression + std::size_t SizeInBytesRaw(bool ignore_tiled = false) const { const u32 compression_factor{GetCompressionFactor(pixel_format)}; - ASSERT(width % compression_factor == 0); - ASSERT(height % compression_factor == 0); - return (width / compression_factor) * (height / compression_factor) * - GetFormatBpp(pixel_format) / CHAR_BIT; + const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)}; + const size_t uncompressed_size{ + Tegra::Texture::CalculateSize((ignore_tiled ? false : is_tiled), bytes_per_pixel, width, + height, depth, block_height, block_depth)}; + + // Divide by compression_factor^2, as height and width are factored by this + return uncompressed_size / (compression_factor * compression_factor); } - /// Returns the total size of this surface in bytes, adjusted for compression - std::size_t SizeInBytesTotal() const { - return SizeInBytes2D() * depth; + /// Returns the size of this surface as an OpenGL texture in bytes + std::size_t SizeInBytesGL() const { + return SizeInBytesRaw(true); + } + + /// Returns the size of this surface as a cube face in bytes + std::size_t SizeInBytesCubeFace() const { + return size_in_bytes / 6; + } + + /// Returns the size of this surface as an OpenGL cube face in bytes + std::size_t SizeInBytesCubeFaceGL() const { + return size_in_bytes_gl / 6; } /// Creates SurfaceParams from a texture configuration @@ -716,9 +748,10 @@ struct SurfaceParams { static SurfaceParams CreateForFramebuffer(std::size_t index); /// Creates SurfaceParams for a depth buffer configuration - static SurfaceParams CreateForDepthBuffer(u32 zeta_width, u32 zeta_height, - Tegra::GPUVAddr zeta_address, - Tegra::DepthFormat format); + static SurfaceParams CreateForDepthBuffer( + u32 zeta_width, u32 zeta_height, Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format, + u32 block_width, u32 block_height, u32 block_depth, + Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type); /// Creates SurfaceParams for a Fermi2D surface copy static SurfaceParams CreateForFermiCopySurface( @@ -731,9 +764,13 @@ struct SurfaceParams { other.depth); } - VAddr addr; + /// Initializes parameters for caching, should be called after everything has been initialized + void InitCacheParameters(Tegra::GPUVAddr gpu_addr); + bool is_tiled; + u32 block_width; u32 block_height; + u32 block_depth; PixelFormat pixel_format; ComponentType component_type; SurfaceType type; @@ -741,11 +778,15 @@ struct SurfaceParams { u32 height; u32 depth; u32 unaligned_height; - std::size_t size_in_bytes_total; - std::size_t size_in_bytes_2d; SurfaceTarget target; u32 max_mip_level; + // Parameters used for caching + VAddr addr; + Tegra::GPUVAddr gpu_addr; + std::size_t size_in_bytes; + std::size_t size_in_bytes_gl; + // Render target specific parameters, not used in caching struct { u32 index; @@ -762,7 +803,8 @@ struct SurfaceReserveKey : Common::HashableStruct<OpenGL::SurfaceParams> { static SurfaceReserveKey Create(const OpenGL::SurfaceParams& params) { SurfaceReserveKey res; res.state = params; - res.state.rt = {}; // Ignore rt config in caching + res.state.gpu_addr = {}; // Ignore GPU vaddr in caching + res.state.rt = {}; // Ignore rt config in caching return res; } }; @@ -777,16 +819,20 @@ struct hash<SurfaceReserveKey> { namespace OpenGL { -class CachedSurface final { +class CachedSurface final : public RasterizerCacheObject { public: CachedSurface(const SurfaceParams& params); - VAddr GetAddr() const { + VAddr GetAddr() const override { return params.addr; } - std::size_t GetSizeInBytes() const { - return params.size_in_bytes_total; + std::size_t GetSizeInBytes() const override { + return cached_size_in_bytes; + } + + void Flush() override { + FlushGLBuffer(); } const OGLTexture& Texture() const { @@ -797,13 +843,6 @@ public: return gl_target; } - static constexpr unsigned int GetGLBytesPerPixel(SurfaceParams::PixelFormat format) { - if (format == SurfaceParams::PixelFormat::Invalid) - return 0; - - return SurfaceParams::GetFormatBpp(format) / CHAR_BIT; - } - const SurfaceParams& GetSurfaceParams() const { return params; } @@ -820,6 +859,7 @@ private: std::vector<u8> gl_buffer; SurfaceParams params; GLenum gl_target; + std::size_t cached_size_in_bytes; }; class RasterizerCacheOpenGL final : public RasterizerCache<Surface> { @@ -836,9 +876,6 @@ public: /// Get the color surface based on the framebuffer configuration and the specified render target Surface GetColorBufferSurface(std::size_t index, bool preserve_contents); - /// Flushes the surface to Switch memory - void FlushSurface(const Surface& surface); - /// Tries to find a framebuffer using on the provided CPU address Surface TryFindFramebufferSurface(VAddr addr) const; @@ -862,6 +899,9 @@ private: /// Tries to get a reserved surface for the specified parameters Surface TryGetReservedSurface(const SurfaceParams& params); + /// Performs a slow but accurate surface copy, flushing to RAM and reinterpreting the data + void AccurateCopySurface(const Surface& src_surface, const Surface& dst_surface); + /// 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. diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 7cd8f91e4..1a03a677f 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -68,6 +68,10 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type) program_result = GLShader::GenerateVertexShader(setup); gl_type = GL_VERTEX_SHADER; break; + case Maxwell::ShaderProgram::Geometry: + program_result = GLShader::GenerateGeometryShader(setup); + gl_type = GL_GEOMETRY_SHADER; + break; case Maxwell::ShaderProgram::Fragment: program_result = GLShader::GenerateFragmentShader(setup); gl_type = GL_FRAGMENT_SHADER; @@ -80,11 +84,16 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type) entries = program_result.second; - OGLShader shader; - shader.Create(program_result.first.c_str(), gl_type); - program.Create(true, shader.handle); - SetShaderUniformBlockBindings(program.handle); - VideoCore::LabelGLObject(GL_PROGRAM, program.handle, addr); + if (program_type != Maxwell::ShaderProgram::Geometry) { + OGLShader shader; + shader.Create(program_result.first.c_str(), gl_type); + program.Create(true, shader.handle); + SetShaderUniformBlockBindings(program.handle); + VideoCore::LabelGLObject(GL_PROGRAM, program.handle, addr); + } else { + // Store shader's code to lazily build it on draw + geometry_programs.code = program_result.first; + } } GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer) { @@ -110,6 +119,21 @@ GLint CachedShader::GetUniformLocation(const GLShader::SamplerEntry& sampler) { return search->second; } +GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program, + const std::string& glsl_topology, + const std::string& debug_name) { + if (target_program.handle != 0) { + return target_program.handle; + } + const std::string source{geometry_programs.code + "layout (" + glsl_topology + ") in;\n"}; + OGLShader shader; + shader.Create(source.c_str(), GL_GEOMETRY_SHADER); + target_program.Create(true, shader.handle); + SetShaderUniformBlockBindings(target_program.handle); + VideoCore::LabelGLObject(GL_PROGRAM, target_program.handle, addr, debug_name); + return target_program.handle; +}; + Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) { const VAddr program_addr{GetShaderAddress(program)}; diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 9bafe43a9..a210f1731 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -7,6 +7,7 @@ #include <map> #include <memory> +#include "common/assert.h" #include "common/common_types.h" #include "video_core/rasterizer_cache.h" #include "video_core/renderer_opengl/gl_resource_manager.h" @@ -18,28 +19,52 @@ class CachedShader; using Shader = std::shared_ptr<CachedShader>; using Maxwell = Tegra::Engines::Maxwell3D::Regs; -class CachedShader final { +class CachedShader final : public RasterizerCacheObject { public: CachedShader(VAddr addr, Maxwell::ShaderProgram program_type); - /// Gets the address of the shader in guest memory, required for cache management - VAddr GetAddr() const { + VAddr GetAddr() const override { return addr; } - /// Gets the size of the shader in guest memory, required for cache management - std::size_t GetSizeInBytes() const { + std::size_t GetSizeInBytes() const override { return GLShader::MAX_PROGRAM_CODE_LENGTH * sizeof(u64); } + // We do not have to flush this cache as things in it are never modified by us. + void Flush() override {} + /// Gets the shader entries for the shader const GLShader::ShaderEntries& GetShaderEntries() const { return entries; } /// Gets the GL program handle for the shader - GLuint GetProgramHandle() const { - return program.handle; + GLuint GetProgramHandle(GLenum primitive_mode) { + if (program_type != Maxwell::ShaderProgram::Geometry) { + return program.handle; + } + switch (primitive_mode) { + case GL_POINTS: + return LazyGeometryProgram(geometry_programs.points, "points", "ShaderPoints"); + case GL_LINES: + case GL_LINE_STRIP: + return LazyGeometryProgram(geometry_programs.lines, "lines", "ShaderLines"); + case GL_LINES_ADJACENCY: + case GL_LINE_STRIP_ADJACENCY: + return LazyGeometryProgram(geometry_programs.lines_adjacency, "lines_adjacency", + "ShaderLinesAdjacency"); + case GL_TRIANGLES: + case GL_TRIANGLE_STRIP: + case GL_TRIANGLE_FAN: + return LazyGeometryProgram(geometry_programs.triangles, "triangles", "ShaderTriangles"); + case GL_TRIANGLES_ADJACENCY: + case GL_TRIANGLE_STRIP_ADJACENCY: + return LazyGeometryProgram(geometry_programs.triangles_adjacency, "triangles_adjacency", + "ShaderLines"); + default: + UNREACHABLE_MSG("Unknown primitive mode."); + } } /// Gets the GL program resource location for the specified resource, caching as needed @@ -49,12 +74,30 @@ public: GLint GetUniformLocation(const GLShader::SamplerEntry& sampler); private: + /// Generates a geometry shader or returns one that already exists. + GLuint LazyGeometryProgram(OGLProgram& target_program, const std::string& glsl_topology, + const std::string& debug_name); + VAddr addr; Maxwell::ShaderProgram program_type; GLShader::ShaderSetup setup; GLShader::ShaderEntries entries; + + // Non-geometry program. OGLProgram program; + // Geometry programs. These are needed because GLSL needs an input topology but it's not + // declared by the hardware. Workaround this issue by generating a different shader per input + // topology class. + struct { + std::string code; + OGLProgram points; + OGLProgram lines; + OGLProgram lines_adjacency; + OGLProgram triangles; + OGLProgram triangles_adjacency; + } geometry_programs; + std::map<u32, GLuint> resource_cache; std::map<u32, GLint> uniform_cache; }; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 85c668ca1..55c33c3a9 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -7,6 +7,7 @@ #include <string> #include <string_view> +#include <boost/optional.hpp> #include <fmt/format.h> #include "common/assert.h" @@ -29,11 +30,32 @@ using Tegra::Shader::SubOp; constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH; constexpr u32 PROGRAM_HEADER_SIZE = sizeof(Tegra::Shader::Header); +enum : u32 { POSITION_VARYING_LOCATION = 0, GENERIC_VARYING_START_LOCATION = 1 }; + +constexpr u32 MAX_GEOMETRY_BUFFERS = 6; +constexpr u32 MAX_ATTRIBUTES = 0x100; // Size in vec4s, this value is untested + class DecompileFail : public std::runtime_error { public: using std::runtime_error::runtime_error; }; +/// Translate topology +static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) { + switch (topology) { + case Tegra::Shader::OutputTopology::PointList: + return "points"; + case Tegra::Shader::OutputTopology::LineStrip: + return "line_strip"; + case Tegra::Shader::OutputTopology::TriangleStrip: + return "triangle_strip"; + default: + LOG_CRITICAL(Render_OpenGL, "Unknown output topology {}", static_cast<u32>(topology)); + UNREACHABLE(); + return "points"; + } +} + /// Describes the behaviour of code path of a given entry point and a return point. enum class ExitMethod { Undetermined, ///< Internal value. Only occur when analyzing JMP loop. @@ -253,8 +275,9 @@ enum class InternalFlag : u64 { class GLSLRegisterManager { public: GLSLRegisterManager(ShaderWriter& shader, ShaderWriter& declarations, - const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix) - : shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix} { + const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix, + const Tegra::Shader::Header& header) + : shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix}, header{header} { BuildRegisterList(); BuildInputList(); } @@ -358,11 +381,13 @@ public: * @param reg The destination register to use. * @param elem The element to use for the operation. * @param attribute The input attribute to use as the source value. + * @param vertex The register that decides which vertex to read from (used in GS). */ void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute, - const Tegra::Shader::IpaMode& input_mode) { + const Tegra::Shader::IpaMode& input_mode, + boost::optional<Register> vertex = {}) { const std::string dest = GetRegisterAsFloat(reg); - const std::string src = GetInputAttribute(attribute, input_mode) + GetSwizzle(elem); + const std::string src = GetInputAttribute(attribute, input_mode, vertex) + GetSwizzle(elem); shader.AddLine(dest + " = " + src + ';'); } @@ -391,16 +416,29 @@ public: * are stored as floats, so this may require conversion. * @param attribute The destination output attribute. * @param elem The element to use for the operation. - * @param reg The register to use as the source value. + * @param val_reg The register to use as the source value. + * @param buf_reg The register that tells which buffer to write to (used in geometry shaders). */ - void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& reg) { + void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& val_reg, + const Register& buf_reg) { const std::string dest = GetOutputAttribute(attribute); - const std::string src = GetRegisterAsFloat(reg); + const std::string src = GetRegisterAsFloat(val_reg); if (!dest.empty()) { // Can happen with unknown/unimplemented output attributes, in which case we ignore the // instruction for now. - shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';'); + if (stage == Maxwell3D::Regs::ShaderStage::Geometry) { + // TODO(Rodrigo): nouveau sets some attributes after setting emitting a geometry + // shader. These instructions use a dirty register as buffer index. To avoid some + // drivers from complaining for the out of boundary writes, guard them. + const std::string buf_index{"min(" + GetRegisterAsInteger(buf_reg) + ", " + + std::to_string(MAX_GEOMETRY_BUFFERS - 1) + ')'}; + shader.AddLine("amem[" + buf_index + "][" + + std::to_string(static_cast<u32>(attribute)) + ']' + + GetSwizzle(elem) + " = " + src + ';'); + } else { + shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';'); + } } } @@ -441,41 +479,123 @@ public: } } - /// Add declarations for registers + /// Add declarations. void GenerateDeclarations(const std::string& suffix) { + GenerateRegisters(suffix); + GenerateInternalFlags(); + GenerateInputAttrs(); + GenerateOutputAttrs(); + GenerateConstBuffers(); + GenerateSamplers(); + GenerateGeometry(); + } + + /// Returns a list of constant buffer declarations. + std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const { + std::vector<ConstBufferEntry> result; + std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(), + std::back_inserter(result), [](const auto& entry) { return entry.IsUsed(); }); + return result; + } + + /// Returns a list of samplers used in the shader. + const std::vector<SamplerEntry>& GetSamplers() const { + return used_samplers; + } + + /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if + /// necessary. + std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type, + bool is_array, bool is_shadow) { + const auto offset = static_cast<std::size_t>(sampler.index.Value()); + + // If this sampler has already been used, return the existing mapping. + const auto itr = + std::find_if(used_samplers.begin(), used_samplers.end(), + [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; }); + + if (itr != used_samplers.end()) { + ASSERT(itr->GetType() == type && itr->IsArray() == is_array && + itr->IsShadow() == is_shadow); + return itr->GetName(); + } + + // Otherwise create a new mapping for this sampler + const std::size_t next_index = used_samplers.size(); + const SamplerEntry entry{stage, offset, next_index, type, is_array, is_shadow}; + used_samplers.emplace_back(entry); + return entry.GetName(); + } + +private: + /// Generates declarations for registers. + void GenerateRegisters(const std::string& suffix) { for (const auto& reg : regs) { declarations.AddLine(GLSLRegister::GetTypeString() + ' ' + reg.GetPrefixString() + std::to_string(reg.GetIndex()) + '_' + suffix + " = 0;"); } declarations.AddNewLine(); + } + /// Generates declarations for internal flags. + void GenerateInternalFlags() { for (u32 ii = 0; ii < static_cast<u64>(InternalFlag::Amount); ii++) { const InternalFlag code = static_cast<InternalFlag>(ii); declarations.AddLine("bool " + GetInternalFlag(code) + " = false;"); } declarations.AddNewLine(); + } + + /// Generates declarations for input attributes. + void GenerateInputAttrs() { + if (stage != Maxwell3D::Regs::ShaderStage::Vertex) { + const std::string attr = + stage == Maxwell3D::Regs::ShaderStage::Geometry ? "gs_position[]" : "position"; + declarations.AddLine("layout (location = " + std::to_string(POSITION_VARYING_LOCATION) + + ") in vec4 " + attr + ';'); + } for (const auto element : declr_input_attribute) { // TODO(bunnei): Use proper number of elements for these u32 idx = static_cast<u32>(element.first) - static_cast<u32>(Attribute::Index::Attribute_0); - declarations.AddLine("layout(location = " + std::to_string(idx) + ")" + - GetInputFlags(element.first) + "in vec4 " + - GetInputAttribute(element.first, element.second) + ';'); + if (stage != Maxwell3D::Regs::ShaderStage::Vertex) { + // If inputs are varyings, add an offset + idx += GENERIC_VARYING_START_LOCATION; + } + + std::string attr{GetInputAttribute(element.first, element.second)}; + if (stage == Maxwell3D::Regs::ShaderStage::Geometry) { + attr = "gs_" + attr + "[]"; + } + declarations.AddLine("layout (location = " + std::to_string(idx) + ") " + + GetInputFlags(element.first) + "in vec4 " + attr + ';'); } + declarations.AddNewLine(); + } + /// Generates declarations for output attributes. + void GenerateOutputAttrs() { + if (stage != Maxwell3D::Regs::ShaderStage::Fragment) { + declarations.AddLine("layout (location = " + std::to_string(POSITION_VARYING_LOCATION) + + ") out vec4 position;"); + } for (const auto& index : declr_output_attribute) { // TODO(bunnei): Use proper number of elements for these - declarations.AddLine("layout(location = " + - std::to_string(static_cast<u32>(index) - - static_cast<u32>(Attribute::Index::Attribute_0)) + - ") out vec4 " + GetOutputAttribute(index) + ';'); + const u32 idx = static_cast<u32>(index) - + static_cast<u32>(Attribute::Index::Attribute_0) + + GENERIC_VARYING_START_LOCATION; + declarations.AddLine("layout (location = " + std::to_string(idx) + ") out vec4 " + + GetOutputAttribute(index) + ';'); } declarations.AddNewLine(); + } + /// Generates declarations for constant buffers. + void GenerateConstBuffers() { for (const auto& entry : GetConstBuffersDeclarations()) { - declarations.AddLine("layout(std140) uniform " + entry.GetName()); + declarations.AddLine("layout (std140) uniform " + entry.GetName()); declarations.AddLine('{'); declarations.AddLine(" vec4 c" + std::to_string(entry.GetIndex()) + "[MAX_CONSTBUFFER_ELEMENTS];"); @@ -483,7 +603,10 @@ public: declarations.AddNewLine(); } declarations.AddNewLine(); + } + /// Generates declarations for samplers. + void GenerateSamplers() { const auto& samplers = GetSamplers(); for (const auto& sampler : samplers) { declarations.AddLine("uniform " + sampler.GetTypeString() + ' ' + sampler.GetName() + @@ -492,44 +615,42 @@ public: declarations.AddNewLine(); } - /// Returns a list of constant buffer declarations - std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const { - std::vector<ConstBufferEntry> result; - std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(), - std::back_inserter(result), [](const auto& entry) { return entry.IsUsed(); }); - return result; - } - - /// Returns a list of samplers used in the shader - const std::vector<SamplerEntry>& GetSamplers() const { - return used_samplers; - } - - /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if - /// necessary. - std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type, - bool is_array, bool is_shadow) { - const std::size_t offset = static_cast<std::size_t>(sampler.index.Value()); + /// Generates declarations used for geometry shaders. + void GenerateGeometry() { + if (stage != Maxwell3D::Regs::ShaderStage::Geometry) + return; - // If this sampler has already been used, return the existing mapping. - const auto itr = - std::find_if(used_samplers.begin(), used_samplers.end(), - [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; }); + declarations.AddLine( + "layout (" + GetTopologyName(header.common3.output_topology) + + ", max_vertices = " + std::to_string(header.common4.max_output_vertices) + ") out;"); + declarations.AddNewLine(); - if (itr != used_samplers.end()) { - ASSERT(itr->GetType() == type && itr->IsArray() == is_array && - itr->IsShadow() == is_shadow); - return itr->GetName(); - } + declarations.AddLine("vec4 amem[" + std::to_string(MAX_GEOMETRY_BUFFERS) + "][" + + std::to_string(MAX_ATTRIBUTES) + "];"); + declarations.AddNewLine(); - // Otherwise create a new mapping for this sampler - const std::size_t next_index = used_samplers.size(); - const SamplerEntry entry{stage, offset, next_index, type, is_array, is_shadow}; - used_samplers.emplace_back(entry); - return entry.GetName(); + constexpr char buffer[] = "amem[output_buffer]"; + declarations.AddLine("void emit_vertex(uint output_buffer) {"); + ++declarations.scope; + for (const auto element : declr_output_attribute) { + declarations.AddLine(GetOutputAttribute(element) + " = " + buffer + '[' + + std::to_string(static_cast<u32>(element)) + "];"); + } + + declarations.AddLine("position = " + std::string(buffer) + '[' + + std::to_string(static_cast<u32>(Attribute::Index::Position)) + "];"); + + // If a geometry shader is attached, it will always flip (it's the last stage before + // fragment). For more info about flipping, refer to gl_shader_gen.cpp. + declarations.AddLine("position.xy *= viewport_flip.xy;"); + declarations.AddLine("gl_Position = position;"); + declarations.AddLine("position.w = 1.0;"); + declarations.AddLine("EmitVertex();"); + --declarations.scope; + declarations.AddLine('}'); + declarations.AddNewLine(); } -private: /// Generates code representing a temporary (GPR) register. std::string GetRegister(const Register& reg, unsigned elem) { if (reg == Register::ZeroIndex) { @@ -586,11 +707,19 @@ private: /// Generates code representing an input attribute register. std::string GetInputAttribute(Attribute::Index attribute, - const Tegra::Shader::IpaMode& input_mode) { + const Tegra::Shader::IpaMode& input_mode, + boost::optional<Register> vertex = {}) { + auto GeometryPass = [&](const std::string& name) { + if (stage == Maxwell3D::Regs::ShaderStage::Geometry && vertex) { + return "gs_" + name + '[' + GetRegisterAsInteger(vertex.value(), 0, false) + ']'; + } + return name; + }; + switch (attribute) { case Attribute::Index::Position: if (stage != Maxwell3D::Regs::ShaderStage::Fragment) { - return "position"; + return GeometryPass("position"); } else { return "vec4(gl_FragCoord.x, gl_FragCoord.y, gl_FragCoord.z, 1.0)"; } @@ -619,7 +748,7 @@ private: UNREACHABLE(); } } - return "input_attribute_" + std::to_string(index); + return GeometryPass("input_attribute_" + std::to_string(index)); } LOG_CRITICAL(HW_GPU, "Unhandled input attribute: {}", static_cast<u32>(attribute)); @@ -672,7 +801,7 @@ private: return out; } - /// Generates code representing an output attribute register. + /// Generates code representing the declaration name of an output attribute register. std::string GetOutputAttribute(Attribute::Index attribute) { switch (attribute) { case Attribute::Index::Position: @@ -708,6 +837,7 @@ private: std::vector<SamplerEntry> used_samplers; const Maxwell3D::Regs::ShaderStage& stage; const std::string& suffix; + const Tegra::Shader::Header& header; }; class GLSLGenerator { @@ -1103,8 +1233,8 @@ private: return offset + 1; } - shader.AddLine("// " + std::to_string(offset) + ": " + opcode->GetName() + " (" + - std::to_string(instr.value) + ')'); + shader.AddLine( + fmt::format("// {}: {} (0x{:016x})", offset, opcode->GetName(), instr.value)); using Tegra::Shader::Pred; ASSERT_MSG(instr.pred.full_pred != Pred::NeverExecute, @@ -1306,7 +1436,6 @@ private: break; } - case OpCode::Type::Shift: { std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, true); std::string op_b; @@ -1348,7 +1477,6 @@ private: } break; } - case OpCode::Type::ArithmeticIntegerImmediate: { std::string op_a = regs.GetRegisterAsInteger(instr.gpr8); std::string op_b = std::to_string(instr.alu.imm20_32.Value()); @@ -1826,7 +1954,7 @@ private: const auto LoadNextElement = [&](u32 reg_offset) { regs.SetRegisterToInputAttibute(instr.gpr0.Value() + reg_offset, next_element, static_cast<Attribute::Index>(next_index), - input_mode); + input_mode, instr.gpr39.Value()); // Load the next attribute element into the following register. If the element // to load goes beyond the vec4 size, load the first element of the next @@ -1890,8 +2018,8 @@ private: const auto StoreNextElement = [&](u32 reg_offset) { regs.SetOutputAttributeToRegister(static_cast<Attribute::Index>(next_index), - next_element, - instr.gpr0.Value() + reg_offset); + next_element, instr.gpr0.Value() + reg_offset, + instr.gpr39.Value()); // Load the next attribute element into the following register. If the element // to load goes beyond the vec4 size, load the first element of the next @@ -1908,9 +2036,9 @@ private: break; } case OpCode::Id::TEX: { - ASSERT_MSG(instr.tex.array == 0, "TEX arrays unimplemented"); Tegra::Shader::TextureType texture_type{instr.tex.texture_type}; std::string coord; + const bool is_array = instr.tex.array != 0; ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), "NODEP is not implemented"); @@ -1925,21 +2053,59 @@ private: switch (num_coordinates) { case 1: { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - coord = "float coords = " + x + ';'; + if (is_array) { + const std::string index = regs.GetRegisterAsInteger(instr.gpr8); + const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + coord = "vec2 coords = vec2(" + x + ", " + index + ");"; + } else { + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + coord = "float coords = " + x + ';'; + } break; } case 2: { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + if (is_array) { + const std::string index = regs.GetRegisterAsInteger(instr.gpr8); + const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + index + ");"; + } else { + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + } break; } case 3: { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string z = regs.GetRegisterAsFloat(instr.gpr20); - coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; + if (depth_compare) { + if (is_array) { + const std::string index = regs.GetRegisterAsInteger(instr.gpr8); + const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string y = regs.GetRegisterAsFloat(instr.gpr20); + const std::string z = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1); + coord = "vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " + index + + ");"; + } else { + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string z = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; + } + } else { + if (is_array) { + const std::string index = regs.GetRegisterAsInteger(instr.gpr8); + const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2); + const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 3); + coord = "vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " + index + + ");"; + } else { + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; + } + } break; } default: @@ -1958,7 +2124,7 @@ private: std::string op_c; const std::string sampler = - GetSampler(instr.sampler, texture_type, false, depth_compare); + GetSampler(instr.sampler, texture_type, is_array, depth_compare); // Add an extra scope and declare the texture coords inside to prevent // overwriting them in case they are used as outputs of the texs instruction. @@ -1978,10 +2144,13 @@ private: } case Tegra::Shader::TextureProcessMode::LB: case Tegra::Shader::TextureProcessMode::LBA: { - if (num_coordinates <= 2) { - op_c = regs.GetRegisterAsFloat(instr.gpr20); + if (depth_compare) { + if (is_array) + op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 2); + else + op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1); } else { - op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1); + op_c = regs.GetRegisterAsFloat(instr.gpr20); } // TODO: Figure if A suffix changes the equation at all. texture = "texture(" + sampler + ", coords, " + op_c + ')'; @@ -2124,6 +2293,8 @@ private: ASSERT_MSG(!instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ), "MZ is not implemented"); + u32 op_c_offset = 0; + switch (texture_type) { case Tegra::Shader::TextureType::Texture1D: { const std::string x = regs.GetRegisterAsInteger(instr.gpr8); @@ -2138,6 +2309,7 @@ private: const std::string x = regs.GetRegisterAsInteger(instr.gpr8); const std::string y = regs.GetRegisterAsInteger(instr.gpr20); coord = "ivec2 coords = ivec2(" + x + ", " + y + ");"; + op_c_offset = 1; } break; } @@ -2149,13 +2321,14 @@ private: const std::string sampler = GetSampler(instr.sampler, texture_type, is_array, false); std::string texture = "texelFetch(" + sampler + ", coords, 0)"; - const std::string op_c = regs.GetRegisterAsInteger(instr.gpr20.Value() + 1); switch (instr.tlds.GetTextureProcessMode()) { case Tegra::Shader::TextureProcessMode::LZ: { texture = "texelFetch(" + sampler + ", coords, 0)"; break; } case Tegra::Shader::TextureProcessMode::LL: { + const std::string op_c = + regs.GetRegisterAsInteger(instr.gpr20.Value() + op_c_offset); texture = "texelFetch(" + sampler + ", coords, " + op_c + ')'; break; } @@ -2496,14 +2669,14 @@ private: const std::string pred = GetPredicateCondition(instr.csetp.pred39, instr.csetp.neg_pred39 != 0); const std::string combiner = GetPredicateCombiner(instr.csetp.op); - const std::string controlCode = regs.GetControlCode(instr.csetp.cc); + const std::string control_code = regs.GetControlCode(instr.csetp.cc); if (instr.csetp.pred3 != static_cast<u64>(Pred::UnusedIndex)) { SetPredicate(instr.csetp.pred3, - '(' + controlCode + ") " + combiner + " (" + pred + ')'); + '(' + control_code + ") " + combiner + " (" + pred + ')'); } if (instr.csetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) { SetPredicate(instr.csetp.pred0, - "!(" + controlCode + ") " + combiner + " (" + pred + ')'); + "!(" + control_code + ") " + combiner + " (" + pred + ')'); } break; } @@ -2734,6 +2907,52 @@ private: break; } + case OpCode::Id::OUT_R: { + ASSERT(instr.gpr20.Value() == Register::ZeroIndex); + ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry, + "OUT is expected to be used in a geometry shader."); + + if (instr.out.emit) { + // gpr0 is used to store the next address. Hardware returns a pointer but + // we just return the next index with a cyclic cap. + const std::string current{regs.GetRegisterAsInteger(instr.gpr8, 0, false)}; + const std::string next = "((" + current + " + 1" + ") % " + + std::to_string(MAX_GEOMETRY_BUFFERS) + ')'; + shader.AddLine("emit_vertex(" + current + ");"); + regs.SetRegisterToInteger(instr.gpr0, false, 0, next, 1, 1); + } + if (instr.out.cut) { + shader.AddLine("EndPrimitive();"); + } + + break; + } + case OpCode::Id::MOV_SYS: { + switch (instr.sys20) { + case Tegra::Shader::SystemVariable::InvocationInfo: { + LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete"); + regs.SetRegisterToInteger(instr.gpr0, false, 0, "0u", 1, 1); + break; + } + default: { + LOG_CRITICAL(HW_GPU, "Unhandled system move: {}", + static_cast<u32>(instr.sys20.Value())); + UNREACHABLE(); + } + } + break; + } + case OpCode::Id::ISBERD: { + ASSERT(instr.isberd.o == 0); + ASSERT(instr.isberd.skew == 0); + ASSERT(instr.isberd.shift == Tegra::Shader::IsberdShift::None); + ASSERT(instr.isberd.mode == Tegra::Shader::IsberdMode::None); + ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry, + "ISBERD is expected to be used in a geometry shader."); + LOG_WARNING(HW_GPU, "ISBERD instruction is incomplete"); + regs.SetRegisterToFloat(instr.gpr0, 0, regs.GetRegisterAsFloat(instr.gpr8), 1, 1); + break; + } case OpCode::Id::BRA: { ASSERT_MSG(instr.bra.constant_buffer == 0, "BRA with constant buffers are not implemented"); @@ -2777,6 +2996,88 @@ private: LOG_WARNING(HW_GPU, "DEPBAR instruction is stubbed"); break; } + case OpCode::Id::VMAD: { + const bool signed_a = instr.vmad.signed_a == 1; + const bool signed_b = instr.vmad.signed_b == 1; + const bool result_signed = signed_a || signed_b; + boost::optional<std::string> forced_result; + + auto Unpack = [&](const std::string& op, bool is_chunk, bool is_signed, + Tegra::Shader::VmadType type, u64 byte_height) { + const std::string value = [&]() { + if (!is_chunk) { + const auto offset = static_cast<u32>(byte_height * 8); + return "((" + op + " >> " + std::to_string(offset) + ") & 0xff)"; + } + const std::string zero = "0"; + + switch (type) { + case Tegra::Shader::VmadType::Size16_Low: + return '(' + op + " & 0xffff)"; + case Tegra::Shader::VmadType::Size16_High: + return '(' + op + " >> 16)"; + case Tegra::Shader::VmadType::Size32: + // TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when + // this type is used (1 * 1 + 0 == 0x5b800000). Until a better + // explanation is found: assert. + UNREACHABLE_MSG("Unimplemented"); + return zero; + case Tegra::Shader::VmadType::Invalid: + // Note(Rodrigo): This flag is invalid according to nvdisasm. From my + // testing (even though it's invalid) this makes the whole instruction + // assign zero to target register. + forced_result = boost::make_optional(zero); + return zero; + default: + UNREACHABLE(); + return zero; + } + }(); + + if (is_signed) { + return "int(" + value + ')'; + } + return value; + }; + + const std::string op_a = Unpack(regs.GetRegisterAsInteger(instr.gpr8, 0, false), + instr.vmad.is_byte_chunk_a != 0, signed_a, + instr.vmad.type_a, instr.vmad.byte_height_a); + + std::string op_b; + if (instr.vmad.use_register_b) { + op_b = Unpack(regs.GetRegisterAsInteger(instr.gpr20, 0, false), + instr.vmad.is_byte_chunk_b != 0, signed_b, instr.vmad.type_b, + instr.vmad.byte_height_b); + } else { + op_b = '(' + + std::to_string(signed_b ? static_cast<s16>(instr.alu.GetImm20_16()) + : instr.alu.GetImm20_16()) + + ')'; + } + + const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39, 0, result_signed); + + std::string result; + if (forced_result) { + result = *forced_result; + } else { + result = '(' + op_a + " * " + op_b + " + " + op_c + ')'; + + switch (instr.vmad.shr) { + case Tegra::Shader::VmadShr::Shr7: + result = '(' + result + " >> 7)"; + break; + case Tegra::Shader::VmadShr::Shr15: + result = '(' + result + " >> 15)"; + break; + } + } + regs.SetRegisterToInteger(instr.gpr0, result_signed, 1, result, 1, 1, + instr.vmad.saturate == 1, 0, Register::Size::Word, + instr.vmad.cc); + break; + } default: { LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName()); UNREACHABLE(); @@ -2907,7 +3208,7 @@ private: ShaderWriter shader; ShaderWriter declarations; - GLSLRegisterManager regs{shader, declarations, stage, suffix}; + GLSLRegisterManager regs{shader, declarations, stage, suffix, header}; // Declarations std::set<std::string> declr_predicates; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index b0466c18f..1e5eb32df 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -17,7 +17,18 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) { std::string out = "#version 430 core\n"; out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; out += Decompiler::GetCommonDeclarations(); - out += "bool exec_vertex();\n"; + + out += R"( +out gl_PerVertex { + vec4 gl_Position; +}; + +layout(std140) uniform vs_config { + vec4 viewport_flip; + uvec4 instance_id; + uvec4 flip_stage; +}; +)"; if (setup.IsDualProgram()) { out += "bool exec_vertex_b();\n"; @@ -28,18 +39,17 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) { Maxwell3D::Regs::ShaderStage::Vertex, "vertex") .get_value_or({}); - out += R"( - -out gl_PerVertex { - vec4 gl_Position; -}; + out += program.first; -out vec4 position; + if (setup.IsDualProgram()) { + ProgramResult program_b = + Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET, + Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b") + .get_value_or({}); + out += program_b.first; + } -layout (std140) uniform vs_config { - vec4 viewport_flip; - uvec4 instance_id; -}; + out += R"( void main() { position = vec4(0.0, 0.0, 0.0, 0.0); @@ -52,27 +62,52 @@ void main() { out += R"( - // Viewport can be flipped, which is unsupported by glViewport - position.xy *= viewport_flip.xy; + // Check if the flip stage is VertexB + if (flip_stage[0] == 1) { + // Viewport can be flipped, which is unsupported by glViewport + position.xy *= viewport_flip.xy; + } gl_Position = position; // TODO(bunnei): This is likely a hack, position.w should be interpolated as 1.0 // For now, this is here to bring order in lieu of proper emulation - position.w = 1.0; + if (flip_stage[0] == 1) { + position.w = 1.0; + } } )"; - out += program.first; + return {out, program.second}; +} - if (setup.IsDualProgram()) { - ProgramResult program_b = - Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET, - Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b") - .get_value_or({}); - out += program_b.first; - } +ProgramResult GenerateGeometryShader(const ShaderSetup& setup) { + std::string out = "#version 430 core\n"; + out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; + out += Decompiler::GetCommonDeclarations(); + out += "bool exec_geometry();\n"; + + ProgramResult program = + Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET, + Maxwell3D::Regs::ShaderStage::Geometry, "geometry") + .get_value_or({}); + out += R"( +out gl_PerVertex { + vec4 gl_Position; +}; +layout (std140) uniform gs_config { + vec4 viewport_flip; + uvec4 instance_id; + uvec4 flip_stage; +}; + +void main() { + exec_geometry(); +} + +)"; + out += program.first; return {out, program.second}; } @@ -87,7 +122,6 @@ ProgramResult GenerateFragmentShader(const ShaderSetup& setup) { Maxwell3D::Regs::ShaderStage::Fragment, "fragment") .get_value_or({}); out += R"( -in vec4 position; layout(location = 0) out vec4 FragColor0; layout(location = 1) out vec4 FragColor1; layout(location = 2) out vec4 FragColor2; @@ -100,6 +134,7 @@ layout(location = 7) out vec4 FragColor7; layout (std140) uniform fs_config { vec4 viewport_flip; uvec4 instance_id; + uvec4 flip_stage; }; void main() { @@ -110,5 +145,4 @@ void main() { out += program.first; return {out, program.second}; } - -} // namespace OpenGL::GLShader +} // namespace OpenGL::GLShader
\ No newline at end of file diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index e56f39e78..79596087a 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -196,6 +196,12 @@ private: ProgramResult GenerateVertexShader(const ShaderSetup& setup); /** + * Generates the GLSL geometry shader program source code for the given GS program + * @returns String of the shader source code + */ +ProgramResult GenerateGeometryShader(const ShaderSetup& setup); + +/** * Generates the GLSL fragment shader program source code for the given FS program * @returns String of the shader source code */ diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp index 022d32a86..010857ec6 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -18,6 +18,14 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& sh // We only assign the instance to the first component of the vector, the rest is just padding. instance_id[0] = state.current_instance; + + // Assign in which stage the position has to be flipped + // (the last stage before the fragment shader). + if (gpu.regs.shader_config[static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry)].enable) { + flip_stage[0] = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry); + } else { + flip_stage[0] = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::VertexB); + } } } // namespace OpenGL::GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index 3de15ba9b..b3a191cf2 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -21,8 +21,9 @@ struct MaxwellUniformData { void SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage); alignas(16) GLvec4 viewport_flip; alignas(16) GLuvec4 instance_id; + alignas(16) GLuvec4 flip_stage; }; -static_assert(sizeof(MaxwellUniformData) == 32, "MaxwellUniformData structure size is incorrect"); +static_assert(sizeof(MaxwellUniformData) == 48, "MaxwellUniformData structure size is incorrect"); static_assert(sizeof(MaxwellUniformData) < 16384, "MaxwellUniformData structure must be less than 16kb as per the OpenGL spec"); @@ -36,6 +37,10 @@ public: vs = program; } + void UseProgrammableGeometryShader(GLuint program) { + gs = program; + } + void UseProgrammableFragmentShader(GLuint program) { fs = program; } diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 3d5476e5d..18ab723f7 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -4,6 +4,7 @@ #include <cmath> #include <cstring> +#include "common/alignment.h" #include "common/assert.h" #include "core/memory.h" #include "video_core/gpu.h" @@ -39,72 +40,146 @@ struct alignas(64) SwizzleTable { constexpr auto legacy_swizzle_table = SwizzleTable<8, 64, 1>(); constexpr auto fast_swizzle_table = SwizzleTable<8, 4, 16>(); -static void LegacySwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel, - u8* swizzled_data, u8* unswizzled_data, bool unswizzle, - u32 block_height) { +/** + * This function manages ALL the GOBs(Group of Bytes) Inside a single block. + * Instead of going gob by gob, we map the coordinates inside a block and manage from + * those. Block_Width is assumed to be 1. + */ +void PreciseProcessBlock(u8* swizzled_data, u8* unswizzled_data, const bool unswizzle, + const u32 x_start, const u32 y_start, const u32 z_start, const u32 x_end, + const u32 y_end, const u32 z_end, const u32 tile_offset, + const u32 xy_block_size, const u32 layer_z, const u32 stride_x, + const u32 bytes_per_pixel, const u32 out_bytes_per_pixel) { std::array<u8*, 2> data_ptrs; - const std::size_t stride = width * bytes_per_pixel; - const std::size_t gobs_in_x = 64; - const std::size_t gobs_in_y = 8; - const std::size_t gobs_size = gobs_in_x * gobs_in_y; - const std::size_t image_width_in_gobs{(stride + gobs_in_x - 1) / gobs_in_x}; - for (std::size_t y = 0; y < height; ++y) { - const std::size_t gob_y_address = - (y / (gobs_in_y * block_height)) * gobs_size * block_height * image_width_in_gobs + - (y % (gobs_in_y * block_height) / gobs_in_y) * gobs_size; - const auto& table = legacy_swizzle_table[y % gobs_in_y]; - for (std::size_t x = 0; x < width; ++x) { - const std::size_t gob_address = - gob_y_address + (x * bytes_per_pixel / gobs_in_x) * gobs_size * block_height; - const std::size_t x2 = x * bytes_per_pixel; - const std::size_t swizzle_offset = gob_address + table[x2 % gobs_in_x]; - const std::size_t pixel_index = (x + y * width) * out_bytes_per_pixel; - - data_ptrs[unswizzle] = swizzled_data + swizzle_offset; - data_ptrs[!unswizzle] = unswizzled_data + pixel_index; - - std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); + u32 z_address = tile_offset; + const u32 gob_size_x = 64; + const u32 gob_size_y = 8; + const u32 gob_size_z = 1; + const u32 gob_size = gob_size_x * gob_size_y * gob_size_z; + for (u32 z = z_start; z < z_end; z++) { + u32 y_address = z_address; + u32 pixel_base = layer_z * z + y_start * stride_x; + for (u32 y = y_start; y < y_end; y++) { + const auto& table = legacy_swizzle_table[y % gob_size_y]; + for (u32 x = x_start; x < x_end; x++) { + const u32 swizzle_offset{y_address + table[x * bytes_per_pixel % gob_size_x]}; + const u32 pixel_index{x * out_bytes_per_pixel + pixel_base}; + data_ptrs[unswizzle] = swizzled_data + swizzle_offset; + data_ptrs[!unswizzle] = unswizzled_data + pixel_index; + std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); + } + pixel_base += stride_x; + if ((y + 1) % gob_size_y == 0) + y_address += gob_size; } + z_address += xy_block_size; } } -static void FastSwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel, - u8* swizzled_data, u8* unswizzled_data, bool unswizzle, - u32 block_height) { +/** + * This function manages ALL the GOBs(Group of Bytes) Inside a single block. + * Instead of going gob by gob, we map the coordinates inside a block and manage from + * those. Block_Width is assumed to be 1. + */ +void FastProcessBlock(u8* swizzled_data, u8* unswizzled_data, const bool unswizzle, + const u32 x_start, const u32 y_start, const u32 z_start, const u32 x_end, + const u32 y_end, const u32 z_end, const u32 tile_offset, + const u32 xy_block_size, const u32 layer_z, const u32 stride_x, + const u32 bytes_per_pixel, const u32 out_bytes_per_pixel) { std::array<u8*, 2> data_ptrs; - const std::size_t stride{width * bytes_per_pixel}; - const std::size_t gobs_in_x = 64; - const std::size_t gobs_in_y = 8; - const std::size_t gobs_size = gobs_in_x * gobs_in_y; - const std::size_t image_width_in_gobs{(stride + gobs_in_x - 1) / gobs_in_x}; - const std::size_t copy_size{16}; - for (std::size_t y = 0; y < height; ++y) { - const std::size_t initial_gob = - (y / (gobs_in_y * block_height)) * gobs_size * block_height * image_width_in_gobs + - (y % (gobs_in_y * block_height) / gobs_in_y) * gobs_size; - const std::size_t pixel_base{y * width * out_bytes_per_pixel}; - const auto& table = fast_swizzle_table[y % gobs_in_y]; - for (std::size_t xb = 0; xb < stride; xb += copy_size) { - const std::size_t gob_address{initial_gob + - (xb / gobs_in_x) * gobs_size * block_height}; - const std::size_t swizzle_offset{gob_address + table[(xb / 16) % 4]}; - const std::size_t out_x = xb * out_bytes_per_pixel / bytes_per_pixel; - const std::size_t pixel_index{out_x + pixel_base}; - data_ptrs[unswizzle] = swizzled_data + swizzle_offset; - data_ptrs[!unswizzle] = unswizzled_data + pixel_index; - std::memcpy(data_ptrs[0], data_ptrs[1], copy_size); + u32 z_address = tile_offset; + const u32 x_startb = x_start * bytes_per_pixel; + const u32 x_endb = x_end * bytes_per_pixel; + const u32 copy_size = 16; + const u32 gob_size_x = 64; + const u32 gob_size_y = 8; + const u32 gob_size_z = 1; + const u32 gob_size = gob_size_x * gob_size_y * gob_size_z; + for (u32 z = z_start; z < z_end; z++) { + u32 y_address = z_address; + u32 pixel_base = layer_z * z + y_start * stride_x; + for (u32 y = y_start; y < y_end; y++) { + const auto& table = fast_swizzle_table[y % gob_size_y]; + for (u32 xb = x_startb; xb < x_endb; xb += copy_size) { + const u32 swizzle_offset{y_address + table[(xb / copy_size) % 4]}; + const u32 out_x = xb * out_bytes_per_pixel / bytes_per_pixel; + const u32 pixel_index{out_x + pixel_base}; + data_ptrs[unswizzle] = swizzled_data + swizzle_offset; + data_ptrs[!unswizzle] = unswizzled_data + pixel_index; + std::memcpy(data_ptrs[0], data_ptrs[1], copy_size); + } + pixel_base += stride_x; + if ((y + 1) % gob_size_y == 0) + y_address += gob_size; } + z_address += xy_block_size; } } -void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel, - u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height) { +/** + * This function unswizzles or swizzles a texture by mapping Linear to BlockLinear Textue. + * The body of this function takes care of splitting the swizzled texture into blocks, + * and managing the extents of it. Once all the parameters of a single block are obtained, + * the function calls 'ProcessBlock' to process that particular Block. + * + * Documentation for the memory layout and decoding can be found at: + * https://envytools.readthedocs.io/en/latest/hw/memory/g80-surface.html#blocklinear-surfaces + */ +template <bool fast> +void SwizzledData(u8* swizzled_data, u8* unswizzled_data, const bool unswizzle, const u32 width, + const u32 height, const u32 depth, const u32 bytes_per_pixel, + const u32 out_bytes_per_pixel, const u32 block_height, const u32 block_depth) { + auto div_ceil = [](const u32 x, const u32 y) { return ((x + y - 1) / y); }; + const u32 stride_x = width * out_bytes_per_pixel; + const u32 layer_z = height * stride_x; + const u32 gob_x_bytes = 64; + const u32 gob_elements_x = gob_x_bytes / bytes_per_pixel; + const u32 gob_elements_y = 8; + const u32 gob_elements_z = 1; + const u32 block_x_elements = gob_elements_x; + const u32 block_y_elements = gob_elements_y * block_height; + const u32 block_z_elements = gob_elements_z * block_depth; + const u32 blocks_on_x = div_ceil(width, block_x_elements); + const u32 blocks_on_y = div_ceil(height, block_y_elements); + const u32 blocks_on_z = div_ceil(depth, block_z_elements); + const u32 blocks = blocks_on_x * blocks_on_y * blocks_on_z; + const u32 gob_size = gob_x_bytes * gob_elements_y * gob_elements_z; + const u32 xy_block_size = gob_size * block_height; + const u32 block_size = xy_block_size * block_depth; + u32 tile_offset = 0; + for (u32 zb = 0; zb < blocks_on_z; zb++) { + const u32 z_start = zb * block_z_elements; + const u32 z_end = std::min(depth, z_start + block_z_elements); + for (u32 yb = 0; yb < blocks_on_y; yb++) { + const u32 y_start = yb * block_y_elements; + const u32 y_end = std::min(height, y_start + block_y_elements); + for (u32 xb = 0; xb < blocks_on_x; xb++) { + const u32 x_start = xb * block_x_elements; + const u32 x_end = std::min(width, x_start + block_x_elements); + if (fast) { + FastProcessBlock(swizzled_data, unswizzled_data, unswizzle, x_start, y_start, + z_start, x_end, y_end, z_end, tile_offset, xy_block_size, + layer_z, stride_x, bytes_per_pixel, out_bytes_per_pixel); + } else { + PreciseProcessBlock(swizzled_data, unswizzled_data, unswizzle, x_start, y_start, + z_start, x_end, y_end, z_end, tile_offset, xy_block_size, + layer_z, stride_x, bytes_per_pixel, out_bytes_per_pixel); + } + tile_offset += block_size; + } + } + } +} + +void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel, + u32 out_bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, + bool unswizzle, u32 block_height, u32 block_depth) { if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % 16 == 0) { - FastSwizzleData(width, height, bytes_per_pixel, out_bytes_per_pixel, swizzled_data, - unswizzled_data, unswizzle, block_height); + SwizzledData<true>(swizzled_data, unswizzled_data, unswizzle, width, height, depth, + bytes_per_pixel, out_bytes_per_pixel, block_height, block_depth); } else { - LegacySwizzleData(width, height, bytes_per_pixel, out_bytes_per_pixel, swizzled_data, - unswizzled_data, unswizzle, block_height); + SwizzledData<false>(swizzled_data, unswizzled_data, unswizzle, width, height, depth, + bytes_per_pixel, out_bytes_per_pixel, block_height, block_depth); } } @@ -125,7 +200,9 @@ u32 BytesPerPixel(TextureFormat format) { case TextureFormat::R32_G32_B32: return 12; case TextureFormat::ASTC_2D_4X4: + case TextureFormat::ASTC_2D_5X4: case TextureFormat::ASTC_2D_8X8: + case TextureFormat::ASTC_2D_8X5: case TextureFormat::A8R8G8B8: case TextureFormat::A2B10G10R10: case TextureFormat::BF10GF11RF11: @@ -152,10 +229,11 @@ u32 BytesPerPixel(TextureFormat format) { } std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width, - u32 height, u32 block_height) { - std::vector<u8> unswizzled_data(width * height * bytes_per_pixel); - CopySwizzledData(width / tile_size, height / tile_size, bytes_per_pixel, bytes_per_pixel, - Memory::GetPointer(address), unswizzled_data.data(), true, block_height); + u32 height, u32 depth, u32 block_height, u32 block_depth) { + std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel); + CopySwizzledData(width / tile_size, height / tile_size, depth, bytes_per_pixel, bytes_per_pixel, + Memory::GetPointer(address), unswizzled_data.data(), true, block_height, + block_depth); return unswizzled_data; } @@ -199,4 +277,19 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat return rgba_data; } +std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, + u32 block_height, u32 block_depth) { + if (tiled) { + const u32 gobs_in_x = 64 / bytes_per_pixel; + const u32 gobs_in_y = 8; + const u32 gobs_in_z = 1; + const u32 aligned_width = Common::AlignUp(width, gobs_in_x); + const u32 aligned_height = Common::AlignUp(height, gobs_in_y * block_height); + const u32 aligned_depth = Common::AlignUp(depth, gobs_in_z * block_depth); + return aligned_width * aligned_height * aligned_depth * bytes_per_pixel; + } else { + return width * height * depth * bytes_per_pixel; + } +} + } // namespace Tegra::Texture diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h index 1f7b731be..aaf316947 100644 --- a/src/video_core/textures/decoders.h +++ b/src/video_core/textures/decoders.h @@ -14,17 +14,14 @@ namespace Tegra::Texture { * Unswizzles a swizzled texture without changing its format. */ std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width, - u32 height, u32 block_height = TICEntry::DefaultBlockHeight); - -/** - * Unswizzles a swizzled depth texture without changing its format. - */ -std::vector<u8> UnswizzleDepthTexture(VAddr address, DepthFormat format, u32 width, u32 height, - u32 block_height = TICEntry::DefaultBlockHeight); + u32 height, u32 depth, + u32 block_height = TICEntry::DefaultBlockHeight, + u32 block_depth = TICEntry::DefaultBlockHeight); /// Copies texture data from a buffer and performs swizzling/unswizzling as necessary. -void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel, - u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height); +void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel, + u32 out_bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, + bool unswizzle, u32 block_height, u32 block_depth); /** * Decodes an unswizzled texture into a A8R8G8B8 texture. @@ -32,4 +29,10 @@ void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width, u32 height); +/** + * This function calculates the correct size of a texture depending if it's tiled or not. + */ +std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, + u32 block_height, u32 block_depth); + } // namespace Tegra::Texture diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index 8f31d825a..5947bd2b9 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -141,6 +141,7 @@ static_assert(sizeof(TextureHandle) == 4, "TextureHandle has wrong size"); struct TICEntry { static constexpr u32 DefaultBlockHeight = 16; + static constexpr u32 DefaultBlockDepth = 1; union { u32 raw; @@ -161,7 +162,9 @@ struct TICEntry { BitField<21, 3, TICHeaderVersion> header_version; }; union { + BitField<0, 3, u32> block_width; BitField<3, 3, u32> block_height; + BitField<6, 3, u32> block_depth; // High 16 bits of the pitch value BitField<0, 16, u32> pitch_high; @@ -202,13 +205,24 @@ struct TICEntry { return depth_minus_1 + 1; } + u32 BlockWidth() const { + ASSERT(IsTiled()); + // The block height is stored in log2 format. + return 1 << block_width; + } + u32 BlockHeight() const { - ASSERT(header_version == TICHeaderVersion::BlockLinear || - header_version == TICHeaderVersion::BlockLinearColorKey); + ASSERT(IsTiled()); // The block height is stored in log2 format. return 1 << block_height; } + u32 BlockDepth() const { + ASSERT(IsTiled()); + // The block height is stored in log2 format. + return 1 << block_depth; + } + bool IsTiled() const { return header_version == TICHeaderVersion::BlockLinear || header_version == TICHeaderVersion::BlockLinearColorKey; diff --git a/src/video_core/utils.h b/src/video_core/utils.h index 681919ae3..237cc1307 100644 --- a/src/video_core/utils.h +++ b/src/video_core/utils.h @@ -169,16 +169,20 @@ static void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, const std::string nice_addr = fmt::format("0x{:016x}", addr); std::string object_label; - switch (identifier) { - case GL_TEXTURE: - object_label = extra_info + "@" + nice_addr; - break; - case GL_PROGRAM: - object_label = "ShaderProgram@" + nice_addr; - break; - default: - object_label = fmt::format("Object(0x{:x})@{}", identifier, nice_addr); - break; + if (extra_info.empty()) { + switch (identifier) { + case GL_TEXTURE: + object_label = "Texture@" + nice_addr; + break; + case GL_PROGRAM: + object_label = "Shader@" + nice_addr; + break; + default: + object_label = fmt::format("Object(0x{:x})@{}", identifier, nice_addr); + break; + } + } else { + object_label = extra_info + '@' + nice_addr; } glObjectLabel(identifier, handle, -1, static_cast<const GLchar*>(object_label.c_str())); } diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp index 033ea1ea4..0a8f2bd9e 100644 --- a/src/web_service/telemetry_json.cpp +++ b/src/web_service/telemetry_json.cpp @@ -2,96 +2,114 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <thread> -#include "common/assert.h" +#include <json.hpp> #include "common/detached_tasks.h" +#include "common/web_result.h" #include "web_service/telemetry_json.h" #include "web_service/web_backend.h" namespace WebService { -TelemetryJson::TelemetryJson(const std::string& host, const std::string& username, - const std::string& token) - : host(std::move(host)), username(std::move(username)), token(std::move(token)) {} -TelemetryJson::~TelemetryJson() = default; +struct TelemetryJson::Impl { + Impl(std::string host, std::string username, std::string token) + : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {} -template <class T> -void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) { - sections[static_cast<u8>(type)][name] = value; -} + nlohmann::json& TopSection() { + return sections[static_cast<u8>(Telemetry::FieldType::None)]; + } -void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) { - TopSection()[name] = sections[static_cast<unsigned>(type)]; -} + const nlohmann::json& TopSection() const { + return sections[static_cast<u8>(Telemetry::FieldType::None)]; + } + + template <class T> + void Serialize(Telemetry::FieldType type, const std::string& name, T value) { + sections[static_cast<u8>(type)][name] = value; + } + + void SerializeSection(Telemetry::FieldType type, const std::string& name) { + TopSection()[name] = sections[static_cast<unsigned>(type)]; + } + + nlohmann::json output; + std::array<nlohmann::json, 7> sections; + std::string host; + std::string username; + std::string token; +}; + +TelemetryJson::TelemetryJson(std::string host, std::string username, std::string token) + : impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {} +TelemetryJson::~TelemetryJson() = default; void TelemetryJson::Visit(const Telemetry::Field<bool>& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field<double>& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field<float>& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field<u8>& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field<u16>& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field<u32>& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field<u64>& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field<s8>& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field<s16>& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field<s32>& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field<s64>& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); } void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) { - Serialize(field.GetType(), field.GetName(), std::string(field.GetValue())); + impl->Serialize(field.GetType(), field.GetName(), std::string(field.GetValue())); } void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) { - Serialize(field.GetType(), field.GetName(), field.GetValue().count()); + impl->Serialize(field.GetType(), field.GetName(), field.GetValue().count()); } void TelemetryJson::Complete() { - SerializeSection(Telemetry::FieldType::App, "App"); - SerializeSection(Telemetry::FieldType::Session, "Session"); - SerializeSection(Telemetry::FieldType::Performance, "Performance"); - SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); - SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); - SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); - - auto content = TopSection().dump(); + impl->SerializeSection(Telemetry::FieldType::App, "App"); + impl->SerializeSection(Telemetry::FieldType::Session, "Session"); + impl->SerializeSection(Telemetry::FieldType::Performance, "Performance"); + impl->SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); + impl->SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); + impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); + + auto content = impl->TopSection().dump(); // Send the telemetry async but don't handle the errors since they were written to the log Common::DetachedTasks::AddTask( - [host{this->host}, username{this->username}, token{this->token}, content]() { + [host{impl->host}, username{impl->username}, token{impl->token}, content]() { Client{host, username, token}.PostJson("/telemetry", content, true); }); } diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h index 0fe6f9a3e..93371414a 100644 --- a/src/web_service/telemetry_json.h +++ b/src/web_service/telemetry_json.h @@ -4,11 +4,9 @@ #pragma once -#include <array> +#include <chrono> #include <string> -#include <json.hpp> #include "common/telemetry.h" -#include "common/web_result.h" namespace WebService { @@ -18,8 +16,8 @@ namespace WebService { */ class TelemetryJson : public Telemetry::VisitorInterface { public: - TelemetryJson(const std::string& host, const std::string& username, const std::string& token); - ~TelemetryJson(); + TelemetryJson(std::string host, std::string username, std::string token); + ~TelemetryJson() override; void Visit(const Telemetry::Field<bool>& field) override; void Visit(const Telemetry::Field<double>& field) override; @@ -39,20 +37,8 @@ public: void Complete() override; private: - nlohmann::json& TopSection() { - return sections[static_cast<u8>(Telemetry::FieldType::None)]; - } - - template <class T> - void Serialize(Telemetry::FieldType type, const std::string& name, T value); - - void SerializeSection(Telemetry::FieldType type, const std::string& name); - - nlohmann::json output; - std::array<nlohmann::json, 7> sections; - std::string host; - std::string username; - std::string token; + struct Impl; + std::unique_ptr<Impl> impl; }; } // namespace WebService diff --git a/src/web_service/verify_login.cpp b/src/web_service/verify_login.cpp index 124aa3863..ca4b43b93 100644 --- a/src/web_service/verify_login.cpp +++ b/src/web_service/verify_login.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <json.hpp> +#include "common/web_result.h" #include "web_service/verify_login.h" #include "web_service/web_backend.h" diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp index 787b0fbcb..b7737b615 100644 --- a/src/web_service/web_backend.cpp +++ b/src/web_service/web_backend.cpp @@ -3,9 +3,11 @@ // Refer to the license.txt file included. #include <cstdlib> +#include <mutex> #include <string> -#include <thread> #include <LUrlParser.h> +#include <httplib.h> +#include "common/common_types.h" #include "common/logging/log.h" #include "common/web_result.h" #include "core/settings.h" @@ -20,99 +22,132 @@ constexpr u32 HTTPS_PORT = 443; constexpr u32 TIMEOUT_SECONDS = 30; -Client::JWTCache Client::jwt_cache{}; +struct Client::Impl { + Impl(std::string host, std::string username, std::string token) + : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} { + std::lock_guard<std::mutex> lock(jwt_cache.mutex); + if (this->username == jwt_cache.username && this->token == jwt_cache.token) { + jwt = jwt_cache.jwt; + } + } + + /// A generic function handles POST, GET and DELETE request together + Common::WebResult GenericJson(const std::string& method, const std::string& path, + const std::string& data, bool allow_anonymous) { + if (jwt.empty()) { + UpdateJWT(); + } + + if (jwt.empty() && !allow_anonymous) { + LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); + return Common::WebResult{Common::WebResult::Code::CredentialsMissing, + "Credentials needed"}; + } + + auto result = GenericJson(method, path, data, jwt); + if (result.result_string == "401") { + // Try again with new JWT + UpdateJWT(); + result = GenericJson(method, path, data, jwt); + } -Client::Client(const std::string& host, const std::string& username, const std::string& token) - : host(host), username(username), token(token) { - std::lock_guard<std::mutex> lock(jwt_cache.mutex); - if (username == jwt_cache.username && token == jwt_cache.token) { - jwt = jwt_cache.jwt; + return result; } -} -Common::WebResult Client::GenericJson(const std::string& method, const std::string& path, - const std::string& data, const std::string& jwt, - const std::string& username, const std::string& token) { - if (cli == nullptr) { - auto parsedUrl = LUrlParser::clParseURL::ParseURL(host); - int port; - if (parsedUrl.m_Scheme == "http") { - if (!parsedUrl.GetPort(&port)) { - port = HTTP_PORT; - } - cli = - std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS); - } else if (parsedUrl.m_Scheme == "https") { - if (!parsedUrl.GetPort(&port)) { - port = HTTPS_PORT; + /** + * A generic function with explicit authentication method specified + * JWT is used if the jwt parameter is not empty + * username + token is used if jwt is empty but username and token are not empty + * anonymous if all of jwt, username and token are empty + */ + Common::WebResult GenericJson(const std::string& method, const std::string& path, + const std::string& data, const std::string& jwt = "", + const std::string& username = "", const std::string& token = "") { + if (cli == nullptr) { + auto parsedUrl = LUrlParser::clParseURL::ParseURL(host); + int port; + if (parsedUrl.m_Scheme == "http") { + if (!parsedUrl.GetPort(&port)) { + port = HTTP_PORT; + } + cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, + TIMEOUT_SECONDS); + } else if (parsedUrl.m_Scheme == "https") { + if (!parsedUrl.GetPort(&port)) { + port = HTTPS_PORT; + } + cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port, + TIMEOUT_SECONDS); + } else { + LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme); + return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"}; } - cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port, - TIMEOUT_SECONDS); - } else { - LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme); - return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"}; } - } - if (cli == nullptr) { - LOG_ERROR(WebService, "Invalid URL {}", host + path); - return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"}; - } + if (cli == nullptr) { + LOG_ERROR(WebService, "Invalid URL {}", host + path); + return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"}; + } - httplib::Headers params; - if (!jwt.empty()) { - params = { - {std::string("Authorization"), fmt::format("Bearer {}", jwt)}, - }; - } else if (!username.empty()) { - params = { - {std::string("x-username"), username}, - {std::string("x-token"), token}, + httplib::Headers params; + if (!jwt.empty()) { + params = { + {std::string("Authorization"), fmt::format("Bearer {}", jwt)}, + }; + } else if (!username.empty()) { + params = { + {std::string("x-username"), username}, + {std::string("x-token"), token}, + }; + } + + params.emplace(std::string("api-version"), + std::string(API_VERSION.begin(), API_VERSION.end())); + if (method != "GET") { + params.emplace(std::string("Content-Type"), std::string("application/json")); }; - } - params.emplace(std::string("api-version"), std::string(API_VERSION.begin(), API_VERSION.end())); - if (method != "GET") { - params.emplace(std::string("Content-Type"), std::string("application/json")); - }; + httplib::Request request; + request.method = method; + request.path = path; + request.headers = params; + request.body = data; - httplib::Request request; - request.method = method; - request.path = path; - request.headers = params; - request.body = data; + httplib::Response response; - httplib::Response response; + if (!cli->send(request, response)) { + LOG_ERROR(WebService, "{} to {} returned null", method, host + path); + return Common::WebResult{Common::WebResult::Code::LibError, "Null response"}; + } - if (!cli->send(request, response)) { - LOG_ERROR(WebService, "{} to {} returned null", method, host + path); - return Common::WebResult{Common::WebResult::Code::LibError, "Null response"}; - } + if (response.status >= 400) { + LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path, + response.status); + return Common::WebResult{Common::WebResult::Code::HttpError, + std::to_string(response.status)}; + } - if (response.status >= 400) { - LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path, - response.status); - return Common::WebResult{Common::WebResult::Code::HttpError, - std::to_string(response.status)}; - } + auto content_type = response.headers.find("content-type"); - auto content_type = response.headers.find("content-type"); + if (content_type == response.headers.end()) { + LOG_ERROR(WebService, "{} to {} returned no content", method, host + path); + return Common::WebResult{Common::WebResult::Code::WrongContent, ""}; + } - if (content_type == response.headers.end()) { - LOG_ERROR(WebService, "{} to {} returned no content", method, host + path); - return Common::WebResult{Common::WebResult::Code::WrongContent, ""}; + if (content_type->second.find("application/json") == std::string::npos && + content_type->second.find("text/html; charset=utf-8") == std::string::npos) { + LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path, + content_type->second); + return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"}; + } + return Common::WebResult{Common::WebResult::Code::Success, "", response.body}; } - if (content_type->second.find("application/json") == std::string::npos && - content_type->second.find("text/html; charset=utf-8") == std::string::npos) { - LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path, - content_type->second); - return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"}; - } - return Common::WebResult{Common::WebResult::Code::Success, "", response.body}; -} + // Retrieve a new JWT from given username and token + void UpdateJWT() { + if (username.empty() || token.empty()) { + return; + } -void Client::UpdateJWT() { - if (!username.empty() && !token.empty()) { auto result = GenericJson("POST", "/jwt/internal", "", "", username, token); if (result.result_code != Common::WebResult::Code::Success) { LOG_ERROR(WebService, "UpdateJWT failed"); @@ -123,27 +158,39 @@ void Client::UpdateJWT() { jwt_cache.jwt = jwt = result.returned_data; } } -} -Common::WebResult Client::GenericJson(const std::string& method, const std::string& path, - const std::string& data, bool allow_anonymous) { - if (jwt.empty()) { - UpdateJWT(); - } + std::string host; + std::string username; + std::string token; + std::string jwt; + std::unique_ptr<httplib::Client> cli; + + struct JWTCache { + std::mutex mutex; + std::string username; + std::string token; + std::string jwt; + }; + static inline JWTCache jwt_cache; +}; - if (jwt.empty() && !allow_anonymous) { - LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); - return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"}; - } +Client::Client(std::string host, std::string username, std::string token) + : impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {} - auto result = GenericJson(method, path, data, jwt); - if (result.result_string == "401") { - // Try again with new JWT - UpdateJWT(); - result = GenericJson(method, path, data, jwt); - } +Client::~Client() = default; + +Common::WebResult Client::PostJson(const std::string& path, const std::string& data, + bool allow_anonymous) { + return impl->GenericJson("POST", path, data, allow_anonymous); +} + +Common::WebResult Client::GetJson(const std::string& path, bool allow_anonymous) { + return impl->GenericJson("GET", path, "", allow_anonymous); +} - return result; +Common::WebResult Client::DeleteJson(const std::string& path, const std::string& data, + bool allow_anonymous) { + return impl->GenericJson("DELETE", path, data, allow_anonymous); } } // namespace WebService diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h index d75fbcc15..c637e09df 100644 --- a/src/web_service/web_backend.h +++ b/src/web_service/web_backend.h @@ -4,23 +4,19 @@ #pragma once -#include <functional> -#include <mutex> +#include <memory> #include <string> -#include <tuple> -#include <httplib.h> -#include "common/common_types.h" -#include "common/web_result.h" -namespace httplib { -class Client; +namespace Common { +struct WebResult; } namespace WebService { class Client { public: - Client(const std::string& host, const std::string& username, const std::string& token); + Client(std::string host, std::string username, std::string token); + ~Client(); /** * Posts JSON to the specified path. @@ -30,9 +26,7 @@ public: * @return the result of the request. */ Common::WebResult PostJson(const std::string& path, const std::string& data, - bool allow_anonymous) { - return GenericJson("POST", path, data, allow_anonymous); - } + bool allow_anonymous); /** * Gets JSON from the specified path. @@ -40,9 +34,7 @@ public: * @param allow_anonymous If true, allow anonymous unauthenticated requests. * @return the result of the request. */ - Common::WebResult GetJson(const std::string& path, bool allow_anonymous) { - return GenericJson("GET", path, "", allow_anonymous); - } + Common::WebResult GetJson(const std::string& path, bool allow_anonymous); /** * Deletes JSON to the specified path. @@ -52,41 +44,11 @@ public: * @return the result of the request. */ Common::WebResult DeleteJson(const std::string& path, const std::string& data, - bool allow_anonymous) { - return GenericJson("DELETE", path, data, allow_anonymous); - } + bool allow_anonymous); private: - /// A generic function handles POST, GET and DELETE request together - Common::WebResult GenericJson(const std::string& method, const std::string& path, - const std::string& data, bool allow_anonymous); - - /** - * A generic function with explicit authentication method specified - * JWT is used if the jwt parameter is not empty - * username + token is used if jwt is empty but username and token are not empty - * anonymous if all of jwt, username and token are empty - */ - Common::WebResult GenericJson(const std::string& method, const std::string& path, - const std::string& data, const std::string& jwt = "", - const std::string& username = "", const std::string& token = ""); - - // Retrieve a new JWT from given username and token - void UpdateJWT(); - - std::string host; - std::string username; - std::string token; - std::string jwt; - std::unique_ptr<httplib::Client> cli; - - struct JWTCache { - std::mutex mutex; - std::string username; - std::string token; - std::string jwt; - }; - static JWTCache jwt_cache; + struct Impl; + std::unique_ptr<Impl> impl; }; } // namespace WebService diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 7fec15991..71c6ebb41 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -85,8 +85,8 @@ void Config::ReadValues() { Settings::values.resolution_factor = qt_config->value("resolution_factor", 1.0).toFloat(); Settings::values.use_frame_limit = qt_config->value("use_frame_limit", true).toBool(); Settings::values.frame_limit = qt_config->value("frame_limit", 100).toInt(); - Settings::values.use_accurate_framebuffers = - qt_config->value("use_accurate_framebuffers", false).toBool(); + Settings::values.use_accurate_gpu_emulation = + qt_config->value("use_accurate_gpu_emulation", false).toBool(); Settings::values.bg_red = qt_config->value("bg_red", 0.0).toFloat(); Settings::values.bg_green = qt_config->value("bg_green", 0.0).toFloat(); @@ -233,7 +233,7 @@ void Config::SaveValues() { qt_config->setValue("resolution_factor", (double)Settings::values.resolution_factor); qt_config->setValue("use_frame_limit", Settings::values.use_frame_limit); qt_config->setValue("frame_limit", Settings::values.frame_limit); - qt_config->setValue("use_accurate_framebuffers", Settings::values.use_accurate_framebuffers); + qt_config->setValue("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation); // Cast to double because Qt's written float values are not human-readable qt_config->setValue("bg_red", (double)Settings::values.bg_red); diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index cd1549462..8290b4384 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -75,7 +75,7 @@ void ConfigureGraphics::setConfiguration() { static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor))); ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); ui->frame_limit->setValue(Settings::values.frame_limit); - ui->use_accurate_framebuffers->setChecked(Settings::values.use_accurate_framebuffers); + ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); bg_color = QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue); ui->bg_button->setStyleSheet( @@ -87,7 +87,7 @@ void ConfigureGraphics::applyConfiguration() { ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex())); Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); Settings::values.frame_limit = ui->frame_limit->value(); - Settings::values.use_accurate_framebuffers = ui->use_accurate_framebuffers->isChecked(); + Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked(); Settings::values.bg_red = static_cast<float>(bg_color.redF()); Settings::values.bg_green = static_cast<float>(bg_color.greenF()); Settings::values.bg_blue = static_cast<float>(bg_color.blueF()); diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 8fc00af1b..91fcad994 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -50,9 +50,9 @@ </layout> </item> <item> - <widget class="QCheckBox" name="use_accurate_framebuffers"> + <widget class="QCheckBox" name="use_accurate_gpu_emulation"> <property name="text"> - <string>Use accurate framebuffers (slow)</string> + <string>Use accurate GPU emulation (slow)</string> </property> </widget> </item> diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp index cbcd5dd5f..44d423da2 100644 --- a/src/yuzu/debugger/graphics/graphics_surface.cpp +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -386,8 +386,9 @@ void GraphicsSurfaceWidget::OnUpdate() { // TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles. // Needs to be fixed if we plan to use this feature more, otherwise we may remove it. - auto unswizzled_data = Tegra::Texture::UnswizzleTexture( - *address, 1, Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height); + auto unswizzled_data = + Tegra::Texture::UnswizzleTexture(*address, 1, Tegra::Texture::BytesPerPixel(surface_format), + surface_width, surface_height, 1U); auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format, surface_width, surface_height); diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 4a09da685..7403e9ccd 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -66,10 +66,11 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList() } }; - add_threads(Core::System::GetInstance().Scheduler(0)->GetThreadList()); - add_threads(Core::System::GetInstance().Scheduler(1)->GetThreadList()); - add_threads(Core::System::GetInstance().Scheduler(2)->GetThreadList()); - add_threads(Core::System::GetInstance().Scheduler(3)->GetThreadList()); + const auto& system = Core::System::GetInstance(); + add_threads(system.Scheduler(0).GetThreadList()); + add_threads(system.Scheduler(1).GetThreadList()); + add_threads(system.Scheduler(2).GetThreadList()); + add_threads(system.Scheduler(3).GetThreadList()); return item_list; } diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index d2b3de683..3881aba5f 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -27,9 +27,8 @@ #include "yuzu/ui_settings.h" namespace { -void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, - const std::shared_ptr<FileSys::NCA>& nca, std::vector<u8>& icon, - std::string& name) { +void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca, + std::vector<u8>& icon, std::string& name) { auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca); if (icon_file != nullptr) icon = icon_file->ReadAllBytes(); @@ -97,7 +96,7 @@ void GameListWorker::AddInstalledTitlesToGameList() { FileSys::ContentRecordType::Program); for (const auto& game : installed_games) { - const auto& file = cache->GetEntryUnparsed(game); + const auto file = cache->GetEntryUnparsed(game); std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file); if (!loader) continue; @@ -108,9 +107,9 @@ void GameListWorker::AddInstalledTitlesToGameList() { loader->ReadProgramId(program_id); const FileSys::PatchManager patch{program_id}; - const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control); + const auto control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control); if (control != nullptr) - GetMetadataFromControlNCA(patch, control, icon, name); + GetMetadataFromControlNCA(patch, *control, icon, name); auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); @@ -136,9 +135,10 @@ void GameListWorker::AddInstalledTitlesToGameList() { FileSys::ContentRecordType::Control); for (const auto& entry : control_data) { - const auto nca = cache->GetEntry(entry); - if (nca != nullptr) - nca_control_map.insert_or_assign(entry.title_id, nca); + auto nca = cache->GetEntry(entry); + if (nca != nullptr) { + nca_control_map.insert_or_assign(entry.title_id, std::move(nca)); + } } } @@ -154,9 +154,11 @@ void GameListWorker::FillControlMap(const std::string& dir_path) { QFileInfo file_info(physical_name.c_str()); if (!is_dir && file_info.suffix().toStdString() == "nca") { auto nca = - std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read)); - if (nca->GetType() == FileSys::NCAContentType::Control) - nca_control_map.insert_or_assign(nca->GetTitleId(), nca); + std::make_unique<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read)); + if (nca->GetType() == FileSys::NCAContentType::Control) { + const u64 title_id = nca->GetTitleId(); + nca_control_map.insert_or_assign(title_id, std::move(nca)); + } } return true; }; @@ -197,8 +199,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign res2 == Loader::ResultStatus::Success) { // Use from metadata pool. if (nca_control_map.find(program_id) != nca_control_map.end()) { - const auto nca = nca_control_map[program_id]; - GetMetadataFromControlNCA(patch, nca, icon, name); + const auto& nca = nca_control_map[program_id]; + GetMetadataFromControlNCA(patch, *nca, icon, name); } } diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 09d20c42f..0e42d0bde 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h @@ -63,7 +63,7 @@ private: void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); std::shared_ptr<FileSys::VfsFilesystem> vfs; - std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map; + std::map<u64, std::unique_ptr<FileSys::NCA>> nca_control_map; QStringList watch_list; QString dir_path; bool deep_scan; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e11833c5a..bef9df00d 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -31,6 +31,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include <QDialogButtonBox> #include <QFileDialog> #include <QMessageBox> +#include <QtConcurrent/QtConcurrent> #include <QtGui> #include <QtWidgets> #include <fmt/format.h> @@ -171,8 +172,11 @@ GMainWindow::GMainWindow() .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc)); show(); + // Gen keys if necessary + OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); + // Necessary to load titles from nand in gamelist. - Service::FileSystem::CreateFactories(vfs); + Service::FileSystem::CreateFactories(*vfs); game_list->LoadCompatibilityList(); game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); @@ -443,6 +447,8 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen); // Help + connect(ui.action_Rederive, &QAction::triggered, this, + std::bind(&GMainWindow::OnReinitializeKeys, this, ReinitializeKeyBehavior::Warning)); connect(ui.action_About, &QAction::triggered, this, &GMainWindow::OnAbout); } @@ -902,22 +908,20 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, } void GMainWindow::OnMenuLoadFile() { - QString extensions; - for (const auto& piece : game_list->supported_file_extensions) - extensions += "*." + piece + " "; - - extensions += "main "; + const QString extensions = + QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main"); + const QString file_filter = tr("Switch Executable (%1);;All Files (*.*)", + "%1 is an identifier for the Switch executable file extensions.") + .arg(extensions); + const QString filename = QFileDialog::getOpenFileName( + this, tr("Load File"), UISettings::values.roms_path, file_filter); - QString file_filter = tr("Switch Executable") + " (" + extensions + ")"; - file_filter += ";;" + tr("All Files (*.*)"); - - QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), - UISettings::values.roms_path, file_filter); - if (!filename.isEmpty()) { - UISettings::values.roms_path = QFileInfo(filename).path(); - - BootGame(filename); + if (filename.isEmpty()) { + return; } + + UISettings::values.roms_path = QFileInfo(filename).path(); + BootGame(filename); } void GMainWindow::OnMenuLoadFolder() { @@ -1133,7 +1137,7 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) FileUtil::GetUserPath(target == EmulatedDirectoryTarget::SDMC ? FileUtil::UserPath::SDMCDir : FileUtil::UserPath::NANDDir, dir_path.toStdString()); - Service::FileSystem::CreateFactories(vfs); + Service::FileSystem::CreateFactories(*vfs); game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); } } @@ -1375,6 +1379,86 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det } } +void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { + if (behavior == ReinitializeKeyBehavior::Warning) { + const auto res = QMessageBox::information( + this, tr("Confirm Key Rederivation"), + tr("You are about to force rederive all of your keys. \nIf you do not know what this " + "means or what you are doing, \nthis is a potentially destructive action. \nPlease " + "make " + "sure this is what you want \nand optionally make backups.\n\nThis will delete your " + "autogenerated key files and re-run the key derivation module."), + QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel}); + + if (res == QMessageBox::Cancel) + return; + + FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) + + "prod.keys_autogenerated"); + FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) + + "console.keys_autogenerated"); + FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) + + "title.keys_autogenerated"); + } + + Core::Crypto::KeyManager keys{}; + if (keys.BaseDeriveNecessary()) { + Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory( + FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), FileSys::Mode::Read)}; + + const auto function = [this, &keys, &pdm] { + keys.PopulateFromPartitionData(pdm); + Service::FileSystem::CreateFactories(*vfs); + keys.DeriveETicket(pdm); + }; + + QString errors; + + if (!pdm.HasFuses()) + errors += tr("- Missing fuses - Cannot derive SBK\n"); + if (!pdm.HasBoot0()) + errors += tr("- Missing BOOT0 - Cannot derive master keys\n"); + if (!pdm.HasPackage2()) + errors += tr("- Missing BCPKG2-1-Normal-Main - Cannot derive general keys\n"); + if (!pdm.HasProdInfo()) + errors += tr("- Missing PRODINFO - Cannot derive title keys\n"); + + if (!errors.isEmpty()) { + + QMessageBox::warning( + this, tr("Warning Missing Derivation Components"), + tr("The following are missing from your configuration that may hinder key " + "derivation. It will be attempted but may not complete.<br><br>") + + errors + + tr("<br><br>You can get all of these and dump all of your games easily by " + "following <a href='https://yuzu-emu.org/help/quickstart/quickstart/'>the " + "quickstart guide</a>. Alternatively, you can use another method of dumping " + "to obtain all of your keys.")); + } + + QProgressDialog prog; + prog.setRange(0, 0); + prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your " + "system's performance.")); + prog.setWindowTitle(tr("Deriving Keys")); + + prog.show(); + + auto future = QtConcurrent::run(function); + while (!future.isFinished()) { + QCoreApplication::processEvents(); + } + + prog.close(); + } + + Service::FileSystem::CreateFactories(*vfs); + + if (behavior == ReinitializeKeyBehavior::Warning) { + game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); + } +} + bool GMainWindow::ConfirmClose() { if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) return true; @@ -1483,7 +1567,7 @@ void GMainWindow::UpdateUITheme() { emit UpdateThemedIcons(); } -void GMainWindow::SetDiscordEnabled(bool state) { +void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { #ifdef USE_DISCORD_PRESENCE if (state) { discord_rpc = std::make_unique<DiscordRPC::DiscordImpl>(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index fe0e9a50a..3663d6aed 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -41,6 +41,11 @@ enum class EmulatedDirectoryTarget { SDMC, }; +enum class ReinitializeKeyBehavior { + NoWarning, + Warning, +}; + namespace DiscordRPC { class DiscordInterface; } @@ -167,6 +172,7 @@ private slots: void HideFullscreen(); void ToggleWindowMode(); void OnCoreError(Core::System::ResultStatus, std::string); + void OnReinitializeKeys(ReinitializeKeyBehavior behavior); private: void UpdateStatusBar(); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index cb1664b21..9851f507d 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -103,6 +103,7 @@ </property> <addaction name="action_Report_Compatibility"/> <addaction name="separator"/> + <addaction name="action_Rederive"/> <addaction name="action_About"/> </widget> <addaction name="menu_File"/> @@ -159,6 +160,11 @@ <string>&Stop</string> </property> </action> + <action name="action_Rederive"> + <property name="text"> + <string>Reinitialize keys...</string> + </property> + </action> <action name="action_About"> <property name="text"> <string>About yuzu</string> diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 2470f4640..5e42e48b2 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -99,8 +99,8 @@ void Config::ReadValues() { Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true); Settings::values.frame_limit = static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); - Settings::values.use_accurate_framebuffers = - sdl2_config->GetBoolean("Renderer", "use_accurate_framebuffers", false); + Settings::values.use_accurate_gpu_emulation = + sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false); Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0); Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 762396e3b..a97b75f7b 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -110,9 +110,9 @@ use_frame_limit = # 1 - 9999: Speed limit as a percentage of target game speed. 100 (default) frame_limit = -# Whether to use accurate framebuffers +# Whether to use accurate GPU emulation # 0 (default): Off (fast), 1 : On (slow) -use_accurate_framebuffers = +use_accurate_gpu_emulation = # The clear color for the renderer. What shows up on the sides of the bottom screen. # Must be in range of 0.0-1.0. Defaults to 1.0 for all. diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 27aba95f6..c8b93b85b 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -175,7 +175,7 @@ int main(int argc, char** argv) { Core::System& system{Core::System::GetInstance()}; system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); - Service::FileSystem::CreateFactories(system.GetFilesystem()); + Service::FileSystem::CreateFactories(*system.GetFilesystem()); SCOPE_EXIT({ system.Shutdown(); }); |