diff options
Diffstat (limited to '')
123 files changed, 3906 insertions, 1810 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/src/common/alignment.h b/src/common/alignment.h index 225770fab..d94a2291f 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -19,4 +19,16 @@ constexpr T AlignDown(T value, std::size_t size) { return static_cast<T>(value - value % size); } +template <typename T> +constexpr bool Is4KBAligned(T value) { + static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); + return (value & 0xFFF) == 0; +} + +template <typename T> +constexpr bool IsWordAligned(T value) { + static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); + return (value & 0b11) == 0; +} + } // namespace Common 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 4fddaafd1..4755ec822 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -236,6 +236,24 @@ add_library(core STATIC hle/service/hid/irs.h hle/service/hid/xcd.cpp hle/service/hid/xcd.h + hle/service/hid/controllers/controller_base.cpp + hle/service/hid/controllers/controller_base.h + hle/service/hid/controllers/debug_pad.cpp + hle/service/hid/controllers/debug_pad.h + hle/service/hid/controllers/gesture.cpp + hle/service/hid/controllers/gesture.h + hle/service/hid/controllers/keyboard.cpp + hle/service/hid/controllers/keyboard.h + hle/service/hid/controllers/mouse.cpp + hle/service/hid/controllers/mouse.h + hle/service/hid/controllers/npad.cpp + hle/service/hid/controllers/npad.h + hle/service/hid/controllers/stubbed.cpp + hle/service/hid/controllers/stubbed.h + hle/service/hid/controllers/touchscreen.cpp + hle/service/hid/controllers/touchscreen.h + hle/service/hid/controllers/xpad.cpp + hle/service/hid/controllers/xpad.h hle/service/lbl/lbl.cpp hle/service/lbl/lbl.h hle/service/ldn/ldn.cpp @@ -400,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 0762321a9..4d2491870 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -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 e2fb9e038..7cb86ed92 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(); @@ -139,16 +139,16 @@ struct System::Impl { 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); @@ -160,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(); } } @@ -245,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 @@ -282,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 @@ -298,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; @@ -354,12 +355,15 @@ 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::Process* System::CurrentProcess() { @@ -371,8 +375,7 @@ const Kernel::Process* System::CurrentProcess() const { } ARM_Interface& System::ArmInterface(std::size_t core_index) { - ASSERT(core_index < NUM_CPU_CORES); - return impl->cpu_cores[core_index]->ArmInterface(); + return CpuCore(core_index).ArmInterface(); } Cpu& System::CpuCore(std::size_t core_index) { @@ -380,6 +383,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 ea4d53914..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,7 +175,10 @@ 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 pointer to the current process Kernel::Process* 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 a59a7e1f5..fd0786068 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -98,7 +98,7 @@ std::array<u8, 144> DecryptKeyblob(const std::array<u8, 176>& encrypted_keyblob, return keyblob; } -void KeyManager::DeriveGeneralPurposeKeys(u8 crypto_revision) { +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 = @@ -147,31 +147,38 @@ boost::optional<Key128> DeriveSDSeed() { "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; } @@ -234,7 +241,9 @@ std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { return {}; std::vector<u8> buffer(ticket_save.GetSize()); - ticket_save.ReadBytes(buffer.data(), buffer.size()); + if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) { + return {}; + } std::vector<TicketRaw> out; u32 magic{}; @@ -261,6 +270,9 @@ static std::array<u8, size> operator^(const std::array<u8, size>& lhs, 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); @@ -268,7 +280,7 @@ static std::array<u8, target_size> MGF1(const std::array<u8, in_size>& seed) { size_t i = 0; while (out.size() < target_size) { out.resize(out.size() + 0x20); - seed_exp[in_size + 3] = i; + seed_exp[in_size + 3] = static_cast<u8>(i); mbedtls_sha256(seed_exp.data(), seed_exp.size(), out.data() + out.size() - 0x20, 0); ++i; } @@ -299,10 +311,11 @@ boost::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, 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')) + 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)); @@ -871,9 +884,9 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) { "/system/save/80000000000000e2", "rb+"); + const auto blob2 = GetTicketblob(save2); auto res = GetTicketblob(save1); - const auto res2 = GetTicketblob(save2); - std::copy(res2.begin(), res2.end(), std::back_inserter(res)); + res.insert(res.end(), blob2.begin(), blob2.end()); for (const auto& raw : res) { const auto pair = ParseTicket(raw, rsa_key); diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index a41abbdfc..cccb3c0ae 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -175,7 +175,7 @@ private: void WriteKeyToFile(KeyCategory category, std::string_view keyname, const std::array<u8, Size>& key); - void DeriveGeneralPurposeKeys(u8 crypto_revision); + 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); diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp index d1c04e98d..ed0775444 100644 --- a/src/core/crypto/partition_data_manager.cpp +++ b/src/core/crypto/partition_data_manager.cpp @@ -11,7 +11,6 @@ #include <array> #include <cctype> #include <cstring> -#include <boost/optional/optional.hpp> #include <mbedtls/sha256.h> #include "common/assert.h" #include "common/common_funcs.h" @@ -19,7 +18,7 @@ #include "common/hex_util.h" #include "common/logging/log.h" #include "common/string_util.h" -#include "core/crypto/ctr_encryption_layer.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" @@ -302,10 +301,10 @@ FileSys::VirtualFile FindFileInDirWithNames(const FileSys::VirtualDir& dir, return nullptr; } -PartitionDataManager::PartitionDataManager(FileSys::VirtualDir sysdata_dir) +PartitionDataManager::PartitionDataManager(const FileSys::VirtualDir& sysdata_dir) : boot0(FindFileInDirWithNames(sysdata_dir, "BOOT0")), - fuses(FindFileInDirWithNames(sysdata_dir, "fuse")), - kfuses(FindFileInDirWithNames(sysdata_dir, "kfuse")), + 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"), @@ -314,13 +313,14 @@ PartitionDataManager::PartitionDataManager(FileSys::VirtualDir sysdata_dir) 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()), - prodinfo(FindFileInDirWithNames(sysdata_dir, "PRODINFO")) {} + : package1_decrypted->ReadAllBytes()) { +} PartitionDataManager::~PartitionDataManager() = default; @@ -332,18 +332,19 @@ FileSys::VirtualFile PartitionDataManager::GetBoot0Raw() const { return boot0; } -std::array<u8, 176> PartitionDataManager::GetEncryptedKeyblob(u8 index) const { - if (HasBoot0() && index < 32) +PartitionDataManager::EncryptedKeyBlob PartitionDataManager::GetEncryptedKeyblob( + std::size_t index) const { + if (HasBoot0() && index < NUM_ENCRYPTED_KEYBLOBS) return GetEncryptedKeyblobs()[index]; return {}; } -std::array<std::array<u8, 176>, 32> PartitionDataManager::GetEncryptedKeyblobs() const { +PartitionDataManager::EncryptedKeyBlobs PartitionDataManager::GetEncryptedKeyblobs() const { if (!HasBoot0()) return {}; - std::array<std::array<u8, 176>, 32> out{}; - for (size_t i = 0; i < 0x20; ++i) + EncryptedKeyBlobs out{}; + for (size_t i = 0; i < out.size(); ++i) boot0->Read(out[i].data(), out[i].size(), 0x180000 + i * 0x200); return out; } @@ -389,7 +390,7 @@ std::array<u8, 16> PartitionDataManager::GetKeyblobMACKeySource() const { return FindKeyFromHex(package1_decrypted_bytes, source_hashes[0]); } -std::array<u8, 16> PartitionDataManager::GetKeyblobKeySource(u8 revision) const { +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...", @@ -446,7 +447,7 @@ bool AttemptDecrypt(const std::array<u8, 16>& key, Package2Header& header) { return false; } -void PartitionDataManager::DecryptPackage2(std::array<std::array<u8, 16>, 0x20> package2_keys, +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)], @@ -456,43 +457,38 @@ void PartitionDataManager::DecryptPackage2(std::array<std::array<u8, 16>, 0x20> if (file->ReadObject(&header) != sizeof(Package2Header)) return; - u8 revision = 0xFF; + std::size_t revision = 0xFF; if (header.magic != Common::MakeMagic('P', 'K', '2', '1')) { - for (size_t i = 0; i < package2_keys.size(); ++i) { - if (AttemptDecrypt(package2_keys[i], header)) + 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 std::vector<u8> s1_iv(header.section_ctr[1].begin(), header.section_ctr[1].end()); - 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(s1_iv); + cipher.SetIV({header.section_ctr[1].begin(), header.section_ctr[1].end()}); cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt); - // package2_decrypted[static_cast<size_t>(type)] = s1; - INIHeader ini; std::memcpy(&ini, c.data(), sizeof(INIHeader)); if (ini.magic != Common::MakeMagic('I', 'N', 'I', '1')) return; - std::map<u64, KIPHeader> kips{}; 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; - kips.emplace(offset, kip); const auto name = Common::StringFromFixedZeroTerminatedBuffer(kip.name.data(), kip.name.size()); @@ -503,33 +499,30 @@ void PartitionDataManager::DecryptPackage2(std::array<std::array<u8, 16>, 0x20> continue; } - std::vector<u8> text(kip.sections[0].size_compressed); - std::vector<u8> rodata(kip.sections[1].size_compressed); - std::vector<u8> data(kip.sections[2].size_compressed); + 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}); - u64 offset_sec = sizeof(KIPHeader) + offset; - std::memcpy(text.data(), c.data() + offset_sec, text.size()); - offset_sec += text.size(); - std::memcpy(rodata.data(), c.data() + offset_sec, rodata.size()); - offset_sec += rodata.size(); - std::memcpy(data.data(), c.data() + offset_sec, data.size()); + const auto rodata_end = text_end + kip.sections[1].size_compressed; + const std::vector<u8> rodata = DecompressBLZ({text_end, rodata_end}); - offset += sizeof(KIPHeader) + kip.sections[0].size_compressed + - kip.sections[1].size_compressed + kip.sections[2].size_compressed; + const auto data_end = rodata_end + kip.sections[2].size_compressed; + const std::vector<u8> data = DecompressBLZ({rodata_end, data_end}); - text = DecompressBLZ(text); - rodata = DecompressBLZ(rodata); - data = DecompressBLZ(data); + 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()); - std::vector<u8> out(text.size() + rodata.size() + data.size()); - std::memcpy(out.data(), text.data(), text.size()); - std::memcpy(out.data() + text.size(), rodata.data(), rodata.size()); - std::memcpy(out.data() + text.size() + rodata.size(), data.data(), data.size()); + offset += sizeof(KIPHeader) + kip.sections[0].size_compressed + + kip.sections[1].size_compressed + kip.sections[2].size_compressed; if (name == "FS") - package2_fs[static_cast<size_t>(type)] = out; + package2_fs[static_cast<size_t>(type)] = std::move(out); else if (name == "spl") - package2_spl[static_cast<size_t>(type)] = out; + package2_spl[static_cast<size_t>(type)] = std::move(out); } } diff --git a/src/core/crypto/partition_data_manager.h b/src/core/crypto/partition_data_manager.h index 45c7fecfa..0ad007c72 100644 --- a/src/core/crypto/partition_data_manager.h +++ b/src/core/crypto/partition_data_manager.h @@ -5,9 +5,7 @@ #pragma once #include <vector> -#include "common/common_funcs.h" #include "common/common_types.h" -#include "common/swap.h" #include "core/file_sys/vfs_types.h" namespace Core::Crypto { @@ -24,15 +22,20 @@ enum class Package2Type { 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; - explicit PartitionDataManager(FileSys::VirtualDir sysdata_dir); + 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; - std::array<u8, 0xB0> GetEncryptedKeyblob(u8 index) const; - std::array<std::array<u8, 0xB0>, 0x20> GetEncryptedKeyblobs() 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; @@ -43,7 +46,7 @@ public: std::vector<u8> GetPackage1Decrypted() const; std::array<u8, 0x10> GetMasterKeySource() const; std::array<u8, 0x10> GetKeyblobMACKeySource() const; - std::array<u8, 0x10> GetKeyblobKeySource(u8 revision) const; + std::array<u8, 0x10> GetKeyblobKeySource(std::size_t revision) const; // Fuses bool HasFuses() const; @@ -57,7 +60,8 @@ public: // Package2 bool HasPackage2(Package2Type type = Package2Type::NormalMain) const; FileSys::VirtualFile GetPackage2Raw(Package2Type type = Package2Type::NormalMain) const; - void DecryptPackage2(std::array<std::array<u8, 16>, 0x20> package2, Package2Type type); + 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( 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/card_image.cpp b/src/core/file_sys/card_image.cpp index 8f5142a07..ecdd7505b 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -122,14 +122,16 @@ u64 XCI::GetProgramTitleID() const { return secure_partition->GetProgramTitleID(); } -std::shared_ptr<NCA> XCI::GetProgramNCA() const { - return program; +bool XCI::HasProgramNCA() const { + return program != nullptr; } VirtualFile XCI::GetProgramNCAFile() const { - if (GetProgramNCA() == nullptr) + if (!HasProgramNCA()) { return nullptr; - return GetProgramNCA()->GetBaseFile(); + } + + return program->GetBaseFile(); } const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const { diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index ce514dfa0..48cbef666 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -80,7 +80,7 @@ public: u64 GetProgramTitleID() const; - std::shared_ptr<NCA> GetProgramNCA() const; + bool HasProgramNCA() const; VirtualFile GetProgramNCAFile() const; const std::vector<std::shared_ptr<NCA>>& GetNCAs() const; std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const; diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index aa1b3c17d..6c356d85d 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -97,11 +97,288 @@ union NCASectionHeader { }; static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size."); -bool IsValidNCA(const NCAHeader& header) { +static bool IsValidNCA(const NCAHeader& header) { // TODO(DarkLordZach): Add NCA2/NCA0 support. return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); } +NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset) + : file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)) { + if (file == nullptr) { + status = Loader::ResultStatus::ErrorNullFile; + return; + } + + if (sizeof(NCAHeader) != file->ReadObject(&header)) { + LOG_ERROR(Loader, "File reader errored out during header read."); + status = Loader::ResultStatus::ErrorBadNCAHeader; + return; + } + + if (!HandlePotentialHeaderDecryption()) { + return; + } + + has_rights_id = std::any_of(header.rights_id.begin(), header.rights_id.end(), + [](char c) { return c != '\0'; }); + + const std::vector<NCASectionHeader> sections = ReadSectionHeaders(); + is_update = std::any_of(sections.begin(), sections.end(), [](const NCASectionHeader& header) { + return header.raw.header.crypto_type == NCASectionCryptoType::BKTR; + }); + + if (!ReadSections(sections, bktr_base_ivfc_offset)) { + return; + } + + status = Loader::ResultStatus::Success; +} + +NCA::~NCA() = default; + +bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) { + if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { + status = Loader::ResultStatus::ErrorNCA2; + return false; + } + + if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) { + status = Loader::ResultStatus::ErrorNCA0; + return false; + } + + return true; +} + +bool NCA::HandlePotentialHeaderDecryption() { + if (IsValidNCA(header)) { + return true; + } + + if (!CheckSupportedNCA(header)) { + return false; + } + + NCAHeader dec_header{}; + Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( + keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); + cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, + Core::Crypto::Op::Decrypt); + if (IsValidNCA(dec_header)) { + header = dec_header; + encrypted = true; + } else { + if (!CheckSupportedNCA(dec_header)) { + return false; + } + + if (keys.HasKey(Core::Crypto::S256KeyType::Header)) { + status = Loader::ResultStatus::ErrorIncorrectHeaderKey; + } else { + status = Loader::ResultStatus::ErrorMissingHeaderKey; + } + return false; + } + + return true; +} + +std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const { + const std::ptrdiff_t number_sections = + std::count_if(std::begin(header.section_tables), std::end(header.section_tables), + [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); + + std::vector<NCASectionHeader> sections(number_sections); + const auto length_sections = SECTION_HEADER_SIZE * number_sections; + + if (encrypted) { + auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET); + Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( + keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); + cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, + Core::Crypto::Op::Decrypt); + } else { + file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); + } + + return sections; +} + +bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) { + for (std::size_t i = 0; i < sections.size(); ++i) { + const auto& section = sections[i]; + + if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { + if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) { + return false; + } + } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { + if (!ReadPFS0Section(section, header.section_tables[i])) { + return false; + } + } + } + + return true; +} + +bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry, + u64 bktr_base_ivfc_offset) { + const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER; + ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; + const std::size_t romfs_offset = base_offset + ivfc_offset; + const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; + auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset); + auto dec = Decrypt(section, raw, romfs_offset); + + if (dec == nullptr) { + if (status != Loader::ResultStatus::Success) + return false; + if (has_rights_id) + status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; + else + status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; + return false; + } + + if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) { + if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') || + section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) { + status = Loader::ResultStatus::ErrorBadBKTRHeader; + return false; + } + + if (section.bktr.relocation.offset + section.bktr.relocation.size != + section.bktr.subsection.offset) { + status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation; + return false; + } + + const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); + if (section.bktr.subsection.offset + section.bktr.subsection.size != size) { + status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd; + return false; + } + + const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; + RelocationBlock relocation_block{}; + if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) != + sizeof(RelocationBlock)) { + status = Loader::ResultStatus::ErrorBadRelocationBlock; + return false; + } + SubsectionBlock subsection_block{}; + if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) != + sizeof(RelocationBlock)) { + status = Loader::ResultStatus::ErrorBadSubsectionBlock; + return false; + } + + std::vector<RelocationBucketRaw> relocation_buckets_raw( + (section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw)); + if (dec->ReadBytes(relocation_buckets_raw.data(), + section.bktr.relocation.size - sizeof(RelocationBlock), + section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) != + section.bktr.relocation.size - sizeof(RelocationBlock)) { + status = Loader::ResultStatus::ErrorBadRelocationBuckets; + return false; + } + + std::vector<SubsectionBucketRaw> subsection_buckets_raw( + (section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw)); + if (dec->ReadBytes(subsection_buckets_raw.data(), + section.bktr.subsection.size - sizeof(SubsectionBlock), + section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) != + section.bktr.subsection.size - sizeof(SubsectionBlock)) { + status = Loader::ResultStatus::ErrorBadSubsectionBuckets; + return false; + } + + std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size()); + std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(), + relocation_buckets.begin(), &ConvertRelocationBucketRaw); + std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size()); + std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(), + subsection_buckets.begin(), &ConvertSubsectionBucketRaw); + + u32 ctr_low; + std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low)); + subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low}); + subsection_buckets.back().entries.push_back({size, {0}, 0}); + + boost::optional<Core::Crypto::Key128> key = boost::none; + if (encrypted) { + if (has_rights_id) { + status = Loader::ResultStatus::Success; + key = GetTitlekey(); + if (key == boost::none) { + status = Loader::ResultStatus::ErrorMissingTitlekey; + return false; + } + } else { + key = GetKeyAreaKey(NCASectionCryptoType::BKTR); + if (key == boost::none) { + status = Loader::ResultStatus::ErrorMissingKeyAreaKey; + return false; + } + } + } + + if (bktr_base_romfs == nullptr) { + status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; + return false; + } + + auto bktr = std::make_shared<BKTR>( + bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset), + relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted, + encrypted ? key.get() : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset, + section.raw.section_ctr); + + // BKTR applies to entire IVFC, so make an offset version to level 6 + files.push_back(std::make_shared<OffsetVfsFile>( + bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset)); + } else { + files.push_back(std::move(dec)); + } + + romfs = files.back(); + return true; +} + +bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) { + const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) + + section.pfs0.pfs0_header_offset; + const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); + + auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset); + if (dec != nullptr) { + auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec)); + + if (npfs->GetStatus() == Loader::ResultStatus::Success) { + dirs.push_back(std::move(npfs)); + if (IsDirectoryExeFS(dirs.back())) + exefs = dirs.back(); + } else { + if (has_rights_id) + status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; + else + status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; + return false; + } + } else { + if (status != Loader::ResultStatus::Success) + return false; + if (has_rights_id) + status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; + else + status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; + return false; + } + + return true; +} + u8 NCA::GetCryptoRevision() const { u8 master_key_id = header.crypto_type; if (header.crypto_type_2 > master_key_id) @@ -133,7 +410,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; @@ -167,7 +444,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() { return titlekey; } -VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting_offset) { +VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) { if (!encrypted) return in; @@ -215,256 +492,6 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting } } -NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset) - : file(std::move(file_)), - bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) { - status = Loader::ResultStatus::Success; - - if (file == nullptr) { - status = Loader::ResultStatus::ErrorNullFile; - return; - } - - if (sizeof(NCAHeader) != file->ReadObject(&header)) { - LOG_ERROR(Loader, "File reader errored out during header read."); - status = Loader::ResultStatus::ErrorBadNCAHeader; - return; - } - - encrypted = false; - - if (!IsValidNCA(header)) { - if (header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { - status = Loader::ResultStatus::ErrorNCA2; - return; - } - if (header.magic == Common::MakeMagic('N', 'C', 'A', '0')) { - status = Loader::ResultStatus::ErrorNCA0; - return; - } - - NCAHeader dec_header{}; - Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( - keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); - cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, - Core::Crypto::Op::Decrypt); - if (IsValidNCA(dec_header)) { - header = dec_header; - encrypted = true; - } else { - if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { - status = Loader::ResultStatus::ErrorNCA2; - return; - } - if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) { - status = Loader::ResultStatus::ErrorNCA0; - return; - } - - if (!keys.HasKey(Core::Crypto::S256KeyType::Header)) - status = Loader::ResultStatus::ErrorMissingHeaderKey; - else - status = Loader::ResultStatus::ErrorIncorrectHeaderKey; - return; - } - } - - has_rights_id = std::find_if_not(header.rights_id.begin(), header.rights_id.end(), - [](char c) { return c == '\0'; }) != header.rights_id.end(); - - const std::ptrdiff_t number_sections = - std::count_if(std::begin(header.section_tables), std::end(header.section_tables), - [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); - - std::vector<NCASectionHeader> sections(number_sections); - const auto length_sections = SECTION_HEADER_SIZE * number_sections; - - if (encrypted) { - auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET); - Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( - keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); - cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, - Core::Crypto::Op::Decrypt); - } else { - file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); - } - - is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) { - return header.raw.header.crypto_type == NCASectionCryptoType::BKTR; - }) != sections.end(); - ivfc_offset = 0; - - for (std::ptrdiff_t i = 0; i < number_sections; ++i) { - auto section = sections[i]; - - if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { - const std::size_t base_offset = - header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER; - ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; - const std::size_t romfs_offset = base_offset + ivfc_offset; - const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; - auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset); - auto dec = Decrypt(section, raw, romfs_offset); - - if (dec == nullptr) { - if (status != Loader::ResultStatus::Success) - return; - if (has_rights_id) - status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; - else - status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; - return; - } - - if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) { - if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') || - section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) { - status = Loader::ResultStatus::ErrorBadBKTRHeader; - return; - } - - if (section.bktr.relocation.offset + section.bktr.relocation.size != - section.bktr.subsection.offset) { - status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation; - return; - } - - const u64 size = - MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - - header.section_tables[i].media_offset); - if (section.bktr.subsection.offset + section.bktr.subsection.size != size) { - status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd; - return; - } - - const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; - RelocationBlock relocation_block{}; - if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) != - sizeof(RelocationBlock)) { - status = Loader::ResultStatus::ErrorBadRelocationBlock; - return; - } - SubsectionBlock subsection_block{}; - if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) != - sizeof(RelocationBlock)) { - status = Loader::ResultStatus::ErrorBadSubsectionBlock; - return; - } - - std::vector<RelocationBucketRaw> relocation_buckets_raw( - (section.bktr.relocation.size - sizeof(RelocationBlock)) / - sizeof(RelocationBucketRaw)); - if (dec->ReadBytes(relocation_buckets_raw.data(), - section.bktr.relocation.size - sizeof(RelocationBlock), - section.bktr.relocation.offset + sizeof(RelocationBlock) - - offset) != - section.bktr.relocation.size - sizeof(RelocationBlock)) { - status = Loader::ResultStatus::ErrorBadRelocationBuckets; - return; - } - - std::vector<SubsectionBucketRaw> subsection_buckets_raw( - (section.bktr.subsection.size - sizeof(SubsectionBlock)) / - sizeof(SubsectionBucketRaw)); - if (dec->ReadBytes(subsection_buckets_raw.data(), - section.bktr.subsection.size - sizeof(SubsectionBlock), - section.bktr.subsection.offset + sizeof(SubsectionBlock) - - offset) != - section.bktr.subsection.size - sizeof(SubsectionBlock)) { - status = Loader::ResultStatus::ErrorBadSubsectionBuckets; - return; - } - - std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size()); - std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(), - relocation_buckets.begin(), &ConvertRelocationBucketRaw); - std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size()); - std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(), - subsection_buckets.begin(), &ConvertSubsectionBucketRaw); - - u32 ctr_low; - std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low)); - subsection_buckets.back().entries.push_back( - {section.bktr.relocation.offset, {0}, ctr_low}); - subsection_buckets.back().entries.push_back({size, {0}, 0}); - - boost::optional<Core::Crypto::Key128> key = boost::none; - if (encrypted) { - if (has_rights_id) { - status = Loader::ResultStatus::Success; - key = GetTitlekey(); - if (key == boost::none) { - status = Loader::ResultStatus::ErrorMissingTitlekey; - return; - } - } else { - key = GetKeyAreaKey(NCASectionCryptoType::BKTR); - if (key == boost::none) { - status = Loader::ResultStatus::ErrorMissingKeyAreaKey; - return; - } - } - } - - if (bktr_base_romfs == nullptr) { - status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; - return; - } - - auto bktr = std::make_shared<BKTR>( - bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset), - relocation_block, relocation_buckets, subsection_block, subsection_buckets, - encrypted, encrypted ? key.get() : Core::Crypto::Key128{}, base_offset, - bktr_base_ivfc_offset, section.raw.section_ctr); - - // BKTR applies to entire IVFC, so make an offset version to level 6 - - files.push_back(std::make_shared<OffsetVfsFile>( - bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset)); - romfs = files.back(); - } else { - files.push_back(std::move(dec)); - romfs = files.back(); - } - } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { - u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * - MEDIA_OFFSET_MULTIPLIER) + - section.pfs0.pfs0_header_offset; - u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - - header.section_tables[i].media_offset); - auto dec = - Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset); - if (dec != nullptr) { - auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec)); - - if (npfs->GetStatus() == Loader::ResultStatus::Success) { - dirs.push_back(std::move(npfs)); - if (IsDirectoryExeFS(dirs.back())) - exefs = dirs.back(); - } else { - if (has_rights_id) - status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; - else - status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; - return; - } - } else { - if (status != Loader::ResultStatus::Success) - return; - if (has_rights_id) - status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; - else - status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; - return; - } - } - } - - status = Loader::ResultStatus::Success; -} - -NCA::~NCA() = default; - Loader::ResultStatus NCA::GetStatus() const { return status; } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index f9f66cae9..1c903cd3f 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -73,8 +73,6 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) { return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; } -bool IsValidNCA(const NCAHeader& header); - // An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner. // After construction, use GetStatus to determine if the file is valid and ready to be used. class NCA : public ReadOnlyVfsDirectory { @@ -106,10 +104,19 @@ protected: bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; private: + bool CheckSupportedNCA(const NCAHeader& header); + bool HandlePotentialHeaderDecryption(); + + std::vector<NCASectionHeader> ReadSectionHeaders() const; + bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset); + bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry, + u64 bktr_base_ivfc_offset); + bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry); + u8 GetCryptoRevision() const; boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const; boost::optional<Core::Crypto::Key128> GetTitlekey(); - VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset); + VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset); std::vector<VirtualDir> dirs; std::vector<VirtualFile> files; @@ -118,15 +125,15 @@ private: VirtualDir exefs = nullptr; VirtualFile file; VirtualFile bktr_base_romfs; - u64 ivfc_offset; + u64 ivfc_offset = 0; NCAHeader header{}; bool has_rights_id{}; Loader::ResultStatus status{}; - bool encrypted; - bool is_update; + bool encrypted = false; + bool is_update = false; Core::Crypto::KeyManager keys; }; 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/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 019caebe9..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; @@ -346,7 +352,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam } std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const { - const auto& installed{Service::FileSystem::GetUnionContents()}; + const auto installed{Service::FileSystem::GetUnionContents()}; const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control); if (base_control_nca == nullptr) 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/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/process.cpp b/src/core/hle/kernel/process.cpp index c80b2c507..073dd5a7d 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -153,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()); } /** diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 73ec01e11..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,9 +58,23 @@ 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 { struct Segment { diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index e406df829..3b8a2e230 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -8,6 +8,7 @@ #include <mutex> #include <vector> +#include "common/alignment.h" #include "common/assert.h" #include "common/logging/log.h" #include "common/microprofile.h" @@ -36,9 +37,6 @@ namespace Kernel { 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 @@ -69,11 +67,11 @@ bool IsInsideNewMapRegion(const VMManager& vm, VAddr address, u64 size) { // 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)) { + if (!Common::Is4KBAligned(dst_addr) || !Common::Is4KBAligned(src_addr)) { return ERR_INVALID_ADDRESS; } - if (size == 0 || !Is4KBAligned(size)) { + if (size == 0 || !Common::Is4KBAligned(size)) { return ERR_INVALID_SIZE; } @@ -352,6 +350,10 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr, return ERR_INVALID_ADDRESS_STATE; } + if (!Common::IsWordAligned(mutex_addr)) { + return ERR_INVALID_ADDRESS; + } + auto& handle_table = Core::System::GetInstance().Kernel().HandleTable(); return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle, requesting_thread_handle); @@ -365,6 +367,10 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) { return ERR_INVALID_ADDRESS_STATE; } + if (!Common::IsWordAligned(mutex_addr)) { + return ERR_INVALID_ADDRESS; + } + return Mutex::Release(mutex_addr); } @@ -389,6 +395,12 @@ static void Break(u32 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(); } } @@ -442,25 +454,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; @@ -577,14 +576,18 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s "called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}", shared_memory_handle, addr, size, permissions); - if (!Is4KBAligned(addr)) { + if (!Common::Is4KBAligned(addr)) { return ERR_INVALID_ADDRESS; } - if (size == 0 || !Is4KBAligned(size)) { + if (size == 0 || !Common::Is4KBAligned(size)) { return ERR_INVALID_SIZE; } + if (!IsValidAddressRange(addr, size)) { + return ERR_INVALID_ADDRESS_STATE; + } + const auto permissions_type = static_cast<MemoryPermission>(permissions); if (permissions_type != MemoryPermission::Read && permissions_type != MemoryPermission::ReadWrite) { @@ -598,26 +601,46 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s return ERR_INVALID_HANDLE; } - return shared_memory->Map(Core::CurrentProcess(), addr, permissions_type, - MemoryPermission::DontCare); + auto* const current_process = Core::CurrentProcess(); + const auto& vm_manager = current_process->VMManager(); + + if (!vm_manager.IsWithinASLRRegion(addr, size)) { + return ERR_INVALID_MEMORY_RANGE; + } + + return shared_memory->Map(current_process, addr, permissions_type, MemoryPermission::DontCare); } static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size) { LOG_WARNING(Kernel_SVC, "called, shared_memory_handle=0x{:08X}, addr=0x{:X}, size=0x{:X}", shared_memory_handle, addr, size); - if (!Is4KBAligned(addr)) { + if (!Common::Is4KBAligned(addr)) { return ERR_INVALID_ADDRESS; } - if (size == 0 || !Is4KBAligned(size)) { + if (size == 0 || !Common::Is4KBAligned(size)) { return ERR_INVALID_SIZE; } + if (!IsValidAddressRange(addr, size)) { + return ERR_INVALID_ADDRESS_STATE; + } + auto& kernel = Core::System::GetInstance().Kernel(); auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle); + if (!shared_memory) { + return ERR_INVALID_HANDLE; + } + + auto* const current_process = Core::CurrentProcess(); + const auto& vm_manager = current_process->VMManager(); - return shared_memory->Unmap(Core::CurrentProcess(), addr); + if (!vm_manager.IsWithinASLRRegion(addr, size)) { + return ERR_INVALID_MEMORY_RANGE; + } + + return shared_memory->Unmap(current_process, addr); } /// Query process memory @@ -803,7 +826,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) @@ -1092,6 +1115,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(); @@ -1227,7 +1273,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 cbb80c3c4..b09753c80 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -77,6 +77,14 @@ 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(static_cast<u32>(Param(0)), Param(1)).raw); diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 352ce1725..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(); } @@ -230,7 +230,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name 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->scheduler = &Core::System::GetInstance().Scheduler(processor_id); thread->scheduler->AddThread(thread, priority); thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread); @@ -375,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 @@ -397,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/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index e412309fd..e1a34eef1 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,38 @@ 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; +} + +bool VMManager::IsWithinASLRRegion(VAddr begin, u64 size) const { + const VAddr range_end = begin + size; + const VAddr aslr_start = GetASLRRegionBaseAddress(); + const VAddr aslr_end = GetASLRRegionEndAddress(); + + if (aslr_start > begin || begin > range_end || range_end - 1 > aslr_end - 1) { + return false; + } + + if (range_end > heap_region_base && heap_region_end > begin) { + return false; + } + + if (range_end > map_region_base && map_region_end > begin) { + return false; + } + + return true; +} + 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..84c890224 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -205,6 +205,18 @@ 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; + + /// Determines whether or not the specified address range is within the ASLR region. + bool IsWithinASLRRegion(VAddr address, u64 size) const; + + /// Gets the size of the ASLR region + u64 GetASLRRegionSize() const; + /// Gets the base address of the code region. VAddr GetCodeRegionBaseAddress() const; @@ -306,6 +318,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/am/am.cpp b/src/core/hle/service/am/am.cpp index 69bfce1c1..4d1f83170 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -638,10 +638,12 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF {24, nullptr, "GetLaunchStorageInfoForDebug"}, {25, nullptr, "ExtendSaveData"}, {26, nullptr, "GetSaveDataSize"}, - {30, nullptr, "BeginBlockingHomeButtonShortAndLongPressed"}, - {31, nullptr, "EndBlockingHomeButtonShortAndLongPressed"}, - {32, nullptr, "BeginBlockingHomeButton"}, - {33, nullptr, "EndBlockingHomeButton"}, + {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, + "BeginBlockingHomeButtonShortAndLongPressed"}, + {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, + "EndBlockingHomeButtonShortAndLongPressed"}, + {32, &IApplicationFunctions::BeginBlockingHomeButton, "BeginBlockingHomeButton"}, + {33, &IApplicationFunctions::EndBlockingHomeButton, "EndBlockingHomeButton"}, {40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"}, {50, &IApplicationFunctions::GetPseudoDeviceId, "GetPseudoDeviceId"}, {60, nullptr, "SetMediaPlaybackStateForApplication"}, @@ -669,6 +671,32 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF IApplicationFunctions::~IApplicationFunctions() = default; +void IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed( + Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed( + Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void IApplicationFunctions::BeginBlockingHomeButton(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + +void IApplicationFunctions::EndBlockingHomeButton(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { constexpr std::array<u8, 0x88> data{{ 0xca, 0x97, 0x94, 0xc7, // Magic diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index b39b0d838..095f94851 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -154,6 +154,10 @@ private: void SetGamePlayRecordingState(Kernel::HLERequestContext& ctx); void NotifyRunning(Kernel::HLERequestContext& ctx); void GetPseudoDeviceId(Kernel::HLERequestContext& ctx); + void BeginBlockingHomeButtonShortAndLongPressed(Kernel::HLERequestContext& ctx); + void EndBlockingHomeButtonShortAndLongPressed(Kernel::HLERequestContext& ctx); + void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx); + void EndBlockingHomeButton(Kernel::HLERequestContext& ctx); }; class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { diff --git a/src/core/hle/service/am/omm.cpp b/src/core/hle/service/am/omm.cpp index 1c37f849f..6ab3fb906 100644 --- a/src/core/hle/service/am/omm.cpp +++ b/src/core/hle/service/am/omm.cpp @@ -17,22 +17,24 @@ OMM::OMM() : ServiceFramework{"omm"} { {5, nullptr, "GetCradleStatus"}, {6, nullptr, "FadeInDisplay"}, {7, nullptr, "FadeOutDisplay"}, - {8, nullptr, "Unknown1"}, - {9, nullptr, "Unknown2"}, - {10, nullptr, "Unknown3"}, - {11, nullptr, "Unknown4"}, - {12, nullptr, "Unknown5"}, - {13, nullptr, "Unknown6"}, - {14, nullptr, "Unknown7"}, - {15, nullptr, "Unknown8"}, - {16, nullptr, "Unknown9"}, - {17, nullptr, "Unknown10"}, - {18, nullptr, "Unknown11"}, - {19, nullptr, "Unknown12"}, - {20, nullptr, "Unknown13"}, - {21, nullptr, "Unknown14"}, - {22, nullptr, "Unknown15"}, - {23, nullptr, "Unknown16"}, + {8, nullptr, "GetCradleFwVersion"}, + {9, nullptr, "NotifyCecSettingsChanged"}, + {10, nullptr, "SetOperationModePolicy"}, + {11, nullptr, "GetDefaultDisplayResolution"}, + {12, nullptr, "GetDefaultDisplayResolutionChangeEvent"}, + {13, nullptr, "UpdateDefaultDisplayResolution"}, + {14, nullptr, "ShouldSleepOnBoot"}, + {15, nullptr, "NotifyHdcpApplicationExecutionStarted"}, + {16, nullptr, "NotifyHdcpApplicationExecutionFinished"}, + {17, nullptr, "NotifyHdcpApplicationDrawingStarted"}, + {18, nullptr, "NotifyHdcpApplicationDrawingFinished"}, + {19, nullptr, "GetHdcpAuthenticationFailedEvent"}, + {20, nullptr, "GetHdcpAuthenticationFailedEmulationEnabled"}, + {21, nullptr, "SetHdcpAuthenticationFailedEmulation"}, + {22, nullptr, "GetHdcpStateChangeEvent"}, + {23, nullptr, "GetHdcpState"}, + {24, nullptr, "ShowCardUpdateProcessing"}, + {25, nullptr, "SetApplicationCecSettingsAndNotifyChanged"}, }; // clang-format on diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 0ecfb5af1..428069df2 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -7,10 +7,13 @@ #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/event.h" #include "core/hle/kernel/process.h" #include "core/hle/service/aoc/aoc_u.h" #include "core/hle/service/filesystem/filesystem.h" @@ -19,7 +22,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; @@ -53,9 +56,13 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs {5, &AOC_U::GetAddOnContentBaseId, "GetAddOnContentBaseId"}, {6, nullptr, "PrepareAddOnContentByApplicationId"}, {7, &AOC_U::PrepareAddOnContent, "PrepareAddOnContent"}, - {8, nullptr, "GetAddOnContentListChangedEvent"}, + {8, &AOC_U::GetAddOnContentListChangedEvent, "GetAddOnContentListChangedEvent"}, }; RegisterHandlers(functions); + + auto& kernel = Core::System::GetInstance().Kernel(); + aoc_change_event = Kernel::Event::Create(kernel, Kernel::ResetType::Sticky, + "GetAddOnContentListChanged:Event"); } AOC_U::~AOC_U() = default; @@ -97,14 +104,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) { @@ -118,6 +135,14 @@ void AOC_U::PrepareAddOnContent(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } +void AOC_U::GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AOC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(aoc_change_event); +} + void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared<AOC_U>()->InstallAsService(service_manager); } diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h index b3c7cab7a..68d94fdaa 100644 --- a/src/core/hle/service/aoc/aoc_u.h +++ b/src/core/hle/service/aoc/aoc_u.h @@ -18,8 +18,10 @@ private: void ListAddOnContent(Kernel::HLERequestContext& ctx); void GetAddOnContentBaseId(Kernel::HLERequestContext& ctx); void PrepareAddOnContent(Kernel::HLERequestContext& ctx); + void GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx); std::vector<u64> add_on_content; + Kernel::SharedPtr<Kernel::Event> aoc_change_event; }; /// Registers all AOC services with the specified service manager. diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 6073f4ecd..fac6785a5 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -22,20 +22,22 @@ class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { public: explicit IAudioRenderer(AudioCore::AudioRendererParameter audren_params) : ServiceFramework("IAudioRenderer") { + // clang-format off static const FunctionInfo functions[] = { - {0, &IAudioRenderer::GetAudioRendererSampleRate, "GetAudioRendererSampleRate"}, - {1, &IAudioRenderer::GetAudioRendererSampleCount, "GetAudioRendererSampleCount"}, - {2, &IAudioRenderer::GetAudioRendererMixBufferCount, "GetAudioRendererMixBufferCount"}, - {3, &IAudioRenderer::GetAudioRendererState, "GetAudioRendererState"}, - {4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"}, - {5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"}, - {6, &IAudioRenderer::StopAudioRenderer, "StopAudioRenderer"}, + {0, &IAudioRenderer::GetSampleRate, "GetSampleRate"}, + {1, &IAudioRenderer::GetSampleCount, "GetSampleCount"}, + {2, &IAudioRenderer::GetMixBufferCount, "GetMixBufferCount"}, + {3, &IAudioRenderer::GetState, "GetState"}, + {4, &IAudioRenderer::RequestUpdate, "RequestUpdate"}, + {5, &IAudioRenderer::Start, "Start"}, + {6, &IAudioRenderer::Stop, "Stop"}, {7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"}, - {8, nullptr, "SetAudioRendererRenderingTimeLimit"}, - {9, nullptr, "GetAudioRendererRenderingTimeLimit"}, - {10, nullptr, "RequestUpdateAudioRendererAuto"}, + {8, nullptr, "SetRenderingTimeLimit"}, + {9, nullptr, "GetRenderingTimeLimit"}, + {10, nullptr, "RequestUpdateAuto"}, {11, nullptr, "ExecuteAudioRendererRendering"}, }; + // clang-format on RegisterHandlers(functions); auto& kernel = Core::System::GetInstance().Kernel(); @@ -49,42 +51,42 @@ private: system_event->Signal(); } - void GetAudioRendererSampleRate(Kernel::HLERequestContext& ctx) { + void GetSampleRate(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(renderer->GetSampleRate()); LOG_DEBUG(Service_Audio, "called"); } - void GetAudioRendererSampleCount(Kernel::HLERequestContext& ctx) { + void GetSampleCount(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(renderer->GetSampleCount()); LOG_DEBUG(Service_Audio, "called"); } - void GetAudioRendererState(Kernel::HLERequestContext& ctx) { + void GetState(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(static_cast<u32>(renderer->GetStreamState())); LOG_DEBUG(Service_Audio, "called"); } - void GetAudioRendererMixBufferCount(Kernel::HLERequestContext& ctx) { + void GetMixBufferCount(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(renderer->GetMixBufferCount()); LOG_DEBUG(Service_Audio, "called"); } - void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) { + void RequestUpdate(Kernel::HLERequestContext& ctx) { ctx.WriteBuffer(renderer->UpdateAudioRenderer(ctx.ReadBuffer())); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_Audio, "(STUBBED) called"); } - void StartAudioRenderer(Kernel::HLERequestContext& ctx) { + void Start(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -92,7 +94,7 @@ private: LOG_WARNING(Service_Audio, "(STUBBED) called"); } - void StopAudioRenderer(Kernel::HLERequestContext& ctx) { + void Stop(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -129,6 +131,7 @@ public: {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, {11, nullptr, "QueryAudioDeviceInputEvent"}, {12, nullptr, "QueryAudioDeviceOutputEvent"}, + {13, nullptr, "GetAudioSystemMasterVolumeSetting"}, }; RegisterHandlers(functions); diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index d40f18565..6701cb913 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -9,6 +9,7 @@ namespace Service::ES { class ETicket final : public ServiceFramework<ETicket> { public: explicit ETicket() : ServiceFramework{"es"} { + // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "ImportTicket"}, {2, nullptr, "ImportTicketCertificateSet"}, @@ -37,15 +38,18 @@ public: {25, nullptr, "DeletePrepurchaseRecord"}, {26, nullptr, "DeleteAllPrepurchaseRecord"}, {27, nullptr, "CountPrepurchaseRecord"}, - {28, nullptr, "ListPrepurchaseRecord"}, + {28, nullptr, "ListPrepurchaseRecordRightsIds"}, {29, nullptr, "ListPrepurchaseRecordInfo"}, - {30, nullptr, "Unknown1"}, - {31, nullptr, "Unknown2"}, - {32, nullptr, "Unknown3"}, - {33, nullptr, "Unknown4"}, - {34, nullptr, "Unknown5"}, - {35, nullptr, "Unknown6"}, + {30, nullptr, "CountTicket"}, + {31, nullptr, "ListTicketRightsIds"}, + {32, nullptr, "CountPrepurchaseRecordEx"}, + {33, nullptr, "ListPrepurchaseRecordRightsIdsEx"}, + {34, nullptr, "GetEncryptedTicketSize"}, + {35, nullptr, "GetEncryptedTicketData"}, + {36, nullptr, "DeleteAllInactiveELicenseRequiredPersonalizedTicket"}, + {503, nullptr, "GetTitleKey"}, }; + // clang-format on RegisterHandlers(functions); } }; 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/hid/controllers/controller_base.cpp b/src/core/hle/service/hid/controllers/controller_base.cpp new file mode 100644 index 000000000..0993a7815 --- /dev/null +++ b/src/core/hle/service/hid/controllers/controller_base.cpp @@ -0,0 +1,30 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/hid/controllers/controller_base.h" + +namespace Service::HID { + +ControllerBase::ControllerBase() = default; +ControllerBase::~ControllerBase() = default; + +void ControllerBase::ActivateController() { + if (is_activated) { + OnRelease(); + } + is_activated = true; + OnInit(); +} + +void ControllerBase::DeactivateController() { + if (is_activated) { + OnRelease(); + } + is_activated = false; +} + +bool ControllerBase::IsControllerActivated() const { + return is_activated; +} +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/controller_base.h b/src/core/hle/service/hid/controllers/controller_base.h new file mode 100644 index 000000000..f0e092b1b --- /dev/null +++ b/src/core/hle/service/hid/controllers/controller_base.h @@ -0,0 +1,45 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "common/swap.h" + +namespace Service::HID { +class ControllerBase { +public: + ControllerBase(); + virtual ~ControllerBase(); + + // Called when the controller is initialized + virtual void OnInit() = 0; + + // When the controller is released + virtual void OnRelease() = 0; + + // When the controller is requesting an update for the shared memory + virtual void OnUpdate(u8* data, std::size_t size) = 0; + + // Called when input devices should be loaded + virtual void OnLoadInputDevices() = 0; + + void ActivateController(); + + void DeactivateController(); + + bool IsControllerActivated() const; + +protected: + bool is_activated{false}; + + struct CommonHeader { + s64_le timestamp; + s64_le total_entry_count; + s64_le last_entry_index; + s64_le entry_count; + }; + static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size"); +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp new file mode 100644 index 000000000..3d100763f --- /dev/null +++ b/src/core/hle/service/hid/controllers/debug_pad.cpp @@ -0,0 +1,42 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/common_types.h" +#include "core/core_timing.h" +#include "core/hle/service/hid/controllers/debug_pad.h" + +namespace Service::HID { + +Controller_DebugPad::Controller_DebugPad() = default; +Controller_DebugPad::~Controller_DebugPad() = default; + +void Controller_DebugPad::OnInit() {} + +void Controller_DebugPad::OnRelease() {} + +void Controller_DebugPad::OnUpdate(u8* data, std::size_t size) { + shared_memory.header.timestamp = CoreTiming::GetTicks(); + shared_memory.header.total_entry_count = 17; + + if (!IsControllerActivated()) { + shared_memory.header.entry_count = 0; + shared_memory.header.last_entry_index = 0; + return; + } + shared_memory.header.entry_count = 16; + + const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index]; + shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17; + auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index]; + + cur_entry.sampling_number = last_entry.sampling_number + 1; + cur_entry.sampling_number2 = cur_entry.sampling_number; + // TODO(ogniK): Update debug pad states + + std::memcpy(data, &shared_memory, sizeof(SharedMemory)); +} + +void Controller_DebugPad::OnLoadInputDevices() {} +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h new file mode 100644 index 000000000..62b4f2682 --- /dev/null +++ b/src/core/hle/service/hid/controllers/debug_pad.h @@ -0,0 +1,56 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/service/hid/controllers/controller_base.h" + +namespace Service::HID { +class Controller_DebugPad final : public ControllerBase { +public: + Controller_DebugPad(); + ~Controller_DebugPad() override; + + // Called when the controller is initialized + void OnInit() override; + + // When the controller is released + void OnRelease() override; + + // When the controller is requesting an update for the shared memory + void OnUpdate(u8* data, std::size_t size) override; + + // Called when input devices should be loaded + void OnLoadInputDevices() override; + +private: + struct AnalogStick { + s32_le x; + s32_le y; + }; + static_assert(sizeof(AnalogStick) == 0x8); + + struct PadStates { + s64_le sampling_number; + s64_le sampling_number2; + u32_le attribute; + u32_le button_state; + AnalogStick r_stick; + AnalogStick l_stick; + }; + static_assert(sizeof(PadStates) == 0x28, "PadStates is an invalid state"); + + struct SharedMemory { + CommonHeader header; + std::array<PadStates, 17> pad_states; + INSERT_PADDING_BYTES(0x138); + }; + static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size"); + SharedMemory shared_memory{}; +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp new file mode 100644 index 000000000..898572277 --- /dev/null +++ b/src/core/hle/service/hid/controllers/gesture.cpp @@ -0,0 +1,43 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/common_types.h" +#include "core/core_timing.h" +#include "core/hle/service/hid/controllers/gesture.h" + +namespace Service::HID { +constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3BA00; + +Controller_Gesture::Controller_Gesture() = default; +Controller_Gesture::~Controller_Gesture() = default; + +void Controller_Gesture::OnInit() {} + +void Controller_Gesture::OnRelease() {} + +void Controller_Gesture::OnUpdate(u8* data, std::size_t size) { + shared_memory.header.timestamp = CoreTiming::GetTicks(); + shared_memory.header.total_entry_count = 17; + + if (!IsControllerActivated()) { + shared_memory.header.entry_count = 0; + shared_memory.header.last_entry_index = 0; + return; + } + shared_memory.header.entry_count = 16; + + const auto& last_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index]; + shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17; + auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index]; + + cur_entry.sampling_number = last_entry.sampling_number + 1; + cur_entry.sampling_number2 = cur_entry.sampling_number; + // TODO(ogniK): Update gesture states + + std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); +} + +void Controller_Gesture::OnLoadInputDevices() {} +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/gesture.h b/src/core/hle/service/hid/controllers/gesture.h new file mode 100644 index 000000000..1056ffbcd --- /dev/null +++ b/src/core/hle/service/hid/controllers/gesture.h @@ -0,0 +1,63 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/service/hid/controllers/controller_base.h" + +namespace Service::HID { +class Controller_Gesture final : public ControllerBase { +public: + Controller_Gesture(); + ~Controller_Gesture() override; + + // Called when the controller is initialized + void OnInit() override; + + // When the controller is released + void OnRelease() override; + + // When the controller is requesting an update for the shared memory + void OnUpdate(u8* data, size_t size) override; + + // Called when input devices should be loaded + void OnLoadInputDevices() override; + +private: + struct Locations { + s32_le x; + s32_le y; + }; + + struct GestureState { + s64_le sampling_number; + s64_le sampling_number2; + + s64_le detection_count; + s32_le type; + s32_le dir; + s32_le x; + s32_le y; + s32_le delta_x; + s32_le delta_y; + f32 vel_x; + f32 vel_y; + s32_le attributes; + f32 scale; + f32 rotation; + s32_le location_count; + std::array<Locations, 4> locations; + }; + static_assert(sizeof(GestureState) == 0x68, "GestureState is an invalid size"); + + struct SharedMemory { + CommonHeader header; + std::array<GestureState, 17> gesture_states; + }; + SharedMemory shared_memory{}; +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp new file mode 100644 index 000000000..ccfbce9ac --- /dev/null +++ b/src/core/hle/service/hid/controllers/keyboard.cpp @@ -0,0 +1,43 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/common_types.h" +#include "core/core_timing.h" +#include "core/hle/service/hid/controllers/keyboard.h" + +namespace Service::HID { +constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800; + +Controller_Keyboard::Controller_Keyboard() = default; +Controller_Keyboard::~Controller_Keyboard() = default; + +void Controller_Keyboard::OnInit() {} + +void Controller_Keyboard::OnRelease() {} + +void Controller_Keyboard::OnUpdate(u8* data, std::size_t size) { + shared_memory.header.timestamp = CoreTiming::GetTicks(); + shared_memory.header.total_entry_count = 17; + + if (!IsControllerActivated()) { + shared_memory.header.entry_count = 0; + shared_memory.header.last_entry_index = 0; + return; + } + shared_memory.header.entry_count = 16; + + const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index]; + shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17; + auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index]; + + cur_entry.sampling_number = last_entry.sampling_number + 1; + cur_entry.sampling_number2 = cur_entry.sampling_number; + // TODO(ogniK): Update keyboard states + + std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); +} + +void Controller_Keyboard::OnLoadInputDevices() {} +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h new file mode 100644 index 000000000..493e68fce --- /dev/null +++ b/src/core/hle/service/hid/controllers/keyboard.h @@ -0,0 +1,50 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/service/hid/controllers/controller_base.h" + +namespace Service::HID { +class Controller_Keyboard final : public ControllerBase { +public: + Controller_Keyboard(); + ~Controller_Keyboard() override; + + // Called when the controller is initialized + void OnInit() override; + + // When the controller is released + void OnRelease() override; + + // When the controller is requesting an update for the shared memory + void OnUpdate(u8* data, std::size_t size) override; + + // Called when input devices should be loaded + void OnLoadInputDevices() override; + +private: + struct KeyboardState { + s64_le sampling_number; + s64_le sampling_number2; + + s32_le modifier; + s32_le attribute; + std::array<u8, 32> key; + }; + static_assert(sizeof(KeyboardState) == 0x38, "KeyboardState is an invalid size"); + + struct SharedMemory { + CommonHeader header; + std::array<KeyboardState, 17> pad_states; + INSERT_PADDING_BYTES(0x28); + }; + static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size"); + SharedMemory shared_memory{}; +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp new file mode 100644 index 000000000..4e246a57d --- /dev/null +++ b/src/core/hle/service/hid/controllers/mouse.cpp @@ -0,0 +1,43 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/common_types.h" +#include "core/core_timing.h" +#include "core/hle/service/hid/controllers/mouse.h" + +namespace Service::HID { +constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400; + +Controller_Mouse::Controller_Mouse() = default; +Controller_Mouse::~Controller_Mouse() = default; + +void Controller_Mouse::OnInit() {} + +void Controller_Mouse::OnRelease() {} + +void Controller_Mouse::OnUpdate(u8* data, std::size_t size) { + shared_memory.header.timestamp = CoreTiming::GetTicks(); + shared_memory.header.total_entry_count = 17; + + if (!IsControllerActivated()) { + shared_memory.header.entry_count = 0; + shared_memory.header.last_entry_index = 0; + return; + } + shared_memory.header.entry_count = 16; + + auto& last_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index]; + shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17; + auto& cur_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index]; + + cur_entry.sampling_number = last_entry.sampling_number + 1; + cur_entry.sampling_number2 = cur_entry.sampling_number; + // TODO(ogniK): Update mouse states + + std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); +} + +void Controller_Mouse::OnLoadInputDevices() {} +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h new file mode 100644 index 000000000..543b0b71f --- /dev/null +++ b/src/core/hle/service/hid/controllers/mouse.h @@ -0,0 +1,50 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/service/hid/controllers/controller_base.h" + +namespace Service::HID { +class Controller_Mouse final : public ControllerBase { +public: + Controller_Mouse(); + ~Controller_Mouse() override; + + // Called when the controller is initialized + void OnInit() override; + + // When the controller is released + void OnRelease() override; + + // When the controller is requesting an update for the shared memory + void OnUpdate(u8* data, std::size_t size) override; + + // Called when input devices should be loaded + void OnLoadInputDevices() override; + +private: + struct MouseState { + s64_le sampling_number; + s64_le sampling_number2; + s32_le x; + s32_le y; + s32_le delta_x; + s32_le delta_y; + s32_le mouse_wheel; + s32_le button; + s32_le attribute; + }; + static_assert(sizeof(MouseState) == 0x30, "MouseState is an invalid size"); + + struct SharedMemory { + CommonHeader header; + std::array<MouseState, 17> mouse_states; + }; + SharedMemory shared_memory{}; +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp new file mode 100644 index 000000000..b26593b4f --- /dev/null +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -0,0 +1,436 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <array> +#include <cstring> +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/frontend/input.h" +#include "core/hle/kernel/event.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/settings.h" + +namespace Service::HID { + +constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28; +constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A; +constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6; +constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E; +constexpr s32 HID_JOYSTICK_MAX = 0x7fff; +constexpr s32 HID_JOYSTICK_MIN = -0x7fff; +constexpr std::size_t NPAD_OFFSET = 0x9A00; +constexpr u32 BATTERY_FULL = 2; + +constexpr std::array<u32, 10> npad_id_list{ + 0, 1, 2, 3, 4, 5, 6, 7, 32, 16, +}; + +enum class JoystickId : std::size_t { + Joystick_Left, + Joystick_Right, +}; + +Controller_NPad::Controller_NPad() = default; +Controller_NPad::~Controller_NPad() = default; + +void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { + const auto controller_type = connected_controllers[controller_idx].type; + auto& controller = shared_memory_entries[controller_idx]; + if (controller_type == NPadControllerType::None) { + return; + } + controller.joy_styles.raw = 0; // Zero out + controller.device_type.raw = 0; + switch (controller_type) { + case NPadControllerType::Handheld: + controller.joy_styles.handheld.Assign(1); + controller.device_type.handheld.Assign(1); + controller.pad_assignment = NPadAssignments::Dual; + break; + case NPadControllerType::JoyDual: + controller.joy_styles.joycon_dual.Assign(1); + controller.device_type.joycon_left.Assign(1); + controller.device_type.joycon_right.Assign(1); + controller.pad_assignment = NPadAssignments::Dual; + break; + case NPadControllerType::JoyLeft: + controller.joy_styles.joycon_left.Assign(1); + controller.device_type.joycon_left.Assign(1); + controller.pad_assignment = NPadAssignments::Dual; + break; + case NPadControllerType::JoyRight: + controller.joy_styles.joycon_right.Assign(1); + controller.device_type.joycon_right.Assign(1); + controller.pad_assignment = NPadAssignments::Dual; + break; + case NPadControllerType::Pokeball: + controller.joy_styles.pokeball.Assign(1); + controller.device_type.pokeball.Assign(1); + controller.pad_assignment = NPadAssignments::Single; + break; + case NPadControllerType::ProController: + controller.joy_styles.pro_controller.Assign(1); + controller.device_type.pro_controller.Assign(1); + controller.pad_assignment = NPadAssignments::Single; + break; + } + + controller.single_color_error = ColorReadError::ReadOk; + controller.single_color.body_color = 0; + controller.single_color.button_color = 0; + + controller.dual_color_error = ColorReadError::ReadOk; + controller.left_color.body_color = JOYCON_BODY_NEON_BLUE; + controller.left_color.button_color = JOYCON_BUTTONS_NEON_BLUE; + controller.right_color.body_color = JOYCON_BODY_NEON_RED; + controller.right_color.button_color = JOYCON_BUTTONS_NEON_RED; + + controller.properties.is_vertical.Assign(1); // TODO(ogniK): Swap joycons orientations + controller.properties.use_plus.Assign(1); + controller.properties.use_minus.Assign(1); + controller.battery_level[0] = BATTERY_FULL; + controller.battery_level[1] = BATTERY_FULL; + controller.battery_level[2] = BATTERY_FULL; +} + +void Controller_NPad::OnInit() { + auto& kernel = Core::System::GetInstance().Kernel(); + styleset_changed_event = + Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "npad:NpadStyleSetChanged"); + + if (!IsControllerActivated()) + return; + std::size_t controller{}; + if (style.raw == 0) { + // We want to support all controllers + style.handheld.Assign(1); + style.joycon_left.Assign(1); + style.joycon_right.Assign(1); + style.joycon_dual.Assign(1); + style.pro_controller.Assign(1); + style.pokeball.Assign(1); + } + if (std::none_of(connected_controllers.begin(), connected_controllers.end(), + [](const ControllerHolder& controller) { return controller.is_connected; })) { + supported_npad_id_types.resize(npad_id_list.size()); + std::memcpy(supported_npad_id_types.data(), npad_id_list.data(), + npad_id_list.size() * sizeof(u32)); + AddNewController(NPadControllerType::JoyDual); + } +} + +void Controller_NPad::OnLoadInputDevices() { + std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, + Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END, + buttons.begin(), Input::CreateDevice<Input::ButtonDevice>); + std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, + Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END, + sticks.begin(), Input::CreateDevice<Input::AnalogDevice>); +} + +void Controller_NPad::OnRelease() {} + +void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { + if (!IsControllerActivated()) + return; + for (std::size_t i = 0; i < shared_memory_entries.size(); i++) { + auto& npad = shared_memory_entries[i]; + const std::array<NPadGeneric*, 7> controller_npads{&npad.main_controller_states, + &npad.handheld_states, + &npad.dual_states, + &npad.left_joy_states, + &npad.right_joy_states, + &npad.pokeball_states, + &npad.libnx}; + + for (auto* main_controller : controller_npads) { + main_controller->common.entry_count = 16; + main_controller->common.total_entry_count = 17; + + const auto& last_entry = + main_controller->npad[main_controller->common.last_entry_index]; + + main_controller->common.timestamp = CoreTiming::GetTicks(); + main_controller->common.last_entry_index = + (main_controller->common.last_entry_index + 1) % 17; + + auto& cur_entry = main_controller->npad[main_controller->common.last_entry_index]; + + cur_entry.timestamp = last_entry.timestamp + 1; + cur_entry.timestamp2 = cur_entry.timestamp; + } + + const auto& controller_type = connected_controllers[i].type; + + if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) { + continue; + } + + // Pad states + ControllerPadState pad_state{}; + using namespace Settings::NativeButton; + pad_state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l_stick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus()); + + pad_state.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus()); + + pad_state.l_stick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l_stick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l_stick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l_stick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus()); + + pad_state.r_stick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus()); + + pad_state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus()); + + AnalogPosition lstick_entry{}; + AnalogPosition rstick_entry{}; + + const auto [stick_l_x_f, stick_l_y_f] = + sticks[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus(); + const auto [stick_r_x_f, stick_r_y_f] = + sticks[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus(); + lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX); + lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX); + rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); + rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX); + + auto& main_controller = + npad.main_controller_states.npad[npad.main_controller_states.common.last_entry_index]; + auto& handheld_entry = + npad.handheld_states.npad[npad.handheld_states.common.last_entry_index]; + auto& dual_entry = npad.dual_states.npad[npad.dual_states.common.last_entry_index]; + auto& left_entry = npad.left_joy_states.npad[npad.left_joy_states.common.last_entry_index]; + auto& right_entry = + npad.right_joy_states.npad[npad.right_joy_states.common.last_entry_index]; + auto& pokeball_entry = + npad.pokeball_states.npad[npad.pokeball_states.common.last_entry_index]; + auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index]; + + if (hold_type == NpadHoldType::Horizontal) { + // TODO(ogniK): Remap buttons for different orientations + } + libnx_entry.connection_status.raw = 0; + + switch (controller_type) { + case NPadControllerType::Handheld: + handheld_entry.connection_status.raw = 0; + handheld_entry.connection_status.IsConnected.Assign(1); + if (!Settings::values.use_docked_mode) { + handheld_entry.connection_status.IsWired.Assign(1); + } + handheld_entry.pad_states.raw = pad_state.raw; + handheld_entry.l_stick = lstick_entry; + handheld_entry.r_stick = rstick_entry; + break; + case NPadControllerType::JoyDual: + dual_entry.connection_status.raw = 0; + + dual_entry.connection_status.IsLeftJoyConnected.Assign(1); + dual_entry.connection_status.IsRightJoyConnected.Assign(1); + dual_entry.connection_status.IsConnected.Assign(1); + + libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); + libnx_entry.connection_status.IsRightJoyConnected.Assign(1); + libnx_entry.connection_status.IsConnected.Assign(1); + + dual_entry.pad_states.raw = pad_state.raw; + dual_entry.l_stick = lstick_entry; + dual_entry.r_stick = rstick_entry; + case NPadControllerType::JoyLeft: + left_entry.connection_status.raw = 0; + + left_entry.connection_status.IsConnected.Assign(1); + left_entry.pad_states.raw = pad_state.raw; + left_entry.l_stick = lstick_entry; + left_entry.r_stick = rstick_entry; + break; + case NPadControllerType::JoyRight: + right_entry.connection_status.raw = 0; + + right_entry.connection_status.IsConnected.Assign(1); + right_entry.pad_states.raw = pad_state.raw; + right_entry.l_stick = lstick_entry; + right_entry.r_stick = rstick_entry; + break; + case NPadControllerType::Pokeball: + pokeball_entry.connection_status.raw = 0; + + pokeball_entry.connection_status.IsConnected.Assign(1); + pokeball_entry.connection_status.IsWired.Assign(1); + + pokeball_entry.pad_states.raw = pad_state.raw; + pokeball_entry.l_stick = lstick_entry; + pokeball_entry.r_stick = rstick_entry; + break; + case NPadControllerType::ProController: + main_controller.connection_status.raw = 0; + + main_controller.connection_status.IsConnected.Assign(1); + main_controller.connection_status.IsWired.Assign(1); + main_controller.pad_states.raw = pad_state.raw; + main_controller.l_stick = lstick_entry; + main_controller.r_stick = rstick_entry; + break; + } + + // LibNX exclusively uses this section, so we always update it since LibNX doesn't activate + // any controllers. + libnx_entry.pad_states.raw = pad_state.raw; + libnx_entry.l_stick = lstick_entry; + libnx_entry.r_stick = rstick_entry; + } + std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(), + shared_memory_entries.size() * sizeof(NPadEntry)); +} // namespace Service::HID + +void Controller_NPad::SetSupportedStyleSet(NPadType style_set) { + style.raw = style_set.raw; +} + +Controller_NPad::NPadType Controller_NPad::GetSupportedStyleSet() const { + return style; +} + +void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) { + ASSERT(length > 0 && (length % sizeof(u32)) == 0); + supported_npad_id_types.clear(); + supported_npad_id_types.resize(length / sizeof(u32)); + std::memcpy(supported_npad_id_types.data(), data, length); +} + +void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) { + ASSERT(max_length < supported_npad_id_types.size()); + std::memcpy(data, supported_npad_id_types.data(), supported_npad_id_types.size()); +} + +std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const { + return supported_npad_id_types.size(); +} + +void Controller_NPad::SetHoldType(NpadHoldType joy_hold_type) { + hold_type = joy_hold_type; +} +Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const { + return hold_type; +} + +void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) { + ASSERT(npad_id < shared_memory_entries.size()); + shared_memory_entries[npad_id].pad_assignment = assignment_mode; +} + +void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids, + const std::vector<Vibration>& vibrations) { + if (!can_controllers_vibrate) { + return; + } + for (std::size_t i = 0; i < controller_ids.size(); i++) { + std::size_t controller_pos = i; + // Handheld controller conversion + if (controller_pos == 32) { + controller_pos = 8; + } + // Unknown controller conversion + if (controller_pos == 16) { + controller_pos = 9; + } + if (connected_controllers[controller_pos].is_connected) { + // TODO(ogniK): Vibrate the physical controller + } + } + LOG_WARNING(Service_HID, "(STUBBED) called"); + last_processed_vibration = vibrations.back(); +} + +Kernel::SharedPtr<Kernel::Event> Controller_NPad::GetStyleSetChangedEvent() const { + return styleset_changed_event; +} + +Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { + return last_processed_vibration; +} +void Controller_NPad::AddNewController(NPadControllerType controller) { + if (controller == NPadControllerType::Handheld) { + connected_controllers[8] = {controller, true}; + InitNewlyAddedControler(8); + return; + } + const auto pos = + std::find_if(connected_controllers.begin(), connected_controllers.end() - 2, + [](const ControllerHolder& holder) { return !holder.is_connected; }); + if (pos == connected_controllers.end() - 2) { + LOG_ERROR(Service_HID, "Cannot connect any more controllers!"); + return; + } + const auto controller_id = std::distance(connected_controllers.begin(), pos); + connected_controllers[controller_id] = {controller, true}; + InitNewlyAddedControler(controller_id); +} + +void Controller_NPad::ConnectNPad(u32 npad_id) { + if (npad_id >= connected_controllers.size()) + return; + connected_controllers[npad_id].is_connected = true; +} + +void Controller_NPad::DisconnectNPad(u32 npad_id) { + if (npad_id >= connected_controllers.size()) + return; + connected_controllers[npad_id].is_connected = false; +} + +Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) { + if (npad_id == npad_id_list.back() || npad_id == npad_id_list[npad_id_list.size() - 2]) { + // These are controllers without led patterns + return LedPattern{0, 0, 0, 0}; + } + switch (npad_id) { + case 0: + return LedPattern{1, 0, 0, 0}; + case 1: + return LedPattern{0, 1, 0, 0}; + case 2: + return LedPattern{0, 0, 1, 0}; + case 3: + return LedPattern{0, 0, 0, 1}; + case 4: + return LedPattern{1, 0, 0, 1}; + case 5: + return LedPattern{1, 0, 1, 0}; + case 6: + return LedPattern{1, 0, 1, 1}; + case 7: + return LedPattern{0, 1, 1, 0}; + default: + UNIMPLEMENTED_MSG("Unhandled npad_id {}", npad_id); + return LedPattern{0, 0, 0, 0}; + }; +} +void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { + can_controllers_vibrate = can_vibrate; +} +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h new file mode 100644 index 000000000..7c0f93acf --- /dev/null +++ b/src/core/hle/service/hid/controllers/npad.h @@ -0,0 +1,287 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include "common/common_types.h" +#include "core/frontend/input.h" +#include "core/hle/service/hid/controllers/controller_base.h" +#include "core/settings.h" + +namespace Service::HID { + +class Controller_NPad final : public ControllerBase { +public: + Controller_NPad(); + ~Controller_NPad() override; + + // Called when the controller is initialized + void OnInit() override; + + // When the controller is released + void OnRelease() override; + + // When the controller is requesting an update for the shared memory + void OnUpdate(u8* data, std::size_t size) override; + + // Called when input devices should be loaded + void OnLoadInputDevices() override; + + struct NPadType { + union { + u32_le raw{}; + + BitField<0, 1, u32_le> pro_controller; + BitField<1, 1, u32_le> handheld; + BitField<2, 1, u32_le> joycon_dual; + BitField<3, 1, u32_le> joycon_left; + BitField<4, 1, u32_le> joycon_right; + + BitField<6, 1, u32_le> pokeball; // TODO(ogniK): Confirm when possible + }; + }; + static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size"); + + struct Vibration { + f32 amp_low; + f32 freq_low; + f32 amp_high; + f32 freq_high; + }; + static_assert(sizeof(Vibration) == 0x10, "Vibration is an invalid size"); + + enum class NpadHoldType : u64 { + Vertical = 0, + Horizontal = 1, + }; + + enum class NPadAssignments : u32_le { + Dual = 0, + Single = 1, + }; + + enum class NPadControllerType { + None, + ProController, + Handheld, + JoyDual, + JoyLeft, + JoyRight, + Pokeball, + }; + + struct LedPattern { + explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) { + position1.Assign(light1); + position1.Assign(light2); + position1.Assign(light3); + position1.Assign(light4); + } + union { + u64 raw{}; + BitField<0, 1, u64> position1; + BitField<1, 1, u64> position2; + BitField<2, 1, u64> position3; + BitField<3, 1, u64> position4; + }; + }; + + void SetSupportedStyleSet(NPadType style_set); + NPadType GetSupportedStyleSet() const; + + void SetSupportedNPadIdTypes(u8* data, std::size_t length); + void GetSupportedNpadIdTypes(u32* data, std::size_t max_length); + std::size_t GetSupportedNPadIdTypesSize() const; + + void SetHoldType(NpadHoldType joy_hold_type); + NpadHoldType GetHoldType() const; + + void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode); + + void VibrateController(const std::vector<u32>& controller_ids, + const std::vector<Vibration>& vibrations); + + Kernel::SharedPtr<Kernel::Event> GetStyleSetChangedEvent() const; + Vibration GetLastVibration() const; + + void AddNewController(NPadControllerType controller); + + void ConnectNPad(u32 npad_id); + void DisconnectNPad(u32 npad_id); + LedPattern GetLedPattern(u32 npad_id); + void SetVibrationEnabled(bool can_vibrate); + +private: + struct CommonHeader { + s64_le timestamp; + s64_le total_entry_count; + s64_le last_entry_index; + s64_le entry_count; + }; + static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size"); + + struct ControllerColor { + u32_le body_color; + u32_le button_color; + }; + static_assert(sizeof(ControllerColor) == 8, "ControllerColor is an invalid size"); + + struct ControllerPadState { + union { + u64_le raw{}; + // Button states + BitField<0, 1, u64_le> a; + BitField<1, 1, u64_le> b; + BitField<2, 1, u64_le> x; + BitField<3, 1, u64_le> y; + BitField<4, 1, u64_le> l_stick; + BitField<5, 1, u64_le> r_stick; + BitField<6, 1, u64_le> l; + BitField<7, 1, u64_le> r; + BitField<8, 1, u64_le> zl; + BitField<9, 1, u64_le> zr; + BitField<10, 1, u64_le> plus; + BitField<11, 1, u64_le> minus; + + // D-Pad + BitField<12, 1, u64_le> d_left; + BitField<13, 1, u64_le> d_up; + BitField<14, 1, u64_le> d_right; + BitField<15, 1, u64_le> d_down; + + // Left JoyStick + BitField<16, 1, u64_le> l_stick_left; + BitField<17, 1, u64_le> l_stick_up; + BitField<18, 1, u64_le> l_stick_right; + BitField<19, 1, u64_le> l_stick_down; + + // Right JoyStick + BitField<20, 1, u64_le> r_stick_left; + BitField<21, 1, u64_le> r_stick_up; + BitField<22, 1, u64_le> r_stick_right; + BitField<23, 1, u64_le> r_stick_down; + + // Not always active? + BitField<24, 1, u64_le> sl; + BitField<25, 1, u64_le> sr; + }; + }; + static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size"); + + struct AnalogPosition { + s32_le x; + s32_le y; + }; + static_assert(sizeof(AnalogPosition) == 8, "AnalogPosition is an invalid size"); + + struct ConnectionState { + union { + u32_le raw{}; + BitField<0, 1, u32_le> IsConnected; + BitField<1, 1, u32_le> IsWired; + BitField<2, 1, u32_le> IsLeftJoyConnected; + BitField<3, 1, u32_le> IsLeftJoyWired; + BitField<4, 1, u32_le> IsRightJoyConnected; + BitField<5, 1, u32_le> IsRightJoyWired; + }; + }; + static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size"); + + struct GenericStates { + s64_le timestamp; + s64_le timestamp2; + ControllerPadState pad_states; + AnalogPosition l_stick; + AnalogPosition r_stick; + ConnectionState connection_status; + }; + static_assert(sizeof(GenericStates) == 0x30, "NPadGenericStates is an invalid size"); + + struct NPadGeneric { + CommonHeader common; + std::array<GenericStates, 17> npad; + }; + static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size"); + + enum class ColorReadError : u32_le { + ReadOk = 0, + ColorDoesntExist = 1, + NoController = 2, + }; + + struct NPadProperties { + union { + s64_le raw{}; + BitField<11, 1, s64_le> is_vertical; + BitField<12, 1, s64_le> is_horizontal; + BitField<13, 1, s64_le> use_plus; + BitField<14, 1, s64_le> use_minus; + }; + }; + + struct NPadDevice { + union { + u32_le raw{}; + BitField<0, 1, s32_le> pro_controller; + BitField<1, 1, s32_le> handheld; + BitField<2, 1, s32_le> handheld_left; + BitField<3, 1, s32_le> handheld_right; + BitField<4, 1, s32_le> joycon_left; + BitField<5, 1, s32_le> joycon_right; + BitField<6, 1, s32_le> pokeball; + }; + }; + + struct NPadEntry { + NPadType joy_styles; + NPadAssignments pad_assignment; + + ColorReadError single_color_error; + ControllerColor single_color; + + ColorReadError dual_color_error; + ControllerColor left_color; + ControllerColor right_color; + + NPadGeneric main_controller_states; + NPadGeneric handheld_states; + NPadGeneric dual_states; + NPadGeneric left_joy_states; + NPadGeneric right_joy_states; + NPadGeneric pokeball_states; + NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be + // relying on this for the time being + INSERT_PADDING_BYTES( + 0x708 * + 6); // TODO(ogniK): SixAxis states, require more information before implementation + NPadDevice device_type; + NPadProperties properties; + INSERT_PADDING_WORDS(1); + std::array<u32, 3> battery_level; + INSERT_PADDING_BYTES(0x5c); + INSERT_PADDING_BYTES(0xdf8); + }; + static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size"); + + struct ControllerHolder { + Controller_NPad::NPadControllerType type; + bool is_connected; + }; + + NPadType style{}; + std::array<NPadEntry, 10> shared_memory_entries{}; + std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID> + buttons; + std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks; + std::vector<u32> supported_npad_id_types{}; + NpadHoldType hold_type{NpadHoldType::Vertical}; + Kernel::SharedPtr<Kernel::Event> styleset_changed_event; + Vibration last_processed_vibration{}; + std::array<ControllerHolder, 10> connected_controllers{}; + bool can_controllers_vibrate{true}; + + void InitNewlyAddedControler(std::size_t controller_idx); +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/stubbed.cpp b/src/core/hle/service/hid/controllers/stubbed.cpp new file mode 100644 index 000000000..02fcfadd9 --- /dev/null +++ b/src/core/hle/service/hid/controllers/stubbed.cpp @@ -0,0 +1,39 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/common_types.h" +#include "core/core_timing.h" +#include "core/hle/service/hid/controllers/stubbed.h" + +namespace Service::HID { + +Controller_Stubbed::Controller_Stubbed() = default; +Controller_Stubbed::~Controller_Stubbed() = default; + +void Controller_Stubbed::OnInit() {} + +void Controller_Stubbed::OnRelease() {} + +void Controller_Stubbed::OnUpdate(u8* data, std::size_t size) { + if (!smart_update) { + return; + } + + CommonHeader header{}; + header.timestamp = CoreTiming::GetTicks(); + header.total_entry_count = 17; + header.entry_count = 0; + header.last_entry_index = 0; + + std::memcpy(data + common_offset, &header, sizeof(CommonHeader)); +} + +void Controller_Stubbed::OnLoadInputDevices() {} + +void Controller_Stubbed::SetCommonHeaderOffset(std::size_t off) { + common_offset = off; + smart_update = true; +} +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/stubbed.h b/src/core/hle/service/hid/controllers/stubbed.h new file mode 100644 index 000000000..4a21c643e --- /dev/null +++ b/src/core/hle/service/hid/controllers/stubbed.h @@ -0,0 +1,34 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/hle/service/hid/controllers/controller_base.h" + +namespace Service::HID { +class Controller_Stubbed final : public ControllerBase { +public: + Controller_Stubbed(); + ~Controller_Stubbed() override; + + // Called when the controller is initialized + void OnInit() override; + + // When the controller is released + void OnRelease() override; + + // When the controller is requesting an update for the shared memory + void OnUpdate(u8* data, std::size_t size) override; + + // Called when input devices should be loaded + void OnLoadInputDevices() override; + + void SetCommonHeaderOffset(std::size_t off); + +private: + bool smart_update{}; + std::size_t common_offset{}; +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp new file mode 100644 index 000000000..43efef803 --- /dev/null +++ b/src/core/hle/service/hid/controllers/touchscreen.cpp @@ -0,0 +1,65 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/common_types.h" +#include "core/core_timing.h" +#include "core/frontend/emu_window.h" +#include "core/frontend/input.h" +#include "core/hle/service/hid/controllers/touchscreen.h" +#include "core/settings.h" + +namespace Service::HID { +constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400; + +Controller_Touchscreen::Controller_Touchscreen() = default; +Controller_Touchscreen::~Controller_Touchscreen() = default; + +void Controller_Touchscreen::OnInit() {} + +void Controller_Touchscreen::OnRelease() {} + +void Controller_Touchscreen::OnUpdate(u8* data, std::size_t size) { + shared_memory.header.timestamp = CoreTiming::GetTicks(); + shared_memory.header.total_entry_count = 17; + + if (!IsControllerActivated()) { + shared_memory.header.entry_count = 0; + shared_memory.header.last_entry_index = 0; + return; + } + shared_memory.header.entry_count = 16; + + const auto& last_entry = + shared_memory.shared_memory_entries[shared_memory.header.last_entry_index]; + shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17; + auto& cur_entry = shared_memory.shared_memory_entries[shared_memory.header.last_entry_index]; + + cur_entry.sampling_number = last_entry.sampling_number + 1; + cur_entry.sampling_number2 = cur_entry.sampling_number; + + const auto [x, y, pressed] = touch_device->GetStatus(); + auto& touch_entry = cur_entry.states[0]; + if (pressed) { + touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width); + touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height); + touch_entry.diameter_x = 15; + touch_entry.diameter_y = 15; + touch_entry.rotation_angle = 0; + const u64 tick = CoreTiming::GetTicks(); + touch_entry.delta_time = tick - last_touch; + last_touch = tick; + touch_entry.finger = 0; + cur_entry.entry_count = 1; + } else { + cur_entry.entry_count = 0; + } + + std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(TouchScreenSharedMemory)); +} + +void Controller_Touchscreen::OnLoadInputDevices() { + touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device); +} +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h new file mode 100644 index 000000000..e5db6e6ba --- /dev/null +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -0,0 +1,63 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" +#include "core/frontend/input.h" +#include "core/hle/service/hid/controllers/controller_base.h" + +namespace Service::HID { +class Controller_Touchscreen final : public ControllerBase { +public: + Controller_Touchscreen(); + ~Controller_Touchscreen() override; + + // Called when the controller is initialized + void OnInit() override; + + // When the controller is released + void OnRelease() override; + + // When the controller is requesting an update for the shared memory + void OnUpdate(u8* data, std::size_t size) override; + + // Called when input devices should be loaded + void OnLoadInputDevices() override; + +private: + struct TouchState { + u64_le delta_time; + u32_le attribute; + u32_le finger; + u32_le x; + u32_le y; + u32_le diameter_x; + u32_le diameter_y; + u32_le rotation_angle; + }; + static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); + + struct TouchScreenEntry { + s64_le sampling_number; + s64_le sampling_number2; + s32_le entry_count; + std::array<TouchState, 16> states; + }; + static_assert(sizeof(TouchScreenEntry) == 0x298, "TouchScreenEntry is an invalid size"); + + struct TouchScreenSharedMemory { + CommonHeader header; + std::array<TouchScreenEntry, 17> shared_memory_entries{}; + INSERT_PADDING_BYTES(0x3c8); + }; + static_assert(sizeof(TouchScreenSharedMemory) == 0x3000, + "TouchScreenSharedMemory is an invalid size"); + TouchScreenSharedMemory shared_memory{}; + std::unique_ptr<Input::TouchDevice> touch_device; + s64_le last_touch{}; +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/xpad.cpp b/src/core/hle/service/hid/controllers/xpad.cpp new file mode 100644 index 000000000..cd397c70b --- /dev/null +++ b/src/core/hle/service/hid/controllers/xpad.cpp @@ -0,0 +1,45 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/common_types.h" +#include "core/core_timing.h" +#include "core/hle/service/hid/controllers/xpad.h" + +namespace Service::HID { +constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00; + +Controller_XPad::Controller_XPad() = default; +Controller_XPad::~Controller_XPad() = default; + +void Controller_XPad::OnInit() {} + +void Controller_XPad::OnRelease() {} + +void Controller_XPad::OnUpdate(u8* data, std::size_t size) { + for (auto& xpad_entry : shared_memory.shared_memory_entries) { + xpad_entry.header.timestamp = CoreTiming::GetTicks(); + xpad_entry.header.total_entry_count = 17; + + if (!IsControllerActivated()) { + xpad_entry.header.entry_count = 0; + xpad_entry.header.last_entry_index = 0; + return; + } + xpad_entry.header.entry_count = 16; + + const auto& last_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index]; + xpad_entry.header.last_entry_index = (xpad_entry.header.last_entry_index + 1) % 17; + auto& cur_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index]; + + cur_entry.sampling_number = last_entry.sampling_number + 1; + cur_entry.sampling_number2 = cur_entry.sampling_number; + } + // TODO(ogniK): Update xpad states + + std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); +} + +void Controller_XPad::OnLoadInputDevices() {} +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/xpad.h b/src/core/hle/service/hid/controllers/xpad.h new file mode 100644 index 000000000..ff836989f --- /dev/null +++ b/src/core/hle/service/hid/controllers/xpad.h @@ -0,0 +1,60 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/service/hid/controllers/controller_base.h" + +namespace Service::HID { +class Controller_XPad final : public ControllerBase { +public: + Controller_XPad(); + ~Controller_XPad() override; + + // Called when the controller is initialized + void OnInit() override; + + // When the controller is released + void OnRelease() override; + + // When the controller is requesting an update for the shared memory + void OnUpdate(u8* data, std::size_t size) override; + + // Called when input devices should be loaded + void OnLoadInputDevices() override; + +private: + struct AnalogStick { + s32_le x; + s32_le y; + }; + static_assert(sizeof(AnalogStick) == 0x8, "AnalogStick is an invalid size"); + + struct XPadState { + s64_le sampling_number; + s64_le sampling_number2; + s32_le attributes; + u32_le pad_states; + AnalogStick x_stick; + AnalogStick y_stick; + }; + static_assert(sizeof(XPadState) == 0x28, "XPadState is an invalid size"); + + struct XPadEntry { + CommonHeader header; + std::array<XPadState, 17> pad_states{}; + INSERT_PADDING_BYTES(0x138); + }; + static_assert(sizeof(XPadEntry) == 0x400, "XPadEntry is an invalid size"); + + struct SharedMemory { + std::array<XPadEntry, 4> shared_memory_entries{}; + }; + static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size"); + SharedMemory shared_memory{}; +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 7c6b0a4e6..beb89218a 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> +#include "common/common_types.h" #include "common/logging/log.h" #include "core/core.h" #include "core/core_timing.h" @@ -19,6 +21,16 @@ #include "core/hle/service/service.h" #include "core/settings.h" +#include "core/hle/service/hid/controllers/controller_base.h" +#include "core/hle/service/hid/controllers/debug_pad.h" +#include "core/hle/service/hid/controllers/gesture.h" +#include "core/hle/service/hid/controllers/keyboard.h" +#include "core/hle/service/hid/controllers/mouse.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/controllers/stubbed.h" +#include "core/hle/service/hid/controllers/touchscreen.h" +#include "core/hle/service/hid/controllers/xpad.h" + namespace Service::HID { // Updating period for each HID device. @@ -26,6 +38,22 @@ namespace Service::HID { constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100; constexpr u64 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100; constexpr u64 gyroscope_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100; +constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000; +enum class HidController : std::size_t { + DebugPad, + Touchscreen, + Mouse, + Keyboard, + XPad, + Unknown1, + Unknown2, + Unknown3, + SixAxisSensor, + NPad, + Gesture, + + MaxControllers, +}; class IAppletResource final : public ServiceFramework<IAppletResource> { public: @@ -37,19 +65,57 @@ public: auto& kernel = Core::System::GetInstance().Kernel(); shared_mem = Kernel::SharedMemory::Create( - kernel, nullptr, 0x40000, Kernel::MemoryPermission::ReadWrite, + kernel, nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite, Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory"); + MakeController<Controller_DebugPad>(HidController::DebugPad); + MakeController<Controller_Touchscreen>(HidController::Touchscreen); + MakeController<Controller_Mouse>(HidController::Mouse); + MakeController<Controller_Keyboard>(HidController::Keyboard); + MakeController<Controller_XPad>(HidController::XPad); + MakeController<Controller_Stubbed>(HidController::Unknown1); + MakeController<Controller_Stubbed>(HidController::Unknown2); + MakeController<Controller_Stubbed>(HidController::Unknown3); + MakeController<Controller_Stubbed>(HidController::SixAxisSensor); + MakeController<Controller_NPad>(HidController::NPad); + MakeController<Controller_Gesture>(HidController::Gesture); + + // Homebrew doesn't try to activate some controllers, so we activate them by default + GetController<Controller_NPad>(HidController::NPad).ActivateController(); + GetController<Controller_Touchscreen>(HidController::Touchscreen).ActivateController(); + + GetController<Controller_Stubbed>(HidController::Unknown1).SetCommonHeaderOffset(0x4c00); + GetController<Controller_Stubbed>(HidController::Unknown2).SetCommonHeaderOffset(0x4e00); + GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000); + // Register update callbacks pad_update_event = CoreTiming::RegisterEvent( "HID::UpdatePadCallback", - [this](u64 userdata, int cycles_late) { UpdatePadCallback(userdata, cycles_late); }); + [this](u64 userdata, int cycles_late) { UpdateControllers(userdata, cycles_late); }); // TODO(shinyquagsire23): Other update callbacks? (accel, gyro?) CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event); } + void ActivateController(HidController controller) { + controllers[static_cast<size_t>(controller)]->ActivateController(); + } + + void DeactivateController(HidController controller) { + controllers[static_cast<size_t>(controller)]->DeactivateController(); + } + + template <typename T> + void MakeController(HidController controller) { + controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>(); + } + + template <typename T> + T& GetController(HidController controller) { + return static_cast<T&>(*controllers[static_cast<size_t>(controller)]); + } + ~IAppletResource() { CoreTiming::UnscheduleEvent(pad_update_event, 0); } @@ -62,200 +128,15 @@ private: LOG_DEBUG(Service_HID, "called"); } - void LoadInputDevices() { - std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, - Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END, - buttons.begin(), Input::CreateDevice<Input::ButtonDevice>); - std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, - Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END, - sticks.begin(), Input::CreateDevice<Input::AnalogDevice>); - touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device); - // TODO(shinyquagsire23): gyro, mouse, keyboard - } - - void UpdatePadCallback(u64 userdata, int cycles_late) { - SharedMemory mem{}; - std::memcpy(&mem, shared_mem->GetPointer(), sizeof(SharedMemory)); - - if (Settings::values.is_device_reload_pending.exchange(false)) - LoadInputDevices(); - - // Set up controllers as neon red+blue Joy-Con attached to console - ControllerHeader& controller_header = mem.controllers[Controller_Handheld].header; - controller_header.type = ControllerType_Handheld; - controller_header.single_colors_descriptor = ColorDesc_ColorsNonexistent; - controller_header.right_color_body = JOYCON_BODY_NEON_RED; - controller_header.right_color_buttons = JOYCON_BUTTONS_NEON_RED; - controller_header.left_color_body = JOYCON_BODY_NEON_BLUE; - controller_header.left_color_buttons = JOYCON_BUTTONS_NEON_BLUE; - - for (std::size_t controller = 0; controller < mem.controllers.size(); controller++) { - for (auto& layout : mem.controllers[controller].layouts) { - layout.header.num_entries = HID_NUM_ENTRIES; - layout.header.max_entry_index = HID_NUM_ENTRIES - 1; - - // HID shared memory stores the state of the past 17 samples in a circlular buffer, - // each with a timestamp in number of samples since boot. - const ControllerInputEntry& last_entry = layout.entries[layout.header.latest_entry]; - - layout.header.timestamp_ticks = CoreTiming::GetTicks(); - layout.header.latest_entry = (layout.header.latest_entry + 1) % HID_NUM_ENTRIES; - - ControllerInputEntry& entry = layout.entries[layout.header.latest_entry]; - entry.timestamp = last_entry.timestamp + 1; - // TODO(shinyquagsire23): Is this always identical to timestamp? - entry.timestamp_2 = entry.timestamp; - - // TODO(shinyquagsire23): More than just handheld input - if (controller != Controller_Handheld) - continue; - - entry.connection_state = ConnectionState_Connected | ConnectionState_Wired; - - // TODO(shinyquagsire23): Set up some LUTs for each layout mapping in the future? - // For now everything is just the default handheld layout, but split Joy-Con will - // rotate the face buttons and directions for certain layouts. - ControllerPadState& state = entry.buttons; - using namespace Settings::NativeButton; - state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); - state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); - state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); - state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); - state.lstick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus()); - state.rstick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus()); - state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); - state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); - state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus()); - state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus()); - state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus()); - state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus()); - - state.dleft.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus()); - state.dup.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus()); - state.dright.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus()); - state.ddown.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus()); - - state.lstick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus()); - state.lstick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus()); - state.lstick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus()); - state.lstick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus()); - - state.rstick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus()); - state.rstick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus()); - state.rstick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus()); - state.rstick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus()); - - state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus()); - state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus()); - - const auto [stick_l_x_f, stick_l_y_f] = sticks[Joystick_Left]->GetStatus(); - const auto [stick_r_x_f, stick_r_y_f] = sticks[Joystick_Right]->GetStatus(); - entry.joystick_left_x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX); - entry.joystick_left_y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX); - entry.joystick_right_x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); - entry.joystick_right_y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX); + void UpdateControllers(u64 userdata, int cycles_late) { + const bool should_reload = Settings::values.is_device_reload_pending.exchange(false); + for (const auto& controller : controllers) { + if (should_reload) { + controller->OnLoadInputDevices(); } + controller->OnUpdate(shared_mem->GetPointer(), SHARED_MEMORY_SIZE); } - TouchScreen& touchscreen = mem.touchscreen; - const u64 last_entry = touchscreen.header.latest_entry; - const u64 curr_entry = (last_entry + 1) % touchscreen.entries.size(); - const u64 timestamp = CoreTiming::GetTicks(); - const u64 sample_counter = touchscreen.entries[last_entry].header.timestamp + 1; - touchscreen.header.timestamp_ticks = timestamp; - touchscreen.header.num_entries = touchscreen.entries.size(); - touchscreen.header.latest_entry = curr_entry; - touchscreen.header.max_entry_index = touchscreen.entries.size(); - touchscreen.header.timestamp = timestamp; - touchscreen.entries[curr_entry].header.timestamp = sample_counter; - - TouchScreenEntryTouch touch_entry{}; - auto [x, y, pressed] = touch_device->GetStatus(); - touch_entry.timestamp = timestamp; - touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width); - touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height); - touch_entry.touch_index = 0; - - // TODO(DarkLordZach): Maybe try to derive these from EmuWindow? - touch_entry.diameter_x = 15; - touch_entry.diameter_y = 15; - touch_entry.angle = 0; - - // TODO(DarkLordZach): Implement multi-touch support - if (pressed) { - touchscreen.entries[curr_entry].header.num_touches = 1; - touchscreen.entries[curr_entry].touches[0] = touch_entry; - } else { - touchscreen.entries[curr_entry].header.num_touches = 0; - } - - // TODO(shinyquagsire23): Properly implement mouse - Mouse& mouse = mem.mouse; - const u64 last_mouse_entry = mouse.header.latest_entry; - const u64 curr_mouse_entry = (mouse.header.latest_entry + 1) % mouse.entries.size(); - const u64 mouse_sample_counter = mouse.entries[last_mouse_entry].timestamp + 1; - mouse.header.timestamp_ticks = timestamp; - mouse.header.num_entries = mouse.entries.size(); - mouse.header.max_entry_index = mouse.entries.size(); - mouse.header.latest_entry = curr_mouse_entry; - - mouse.entries[curr_mouse_entry].timestamp = mouse_sample_counter; - mouse.entries[curr_mouse_entry].timestamp_2 = mouse_sample_counter; - - // TODO(shinyquagsire23): Properly implement keyboard - Keyboard& keyboard = mem.keyboard; - const u64 last_keyboard_entry = keyboard.header.latest_entry; - const u64 curr_keyboard_entry = - (keyboard.header.latest_entry + 1) % keyboard.entries.size(); - const u64 keyboard_sample_counter = keyboard.entries[last_keyboard_entry].timestamp + 1; - keyboard.header.timestamp_ticks = timestamp; - keyboard.header.num_entries = keyboard.entries.size(); - keyboard.header.latest_entry = last_keyboard_entry; - keyboard.header.max_entry_index = keyboard.entries.size(); - - keyboard.entries[curr_keyboard_entry].timestamp = keyboard_sample_counter; - keyboard.entries[curr_keyboard_entry].timestamp_2 = keyboard_sample_counter; - - // TODO(shinyquagsire23): Figure out what any of these are - for (auto& input : mem.unk_input_1) { - const u64 last_input_entry = input.header.latest_entry; - const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size(); - const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1; - - input.header.timestamp_ticks = timestamp; - input.header.num_entries = input.entries.size(); - input.header.latest_entry = last_input_entry; - input.header.max_entry_index = input.entries.size(); - - input.entries[curr_input_entry].timestamp = input_sample_counter; - input.entries[curr_input_entry].timestamp_2 = input_sample_counter; - } - - for (auto& input : mem.unk_input_2) { - input.header.timestamp_ticks = timestamp; - input.header.num_entries = 17; - input.header.latest_entry = 0; - input.header.max_entry_index = 0; - } - - UnkInput3& input = mem.unk_input_3; - const u64 last_input_entry = input.header.latest_entry; - const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size(); - const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1; - - input.header.timestamp_ticks = timestamp; - input.header.num_entries = input.entries.size(); - input.header.latest_entry = last_input_entry; - input.header.max_entry_index = input.entries.size(); - - input.entries[curr_input_entry].timestamp = input_sample_counter; - input.entries[curr_input_entry].timestamp_2 = input_sample_counter; - - // TODO(shinyquagsire23): Signal events - - std::memcpy(shared_mem->GetPointer(), &mem, sizeof(SharedMemory)); - - // Reschedule recurrent event CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event); } @@ -265,11 +146,8 @@ private: // CoreTiming update events CoreTiming::EventType* pad_update_event; - // Stored input state info - std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID> - buttons; - std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks; - std::unique_ptr<Input::TouchDevice> touch_device; + std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)> + controllers{}; }; class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> { @@ -299,9 +177,10 @@ public: {11, &Hid::ActivateTouchScreen, "ActivateTouchScreen"}, {21, &Hid::ActivateMouse, "ActivateMouse"}, {31, &Hid::ActivateKeyboard, "ActivateKeyboard"}, + {32, nullptr, "SendKeyboardLockKeyEvent"}, {40, nullptr, "AcquireXpadIdEventHandle"}, {41, nullptr, "ReleaseXpadIdEventHandle"}, - {51, nullptr, "ActivateXpad"}, + {51, &Hid::ActivateXpad, "ActivateXpad"}, {55, nullptr, "GetXpadIds"}, {56, nullptr, "ActivateJoyXpad"}, {58, nullptr, "GetJoyXpadLifoHandle"}, @@ -329,6 +208,7 @@ public: {80, nullptr, "GetGyroscopeZeroDriftMode"}, {81, nullptr, "ResetGyroscopeZeroDriftMode"}, {82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"}, + {83, nullptr, "IsFirmwareUpdateAvailableForSixAxisSensor"}, {91, &Hid::ActivateGesture, "ActivateGesture"}, {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"}, {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"}, @@ -362,8 +242,8 @@ public: {206, &Hid::SendVibrationValues, "SendVibrationValues"}, {207, nullptr, "SendVibrationGcErmCommand"}, {208, nullptr, "GetActualVibrationGcErmCommand"}, - {209, nullptr, "BeginPermitVibrationSession"}, - {210, nullptr, "EndPermitVibrationSession"}, + {209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"}, + {210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"}, {300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"}, {301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"}, {302, nullptr, "StopConsoleSixAxisSensor"}, @@ -374,6 +254,7 @@ public: {307, nullptr, "FinalizeSevenSixAxisSensor"}, {308, nullptr, "SetSevenSixAxisSensorFusionStrength"}, {309, nullptr, "GetSevenSixAxisSensorFusionStrength"}, + {310, nullptr, "ResetSevenSixAxisSensorTimestamp"}, {400, nullptr, "IsUsbFullKeyControllerEnabled"}, {401, nullptr, "EnableUsbFullKeyController"}, {402, nullptr, "IsUsbFullKeyControllerConnected"}, @@ -389,28 +270,35 @@ public: {505, nullptr, "SetPalmaFrModeType"}, {506, nullptr, "ReadPalmaStep"}, {507, nullptr, "EnablePalmaStep"}, - {508, nullptr, "SuspendPalmaStep"}, - {509, nullptr, "ResetPalmaStep"}, - {510, nullptr, "ReadPalmaApplicationSection"}, - {511, nullptr, "WritePalmaApplicationSection"}, - {512, nullptr, "ReadPalmaUniqueCode"}, - {513, nullptr, "SetPalmaUniqueCodeInvalid"}, + {508, nullptr, "ResetPalmaStep"}, + {509, nullptr, "ReadPalmaApplicationSection"}, + {510, nullptr, "WritePalmaApplicationSection"}, + {511, nullptr, "ReadPalmaUniqueCode"}, + {512, nullptr, "SetPalmaUniqueCodeInvalid"}, + {513, nullptr, "WritePalmaActivityEntry"}, + {514, nullptr, "WritePalmaRgbLedPatternEntry"}, + {515, nullptr, "WritePalmaWaveEntry"}, + {516, nullptr, "SetPalmaDataBaseIdentificationVersion"}, + {517, nullptr, "GetPalmaDataBaseIdentificationVersion"}, + {518, nullptr, "SuspendPalmaFeature"}, + {519, nullptr, "GetPalmaOperationResult"}, + {520, nullptr, "ReadPalmaPlayLog"}, + {521, nullptr, "ResetPalmaPlayLog"}, + {522, nullptr, "SetIsPalmaAllConnectable"}, + {523, nullptr, "SetIsPalmaPairedConnectable"}, + {524, nullptr, "PairPalma"}, + {525, nullptr, "SetPalmaBoostMode"}, {1000, nullptr, "SetNpadCommunicationMode"}, {1001, nullptr, "GetNpadCommunicationMode"}, }; // clang-format on RegisterHandlers(functions); - - auto& kernel = Core::System::GetInstance().Kernel(); - event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "hid:EventHandle"); } ~Hid() = default; private: std::shared_ptr<IAppletResource> applet_resource; - u32 joy_hold_type{0}; - Kernel::SharedPtr<Kernel::Event> event; void CreateAppletResource(Kernel::HLERequestContext& ctx) { if (applet_resource == nullptr) { @@ -423,31 +311,59 @@ private: LOG_DEBUG(Service_HID, "called"); } + void ActivateXpad(Kernel::HLERequestContext& ctx) { + applet_resource->ActivateController(HidController::XPad); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_DEBUG(Service_HID, "called"); + } + void ActivateDebugPad(Kernel::HLERequestContext& ctx) { + applet_resource->ActivateController(HidController::DebugPad); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); } void ActivateTouchScreen(Kernel::HLERequestContext& ctx) { + applet_resource->ActivateController(HidController::Touchscreen); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); } void ActivateMouse(Kernel::HLERequestContext& ctx) { + applet_resource->ActivateController(HidController::Mouse); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); } void ActivateKeyboard(Kernel::HLERequestContext& ctx) { + applet_resource->ActivateController(HidController::Keyboard); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); + } + + void ActivateGesture(Kernel::HLERequestContext& ctx) { + applet_resource->ActivateController(HidController::Gesture); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_DEBUG(Service_HID, "called"); + } + + void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { + // Should have no effect with how our npad sets up the data + applet_resource->ActivateController(HidController::NPad); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_DEBUG(Service_HID, "called"); } void StartSixAxisSensor(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto handle = rp.PopRaw<u32>(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_HID, "(STUBBED) called"); @@ -468,84 +384,168 @@ private: } void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto supported_styleset = rp.PopRaw<u32>(); + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetSupportedStyleSet({supported_styleset}); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + + LOG_DEBUG(Service_HID, "called"); } void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { + auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); - LOG_WARNING(Service_HID, "(STUBBED) called"); + rb.Push<u32>(controller.GetSupportedStyleSet().raw); + LOG_DEBUG(Service_HID, "called"); } void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) { + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); } void ActivateNpad(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + applet_resource->ActivateController(HidController::NPad); + LOG_DEBUG(Service_HID, "called"); } void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto npad_id = rp.PopRaw<u32>(); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(event); - LOG_WARNING(Service_HID, "(STUBBED) called"); + rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetStyleSetChangedEvent()); + LOG_DEBUG(Service_HID, "called"); } void DisconnectNpad(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto npad_id = rp.PopRaw<u32>(); + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .DisconnectNPad(npad_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); } void GetPlayerLedPattern(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2}; + IPC::RequestParser rp{ctx}; + auto npad_id = rp.PopRaw<u32>(); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + rb.PushRaw<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetLedPattern(npad_id) + .raw); + LOG_DEBUG(Service_HID, "called"); } void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { + auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); + IPC::RequestParser rp{ctx}; + const auto hold_type = rp.PopRaw<u64>(); + controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type}); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); } void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 3}; + const auto& controller = + applet_resource->GetController<Controller_NPad>(HidController::NPad); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push(joy_hold_type); - LOG_WARNING(Service_HID, "(STUBBED) called"); + rb.Push<u64>(static_cast<u64>(controller.GetHoldType())); + LOG_DEBUG(Service_HID, "called"); } void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto npad_id = rp.PopRaw<u32>(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_HID, "(STUBBED) called"); } + void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) { + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetVibrationEnabled(true); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_DEBUG(Service_HID, "called"); + } + + void EndPermitVibrationSession(Kernel::HLERequestContext& ctx) { + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .SetVibrationEnabled(false); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_DEBUG(Service_HID, "called"); + } + void SendVibrationValue(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto controller_id = rp.PopRaw<u32>(); + const auto vibration_values = rp.PopRaw<Controller_NPad::Vibration>(); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .VibrateController({controller_id}, {vibration_values}); + LOG_DEBUG(Service_HID, "called"); } - void GetActualVibrationValue(Kernel::HLERequestContext& ctx) { + void SendVibrationValues(Kernel::HLERequestContext& ctx) { + const auto controllers = ctx.ReadBuffer(0); + const auto vibrations = ctx.ReadBuffer(1); + + std::vector<u32> controller_list(controllers.size() / sizeof(u32)); + std::vector<Controller_NPad::Vibration> vibration_list(vibrations.size() / + sizeof(Controller_NPad::Vibration)); + + std::memcpy(controller_list.data(), controllers.data(), controllers.size()); + std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size()); + std::transform(controller_list.begin(), controller_list.end(), controller_list.begin(), + [](u32 controller_id) { return controller_id - 3; }); + + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .VibrateController(controller_list, vibration_list); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); + } + + void GetActualVibrationValue(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<Controller_NPad::Vibration>( + applet_resource->GetController<Controller_NPad>(HidController::NPad) + .GetLastVibration()); + LOG_DEBUG(Service_HID, "called"); } void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto npad_id = rp.PopRaw<u32>(); + auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad); + controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_DEBUG(Service_HID, "called"); } void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) { @@ -555,6 +555,8 @@ private: } void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto mode = rp.PopRaw<u32>(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_HID, "(STUBBED) called"); @@ -563,8 +565,9 @@ private: void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push<u64>(0); - LOG_WARNING(Service_HID, "(STUBBED) called"); + rb.Push<u32>(1); + rb.Push<u32>(0); + LOG_DEBUG(Service_HID, "called"); } void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { @@ -574,12 +577,6 @@ private: LOG_DEBUG(Service_HID, "called"); } - void SendVibrationValues(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); - } - void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -597,18 +594,6 @@ private: rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_HID, "(STUBBED) called"); } - - void ActivateGesture(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); - } - - void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_HID, "(STUBBED) called"); - } }; class HidDbg final : public ServiceFramework<HidDbg> { @@ -650,6 +635,7 @@ public: {140, nullptr, "DeactivateConsoleSixAxisSensor"}, {141, nullptr, "GetConsoleSixAxisSensorSamplingFrequency"}, {142, nullptr, "DeactivateSevenSixAxisSensor"}, + {143, nullptr, "GetConsoleSixAxisSensorCountStates"}, {201, nullptr, "ActivateFirmwareUpdate"}, {202, nullptr, "DeactivateFirmwareUpdate"}, {203, nullptr, "StartFirmwareUpdate"}, @@ -660,12 +646,23 @@ public: {208, nullptr, "StartFirmwareUpdateForRevert"}, {209, nullptr, "GetAvailableFirmwareVersionForRevert"}, {210, nullptr, "IsFirmwareUpdatingDevice"}, + {211, nullptr, "StartFirmwareUpdateIndividual"}, + {215, nullptr, "SetUsbFirmwareForceUpdateEnabled"}, + {216, nullptr, "SetAllKuinaDevicesToFirmwareUpdateMode"}, {221, nullptr, "UpdateControllerColor"}, {222, nullptr, "ConnectUsbPadsAsync"}, {223, nullptr, "DisconnectUsbPadsAsync"}, {224, nullptr, "UpdateDesignInfo"}, {225, nullptr, "GetUniquePadDriverState"}, {226, nullptr, "GetSixAxisSensorDriverStates"}, + {227, nullptr, "GetRxPacketHistory"}, + {228, nullptr, "AcquireOperationEventHandle"}, + {229, nullptr, "ReadSerialFlash"}, + {230, nullptr, "WriteSerialFlash"}, + {231, nullptr, "GetOperationResult"}, + {232, nullptr, "EnableShipmentMode"}, + {233, nullptr, "ClearPairingInfo"}, + {234, nullptr, "GetUniquePadDeviceTypeSetInternal"}, {301, nullptr, "GetAbstractedPadHandles"}, {302, nullptr, "GetAbstractedPadState"}, {303, nullptr, "GetAbstractedPadsState"}, @@ -673,6 +670,8 @@ public: {322, nullptr, "UnsetAutoPilotVirtualPadState"}, {323, nullptr, "UnsetAllAutoPilotVirtualPadState"}, {350, nullptr, "AddRegisteredDevice"}, + {400, nullptr, "DisableExternalMcuOnNxDevice"}, + {401, nullptr, "DisableRailDeviceFiltering"}, }; // clang-format on @@ -708,7 +707,9 @@ public: {307, nullptr, "GetNpadSystemExtStyle"}, {308, nullptr, "ApplyNpadSystemCommonPolicyFull"}, {309, nullptr, "GetNpadFullKeyGripColor"}, + {310, nullptr, "GetMaskedSupportedNpadStyleSet"}, {311, nullptr, "SetNpadPlayerLedBlinkingDevice"}, + {312, nullptr, "SetSupportedNpadStyleSetAll"}, {321, nullptr, "GetUniquePadsFromNpad"}, {322, nullptr, "GetIrSensorState"}, {323, nullptr, "GetXcdHandleForNpadWithIrSensor"}, @@ -733,6 +734,7 @@ public: {546, nullptr, "AcquireDeviceRegisteredEventForControllerSupport"}, {547, nullptr, "GetAllowedBluetoothLinksCount"}, {548, nullptr, "GetRegisteredDevices"}, + {549, nullptr, "GetConnectableRegisteredDevices"}, {700, nullptr, "ActivateUniquePad"}, {702, nullptr, "AcquireUniquePadConnectionEventHandle"}, {703, nullptr, "GetUniquePadIds"}, @@ -761,6 +763,7 @@ public: {850, nullptr, "IsUsbFullKeyControllerEnabled"}, {851, nullptr, "EnableUsbFullKeyController"}, {852, nullptr, "IsUsbConnected"}, + {870, nullptr, "IsHandheldButtonPressedOnConsoleMode"}, {900, nullptr, "ActivateInputDetector"}, {901, nullptr, "NotifyInputDetector"}, {1000, nullptr, "InitializeFirmwareUpdate"}, @@ -780,6 +783,12 @@ public: {1052, nullptr, "CancelSixAxisSensorAccurateUserCalibration"}, {1053, nullptr, "GetSixAxisSensorAccurateUserCalibrationState"}, {1100, nullptr, "GetHidbusSystemServiceObject"}, + {1120, nullptr, "SetFirmwareHotfixUpdateSkipEnabled"}, + {1130, nullptr, "InitializeUsbFirmwareUpdate"}, + {1131, nullptr, "FinalizeUsbFirmwareUpdate"}, + {1132, nullptr, "CheckUsbFirmwareUpdateRequired"}, + {1133, nullptr, "StartUsbFirmwareUpdate"}, + {1134, nullptr, "GetUsbFirmwareUpdateState"}, }; // clang-format on diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 88d926808..773035460 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -4,408 +4,12 @@ #pragma once -#include <array> -#include "common/bit_field.h" -#include "common/common_types.h" -#include "core/hle/service/service.h" +namespace SM { +class ServiceManager; +} namespace Service::HID { -// Begin enums and output structs - -constexpr u32 HID_NUM_ENTRIES = 17; -constexpr u32 HID_NUM_LAYOUTS = 7; -constexpr s32 HID_JOYSTICK_MAX = 0x8000; -constexpr s32 HID_JOYSTICK_MIN = -0x8000; - -constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28; -constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A; -constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6; -constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E; - -enum ControllerType : u32 { - ControllerType_ProController = 1 << 0, - ControllerType_Handheld = 1 << 1, - ControllerType_JoyconPair = 1 << 2, - ControllerType_JoyconLeft = 1 << 3, - ControllerType_JoyconRight = 1 << 4, -}; - -enum ControllerLayoutType : u32 { - Layout_ProController = 0, // Pro Controller or HID gamepad - Layout_Handheld = 1, // Two Joy-Con docked to rails - Layout_Single = 2, // Horizontal single Joy-Con or pair of Joy-Con, adjusted for orientation - Layout_Left = 3, // Only raw left Joy-Con state, no orientation adjustment - Layout_Right = 4, // Only raw right Joy-Con state, no orientation adjustment - Layout_DefaultDigital = 5, // Same as next, but sticks have 8-direction values only - Layout_Default = 6, // Safe default, single Joy-Con have buttons/sticks rotated for orientation -}; - -enum ControllerColorDescription { - ColorDesc_ColorsNonexistent = 1 << 1, -}; - -enum ControllerConnectionState { - ConnectionState_Connected = 1 << 0, - ConnectionState_Wired = 1 << 1, -}; - -enum ControllerJoystick { - Joystick_Left = 0, - Joystick_Right = 1, -}; - -enum ControllerID { - Controller_Player1 = 0, - Controller_Player2 = 1, - Controller_Player3 = 2, - Controller_Player4 = 3, - Controller_Player5 = 4, - Controller_Player6 = 5, - Controller_Player7 = 6, - Controller_Player8 = 7, - Controller_Handheld = 8, - Controller_Unknown = 9, -}; - -// End enums and output structs - -// Begin UnkInput3 - -struct UnkInput3Header { - u64 timestamp_ticks; - u64 num_entries; - u64 latest_entry; - u64 max_entry_index; -}; -static_assert(sizeof(UnkInput3Header) == 0x20, "HID UnkInput3 header structure has incorrect size"); - -struct UnkInput3Entry { - u64 timestamp; - u64 timestamp_2; - u64 unk_8; - u64 unk_10; - u64 unk_18; -}; -static_assert(sizeof(UnkInput3Entry) == 0x28, "HID UnkInput3 entry structure has incorrect size"); - -struct UnkInput3 { - UnkInput3Header header; - std::array<UnkInput3Entry, 17> entries; - std::array<u8, 0x138> padding; -}; -static_assert(sizeof(UnkInput3) == 0x400, "HID UnkInput3 structure has incorrect size"); - -// End UnkInput3 - -// Begin TouchScreen - -struct TouchScreenHeader { - u64 timestamp_ticks; - u64 num_entries; - u64 latest_entry; - u64 max_entry_index; - u64 timestamp; -}; -static_assert(sizeof(TouchScreenHeader) == 0x28, - "HID touch screen header structure has incorrect size"); - -struct TouchScreenEntryHeader { - u64 timestamp; - u64 num_touches; -}; -static_assert(sizeof(TouchScreenEntryHeader) == 0x10, - "HID touch screen entry header structure has incorrect size"); - -struct TouchScreenEntryTouch { - u64 timestamp; - u32 padding; - u32 touch_index; - u32 x; - u32 y; - u32 diameter_x; - u32 diameter_y; - u32 angle; - u32 padding_2; -}; -static_assert(sizeof(TouchScreenEntryTouch) == 0x28, - "HID touch screen touch structure has incorrect size"); - -struct TouchScreenEntry { - TouchScreenEntryHeader header; - std::array<TouchScreenEntryTouch, 16> touches; - u64 unk; -}; -static_assert(sizeof(TouchScreenEntry) == 0x298, - "HID touch screen entry structure has incorrect size"); - -struct TouchScreen { - TouchScreenHeader header; - std::array<TouchScreenEntry, 17> entries; - std::array<u8, 0x3c0> padding; -}; -static_assert(sizeof(TouchScreen) == 0x3000, "HID touch screen structure has incorrect size"); - -// End TouchScreen - -// Begin Mouse - -struct MouseHeader { - u64 timestamp_ticks; - u64 num_entries; - u64 latest_entry; - u64 max_entry_index; -}; -static_assert(sizeof(MouseHeader) == 0x20, "HID mouse header structure has incorrect size"); - -struct MouseButtonState { - union { - u64 hex{}; - - // Buttons - BitField<0, 1, u64> left; - BitField<1, 1, u64> right; - BitField<2, 1, u64> middle; - BitField<3, 1, u64> forward; - BitField<4, 1, u64> back; - }; -}; - -struct MouseEntry { - u64 timestamp; - u64 timestamp_2; - u32 x; - u32 y; - u32 velocity_x; - u32 velocity_y; - u32 scroll_velocity_x; - u32 scroll_velocity_y; - MouseButtonState buttons; -}; -static_assert(sizeof(MouseEntry) == 0x30, "HID mouse entry structure has incorrect size"); - -struct Mouse { - MouseHeader header; - std::array<MouseEntry, 17> entries; - std::array<u8, 0xB0> padding; -}; -static_assert(sizeof(Mouse) == 0x400, "HID mouse structure has incorrect size"); - -// End Mouse - -// Begin Keyboard - -struct KeyboardHeader { - u64 timestamp_ticks; - u64 num_entries; - u64 latest_entry; - u64 max_entry_index; -}; -static_assert(sizeof(KeyboardHeader) == 0x20, "HID keyboard header structure has incorrect size"); - -struct KeyboardModifierKeyState { - union { - u64 hex{}; - - // Buttons - BitField<0, 1, u64> lctrl; - BitField<1, 1, u64> lshift; - BitField<2, 1, u64> lalt; - BitField<3, 1, u64> lmeta; - BitField<4, 1, u64> rctrl; - BitField<5, 1, u64> rshift; - BitField<6, 1, u64> ralt; - BitField<7, 1, u64> rmeta; - BitField<8, 1, u64> capslock; - BitField<9, 1, u64> scrolllock; - BitField<10, 1, u64> numlock; - }; -}; - -struct KeyboardEntry { - u64 timestamp; - u64 timestamp_2; - KeyboardModifierKeyState modifier; - u32 keys[8]; -}; -static_assert(sizeof(KeyboardEntry) == 0x38, "HID keyboard entry structure has incorrect size"); - -struct Keyboard { - KeyboardHeader header; - std::array<KeyboardEntry, 17> entries; - std::array<u8, 0x28> padding; -}; -static_assert(sizeof(Keyboard) == 0x400, "HID keyboard structure has incorrect size"); - -// End Keyboard - -// Begin UnkInput1 - -struct UnkInput1Header { - u64 timestamp_ticks; - u64 num_entries; - u64 latest_entry; - u64 max_entry_index; -}; -static_assert(sizeof(UnkInput1Header) == 0x20, "HID UnkInput1 header structure has incorrect size"); - -struct UnkInput1Entry { - u64 timestamp; - u64 timestamp_2; - u64 unk_8; - u64 unk_10; - u64 unk_18; -}; -static_assert(sizeof(UnkInput1Entry) == 0x28, "HID UnkInput1 entry structure has incorrect size"); - -struct UnkInput1 { - UnkInput1Header header; - std::array<UnkInput1Entry, 17> entries; - std::array<u8, 0x138> padding; -}; -static_assert(sizeof(UnkInput1) == 0x400, "HID UnkInput1 structure has incorrect size"); - -// End UnkInput1 - -// Begin UnkInput2 - -struct UnkInput2Header { - u64 timestamp_ticks; - u64 num_entries; - u64 latest_entry; - u64 max_entry_index; -}; -static_assert(sizeof(UnkInput2Header) == 0x20, "HID UnkInput2 header structure has incorrect size"); - -struct UnkInput2 { - UnkInput2Header header; - std::array<u8, 0x1E0> padding; -}; -static_assert(sizeof(UnkInput2) == 0x200, "HID UnkInput2 structure has incorrect size"); - -// End UnkInput2 - -// Begin Controller - -struct ControllerMAC { - u64 timestamp; - std::array<u8, 0x8> mac; - u64 unk; - u64 timestamp_2; -}; -static_assert(sizeof(ControllerMAC) == 0x20, "HID controller MAC structure has incorrect size"); - -struct ControllerHeader { - u32 type; - u32 is_half; - u32 single_colors_descriptor; - u32 single_color_body; - u32 single_color_buttons; - u32 split_colors_descriptor; - u32 left_color_body; - u32 left_color_buttons; - u32 right_color_body; - u32 right_color_buttons; -}; -static_assert(sizeof(ControllerHeader) == 0x28, - "HID controller header structure has incorrect size"); - -struct ControllerLayoutHeader { - u64 timestamp_ticks; - u64 num_entries; - u64 latest_entry; - u64 max_entry_index; -}; -static_assert(sizeof(ControllerLayoutHeader) == 0x20, - "HID controller layout header structure has incorrect size"); - -struct ControllerPadState { - union { - u64 hex{}; - - // Buttons - BitField<0, 1, u64> a; - BitField<1, 1, u64> b; - BitField<2, 1, u64> x; - BitField<3, 1, u64> y; - BitField<4, 1, u64> lstick; - BitField<5, 1, u64> rstick; - BitField<6, 1, u64> l; - BitField<7, 1, u64> r; - BitField<8, 1, u64> zl; - BitField<9, 1, u64> zr; - BitField<10, 1, u64> plus; - BitField<11, 1, u64> minus; - - // D-pad buttons - BitField<12, 1, u64> dleft; - BitField<13, 1, u64> dup; - BitField<14, 1, u64> dright; - BitField<15, 1, u64> ddown; - - // Left stick directions - BitField<16, 1, u64> lstick_left; - BitField<17, 1, u64> lstick_up; - BitField<18, 1, u64> lstick_right; - BitField<19, 1, u64> lstick_down; - - // Right stick directions - BitField<20, 1, u64> rstick_left; - BitField<21, 1, u64> rstick_up; - BitField<22, 1, u64> rstick_right; - BitField<23, 1, u64> rstick_down; - - BitField<24, 1, u64> sl; - BitField<25, 1, u64> sr; - }; -}; - -struct ControllerInputEntry { - u64 timestamp; - u64 timestamp_2; - ControllerPadState buttons; - s32 joystick_left_x; - s32 joystick_left_y; - s32 joystick_right_x; - s32 joystick_right_y; - u64 connection_state; -}; -static_assert(sizeof(ControllerInputEntry) == 0x30, - "HID controller input entry structure has incorrect size"); - -struct ControllerLayout { - ControllerLayoutHeader header; - std::array<ControllerInputEntry, 17> entries; -}; -static_assert(sizeof(ControllerLayout) == 0x350, - "HID controller layout structure has incorrect size"); - -struct Controller { - ControllerHeader header; - std::array<ControllerLayout, HID_NUM_LAYOUTS> layouts; - std::array<u8, 0x2a70> unk_1; - ControllerMAC mac_left; - ControllerMAC mac_right; - std::array<u8, 0xdf8> unk_2; -}; -static_assert(sizeof(Controller) == 0x5000, "HID controller structure has incorrect size"); - -// End Controller - -struct SharedMemory { - UnkInput3 unk_input_3; - TouchScreen touchscreen; - Mouse mouse; - Keyboard keyboard; - std::array<UnkInput1, 4> unk_input_1; - std::array<UnkInput2, 3> unk_input_2; - std::array<u8, 0x800> unk_section_8; - std::array<u8, 0x4000> controller_serials; - std::array<Controller, 10> controllers; - std::array<u8, 0x4600> unk_section_9; -}; -static_assert(sizeof(SharedMemory) == 0x40000, "HID Shared Memory structure has incorrect size"); - /// Reload input devices. Used when input configuration changed void ReloadInputDevices(); diff --git a/src/core/hle/service/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp index 7b91bb258..e1f17a926 100644 --- a/src/core/hle/service/mm/mm_u.cpp +++ b/src/core/hle/service/mm/mm_u.cpp @@ -14,14 +14,14 @@ public: explicit MM_U() : ServiceFramework{"mm:u"} { // clang-format off static const FunctionInfo functions[] = { - {0, &MM_U::Initialize, "InitializeOld"}, - {1, &MM_U::Finalize, "FinalizeOld"}, - {2, &MM_U::SetAndWait, "SetAndWaitOld"}, - {3, &MM_U::Get, "GetOld"}, - {4, &MM_U::Initialize, "Initialize"}, - {5, &MM_U::Finalize, "Finalize"}, - {6, &MM_U::SetAndWait, "SetAndWait"}, - {7, &MM_U::Get, "Get"}, + {0, &MM_U::Initialize, "Initialize"}, + {1, &MM_U::Finalize, "Finalize"}, + {2, &MM_U::SetAndWait, "SetAndWait"}, + {3, &MM_U::Get, "Get"}, + {4, &MM_U::InitializeWithId, "InitializeWithId"}, + {5, &MM_U::FinalizeWithId, "FinalizeWithId"}, + {6, &MM_U::SetAndWaitWithId, "SetAndWaitWithId"}, + {7, &MM_U::GetWithId, "GetWithId"}, }; // clang-format on @@ -59,9 +59,43 @@ private: rb.Push(current); } + void InitializeWithId(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_MM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(id); // Any non zero value + } + + void FinalizeWithId(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_MM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SetAndWaitWithId(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + u32 input_id = rp.Pop<u32>(); + min = rp.Pop<u32>(); + max = rp.Pop<u32>(); + current = min; + + LOG_WARNING(Service_MM, "(STUBBED) called, input_id=0x{:X}, min=0x{:X}, max=0x{:X}", + input_id, min, max); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetWithId(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_MM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(current); + } + u32 min{0}; u32 max{0}; u32 current{0}; + u32 id{1}; }; void InstallInterfaces(SM::ServiceManager& service_manager) { diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index 8c07a05c2..39c0c1e63 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -144,7 +144,7 @@ private: } const u64 device_handle{0xDEAD}; - const HID::ControllerID npad_id{HID::Controller_Player1}; + const u32 npad_id{0}; // This is the first player controller id State state{State::NonInitialized}; DeviceState device_state{DeviceState::Initialized}; Kernel::SharedPtr<Kernel::Event> activate_event; diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 10611ed6a..75dcd94a3 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -219,6 +219,7 @@ IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") { {35, nullptr, "GetScanData"}, {36, nullptr, "GetCurrentAccessPoint"}, {37, nullptr, "Shutdown"}, + {38, nullptr, "GetAllowedChannels"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp index 261ad539c..18091c9bb 100644 --- a/src/core/hle/service/nim/nim.cpp +++ b/src/core/hle/service/nim/nim.cpp @@ -71,6 +71,22 @@ public: } }; +class NIM_ECA final : public ServiceFramework<NIM_ECA> { +public: + explicit NIM_ECA() : ServiceFramework{"nim:eca"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "CreateServerInterface"}, + {1, nullptr, "RefreshDebugAvailability"}, + {2, nullptr, "ClearDebugResponse"}, + {3, nullptr, "RegisterDebugResponse"}, + }; + // clang-format on + + RegisterHandlers(functions); + } +}; + class NIM_SHP final : public ServiceFramework<NIM_SHP> { public: explicit NIM_SHP() : ServiceFramework{"nim:shp"} { @@ -214,6 +230,7 @@ private: void InstallInterfaces(SM::ServiceManager& sm) { std::make_shared<NIM>()->InstallAsService(sm); + std::make_shared<NIM_ECA>()->InstallAsService(sm); std::make_shared<NIM_SHP>()->InstallAsService(sm); std::make_shared<NTC>()->InstallAsService(sm); } diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index 98017267c..07c1381fe 100644 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -93,13 +93,23 @@ public: {86, nullptr, "EnableApplicationCrashReport"}, {87, nullptr, "IsApplicationCrashReportEnabled"}, {90, nullptr, "BoostSystemMemoryResourceLimit"}, + {91, nullptr, "Unknown1"}, + {92, nullptr, "Unknown2"}, + {93, nullptr, "GetMainApplicationProgramIndex"}, + {94, nullptr, "LaunchApplication2"}, + {95, nullptr, "GetApplicationLaunchInfo"}, + {96, nullptr, "AcquireApplicationLaunchInfo"}, + {97, nullptr, "GetMainApplicationProgramIndex2"}, + {98, nullptr, "EnableApplicationAllThreadDumpOnCrash"}, {100, nullptr, "ResetToFactorySettings"}, {101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"}, {102, nullptr, "ResetToFactorySettingsForRefurbishment"}, {200, nullptr, "CalculateUserSaveDataStatistics"}, {201, nullptr, "DeleteUserSaveDataAll"}, {210, nullptr, "DeleteUserSystemSaveData"}, + {211, nullptr, "DeleteSaveData"}, {220, nullptr, "UnregisterNetworkServiceAccount"}, + {221, nullptr, "UnregisterNetworkServiceAccountWithUserSaveDataDeletion"}, {300, nullptr, "GetApplicationShellEvent"}, {301, nullptr, "PopApplicationShellEventInfo"}, {302, nullptr, "LaunchLibraryApplet"}, @@ -114,6 +124,7 @@ public: {403, nullptr, "GetMaxApplicationControlCacheCount"}, {404, nullptr, "InvalidateApplicationControlCache"}, {405, nullptr, "ListApplicationControlCacheEntryInfo"}, + {406, nullptr, "GetApplicationControlProperty"}, {502, nullptr, "RequestCheckGameCardRegistration"}, {503, nullptr, "RequestGameCardRegistrationGoldPoint"}, {504, nullptr, "RequestRegisterGameCard"}, @@ -129,6 +140,7 @@ public: {604, nullptr, "RegisterContentsExternalKey"}, {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"}, {606, nullptr, "GetContentMetaStorage"}, + {607, nullptr, "ListAvailableAddOnContent"}, {700, nullptr, "PushDownloadTaskList"}, {701, nullptr, "ClearTaskStatusList"}, {702, nullptr, "RequestDownloadTaskList"}, @@ -148,6 +160,9 @@ public: {907, nullptr, "WithdrawApplicationUpdateRequest"}, {908, nullptr, "ListApplicationRecordInstalledContentMeta"}, {909, nullptr, "WithdrawCleanupAddOnContentsWithNoRightsRecommendation"}, + {910, nullptr, "Unknown3"}, + {911, nullptr, "SetPreInstalledApplication"}, + {912, nullptr, "ClearPreInstalledApplicationFlag"}, {1000, nullptr, "RequestVerifyApplicationDeprecated"}, {1001, nullptr, "CorruptApplicationForDebug"}, {1002, nullptr, "RequestVerifyAddOnContentsRights"}, @@ -162,6 +177,8 @@ public: {1305, nullptr, "TryDeleteRunningApplicationEntity"}, {1306, nullptr, "TryDeleteRunningApplicationCompletely"}, {1307, nullptr, "TryDeleteRunningApplicationContentEntities"}, + {1308, nullptr, "DeleteApplicationCompletelyForDebug"}, + {1309, nullptr, "CleanupUnavailableAddOnContents"}, {1400, nullptr, "PrepareShutdown"}, {1500, nullptr, "FormatSdCard"}, {1501, nullptr, "NeedsSystemUpdateToFormatSdCard"}, @@ -199,6 +216,28 @@ public: {2015, nullptr, "CompareSystemDeliveryInfo"}, {2016, nullptr, "ListNotCommittedContentMeta"}, {2017, nullptr, "CreateDownloadTask"}, + {2018, nullptr, "Unknown4"}, + {2050, nullptr, "Unknown5"}, + {2100, nullptr, "Unknown6"}, + {2101, nullptr, "Unknown7"}, + {2150, nullptr, "CreateRightsEnvironment"}, + {2151, nullptr, "DestroyRightsEnvironment"}, + {2152, nullptr, "ActivateRightsEnvironment"}, + {2153, nullptr, "DeactivateRightsEnvironment"}, + {2154, nullptr, "ForceActivateRightsContextForExit"}, + {2160, nullptr, "AddTargetApplicationToRightsEnvironment"}, + {2161, nullptr, "SetUsersToRightsEnvironment"}, + {2170, nullptr, "GetRightsEnvironmentStatus"}, + {2171, nullptr, "GetRightsEnvironmentStatusChangedEvent"}, + {2180, nullptr, "RequestExtendRightsInRightsEnvironment"}, + {2181, nullptr, "GetLastResultOfExtendRightsInRightsEnvironment"}, + {2182, nullptr, "SetActiveRightsContextUsingStateToRightsEnvironment"}, + {2190, nullptr, "GetRightsEnvironmentHandleForApplication"}, + {2199, nullptr, "GetRightsEnvironmentCountForDebug"}, + {2200, nullptr, "Unknown8"}, + {2201, nullptr, "Unknown9"}, + {2250, nullptr, "Unknown10"}, + {2300, nullptr, "Unknown11"}, }; // clang-format on @@ -348,12 +387,15 @@ public: {0, nullptr, "LaunchProgram"}, {1, nullptr, "TerminateProcess"}, {2, nullptr, "TerminateProgram"}, - {3, nullptr, "GetShellEventHandle"}, - {4, nullptr, "GetShellEventInfo"}, - {5, nullptr, "TerminateApplication"}, - {6, nullptr, "PrepareLaunchProgramFromHost"}, - {7, nullptr, "LaunchApplication"}, - {8, nullptr, "LaunchApplicationWithStorageId"}, + {4, nullptr, "GetShellEventHandle"}, + {5, nullptr, "GetShellEventInfo"}, + {6, nullptr, "TerminateApplication"}, + {7, nullptr, "PrepareLaunchProgramFromHost"}, + {8, nullptr, "LaunchApplication"}, + {9, nullptr, "LaunchApplicationWithStorageId"}, + {10, nullptr, "TerminateApplication2"}, + {11, nullptr, "GetRunningApplicationProcessId"}, + {12, nullptr, "SetCurrentApplicationRightsEnvironmentCanBeActive"}, }; // clang-format on @@ -388,6 +430,7 @@ public: {19, nullptr, "GetReceivedEulaDataSize"}, {20, nullptr, "GetReceivedEulaData"}, {21, nullptr, "SetupToReceiveSystemUpdate"}, + {22, nullptr, "RequestCheckLatestUpdateIncludesRebootlessUpdate"}, }; // clang-format on 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/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/set/set_cal.cpp b/src/core/hle/service/set/set_cal.cpp index 5af356d10..34654bb07 100644 --- a/src/core/hle/service/set/set_cal.cpp +++ b/src/core/hle/service/set/set_cal.cpp @@ -39,7 +39,8 @@ SET_CAL::SET_CAL() : ServiceFramework("set:cal") { {29, nullptr, "GetAmiiboEcqvBlsKey"}, {30, nullptr, "GetAmiiboEcqvBlsCertificate"}, {31, nullptr, "GetAmiiboEcqvBlsRootCertificate"}, - {32, nullptr, "GetUnknownId"}, + {32, nullptr, "GetUsbTypeCPowerSourceCircuitVersion"}, + {33, nullptr, "GetBatteryVersion"}, }; RegisterHandlers(functions); } 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/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 576fe692a..243b499f2 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -127,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')) { @@ -138,7 +138,7 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) { } // Build program image - 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 {}; } @@ -182,7 +182,7 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) { Core::CurrentProcess()->LoadModule(std::move(codeset), load_base); // Register module with GDBStub - GDBStub::RegisterModule(file->GetName(), load_base, load_base); + GDBStub::RegisterModule(file.GetName(), load_base, load_base); return true; } @@ -195,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 ba57db9bf..68efca5c0 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -93,17 +93,14 @@ 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')) @@ -114,7 +111,7 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base, 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]); } @@ -157,7 +154,7 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base, 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()); @@ -172,7 +169,7 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base, Core::CurrentProcess()->LoadModule(std::move(codeset), load_base); // Register module with GDBStub - GDBStub::RegisterModule(file->GetName(), load_base, load_base); + GDBStub::RegisterModule(file.GetName(), load_base, load_base); return load_base + image_size; } @@ -184,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/xci.cpp b/src/core/loader/xci.cpp index 7a619acb4..461607c95 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -59,8 +59,7 @@ ResultStatus AppLoader_XCI::Load(Kernel::Process& process) { if (xci->GetProgramNCAStatus() != ResultStatus::Success) return xci->GetProgramNCAStatus(); - const auto nca = xci->GetProgramNCA(); - if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false)) + if (!xci->HasProgramNCA() && !Core::Crypto::KeyManager::KeyFileExists(false)) return ResultStatus::ErrorMissingProductionKeyFile; const auto result = nca_loader->Load(process); 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/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp index 912e785b9..74e44c7fe 100644 --- a/src/video_core/engines/fermi_2d.cpp +++ b/src/video_core/engines/fermi_2d.cpp @@ -47,9 +47,12 @@ void Fermi2D::HandleSurfaceCopy() { u32 dst_bytes_per_pixel = RenderTargetBytesPerPixel(regs.dst.format); if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst)) { - // TODO(bunnei): The below implementation currently will not get hit, as - // AccelerateSurfaceCopy tries to always copy and will always return success. This should be - // changed once we properly support flushing. + rasterizer.FlushRegion(source_cpu, src_bytes_per_pixel * regs.src.width * regs.src.height); + // We have to invalidate the destination region to evict any outdated surfaces from the + // cache. We do this before actually writing the new data because the destination address + // might contain a dirty surface that will have to be written back to memory. + rasterizer.InvalidateRegion(dest_cpu, + dst_bytes_per_pixel * regs.dst.width * regs.dst.height); if (regs.src.linear == regs.dst.linear) { // If the input layout and the output layout are the same, just perform a raw copy. @@ -62,14 +65,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/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp index 66ae6332d..585290d9f 100644 --- a/src/video_core/engines/kepler_memory.cpp +++ b/src/video_core/engines/kepler_memory.cpp @@ -5,10 +5,14 @@ #include "common/logging/log.h" #include "core/memory.h" #include "video_core/engines/kepler_memory.h" +#include "video_core/rasterizer_interface.h" namespace Tegra::Engines { -KeplerMemory::KeplerMemory(MemoryManager& memory_manager) : memory_manager(memory_manager) {} +KeplerMemory::KeplerMemory(VideoCore::RasterizerInterface& rasterizer, + MemoryManager& memory_manager) + : memory_manager(memory_manager), rasterizer{rasterizer} {} + KeplerMemory::~KeplerMemory() = default; void KeplerMemory::WriteReg(u32 method, u32 value) { @@ -37,6 +41,11 @@ void KeplerMemory::ProcessData(u32 data) { VAddr dest_address = *memory_manager.GpuToCpuAddress(address + state.write_offset * sizeof(u32)); + // We have to invalidate the destination region to evict any outdated surfaces from the cache. + // We do this before actually writing the new data because the destination address might contain + // a dirty surface that will have to be written back to memory. + rasterizer.InvalidateRegion(dest_address, sizeof(u32)); + Memory::Write32(dest_address, data); state.write_offset++; diff --git a/src/video_core/engines/kepler_memory.h b/src/video_core/engines/kepler_memory.h index b0d0078cf..bf4a13cff 100644 --- a/src/video_core/engines/kepler_memory.h +++ b/src/video_core/engines/kepler_memory.h @@ -11,6 +11,10 @@ #include "common/common_types.h" #include "video_core/memory_manager.h" +namespace VideoCore { +class RasterizerInterface; +} + namespace Tegra::Engines { #define KEPLERMEMORY_REG_INDEX(field_name) \ @@ -18,7 +22,7 @@ namespace Tegra::Engines { class KeplerMemory final { public: - KeplerMemory(MemoryManager& memory_manager); + KeplerMemory(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager); ~KeplerMemory(); /// Write the value to the register identified by method. @@ -72,6 +76,7 @@ public: private: MemoryManager& memory_manager; + VideoCore::RasterizerInterface& rasterizer; void ProcessData(u32 data); }; diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index c8d1b6478..c8af1c6b6 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -448,7 +448,10 @@ public: BitField<8, 3, u32> block_depth; BitField<12, 1, InvMemoryLayout> type; } memory_layout; - u32 array_mode; + union { + BitField<0, 16, u32> array_mode; + BitField<16, 1, u32> volume; + }; u32 layer_stride; u32 base_layer; INSERT_PADDING_WORDS(7); diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index aa7481b8c..103cd110e 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -4,12 +4,14 @@ #include "core/memory.h" #include "video_core/engines/maxwell_dma.h" +#include "video_core/rasterizer_interface.h" #include "video_core/textures/decoders.h" namespace Tegra { namespace Engines { -MaxwellDMA::MaxwellDMA(MemoryManager& memory_manager) : memory_manager(memory_manager) {} +MaxwellDMA::MaxwellDMA(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager) + : memory_manager(memory_manager), rasterizer{rasterizer} {} void MaxwellDMA::WriteReg(u32 method, u32 value) { ASSERT_MSG(method < Regs::NUM_REGS, @@ -44,36 +46,79 @@ void MaxwellDMA::HandleCopy() { ASSERT(regs.exec.query_mode == Regs::QueryMode::None); ASSERT(regs.exec.query_intr == Regs::QueryIntr::None); ASSERT(regs.exec.copy_mode == Regs::CopyMode::Unk2); - ASSERT(regs.src_params.pos_x == 0); - ASSERT(regs.src_params.pos_y == 0); ASSERT(regs.dst_params.pos_x == 0); ASSERT(regs.dst_params.pos_y == 0); - if (regs.exec.is_dst_linear == regs.exec.is_src_linear) { - std::size_t copy_size = regs.x_count; + if (!regs.exec.is_dst_linear && !regs.exec.is_src_linear) { + // If both the source and the destination are in block layout, assert. + UNREACHABLE_MSG("Tiled->Tiled DMA transfers are not yet implemented"); + return; + } + if (regs.exec.is_dst_linear && regs.exec.is_src_linear) { // When the enable_2d bit is disabled, the copy is performed as if we were copying a 1D - // buffer of length `x_count`, otherwise we copy a 2D buffer of size (x_count, y_count). - if (regs.exec.enable_2d) { - copy_size = copy_size * regs.y_count; + // buffer of length `x_count`, otherwise we copy a 2D image of dimensions (x_count, + // y_count). + if (!regs.exec.enable_2d) { + Memory::CopyBlock(dest_cpu, source_cpu, regs.x_count); + return; } - Memory::CopyBlock(dest_cpu, source_cpu, copy_size); + // If both the source and the destination are in linear layout, perform a line-by-line + // copy. We're going to take a subrect of size (x_count, y_count) from the source + // rectangle. There is no need to manually flush/invalidate the regions because + // CopyBlock does that for us. + for (u32 line = 0; line < regs.y_count; ++line) { + const VAddr source_line = source_cpu + line * regs.src_pitch; + const VAddr dest_line = dest_cpu + line * regs.dst_pitch; + Memory::CopyBlock(dest_line, source_line, regs.x_count); + } return; } ASSERT(regs.exec.enable_2d == 1); + + std::size_t copy_size = regs.x_count * regs.y_count; + + const auto FlushAndInvalidate = [&](u32 src_size, u32 dst_size) { + // TODO(Subv): For now, manually flush the regions until we implement GPU-accelerated + // copying. + rasterizer.FlushRegion(source_cpu, src_size); + + // We have to invalidate the destination region to evict any outdated surfaces from the + // cache. We do this before actually writing the new data because the destination address + // might contain a dirty surface that will have to be written back to memory. + rasterizer.InvalidateRegion(dest_cpu, dst_size); + }; + u8* src_buffer = Memory::GetPointer(source_cpu); u8* dst_buffer = Memory::GetPointer(dest_cpu); if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) { + ASSERT(regs.src_params.size_z == 1); // 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()); + + u32 src_bytes_per_pixel = regs.src_pitch / regs.src_params.size_x; + + FlushAndInvalidate(regs.src_pitch * regs.src_params.size_y, + copy_size * src_bytes_per_pixel); + + Texture::UnswizzleSubrect(regs.x_count, regs.y_count, regs.dst_pitch, + regs.src_params.size_x, src_bytes_per_pixel, source_cpu, dest_cpu, + regs.src_params.BlockHeight(), regs.src_params.pos_x, + regs.src_params.pos_y); } else { + ASSERT(regs.dst_params.size_z == 1); + ASSERT(regs.src_pitch == regs.x_count); + + u32 src_bpp = regs.src_pitch / regs.x_count; + + FlushAndInvalidate(regs.src_pitch * regs.y_count, + regs.dst_params.size_x * regs.dst_params.size_y * src_bpp); + // 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::SwizzleSubrect(regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x, + src_bpp, dest_cpu, source_cpu, regs.dst_params.BlockHeight()); } } diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h index 311ccb616..5f3704f05 100644 --- a/src/video_core/engines/maxwell_dma.h +++ b/src/video_core/engines/maxwell_dma.h @@ -12,11 +12,15 @@ #include "video_core/gpu.h" #include "video_core/memory_manager.h" +namespace VideoCore { +class RasterizerInterface; +} + namespace Tegra::Engines { class MaxwellDMA final { public: - explicit MaxwellDMA(MemoryManager& memory_manager); + explicit MaxwellDMA(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager); ~MaxwellDMA() = default; /// Write the value to the register identified by method. @@ -43,6 +47,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"); @@ -129,6 +137,8 @@ public: MemoryManager& memory_manager; private: + VideoCore::RasterizerInterface& rasterizer; + /// Performs the copy from the source buffer to the destination buffer as configured in the /// registers. void HandleCopy(); diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 39ae065de..e3d67ff87 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -267,7 +267,7 @@ enum class ControlCode : u64 { GTU = 12, NEU = 13, GEU = 14, - // + T = 15, OFF = 16, LO = 17, SFF = 18, diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 9ba7e3533..83c7e5b0b 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -27,8 +27,8 @@ GPU::GPU(VideoCore::RasterizerInterface& rasterizer) { maxwell_3d = std::make_unique<Engines::Maxwell3D>(rasterizer, *memory_manager); fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager); maxwell_compute = std::make_unique<Engines::MaxwellCompute>(); - maxwell_dma = std::make_unique<Engines::MaxwellDMA>(*memory_manager); - kepler_memory = std::make_unique<Engines::KeplerMemory>(*memory_manager); + maxwell_dma = std::make_unique<Engines::MaxwellDMA>(rasterizer, *memory_manager); + kepler_memory = std::make_unique<Engines::KeplerMemory>(rasterizer, *memory_manager); } GPU::~GPU() = default; 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 84582c777..3daccf82f 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -286,7 +286,8 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { &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)}; @@ -423,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, @@ -433,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), @@ -452,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, @@ -616,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); @@ -626,12 +652,19 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) { } void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) { + FlushRegion(addr, size); InvalidateRegion(addr, size); } bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, const Tegra::Engines::Fermi2D::Regs::Surface& dst) { MICROPROFILE_SCOPE(OpenGL_Blits); + + if (Settings::values.use_accurate_gpu_emulation) { + // Skip the accelerated copy and perform a slow but more accurate copy + return false; + } + res_cache.FermiCopySurface(src, dst); return true; } diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 65a220c41..9c8925383 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -34,16 +34,53 @@ 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, @@ -87,18 +124,18 @@ 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 = config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; params.block_width = 1 << config.memory_layout.block_width; @@ -112,16 +149,17 @@ 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 params.rt.index = static_cast<u32>(index); params.rt.array_mode = config.array_mode; params.rt.layer_stride = config.layer_stride; + params.rt.volume = config.volume; params.rt.base_layer = config.base_layer; + params.InitCacheParameters(config.Address()); + return params; } @@ -130,7 +168,7 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { 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 = 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); @@ -143,18 +181,18 @@ 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_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0, params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0, @@ -167,11 +205,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; } @@ -231,6 +269,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 @@ -274,28 +314,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)) { @@ -323,29 +341,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 @@ -395,6 +411,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>, @@ -403,7 +421,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 @@ -420,17 +438,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>, @@ -455,6 +472,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>, @@ -614,22 +633,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 @@ -639,12 +657,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()); } @@ -690,7 +708,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()}; @@ -740,9 +759,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; @@ -755,22 +786,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)}; @@ -790,7 +828,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{}; @@ -800,7 +840,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: @@ -811,54 +851,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; ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}", params.block_width, static_cast<u32>(params.target)); - ASSERT_MSG(params.block_depth == 1, "Block depth is defined as {} on texture type {}", - params.block_depth, static_cast<u32>(params.target)); - // 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(); + 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); } @@ -867,7 +907,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)); @@ -877,9 +954,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 @@ -888,7 +962,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; @@ -904,7 +978,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); @@ -914,7 +988,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: @@ -922,16 +996,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: @@ -941,7 +1015,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 { @@ -970,7 +1044,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: @@ -1032,10 +1106,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) { @@ -1052,8 +1123,8 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres } else if (preserve_contents) { // If surface parameters changed and we care about keeping the previous data, recreate // the surface from the old one - Unregister(surface); Surface new_surface{RecreateSurface(surface, params)}; + Unregister(surface); Register(new_surface); return new_surface; } else { @@ -1104,6 +1175,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 @@ -1112,6 +1191,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 && @@ -1123,11 +1208,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: @@ -1137,6 +1221,9 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, CopySurface(old_surface, new_surface, copy_pbo.handle); } break; + case SurfaceParams::SurfaceTarget::Texture3D: + AccurateCopySurface(old_surface, new_surface); + break; case SurfaceParams::SurfaceTarget::TextureCubemap: { if (old_params.rt.array_mode != 1) { // TODO(bunnei): This is used by Breath of the Wild, I'm not sure how to implement this diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 66d98ad4e..0dd0d90a3 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, @@ -129,6 +132,8 @@ struct SurfaceParams { case Tegra::Texture::TextureType::Texture2D: case Tegra::Texture::TextureType::Texture2DNoMipmap: return SurfaceTarget::Texture2D; + case Tegra::Texture::TextureType::Texture3D: + return SurfaceTarget::Texture3D; case Tegra::Texture::TextureType::TextureCubemap: return SurfaceTarget::TextureCubemap; case Tegra::Texture::TextureType::Texture1DArray: @@ -220,6 +225,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 +289,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 +562,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 +704,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 @@ -732,7 +766,9 @@ 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; @@ -744,15 +780,20 @@ 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; u32 array_mode; + u32 volume; u32 layer_stride; u32 base_layer; } rt; @@ -765,7 +806,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; } }; @@ -780,16 +822,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 { @@ -800,13 +846,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; } @@ -823,6 +862,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> { @@ -839,9 +879,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; @@ -865,6 +902,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.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 7bb287f56..a210f1731 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -19,20 +19,21 @@ 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; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 23349b1a1..e050b063a 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -1233,6 +1233,7 @@ private: case Tegra::Shader::TextureType::Texture2D: { return 2; } + case Tegra::Shader::TextureType::Texture3D: case Tegra::Shader::TextureType::TextureCube: { return 3; } @@ -1527,7 +1528,6 @@ private: break; } - case OpCode::Type::Shift: { std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, true); std::string op_b; @@ -1569,7 +1569,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()); @@ -2262,9 +2261,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"); @@ -2279,21 +2278,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: @@ -2312,7 +2349,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. @@ -2332,10 +2369,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 + ')'; @@ -2478,6 +2518,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); @@ -2492,6 +2534,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; } @@ -2503,13 +2546,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; } @@ -2895,14 +2939,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; } diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 0d2456b56..f1b40e7f5 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -40,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); } } @@ -126,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: @@ -153,13 +229,54 @@ 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; } +void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width, + u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data, + u32 block_height) { + const u32 image_width_in_gobs{(swizzled_width * bytes_per_pixel + 63) / 64}; + for (u32 line = 0; line < subrect_height; ++line) { + const u32 gob_address_y = + (line / (8 * block_height)) * 512 * block_height * image_width_in_gobs + + (line % (8 * block_height) / 8) * 512; + const auto& table = legacy_swizzle_table[line % 8]; + for (u32 x = 0; x < subrect_width; ++x) { + const u32 gob_address = gob_address_y + (x * bytes_per_pixel / 64) * 512 * block_height; + const u32 swizzled_offset = gob_address + table[(x * bytes_per_pixel) % 64]; + const VAddr source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel; + const VAddr dest_addr = swizzled_data + swizzled_offset; + + Memory::CopyBlock(dest_addr, source_line, bytes_per_pixel); + } + } +} + +void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width, + u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data, + u32 block_height, u32 offset_x, u32 offset_y) { + for (u32 line = 0; line < subrect_height; ++line) { + const u32 y2 = line + offset_y; + const u32 gob_address_y = + (y2 / (8 * block_height)) * 512 * block_height + (y2 % (8 * block_height) / 8) * 512; + const auto& table = legacy_swizzle_table[y2 % 8]; + for (u32 x = 0; x < subrect_width; ++x) { + const u32 x2 = (x + offset_x) * bytes_per_pixel; + const u32 gob_address = gob_address_y + (x2 / 64) * 512 * block_height; + const u32 swizzled_offset = gob_address + table[x2 % 64]; + const VAddr dest_line = unswizzled_data + line * dest_pitch + x * bytes_per_pixel; + const VAddr source_addr = swizzled_data + swizzled_offset; + + Memory::CopyBlock(dest_line, source_addr, bytes_per_pixel); + } + } +} + std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width, u32 height) { std::vector<u8> rgba_data; diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h index 234d250af..4726f54a5 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. @@ -38,4 +35,13 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth); +/// Copies an untiled subrectangle into a tiled surface. +void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width, + u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data, + u32 block_height); +/// Copies a tiled subrectangle into a linear surface. +void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width, + u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data, + u32 block_height, u32 offset_x, u32 offset_y); + } // namespace Tegra::Texture diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index 58d17abcb..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; 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 8f99a1c78..3881aba5f 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -96,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; @@ -107,7 +107,7 @@ 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); @@ -135,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)); + } } } @@ -153,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; }; 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 fc186dc2d..bef9df00d 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -176,7 +176,7 @@ GMainWindow::GMainWindow() 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); @@ -908,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 + " "; + 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); - extensions += "main "; - - 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() { @@ -1139,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); } } @@ -1410,7 +1408,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { const auto function = [this, &keys, &pdm] { keys.PopulateFromPartitionData(pdm); - Service::FileSystem::CreateFactories(vfs); + Service::FileSystem::CreateFactories(*vfs); keys.DeriveETicket(pdm); }; @@ -1430,8 +1428,12 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { 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.\n\n") + - errors); + "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; @@ -1450,7 +1452,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { prog.close(); } - Service::FileSystem::CreateFactories(vfs); + Service::FileSystem::CreateFactories(*vfs); if (behavior == ReinitializeKeyBehavior::Warning) { game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); @@ -1565,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_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(); }); |