diff options
Diffstat (limited to '')
64 files changed, 3676 insertions, 200 deletions
diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp index 81b212e4b..177a74deb 100644 --- a/src/common/fiber.cpp +++ b/src/common/fiber.cpp @@ -2,9 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <mutex> + #include "common/assert.h" #include "common/fiber.h" -#include "common/spin_lock.h" #include "common/virtual_buffer.h" #include <boost/context/detail/fcontext.hpp> @@ -19,7 +20,7 @@ struct Fiber::FiberImpl { VirtualBuffer<u8> stack; VirtualBuffer<u8> rewind_stack; - SpinLock guard{}; + std::mutex guard; std::function<void(void*)> entry_point; std::function<void(void*)> rewind_point; void* rewind_parameter{}; diff --git a/src/common/settings.h b/src/common/settings.h index 86e0fa140..3b7be63b3 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -590,6 +590,9 @@ struct Values { BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"}; std::vector<TouchFromButtonMap> touch_from_button_maps; + BasicSetting<bool> enable_ring_controller{true, "enable_ring_controller"}; + RingconRaw ringcon_analogs; + // Data Storage BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"}; BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"}; diff --git a/src/common/settings_input.h b/src/common/settings_input.h index 4ff37e186..6f42346bc 100644 --- a/src/common/settings_input.h +++ b/src/common/settings_input.h @@ -357,6 +357,7 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods; using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>; using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>; +using RingconRaw = std::string; constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28; constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ffbda7925..62230bae0 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -434,6 +434,8 @@ add_library(core STATIC hle/service/grc/grc.h hle/service/hid/hid.cpp hle/service/hid/hid.h + hle/service/hid/hidbus.cpp + hle/service/hid/hidbus.h hle/service/hid/irs.cpp hle/service/hid/irs.h hle/service/hid/ring_lifo.h @@ -460,6 +462,16 @@ add_library(core STATIC hle/service/hid/controllers/touchscreen.h hle/service/hid/controllers/xpad.cpp hle/service/hid/controllers/xpad.h + hle/service/hid/hidbus/hidbus_base.cpp + hle/service/hid/hidbus/hidbus_base.h + hle/service/hid/hidbus/ringcon.cpp + hle/service/hid/hidbus/ringcon.h + hle/service/hid/hidbus/starlink.cpp + hle/service/hid/hidbus/starlink.h + hle/service/hid/hidbus/stubbed.cpp + hle/service/hid/hidbus/stubbed.h + hle/service/jit/jit_context.cpp + hle/service/jit/jit_context.h hle/service/jit/jit.cpp hle/service/jit/jit.h hle/service/lbl/lbl.cpp diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index ab3210d84..5de4384db 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -88,7 +88,7 @@ public: void CallSVC(u32 swi) override { parent.svc_swi = swi; - parent.jit->HaltExecution(svc_call); + parent.jit.load()->HaltExecution(svc_call); } void AddTicks(u64 ticks) override { @@ -151,6 +151,13 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* config.code_cache_size = 512_MiB; config.far_code_offset = 400_MiB; + // null_jit + if (!page_table) { + // Don't waste too much memory on null_jit + config.code_cache_size = 8_MiB; + config.far_code_offset = 4_MiB; + } + // Safe optimizations if (Settings::values.cpu_debug_mode) { if (!Settings::values.cpuopt_page_tables) { @@ -228,18 +235,18 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* void ARM_Dynarmic_32::Run() { while (true) { - const auto hr = jit->Run(); + const auto hr = jit.load()->Run(); if (Has(hr, svc_call)) { Kernel::Svc::Call(system, svc_swi); } - if (Has(hr, break_loop)) { + if (Has(hr, break_loop) || !uses_wall_clock) { break; } } } void ARM_Dynarmic_32::Step() { - jit->Step(); + jit.load()->Step(); } ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_, @@ -249,24 +256,24 @@ ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handl cb(std::make_unique<DynarmicCallbacks32>(*this)), cp15(std::make_shared<DynarmicCP15>(*this)), core_index{core_index_}, exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor_)}, - jit(MakeJit(nullptr)) {} + null_jit{MakeJit(nullptr)}, jit{null_jit.get()} {} ARM_Dynarmic_32::~ARM_Dynarmic_32() = default; void ARM_Dynarmic_32::SetPC(u64 pc) { - jit->Regs()[15] = static_cast<u32>(pc); + jit.load()->Regs()[15] = static_cast<u32>(pc); } u64 ARM_Dynarmic_32::GetPC() const { - return jit->Regs()[15]; + return jit.load()->Regs()[15]; } u64 ARM_Dynarmic_32::GetReg(int index) const { - return jit->Regs()[index]; + return jit.load()->Regs()[index]; } void ARM_Dynarmic_32::SetReg(int index, u64 value) { - jit->Regs()[index] = static_cast<u32>(value); + jit.load()->Regs()[index] = static_cast<u32>(value); } u128 ARM_Dynarmic_32::GetVectorReg(int index) const { @@ -276,11 +283,11 @@ u128 ARM_Dynarmic_32::GetVectorReg(int index) const { void ARM_Dynarmic_32::SetVectorReg(int index, u128 value) {} u32 ARM_Dynarmic_32::GetPSTATE() const { - return jit->Cpsr(); + return jit.load()->Cpsr(); } void ARM_Dynarmic_32::SetPSTATE(u32 cpsr) { - jit->SetCpsr(cpsr); + jit.load()->SetCpsr(cpsr); } u64 ARM_Dynarmic_32::GetTlsAddress() const { @@ -301,7 +308,7 @@ void ARM_Dynarmic_32::SetTPIDR_EL0(u64 value) { void ARM_Dynarmic_32::SaveContext(ThreadContext32& ctx) { Dynarmic::A32::Context context; - jit->SaveContext(context); + jit.load()->SaveContext(context); ctx.cpu_registers = context.Regs(); ctx.extension_registers = context.ExtRegs(); ctx.cpsr = context.Cpsr(); @@ -314,27 +321,27 @@ void ARM_Dynarmic_32::LoadContext(const ThreadContext32& ctx) { context.ExtRegs() = ctx.extension_registers; context.SetCpsr(ctx.cpsr); context.SetFpscr(ctx.fpscr); - jit->LoadContext(context); + jit.load()->LoadContext(context); } void ARM_Dynarmic_32::PrepareReschedule() { - jit->HaltExecution(break_loop); + jit.load()->HaltExecution(break_loop); } void ARM_Dynarmic_32::SignalInterrupt() { - jit->HaltExecution(break_loop); + jit.load()->HaltExecution(break_loop); } void ARM_Dynarmic_32::ClearInstructionCache() { - jit->ClearCache(); + jit.load()->ClearCache(); } void ARM_Dynarmic_32::InvalidateCacheRange(VAddr addr, std::size_t size) { - jit->InvalidateCacheRange(static_cast<u32>(addr), size); + jit.load()->InvalidateCacheRange(static_cast<u32>(addr), size); } void ARM_Dynarmic_32::ClearExclusiveState() { - jit->ClearExclusiveState(); + jit.load()->ClearExclusiveState(); } void ARM_Dynarmic_32::PageTableChanged(Common::PageTable& page_table, @@ -345,13 +352,14 @@ void ARM_Dynarmic_32::PageTableChanged(Common::PageTable& page_table, auto key = std::make_pair(&page_table, new_address_space_size_in_bits); auto iter = jit_cache.find(key); if (iter != jit_cache.end()) { - jit = iter->second; + jit.store(iter->second.get()); LoadContext(ctx); return; } - jit = MakeJit(&page_table); + std::shared_ptr new_jit = MakeJit(&page_table); + jit.store(new_jit.get()); LoadContext(ctx); - jit_cache.emplace(key, jit); + jit_cache.emplace(key, std::move(new_jit)); } } // namespace Core diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h index 3f68a4ff1..684937353 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.h +++ b/src/core/arm/dynarmic/arm_dynarmic_32.h @@ -4,6 +4,7 @@ #pragma once +#include <atomic> #include <memory> #include <unordered_map> @@ -80,7 +81,11 @@ private: std::shared_ptr<DynarmicCP15> cp15; std::size_t core_index; DynarmicExclusiveMonitor& exclusive_monitor; - std::shared_ptr<Dynarmic::A32::Jit> jit; + + std::shared_ptr<Dynarmic::A32::Jit> null_jit; + + // A raw pointer here is fine; we never delete Jit instances. + std::atomic<Dynarmic::A32::Jit*> jit; // SVC callback u32 svc_swi{}; diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index 68822a1fc..ae0b158c2 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -109,7 +109,7 @@ public: break; } - parent.jit->HaltExecution(Dynarmic::HaltReason::CacheInvalidation); + parent.jit.load()->HaltExecution(Dynarmic::HaltReason::CacheInvalidation); } void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override { @@ -130,7 +130,7 @@ public: void CallSVC(u32 swi) override { parent.svc_swi = swi; - parent.jit->HaltExecution(svc_call); + parent.jit.load()->HaltExecution(svc_call); } void AddTicks(u64 ticks) override { @@ -212,6 +212,13 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* config.code_cache_size = 512_MiB; config.far_code_offset = 400_MiB; + // null_jit + if (!page_table) { + // Don't waste too much memory on null_jit + config.code_cache_size = 8_MiB; + config.far_code_offset = 4_MiB; + } + // Safe optimizations if (Settings::values.cpu_debug_mode) { if (!Settings::values.cpuopt_page_tables) { @@ -289,18 +296,18 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* void ARM_Dynarmic_64::Run() { while (true) { - const auto hr = jit->Run(); + const auto hr = jit.load()->Run(); if (Has(hr, svc_call)) { Kernel::Svc::Call(system, svc_swi); } - if (Has(hr, break_loop)) { + if (Has(hr, break_loop) || !uses_wall_clock) { break; } } } void ARM_Dynarmic_64::Step() { - jit->Step(); + jit.load()->Step(); } ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_, @@ -309,40 +316,40 @@ ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handl : ARM_Interface{system_, interrupt_handlers_, uses_wall_clock_}, cb(std::make_unique<DynarmicCallbacks64>(*this)), core_index{core_index_}, exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor_)}, - jit(MakeJit(nullptr, 48)) {} + null_jit{MakeJit(nullptr, 48)}, jit{null_jit.get()} {} ARM_Dynarmic_64::~ARM_Dynarmic_64() = default; void ARM_Dynarmic_64::SetPC(u64 pc) { - jit->SetPC(pc); + jit.load()->SetPC(pc); } u64 ARM_Dynarmic_64::GetPC() const { - return jit->GetPC(); + return jit.load()->GetPC(); } u64 ARM_Dynarmic_64::GetReg(int index) const { - return jit->GetRegister(index); + return jit.load()->GetRegister(index); } void ARM_Dynarmic_64::SetReg(int index, u64 value) { - jit->SetRegister(index, value); + jit.load()->SetRegister(index, value); } u128 ARM_Dynarmic_64::GetVectorReg(int index) const { - return jit->GetVector(index); + return jit.load()->GetVector(index); } void ARM_Dynarmic_64::SetVectorReg(int index, u128 value) { - jit->SetVector(index, value); + jit.load()->SetVector(index, value); } u32 ARM_Dynarmic_64::GetPSTATE() const { - return jit->GetPstate(); + return jit.load()->GetPstate(); } void ARM_Dynarmic_64::SetPSTATE(u32 pstate) { - jit->SetPstate(pstate); + jit.load()->SetPstate(pstate); } u64 ARM_Dynarmic_64::GetTlsAddress() const { @@ -362,45 +369,47 @@ void ARM_Dynarmic_64::SetTPIDR_EL0(u64 value) { } void ARM_Dynarmic_64::SaveContext(ThreadContext64& ctx) { - ctx.cpu_registers = jit->GetRegisters(); - ctx.sp = jit->GetSP(); - ctx.pc = jit->GetPC(); - ctx.pstate = jit->GetPstate(); - ctx.vector_registers = jit->GetVectors(); - ctx.fpcr = jit->GetFpcr(); - ctx.fpsr = jit->GetFpsr(); + Dynarmic::A64::Jit* j = jit.load(); + ctx.cpu_registers = j->GetRegisters(); + ctx.sp = j->GetSP(); + ctx.pc = j->GetPC(); + ctx.pstate = j->GetPstate(); + ctx.vector_registers = j->GetVectors(); + ctx.fpcr = j->GetFpcr(); + ctx.fpsr = j->GetFpsr(); ctx.tpidr = cb->tpidr_el0; } void ARM_Dynarmic_64::LoadContext(const ThreadContext64& ctx) { - jit->SetRegisters(ctx.cpu_registers); - jit->SetSP(ctx.sp); - jit->SetPC(ctx.pc); - jit->SetPstate(ctx.pstate); - jit->SetVectors(ctx.vector_registers); - jit->SetFpcr(ctx.fpcr); - jit->SetFpsr(ctx.fpsr); + Dynarmic::A64::Jit* j = jit.load(); + j->SetRegisters(ctx.cpu_registers); + j->SetSP(ctx.sp); + j->SetPC(ctx.pc); + j->SetPstate(ctx.pstate); + j->SetVectors(ctx.vector_registers); + j->SetFpcr(ctx.fpcr); + j->SetFpsr(ctx.fpsr); SetTPIDR_EL0(ctx.tpidr); } void ARM_Dynarmic_64::PrepareReschedule() { - jit->HaltExecution(break_loop); + jit.load()->HaltExecution(break_loop); } void ARM_Dynarmic_64::SignalInterrupt() { - jit->HaltExecution(break_loop); + jit.load()->HaltExecution(break_loop); } void ARM_Dynarmic_64::ClearInstructionCache() { - jit->ClearCache(); + jit.load()->ClearCache(); } void ARM_Dynarmic_64::InvalidateCacheRange(VAddr addr, std::size_t size) { - jit->InvalidateCacheRange(addr, size); + jit.load()->InvalidateCacheRange(addr, size); } void ARM_Dynarmic_64::ClearExclusiveState() { - jit->ClearExclusiveState(); + jit.load()->ClearExclusiveState(); } void ARM_Dynarmic_64::PageTableChanged(Common::PageTable& page_table, @@ -411,13 +420,14 @@ void ARM_Dynarmic_64::PageTableChanged(Common::PageTable& page_table, auto key = std::make_pair(&page_table, new_address_space_size_in_bits); auto iter = jit_cache.find(key); if (iter != jit_cache.end()) { - jit = iter->second; + jit.store(iter->second.get()); LoadContext(ctx); return; } - jit = MakeJit(&page_table, new_address_space_size_in_bits); + std::shared_ptr new_jit = MakeJit(&page_table, new_address_space_size_in_bits); + jit.store(new_jit.get()); LoadContext(ctx); - jit_cache.emplace(key, jit); + jit_cache.emplace(key, std::move(new_jit)); } } // namespace Core diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h index 58bc7fbec..86018f196 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.h +++ b/src/core/arm/dynarmic/arm_dynarmic_64.h @@ -4,6 +4,7 @@ #pragma once +#include <atomic> #include <memory> #include <unordered_map> @@ -74,7 +75,10 @@ private: std::size_t core_index; DynarmicExclusiveMonitor& exclusive_monitor; - std::shared_ptr<Dynarmic::A64::Jit> jit; + std::shared_ptr<Dynarmic::A64::Jit> null_jit; + + // A raw pointer here is fine; we never delete Jit instances. + std::atomic<Dynarmic::A64::Jit*> jit; // SVC callback u32 svc_swi{}; diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 888828fd0..28b63be43 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -8,13 +8,13 @@ #include <chrono> #include <functional> #include <memory> +#include <mutex> #include <optional> #include <string> #include <thread> #include <vector> #include "common/common_types.h" -#include "common/spin_lock.h" #include "common/thread.h" #include "common/wall_clock.h" @@ -149,8 +149,8 @@ private: std::shared_ptr<EventType> ev_lost; Common::Event event{}; Common::Event pause_event{}; - Common::SpinLock basic_lock{}; - Common::SpinLock advance_lock{}; + std::mutex basic_lock; + std::mutex advance_lock; std::unique_ptr<std::thread> timer_thread; std::atomic<bool> paused{}; std::atomic<bool> paused_set{}; diff --git a/src/core/hid/emulated_devices.cpp b/src/core/hid/emulated_devices.cpp index cc0dcd931..2f84d2b52 100644 --- a/src/core/hid/emulated_devices.cpp +++ b/src/core/hid/emulated_devices.cpp @@ -15,6 +15,7 @@ EmulatedDevices::EmulatedDevices() = default; EmulatedDevices::~EmulatedDevices() = default; void EmulatedDevices::ReloadFromSettings() { + ring_params = Common::ParamPackage(Settings::values.ringcon_analogs); ReloadInput(); } @@ -66,6 +67,8 @@ void EmulatedDevices::ReloadInput() { key_index++; } + ring_analog_device = Common::Input::CreateDevice<Common::Input::InputDevice>(ring_params); + for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) { if (!mouse_button_devices[index]) { continue; @@ -120,6 +123,13 @@ void EmulatedDevices::ReloadInput() { }, }); } + + if (ring_analog_device) { + ring_analog_device->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); }, + }); + } } void EmulatedDevices::UnloadInput() { @@ -155,6 +165,7 @@ void EmulatedDevices::SaveCurrentConfig() { if (!is_configuring) { return; } + Settings::values.ringcon_analogs = ring_params.Serialize(); } void EmulatedDevices::RestoreConfig() { @@ -164,6 +175,15 @@ void EmulatedDevices::RestoreConfig() { ReloadFromSettings(); } +Common::ParamPackage EmulatedDevices::GetRingParam() const { + return ring_params; +} + +void EmulatedDevices::SetRingParam(Common::ParamPackage param) { + ring_params = std::move(param); + ReloadInput(); +} + void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback, std::size_t index) { if (index >= device_status.keyboard_values.size()) { @@ -410,6 +430,23 @@ void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callbac TriggerOnChange(DeviceTriggerType::Mouse); } +void EmulatedDevices::SetRingAnalog(const Common::Input::CallbackStatus& callback) { + std::lock_guard lock{mutex}; + const auto force_value = TransformToStick(callback); + + device_status.ring_analog_value = force_value.x; + + if (is_configuring) { + device_status.ring_analog_value = {}; + TriggerOnChange(DeviceTriggerType::RingController); + return; + } + + device_status.ring_analog_state.force = force_value.x.value; + + TriggerOnChange(DeviceTriggerType::RingController); +} + KeyboardValues EmulatedDevices::GetKeyboardValues() const { std::scoped_lock lock{mutex}; return device_status.keyboard_values; @@ -425,6 +462,10 @@ MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const { return device_status.mouse_button_values; } +RingAnalogValue EmulatedDevices::GetRingSensorValues() const { + return device_status.ring_analog_value; +} + KeyboardKey EmulatedDevices::GetKeyboard() const { std::scoped_lock lock{mutex}; return device_status.keyboard_state; @@ -450,6 +491,10 @@ AnalogStickState EmulatedDevices::GetMouseWheel() const { return device_status.mouse_wheel_state; } +RingSensorForce EmulatedDevices::GetRingSensorForce() const { + return device_status.ring_analog_state; +} + void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) { std::scoped_lock lock{callback_mutex}; for (const auto& poller_pair : callback_list) { diff --git a/src/core/hid/emulated_devices.h b/src/core/hid/emulated_devices.h index 73e9f0293..fb6451e7a 100644 --- a/src/core/hid/emulated_devices.h +++ b/src/core/hid/emulated_devices.h @@ -26,9 +26,11 @@ using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMouseWheel::NumMouseWheels>; using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>; +using RingAnalogDevice = std::unique_ptr<Common::Input::InputDevice>; using MouseButtonParams = std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>; +using RingAnalogParams = Common::ParamPackage; using KeyboardValues = std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>; @@ -39,12 +41,17 @@ using MouseButtonValues = using MouseAnalogValues = std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>; using MouseStickValue = Common::Input::TouchStatus; +using RingAnalogValue = Common::Input::AnalogStatus; struct MousePosition { f32 x; f32 y; }; +struct RingSensorForce { + f32 force; +}; + struct DeviceStatus { // Data from input_common KeyboardValues keyboard_values{}; @@ -52,6 +59,7 @@ struct DeviceStatus { MouseButtonValues mouse_button_values{}; MouseAnalogValues mouse_analog_values{}; MouseStickValue mouse_stick_value{}; + RingAnalogValue ring_analog_value{}; // Data for HID serices KeyboardKey keyboard_state{}; @@ -59,12 +67,14 @@ struct DeviceStatus { MouseButton mouse_button_state{}; MousePosition mouse_position_state{}; AnalogStickState mouse_wheel_state{}; + RingSensorForce ring_analog_state{}; }; enum class DeviceTriggerType { Keyboard, KeyboardModdifier, Mouse, + RingController, }; struct InterfaceUpdateCallback { @@ -110,6 +120,15 @@ public: /// Reverts any mapped changes made that weren't saved void RestoreConfig(); + // Returns the current mapped ring device + Common::ParamPackage GetRingParam() const; + + /** + * Updates the current mapped ring device + * @param param ParamPackage with ring sensor data to be mapped + */ + void SetRingParam(Common::ParamPackage param); + /// Returns the latest status of button input from the keyboard with parameters KeyboardValues GetKeyboardValues() const; @@ -119,6 +138,9 @@ public: /// Returns the latest status of button input from the mouse with parameters MouseButtonValues GetMouseButtonsValues() const; + /// Returns the latest status of analog input from the ring sensor with parameters + RingAnalogValue GetRingSensorValues() const; + /// Returns the latest status of button input from the keyboard KeyboardKey GetKeyboard() const; @@ -134,6 +156,9 @@ public: /// Returns the latest mouse wheel change AnalogStickState GetMouseWheel() const; + /// Returns the latest ringcon force sensor value + RingSensorForce GetRingSensorForce() const; + /** * Adds a callback to the list of events * @param update_callback InterfaceUpdateCallback that will be triggered @@ -186,6 +211,12 @@ private: void SetMouseStick(const Common::Input::CallbackStatus& callback); /** + * Updates the ring analog sensor status of the ring controller + * @param callback A CallbackStatus containing the force status + */ + void SetRingAnalog(const Common::Input::CallbackStatus& callback); + + /** * Triggers a callback that something has changed on the device status * @param type Input type of the event to trigger */ @@ -193,11 +224,14 @@ private: bool is_configuring{false}; + RingAnalogParams ring_params; + KeyboardDevices keyboard_devices; KeyboardModifierDevices keyboard_modifier_devices; MouseButtonDevices mouse_button_devices; MouseAnalogDevices mouse_analog_devices; MouseStickDevice mouse_stick_device; + RingAnalogDevice ring_analog_device; mutable std::mutex mutex; mutable std::mutex callback_mutex; diff --git a/src/core/hle/kernel/global_scheduler_context.h b/src/core/hle/kernel/global_scheduler_context.h index 6f44b534f..47425a3a1 100644 --- a/src/core/hle/kernel/global_scheduler_context.h +++ b/src/core/hle/kernel/global_scheduler_context.h @@ -8,7 +8,6 @@ #include <vector> #include "common/common_types.h" -#include "common/spin_lock.h" #include "core/hardware_properties.h" #include "core/hle/kernel/k_priority_queue.h" #include "core/hle/kernel/k_scheduler_lock.h" @@ -80,7 +79,7 @@ private: /// Lists all thread ids that aren't deleted/etc. std::vector<KThread*> thread_list; - Common::SpinLock global_list_guard{}; + std::mutex global_list_guard; }; } // namespace Kernel diff --git a/src/core/hle/kernel/k_code_memory.cpp b/src/core/hle/kernel/k_code_memory.cpp index 63bbe02e9..09eaf004c 100644 --- a/src/core/hle/kernel/k_code_memory.cpp +++ b/src/core/hle/kernel/k_code_memory.cpp @@ -35,9 +35,14 @@ ResultCode KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr R_TRY(page_table.LockForCodeMemory(addr, size)) // Clear the memory. - for (const auto& block : m_page_group.Nodes()) { - std::memset(device_memory.GetPointer(block.GetAddress()), 0xFF, block.GetSize()); - } + // + // FIXME: this ends up clobbering address ranges outside the scope of the mapping within + // guest memory, and is not specifically required if the guest program is correctly + // written, so disable until this is further investigated. + // + // for (const auto& block : m_page_group.Nodes()) { + // std::memset(device_memory.GetPointer(block.GetAddress()), 0xFF, block.GetSize()); + // } // Set remaining tracking members. m_address = addr; diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp index 6c0bb1672..526eb4b70 100644 --- a/src/core/hle/kernel/k_scheduler.cpp +++ b/src/core/hle/kernel/k_scheduler.cpp @@ -705,7 +705,7 @@ void KScheduler::Unload(KThread* thread) { prev_thread = nullptr; } - thread->context_guard.Unlock(); + thread->context_guard.unlock(); } void KScheduler::Reload(KThread* thread) { @@ -794,13 +794,13 @@ void KScheduler::SwitchToCurrent() { do { auto next_thread = current_thread.load(); if (next_thread != nullptr) { - const auto locked = next_thread->context_guard.TryLock(); + const auto locked = next_thread->context_guard.try_lock(); if (state.needs_scheduling.load()) { - next_thread->context_guard.Unlock(); + next_thread->context_guard.unlock(); break; } if (next_thread->GetActiveCore() != core_id) { - next_thread->context_guard.Unlock(); + next_thread->context_guard.unlock(); break; } if (!locked) { diff --git a/src/core/hle/kernel/k_spin_lock.cpp b/src/core/hle/kernel/k_spin_lock.cpp index 4412aa4bb..527ff0f9f 100644 --- a/src/core/hle/kernel/k_spin_lock.cpp +++ b/src/core/hle/kernel/k_spin_lock.cpp @@ -4,51 +4,18 @@ #include "core/hle/kernel/k_spin_lock.h" -#if _MSC_VER -#include <intrin.h> -#if _M_AMD64 -#define __x86_64__ 1 -#endif -#if _M_ARM64 -#define __aarch64__ 1 -#endif -#else -#if __x86_64__ -#include <xmmintrin.h> -#endif -#endif - -namespace { - -void ThreadPause() { -#if __x86_64__ - _mm_pause(); -#elif __aarch64__ && _MSC_VER - __yield(); -#elif __aarch64__ - asm("yield"); -#endif -} - -} // namespace - namespace Kernel { void KSpinLock::Lock() { - while (lck.test_and_set(std::memory_order_acquire)) { - ThreadPause(); - } + lck.lock(); } void KSpinLock::Unlock() { - lck.clear(std::memory_order_release); + lck.unlock(); } bool KSpinLock::TryLock() { - if (lck.test_and_set(std::memory_order_acquire)) { - return false; - } - return true; + return lck.try_lock(); } } // namespace Kernel diff --git a/src/core/hle/kernel/k_spin_lock.h b/src/core/hle/kernel/k_spin_lock.h index 4d87d006a..7868b25a5 100644 --- a/src/core/hle/kernel/k_spin_lock.h +++ b/src/core/hle/kernel/k_spin_lock.h @@ -4,7 +4,7 @@ #pragma once -#include <atomic> +#include <mutex> #include "core/hle/kernel/k_scoped_lock.h" @@ -25,7 +25,7 @@ public: [[nodiscard]] bool TryLock(); private: - std::atomic_flag lck = ATOMIC_FLAG_INIT; + std::mutex lck; }; // TODO(bunnei): Alias for now, in case we want to implement these accurately in the future. diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index d3bb1c871..af71987e8 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -723,10 +723,10 @@ void KThread::UpdateState() { ASSERT(kernel.GlobalSchedulerContext().IsLocked()); // Set our suspend flags in state. - const ThreadState old_state = thread_state; + const ThreadState old_state = thread_state.load(std::memory_order_relaxed); const auto new_state = static_cast<ThreadState>(this->GetSuspendFlags()) | (old_state & ThreadState::Mask); - thread_state = new_state; + thread_state.store(new_state, std::memory_order_relaxed); // Note the state change in scheduler. if (new_state != old_state) { @@ -738,8 +738,8 @@ void KThread::Continue() { ASSERT(kernel.GlobalSchedulerContext().IsLocked()); // Clear our suspend flags in state. - const ThreadState old_state = thread_state; - thread_state = old_state & ThreadState::Mask; + const ThreadState old_state = thread_state.load(std::memory_order_relaxed); + thread_state.store(old_state & ThreadState::Mask, std::memory_order_relaxed); // Note the state change in scheduler. KScheduler::OnThreadStateChanged(kernel, this, old_state); @@ -1079,17 +1079,10 @@ void KThread::IfDummyThreadTryWait() { return; } - // Block until we can grab the lock. - KScopedSpinLock lk{dummy_wait_lock}; -} - -void KThread::IfDummyThreadBeginWait() { - if (!IsDummyThread()) { - return; - } - - // Ensure the thread will block when IfDummyThreadTryWait is called. - dummy_wait_lock.Lock(); + // Block until we are no longer waiting. + std::unique_lock lk(dummy_wait_lock); + dummy_wait_cv.wait( + lk, [&] { return GetState() != ThreadState::Waiting || kernel.IsShuttingDown(); }); } void KThread::IfDummyThreadEndWait() { @@ -1097,8 +1090,8 @@ void KThread::IfDummyThreadEndWait() { return; } - // Ensure the thread will no longer block. - dummy_wait_lock.Unlock(); + // Wake up the waiting thread. + dummy_wait_cv.notify_one(); } void KThread::BeginWait(KThreadQueue* queue) { @@ -1107,9 +1100,6 @@ void KThread::BeginWait(KThreadQueue* queue) { // Set our wait queue. wait_queue = queue; - - // Special case for dummy threads to ensure they block. - IfDummyThreadBeginWait(); } void KThread::NotifyAvailable(KSynchronizationObject* signaled_object, ResultCode wait_result_) { @@ -1158,10 +1148,11 @@ void KThread::SetState(ThreadState state) { SetMutexWaitAddressForDebugging({}); SetWaitReasonForDebugging({}); - const ThreadState old_state = thread_state; - thread_state = - static_cast<ThreadState>((old_state & ~ThreadState::Mask) | (state & ThreadState::Mask)); - if (thread_state != old_state) { + const ThreadState old_state = thread_state.load(std::memory_order_relaxed); + thread_state.store( + static_cast<ThreadState>((old_state & ~ThreadState::Mask) | (state & ThreadState::Mask)), + std::memory_order_relaxed); + if (thread_state.load(std::memory_order_relaxed) != old_state) { KScheduler::OnThreadStateChanged(kernel, this, old_state); } } diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index d0fd85130..4892fdf76 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -6,6 +6,8 @@ #include <array> #include <atomic> +#include <condition_variable> +#include <mutex> #include <span> #include <string> #include <utility> @@ -15,6 +17,7 @@ #include "common/common_types.h" #include "common/intrusive_red_black_tree.h" +#include "common/spin_lock.h" #include "core/arm/arm_interface.h" #include "core/hle/kernel/k_affinity_mask.h" #include "core/hle/kernel/k_light_lock.h" @@ -256,11 +259,11 @@ public: [[nodiscard]] std::shared_ptr<Common::Fiber>& GetHostContext(); [[nodiscard]] ThreadState GetState() const { - return thread_state & ThreadState::Mask; + return thread_state.load(std::memory_order_relaxed) & ThreadState::Mask; } [[nodiscard]] ThreadState GetRawState() const { - return thread_state; + return thread_state.load(std::memory_order_relaxed); } void SetState(ThreadState state); @@ -642,7 +645,6 @@ public: // blocking as needed. void IfDummyThreadTryWait(); - void IfDummyThreadBeginWait(); void IfDummyThreadEndWait(); private: @@ -762,13 +764,14 @@ private: s8 priority_inheritance_count{}; bool resource_limit_release_hint{}; StackParameters stack_parameters{}; - KSpinLock context_guard{}; - KSpinLock dummy_wait_lock{}; + Common::SpinLock context_guard{}; // For emulation std::shared_ptr<Common::Fiber> host_context{}; bool is_single_core{}; ThreadType thread_type{}; + std::mutex dummy_wait_lock; + std::condition_variable dummy_wait_cv; // For debugging std::vector<KSynchronizationObject*> wait_objects_for_debugging; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index d840d44e6..5984afd7e 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -140,6 +140,7 @@ struct KernelCore::Impl { CleanupObject(font_shared_mem); CleanupObject(irs_shared_mem); CleanupObject(time_shared_mem); + CleanupObject(hidbus_shared_mem); CleanupObject(system_resource_limit); for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { @@ -622,16 +623,20 @@ struct KernelCore::Impl { constexpr std::size_t font_size{0x1100000}; constexpr std::size_t irs_size{0x8000}; constexpr std::size_t time_size{0x1000}; + constexpr std::size_t hidbus_size{0x1000}; const PAddr hid_phys_addr{system_pool.GetAddress()}; const PAddr font_phys_addr{system_pool.GetAddress() + hid_size}; const PAddr irs_phys_addr{system_pool.GetAddress() + hid_size + font_size}; const PAddr time_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size}; + const PAddr hidbus_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size + + time_size}; hid_shared_mem = KSharedMemory::Create(system.Kernel()); font_shared_mem = KSharedMemory::Create(system.Kernel()); irs_shared_mem = KSharedMemory::Create(system.Kernel()); time_shared_mem = KSharedMemory::Create(system.Kernel()); + hidbus_shared_mem = KSharedMemory::Create(system.Kernel()); hid_shared_mem->Initialize(system.DeviceMemory(), nullptr, {hid_phys_addr, hid_size / PageSize}, @@ -649,6 +654,10 @@ struct KernelCore::Impl { {time_phys_addr, time_size / PageSize}, Svc::MemoryPermission::None, Svc::MemoryPermission::Read, time_phys_addr, time_size, "Time:SharedMemory"); + hidbus_shared_mem->Initialize(system.DeviceMemory(), nullptr, + {hidbus_phys_addr, hidbus_size / PageSize}, + Svc::MemoryPermission::None, Svc::MemoryPermission::Read, + hidbus_phys_addr, hidbus_size, "HidBus:SharedMemory"); } KClientPort* CreateNamedServicePort(std::string name) { @@ -748,6 +757,7 @@ struct KernelCore::Impl { Kernel::KSharedMemory* font_shared_mem{}; Kernel::KSharedMemory* irs_shared_mem{}; Kernel::KSharedMemory* time_shared_mem{}; + Kernel::KSharedMemory* hidbus_shared_mem{}; // Memory layout std::unique_ptr<KMemoryLayout> memory_layout; @@ -1047,6 +1057,14 @@ const Kernel::KSharedMemory& KernelCore::GetTimeSharedMem() const { return *impl->time_shared_mem; } +Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() { + return *impl->hidbus_shared_mem; +} + +const Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() const { + return *impl->hidbus_shared_mem; +} + void KernelCore::Suspend(bool in_suspention) { const bool should_suspend = exception_exited || in_suspention; { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index d709c368b..12e44b8a5 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -264,6 +264,12 @@ public: /// Gets the shared memory object for Time services. const Kernel::KSharedMemory& GetTimeSharedMem() const; + /// Gets the shared memory object for HIDBus services. + Kernel::KSharedMemory& GetHidBusSharedMem(); + + /// Gets the shared memory object for HIDBus services. + const Kernel::KSharedMemory& GetHidBusSharedMem() const; + /// Suspend/unsuspend the OS. void Suspend(bool in_suspention); diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp index 18a5f40f8..cc49e8c7e 100644 --- a/src/core/hle/kernel/physical_core.cpp +++ b/src/core/hle/kernel/physical_core.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/spin_lock.h" #include "core/arm/cpu_interrupt_handler.h" #include "core/arm/dynarmic/arm_dynarmic_32.h" #include "core/arm/dynarmic/arm_dynarmic_64.h" @@ -16,7 +15,7 @@ namespace Kernel { PhysicalCore::PhysicalCore(std::size_t core_index_, Core::System& system_, KScheduler& scheduler_, Core::CPUInterrupts& interrupts_) : core_index{core_index_}, system{system_}, scheduler{scheduler_}, - interrupts{interrupts_}, guard{std::make_unique<Common::SpinLock>()} { + interrupts{interrupts_}, guard{std::make_unique<std::mutex>()} { #ifdef ARCHITECTURE_x86_64 // TODO(bunnei): Initialization relies on a core being available. We may later replace this with // a 32-bit instance of Dynarmic. This should be abstracted out to a CPU manager. diff --git a/src/core/hle/kernel/physical_core.h b/src/core/hle/kernel/physical_core.h index 16a032e89..f2112fc1d 100644 --- a/src/core/hle/kernel/physical_core.h +++ b/src/core/hle/kernel/physical_core.h @@ -6,13 +6,10 @@ #include <cstddef> #include <memory> +#include <mutex> #include "core/arm/arm_interface.h" -namespace Common { -class SpinLock; -} - namespace Kernel { class KScheduler; } // namespace Kernel @@ -91,7 +88,7 @@ private: Core::System& system; Kernel::KScheduler& scheduler; Core::CPUInterrupts& interrupts; - std::unique_ptr<Common::SpinLock> guard; + std::unique_ptr<std::mutex> guard; std::unique_ptr<Core::ARM_Interface> arm_interface; }; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index b2cec2253..9d3e0a658 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -16,6 +16,7 @@ #include "core/hle/kernel/kernel.h" #include "core/hle/service/hid/errors.h" #include "core/hle/service/hid/hid.h" +#include "core/hle/service/hid/hidbus.h" #include "core/hle/service/hid/irs.h" #include "core/hle/service/hid/xcd.h" #include "core/memory.h" @@ -2128,32 +2129,6 @@ public: } }; -class HidBus final : public ServiceFramework<HidBus> { -public: - explicit HidBus(Core::System& system_) : ServiceFramework{system_, "hidbus"} { - // clang-format off - static const FunctionInfo functions[] = { - {1, nullptr, "GetBusHandle"}, - {2, nullptr, "IsExternalDeviceConnected"}, - {3, nullptr, "Initialize"}, - {4, nullptr, "Finalize"}, - {5, nullptr, "EnableExternalDevice"}, - {6, nullptr, "GetExternalDeviceId"}, - {7, nullptr, "SendCommandAsync"}, - {8, nullptr, "GetSendCommandAsynceResult"}, - {9, nullptr, "SetEventForSendCommandAsycResult"}, - {10, nullptr, "GetSharedMemoryHandle"}, - {11, nullptr, "EnableJoyPollingReceiveMode"}, - {12, nullptr, "DisableJoyPollingReceiveMode"}, - {13, nullptr, "GetPollingData"}, - {14, nullptr, "SetStatusManagerType"}, - }; - // clang-format on - - RegisterHandlers(functions); - } -}; - void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { std::make_shared<Hid>(system)->InstallAsService(service_manager); std::make_shared<HidBus>(system)->InstallAsService(service_manager); diff --git a/src/core/hle/service/hid/hidbus.cpp b/src/core/hle/service/hid/hidbus.cpp new file mode 100644 index 000000000..af7662a15 --- /dev/null +++ b/src/core/hle/service/hid/hidbus.cpp @@ -0,0 +1,531 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "common/settings.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/hid/hid_types.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/kernel/k_shared_memory.h" +#include "core/hle/kernel/k_transfer_memory.h" +#include "core/hle/service/hid/hidbus.h" +#include "core/hle/service/hid/hidbus/ringcon.h" +#include "core/hle/service/hid/hidbus/starlink.h" +#include "core/hle/service/hid/hidbus/stubbed.h" +#include "core/hle/service/service.h" +#include "core/memory.h" + +namespace Service::HID { +// (15ms, 66Hz) +constexpr auto hidbus_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000}; + +HidBus::HidBus(Core::System& system_) + : ServiceFramework{system_, "hidbus"}, service_context{system_, service_name} { + + // clang-format off + static const FunctionInfo functions[] = { + {1, &HidBus::GetBusHandle, "GetBusHandle"}, + {2, &HidBus::IsExternalDeviceConnected, "IsExternalDeviceConnected"}, + {3, &HidBus::Initialize, "Initialize"}, + {4, &HidBus::Finalize, "Finalize"}, + {5, &HidBus::EnableExternalDevice, "EnableExternalDevice"}, + {6, &HidBus::GetExternalDeviceId, "GetExternalDeviceId"}, + {7, &HidBus::SendCommandAsync, "SendCommandAsync"}, + {8, &HidBus::GetSendCommandAsynceResult, "GetSendCommandAsynceResult"}, + {9, &HidBus::SetEventForSendCommandAsycResult, "SetEventForSendCommandAsycResult"}, + {10, &HidBus::GetSharedMemoryHandle, "GetSharedMemoryHandle"}, + {11, &HidBus::EnableJoyPollingReceiveMode, "EnableJoyPollingReceiveMode"}, + {12, &HidBus::DisableJoyPollingReceiveMode, "DisableJoyPollingReceiveMode"}, + {13, nullptr, "GetPollingData"}, + {14, &HidBus::SetStatusManagerType, "SetStatusManagerType"}, + }; + // clang-format on + + RegisterHandlers(functions); + + // Register update callbacks + hidbus_update_event = Core::Timing::CreateEvent( + "Hidbus::UpdateCallback", + [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + const auto guard = LockService(); + UpdateHidbus(user_data, ns_late); + }); + + system_.CoreTiming().ScheduleEvent(hidbus_update_ns, hidbus_update_event); +} + +HidBus::~HidBus() { + system.CoreTiming().UnscheduleEvent(hidbus_update_event, 0); +} + +void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + auto& core_timing = system.CoreTiming(); + + if (is_hidbus_enabled) { + for (std::size_t i = 0; i < devices.size(); ++i) { + if (!devices[i].is_device_initializated) { + continue; + } + auto& device = devices[i].device; + device->OnUpdate(); + auto& cur_entry = hidbus_status.entries[devices[i].handle.internal_index]; + cur_entry.is_polling_mode = device->IsPollingMode(); + cur_entry.polling_mode = device->GetPollingMode(); + cur_entry.is_enabled = device->IsEnabled(); + + u8* shared_memory = system.Kernel().GetHidBusSharedMem().GetPointer(); + std::memcpy(shared_memory + (i * sizeof(HidbusStatusManagerEntry)), &hidbus_status, + sizeof(HidbusStatusManagerEntry)); + } + } + + // If ns_late is higher than the update rate ignore the delay + if (ns_late > hidbus_update_ns) { + ns_late = {}; + } + + core_timing.ScheduleEvent(hidbus_update_ns - ns_late, hidbus_update_event); +} + +std::optional<std::size_t> HidBus::GetDeviceIndexFromHandle(BusHandle handle) const { + for (std::size_t i = 0; i < devices.size(); ++i) { + const auto& device_handle = devices[i].handle; + if (handle.abstracted_pad_id == device_handle.abstracted_pad_id && + handle.internal_index == device_handle.internal_index && + handle.player_number == device_handle.player_number && + handle.bus_type == device_handle.bus_type && + handle.is_valid == device_handle.is_valid) { + return i; + } + } + return std::nullopt; +} + +void HidBus::GetBusHandle(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Core::HID::NpadIdType npad_id; + INSERT_PADDING_WORDS_NOINIT(1); + BusType bus_type; + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_INFO(Service_HID, "called, npad_id={}, bus_type={}, applet_resource_user_id={}", + parameters.npad_id, parameters.bus_type, parameters.applet_resource_user_id); + + bool is_handle_found = 0; + std::size_t handle_index = 0; + + for (std::size_t i = 0; i < devices.size(); i++) { + const auto& handle = devices[i].handle; + if (!handle.is_valid) { + continue; + } + if (static_cast<Core::HID::NpadIdType>(handle.player_number) == parameters.npad_id && + handle.bus_type == parameters.bus_type) { + is_handle_found = true; + handle_index = i; + break; + } + } + + // Handle not found. Create a new one + if (!is_handle_found) { + for (std::size_t i = 0; i < devices.size(); i++) { + if (devices[i].handle.is_valid) { + continue; + } + devices[i].handle = { + .abstracted_pad_id = static_cast<u8>(i), + .internal_index = static_cast<u8>(i), + .player_number = static_cast<u8>(parameters.npad_id), + .bus_type = parameters.bus_type, + .is_valid = true, + }; + handle_index = i; + break; + } + } + + struct OutData { + bool is_valid; + INSERT_PADDING_BYTES(7); + BusHandle handle; + }; + static_assert(sizeof(OutData) == 0x10, "OutData has incorrect size."); + + const OutData out_data{ + .is_valid = true, + .handle = devices[handle_index].handle, + }; + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.PushRaw(out_data); +} + +void HidBus::IsExternalDeviceConnected(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + + LOG_INFO(Service_HID, + "Called, abstracted_pad_id={}, bus_type={}, internal_index={}, " + "player_number={}, is_valid={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + const auto& device = devices[device_index.value()].device; + const bool is_attached = device->IsDeviceActivated(); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(is_attached); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +} + +void HidBus::Initialize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_INFO(Service_HID, + "called, abstracted_pad_id={} bus_type={} internal_index={} " + "player_number={} is_valid={}, applet_resource_user_id={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid, applet_resource_user_id); + + is_hidbus_enabled = true; + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + const auto entry_index = devices[device_index.value()].handle.internal_index; + auto& cur_entry = hidbus_status.entries[entry_index]; + + if (bus_handle_.internal_index == 0 && Settings::values.enable_ring_controller) { + MakeDevice<RingController>(bus_handle_); + devices[device_index.value()].is_device_initializated = true; + devices[device_index.value()].device->ActivateDevice(); + cur_entry.is_in_focus = true; + cur_entry.is_connected = true; + cur_entry.is_connected_result = ResultSuccess; + cur_entry.is_enabled = false; + cur_entry.is_polling_mode = false; + } else { + MakeDevice<HidbusStubbed>(bus_handle_); + devices[device_index.value()].is_device_initializated = true; + cur_entry.is_in_focus = true; + cur_entry.is_connected = false; + cur_entry.is_connected_result = ResultSuccess; + cur_entry.is_enabled = false; + cur_entry.is_polling_mode = false; + } + + std::memcpy(system.Kernel().GetHidBusSharedMem().GetPointer(), &hidbus_status, + sizeof(hidbus_status)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +} + +void HidBus::Finalize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + const auto applet_resource_user_id{rp.Pop<u64>()}; + + LOG_INFO(Service_HID, + "called, abstracted_pad_id={}, bus_type={}, internal_index={}, " + "player_number={}, is_valid={}, applet_resource_user_id={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid, applet_resource_user_id); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + const auto entry_index = devices[device_index.value()].handle.internal_index; + auto& cur_entry = hidbus_status.entries[entry_index]; + auto& device = devices[device_index.value()].device; + devices[device_index.value()].is_device_initializated = false; + device->DeactivateDevice(); + + cur_entry.is_in_focus = true; + cur_entry.is_connected = false; + cur_entry.is_connected_result = ResultSuccess; + cur_entry.is_enabled = false; + cur_entry.is_polling_mode = false; + std::memcpy(system.Kernel().GetHidBusSharedMem().GetPointer(), &hidbus_status, + sizeof(hidbus_status)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +} + +void HidBus::EnableExternalDevice(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + bool enable; + INSERT_PADDING_BYTES_NOINIT(7); + BusHandle bus_handle; + u64 inval; + u64 applet_resource_user_id; + }; + static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size."); + + const auto parameters{rp.PopRaw<Parameters>()}; + + LOG_INFO(Service_HID, + "called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, " + "player_number={}, is_valid={}, inval={}, applet_resource_user_id{}", + parameters.enable, parameters.bus_handle.abstracted_pad_id, + parameters.bus_handle.bus_type, parameters.bus_handle.internal_index, + parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval, + parameters.applet_resource_user_id); + + const auto device_index = GetDeviceIndexFromHandle(parameters.bus_handle); + + if (device_index) { + auto& device = devices[device_index.value()].device; + device->Enable(parameters.enable); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +} + +void HidBus::GetExternalDeviceId(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + + LOG_INFO(Service_HID, + "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " + "is_valid={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + const auto& device = devices[device_index.value()].device; + u32 device_id = device->GetDeviceId(); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u32>(device_id); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +} + +void HidBus::SendCommandAsync(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto data = ctx.ReadBuffer(); + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + + LOG_DEBUG(Service_HID, + "called, data_size={}, abstracted_pad_id={}, bus_type={}, internal_index={}, " + "player_number={}, is_valid={}", + data.size(), bus_handle_.abstracted_pad_id, bus_handle_.bus_type, + bus_handle_.internal_index, bus_handle_.player_number, bus_handle_.is_valid); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + auto& device = devices[device_index.value()].device; + device->SetCommand(data); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +}; + +void HidBus::GetSendCommandAsynceResult(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + + LOG_DEBUG(Service_HID, + "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " + "is_valid={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + const auto& device = devices[device_index.value()].device; + const std::vector<u8> data = device->GetReply(); + const u64 data_size = ctx.WriteBuffer(data); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push<u64>(data_size); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +}; + +void HidBus::SetEventForSendCommandAsycResult(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + + LOG_INFO(Service_HID, + "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " + "is_valid={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + const auto& device = devices[device_index.value()].device; + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(device->GetSendCommandAsycEvent()); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +}; + +void HidBus::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(&system.Kernel().GetHidBusSharedMem()); +} + +void HidBus::EnableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto t_mem_size{rp.Pop<u32>()}; + const auto t_mem_handle{ctx.GetCopyHandle(0)}; + const auto polling_mode_{rp.PopEnum<JoyPollingMode>()}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + + ASSERT_MSG(t_mem_size == 0x1000, "t_mem_size is not 0x1000 bytes"); + + auto t_mem = + system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle); + + if (t_mem.IsNull()) { + LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + ASSERT_MSG(t_mem->GetSize() == 0x1000, "t_mem has incorrect size"); + + LOG_INFO(Service_HID, + "called, t_mem_handle=0x{:08X}, polling_mode={}, abstracted_pad_id={}, bus_type={}, " + "internal_index={}, player_number={}, is_valid={}", + t_mem_handle, polling_mode_, bus_handle_.abstracted_pad_id, bus_handle_.bus_type, + bus_handle_.internal_index, bus_handle_.player_number, bus_handle_.is_valid); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + auto& device = devices[device_index.value()].device; + device->SetPollingMode(polling_mode_); + device->SetTransferMemoryPointer(system.Memory().GetPointer(t_mem->GetSourceAddress())); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +} + +void HidBus::DisableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto bus_handle_{rp.PopRaw<BusHandle>()}; + + LOG_INFO(Service_HID, + "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " + "is_valid={}", + bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, + bus_handle_.player_number, bus_handle_.is_valid); + + const auto device_index = GetDeviceIndexFromHandle(bus_handle_); + + if (device_index) { + auto& device = devices[device_index.value()].device; + device->DisablePollingMode(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + return; + } + + LOG_ERROR(Service_HID, "Invalid handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; +} + +void HidBus::SetStatusManagerType(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto manager_type{rp.PopEnum<StatusManagerType>()}; + + LOG_WARNING(Service_HID, "(STUBBED) called, manager_type={}", manager_type); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus.h b/src/core/hle/service/hid/hidbus.h new file mode 100644 index 000000000..b10d5156a --- /dev/null +++ b/src/core/hle/service/hid/hidbus.h @@ -0,0 +1,131 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> + +#include "core/hle/service/hid/hidbus/hidbus_base.h" +#include "core/hle/service/kernel_helpers.h" +#include "core/hle/service/service.h" + +namespace Core::Timing { +struct EventType; +} // namespace Core::Timing + +namespace Core { +class System; +} // namespace Core + +namespace Service::HID { + +class HidBus final : public ServiceFramework<HidBus> { +public: + explicit HidBus(Core::System& system_); + ~HidBus() override; + +private: + static const std::size_t max_number_of_handles = 0x13; + + enum class HidBusDeviceId : std::size_t { + RingController = 0x20, + FamicomRight = 0x21, + Starlink = 0x28, + }; + + // This is nn::hidbus::detail::StatusManagerType + enum class StatusManagerType : u32 { + None, + Type16, + Type32, + }; + + // This is nn::hidbus::BusType + enum class BusType : u8 { + LeftJoyRail, + RightJoyRail, + InternalBus, // Lark microphone + + MaxBusType, + }; + + // This is nn::hidbus::BusHandle + struct BusHandle { + u32 abstracted_pad_id; + u8 internal_index; + u8 player_number; + BusType bus_type; + bool is_valid; + }; + static_assert(sizeof(BusHandle) == 0x8, "BusHandle is an invalid size"); + + // This is nn::hidbus::JoyPollingReceivedData + struct JoyPollingReceivedData { + std::array<u8, 0x30> data; + u64 out_size; + u64 sampling_number; + }; + static_assert(sizeof(JoyPollingReceivedData) == 0x40, + "JoyPollingReceivedData is an invalid size"); + + struct HidbusStatusManagerEntry { + u8 is_connected{}; + INSERT_PADDING_BYTES(0x3); + ResultCode is_connected_result{0}; + u8 is_enabled{}; + u8 is_in_focus{}; + u8 is_polling_mode{}; + u8 reserved{}; + JoyPollingMode polling_mode{}; + INSERT_PADDING_BYTES(0x70); // Unknown + }; + static_assert(sizeof(HidbusStatusManagerEntry) == 0x80, + "HidbusStatusManagerEntry is an invalid size"); + + struct HidbusStatusManager { + std::array<HidbusStatusManagerEntry, max_number_of_handles> entries{}; + INSERT_PADDING_BYTES(0x680); // Unused + }; + static_assert(sizeof(HidbusStatusManager) <= 0x1000, "HidbusStatusManager is an invalid size"); + + struct HidbusDevice { + bool is_device_initializated{}; + BusHandle handle{}; + std::unique_ptr<HidbusBase> device{nullptr}; + }; + + void GetBusHandle(Kernel::HLERequestContext& ctx); + void IsExternalDeviceConnected(Kernel::HLERequestContext& ctx); + void Initialize(Kernel::HLERequestContext& ctx); + void Finalize(Kernel::HLERequestContext& ctx); + void EnableExternalDevice(Kernel::HLERequestContext& ctx); + void GetExternalDeviceId(Kernel::HLERequestContext& ctx); + void SendCommandAsync(Kernel::HLERequestContext& ctx); + void GetSendCommandAsynceResult(Kernel::HLERequestContext& ctx); + void SetEventForSendCommandAsycResult(Kernel::HLERequestContext& ctx); + void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx); + void EnableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx); + void DisableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx); + void SetStatusManagerType(Kernel::HLERequestContext& ctx); + + void UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); + std::optional<std::size_t> GetDeviceIndexFromHandle(BusHandle handle) const; + + template <typename T> + void MakeDevice(BusHandle handle) { + const auto device_index = GetDeviceIndexFromHandle(handle); + if (device_index) { + devices[device_index.value()].device = + std::make_unique<T>(system.HIDCore(), service_context); + } + } + + bool is_hidbus_enabled{false}; + HidbusStatusManager hidbus_status{}; + std::array<HidbusDevice, max_number_of_handles> devices{}; + std::shared_ptr<Core::Timing::EventType> hidbus_update_event; + KernelHelpers::ServiceContext service_context; +}; + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.cpp b/src/core/hle/service/hid/hidbus/hidbus_base.cpp new file mode 100644 index 000000000..09bff10e5 --- /dev/null +++ b/src/core/hle/service/hid/hidbus/hidbus_base.cpp @@ -0,0 +1,72 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hid/hid_core.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/service/hid/hidbus/hidbus_base.h" +#include "core/hle/service/kernel_helpers.h" + +namespace Service::HID { + +HidbusBase::HidbusBase(KernelHelpers::ServiceContext& service_context_) + : service_context(service_context_) { + send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent"); +} +HidbusBase::~HidbusBase() = default; + +void HidbusBase::ActivateDevice() { + if (is_activated) { + return; + } + is_activated = true; + OnInit(); +} + +void HidbusBase::DeactivateDevice() { + if (is_activated) { + OnRelease(); + } + is_activated = false; +} + +bool HidbusBase::IsDeviceActivated() const { + return is_activated; +} + +void HidbusBase::Enable(bool enable) { + device_enabled = enable; +} + +bool HidbusBase::IsEnabled() const { + return device_enabled; +} + +bool HidbusBase::IsPollingMode() const { + return polling_mode_enabled; +} + +JoyPollingMode HidbusBase::GetPollingMode() const { + return polling_mode; +} + +void HidbusBase::SetPollingMode(JoyPollingMode mode) { + polling_mode = mode; + polling_mode_enabled = true; +} + +void HidbusBase::DisablePollingMode() { + polling_mode_enabled = false; +} + +void HidbusBase::SetTransferMemoryPointer(u8* t_mem) { + is_transfer_memory_set = true; + transfer_memory = t_mem; +} + +Kernel::KReadableEvent& HidbusBase::GetSendCommandAsycEvent() const { + return send_command_async_event->GetReadableEvent(); +} + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.h b/src/core/hle/service/hid/hidbus/hidbus_base.h new file mode 100644 index 000000000..13d073a3d --- /dev/null +++ b/src/core/hle/service/hid/hidbus/hidbus_base.h @@ -0,0 +1,179 @@ +// Copyright 2021 yuzu Emulator Project +// 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/hle/result.h" + +namespace Kernel { +class KEvent; +class KReadableEvent; +} // namespace Kernel + +namespace Service::KernelHelpers { +class ServiceContext; +} + +namespace Service::HID { + +// This is nn::hidbus::JoyPollingMode +enum class JoyPollingMode : u32 { + SixAxisSensorDisable, + SixAxisSensorEnable, + ButtonOnly, +}; + +struct DataAccessorHeader { + ResultCode result{ResultUnknown}; + INSERT_PADDING_WORDS(0x1); + std::array<u8, 0x18> unused{}; + u64 latest_entry{}; + u64 total_entries{}; +}; +static_assert(sizeof(DataAccessorHeader) == 0x30, "DataAccessorHeader is an invalid size"); + +struct JoyDisableSixAxisPollingData { + std::array<u8, 0x26> data; + u8 out_size; + INSERT_PADDING_BYTES(0x1); + u64 sampling_number; +}; +static_assert(sizeof(JoyDisableSixAxisPollingData) == 0x30, + "JoyDisableSixAxisPollingData is an invalid size"); + +struct JoyEnableSixAxisPollingData { + std::array<u8, 0x8> data; + u8 out_size; + INSERT_PADDING_BYTES(0x7); + u64 sampling_number; +}; +static_assert(sizeof(JoyEnableSixAxisPollingData) == 0x18, + "JoyEnableSixAxisPollingData is an invalid size"); + +struct JoyButtonOnlyPollingData { + std::array<u8, 0x2c> data; + u8 out_size; + INSERT_PADDING_BYTES(0x3); + u64 sampling_number; +}; +static_assert(sizeof(JoyButtonOnlyPollingData) == 0x38, + "JoyButtonOnlyPollingData is an invalid size"); + +struct JoyDisableSixAxisPollingEntry { + u64 sampling_number; + JoyDisableSixAxisPollingData polling_data; +}; +static_assert(sizeof(JoyDisableSixAxisPollingEntry) == 0x38, + "JoyDisableSixAxisPollingEntry is an invalid size"); + +struct JoyEnableSixAxisPollingEntry { + u64 sampling_number; + JoyEnableSixAxisPollingData polling_data; +}; +static_assert(sizeof(JoyEnableSixAxisPollingEntry) == 0x20, + "JoyEnableSixAxisPollingEntry is an invalid size"); + +struct JoyButtonOnlyPollingEntry { + u64 sampling_number; + JoyButtonOnlyPollingData polling_data; +}; +static_assert(sizeof(JoyButtonOnlyPollingEntry) == 0x40, + "JoyButtonOnlyPollingEntry is an invalid size"); + +struct JoyDisableSixAxisDataAccessor { + DataAccessorHeader header{}; + std::array<JoyDisableSixAxisPollingEntry, 0xb> entries{}; +}; +static_assert(sizeof(JoyDisableSixAxisDataAccessor) == 0x298, + "JoyDisableSixAxisDataAccessor is an invalid size"); + +struct JoyEnableSixAxisDataAccessor { + DataAccessorHeader header{}; + std::array<JoyEnableSixAxisPollingEntry, 0xb> entries{}; +}; +static_assert(sizeof(JoyEnableSixAxisDataAccessor) == 0x190, + "JoyEnableSixAxisDataAccessor is an invalid size"); + +struct ButtonOnlyPollingDataAccessor { + DataAccessorHeader header; + std::array<JoyButtonOnlyPollingEntry, 0xb> entries; +}; +static_assert(sizeof(ButtonOnlyPollingDataAccessor) == 0x2F0, + "ButtonOnlyPollingDataAccessor is an invalid size"); + +class HidbusBase { +public: + explicit HidbusBase(KernelHelpers::ServiceContext& service_context_); + virtual ~HidbusBase(); + + void ActivateDevice(); + + void DeactivateDevice(); + + bool IsDeviceActivated() const; + + // Enables/disables the device + void Enable(bool enable); + + // returns true if device is enabled + bool IsEnabled() const; + + // returns true if polling mode is enabled + bool IsPollingMode() const; + + // returns polling mode + JoyPollingMode GetPollingMode() const; + + // Sets and enables JoyPollingMode + void SetPollingMode(JoyPollingMode mode); + + // Disables JoyPollingMode + void DisablePollingMode(); + + // Called on EnableJoyPollingReceiveMode + void SetTransferMemoryPointer(u8* t_mem); + + Kernel::KReadableEvent& GetSendCommandAsycEvent() const; + + virtual void OnInit() {} + + virtual void OnRelease() {} + + // Updates device transfer memory + virtual void OnUpdate() {} + + // Returns the device ID of the joycon + virtual u8 GetDeviceId() const { + return {}; + } + + // Assigns a command from data + virtual bool SetCommand(const std::vector<u8>& data) { + return {}; + } + + // Returns a reply from a command + virtual std::vector<u8> GetReply() const { + return {}; + } + +protected: + bool is_activated{}; + bool device_enabled{}; + bool polling_mode_enabled{}; + JoyPollingMode polling_mode = {}; + // TODO(German77): All data accessors need to be replaced with a ring lifo object + JoyDisableSixAxisDataAccessor disable_sixaxis_data{}; + JoyEnableSixAxisDataAccessor enable_sixaxis_data{}; + ButtonOnlyPollingDataAccessor button_only_data{}; + + u8* transfer_memory{nullptr}; + bool is_transfer_memory_set{}; + + Kernel::KEvent* send_command_async_event; + KernelHelpers::ServiceContext& service_context; +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/ringcon.cpp b/src/core/hle/service/hid/hidbus/ringcon.cpp new file mode 100644 index 000000000..5ec3cc83c --- /dev/null +++ b/src/core/hle/service/hid/hidbus/ringcon.cpp @@ -0,0 +1,286 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hid/emulated_devices.h" +#include "core/hid/hid_core.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_readable_event.h" +#include "core/hle/service/hid/hidbus/ringcon.h" + +namespace Service::HID { + +RingController::RingController(Core::HID::HIDCore& hid_core_, + KernelHelpers::ServiceContext& service_context_) + : HidbusBase(service_context_) { + input = hid_core_.GetEmulatedDevices(); +} + +RingController::~RingController() = default; + +void RingController::OnInit() { + return; +} + +void RingController::OnRelease() { + return; +}; + +void RingController::OnUpdate() { + if (!is_activated) { + return; + } + + if (!device_enabled) { + return; + } + + if (!polling_mode_enabled || !is_transfer_memory_set) { + return; + } + + // TODO: Increment multitasking counters from motion and sensor data + + switch (polling_mode) { + case JoyPollingMode::SixAxisSensorEnable: { + enable_sixaxis_data.header.total_entries = 10; + enable_sixaxis_data.header.result = ResultSuccess; + const auto& last_entry = + enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry]; + + enable_sixaxis_data.header.latest_entry = + (enable_sixaxis_data.header.latest_entry + 1) % 10; + auto& curr_entry = enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry]; + + curr_entry.sampling_number = last_entry.sampling_number + 1; + curr_entry.polling_data.sampling_number = curr_entry.sampling_number; + + const RingConData ringcon_value = GetSensorValue(); + curr_entry.polling_data.out_size = sizeof(ringcon_value); + std::memcpy(curr_entry.polling_data.data.data(), &ringcon_value, sizeof(ringcon_value)); + + std::memcpy(transfer_memory, &enable_sixaxis_data, sizeof(enable_sixaxis_data)); + break; + } + default: + LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode); + break; + } +} + +RingController::RingConData RingController::GetSensorValue() const { + RingConData ringcon_sensor_value{ + .status = DataValid::Valid, + .data = 0, + }; + + const f32 force_value = input->GetRingSensorForce().force * range; + ringcon_sensor_value.data = static_cast<s16>(force_value) + idle_value; + + return ringcon_sensor_value; +} + +u8 RingController::GetDeviceId() const { + return device_id; +} + +std::vector<u8> RingController::GetReply() const { + const RingConCommands current_command = command; + + switch (current_command) { + case RingConCommands::GetFirmwareVersion: + return GetFirmwareVersionReply(); + case RingConCommands::ReadId: + return GetReadIdReply(); + case RingConCommands::c20105: + return GetC020105Reply(); + case RingConCommands::ReadUnkCal: + return GetReadUnkCalReply(); + case RingConCommands::ReadFactoryCal: + return GetReadFactoryCalReply(); + case RingConCommands::ReadUserCal: + return GetReadUserCalReply(); + case RingConCommands::ReadRepCount: + return GetReadRepCountReply(); + case RingConCommands::ReadTotalPushCount: + return GetReadTotalPushCountReply(); + case RingConCommands::ResetRepCount: + return GetResetRepCountReply(); + case RingConCommands::SaveCalData: + return GetSaveDataReply(); + default: + return GetErrorReply(); + } +} + +bool RingController::SetCommand(const std::vector<u8>& data) { + if (data.size() < 4) { + LOG_ERROR(Service_HID, "Command size not supported {}", data.size()); + command = RingConCommands::Error; + return false; + } + + std::memcpy(&command, data.data(), sizeof(RingConCommands)); + + switch (command) { + case RingConCommands::GetFirmwareVersion: + case RingConCommands::ReadId: + case RingConCommands::c20105: + case RingConCommands::ReadUnkCal: + case RingConCommands::ReadFactoryCal: + case RingConCommands::ReadUserCal: + case RingConCommands::ReadRepCount: + case RingConCommands::ReadTotalPushCount: + ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes"); + send_command_async_event->GetWritableEvent().Signal(); + return true; + case RingConCommands::ResetRepCount: + ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes"); + total_rep_count = 0; + send_command_async_event->GetWritableEvent().Signal(); + return true; + case RingConCommands::SaveCalData: { + ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes"); + + SaveCalData save_info{}; + std::memcpy(&save_info, data.data(), sizeof(SaveCalData)); + user_calibration = save_info.calibration; + send_command_async_event->GetWritableEvent().Signal(); + return true; + } + default: + LOG_ERROR(Service_HID, "Command not implemented {}", command); + command = RingConCommands::Error; + // Signal a reply to avoid softlocking the game + send_command_async_event->GetWritableEvent().Signal(); + return false; + } +} + +std::vector<u8> RingController::GetFirmwareVersionReply() const { + const FirmwareVersionReply reply{ + .status = DataValid::Valid, + .firmware = version, + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetReadIdReply() const { + // The values are hardcoded from a real joycon + const ReadIdReply reply{ + .status = DataValid::Valid, + .id_l_x0 = 8, + .id_l_x0_2 = 41, + .id_l_x4 = 22294, + .id_h_x0 = 19777, + .id_h_x0_2 = 13621, + .id_h_x4 = 8245, + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetC020105Reply() const { + const Cmd020105Reply reply{ + .status = DataValid::Valid, + .data = 1, + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetReadUnkCalReply() const { + const ReadUnkCalReply reply{ + .status = DataValid::Valid, + .data = 0, + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetReadFactoryCalReply() const { + const ReadFactoryCalReply reply{ + .status = DataValid::Valid, + .calibration = factory_calibration, + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetReadUserCalReply() const { + const ReadUserCalReply reply{ + .status = DataValid::Valid, + .calibration = user_calibration, + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetReadRepCountReply() const { + const GetThreeByteReply reply{ + .status = DataValid::Valid, + .data = {total_rep_count, 0, 0}, + .crc = GetCrcValue({total_rep_count, 0, 0, 0}), + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetReadTotalPushCountReply() const { + const GetThreeByteReply reply{ + .status = DataValid::Valid, + .data = {total_push_count, 0, 0}, + .crc = GetCrcValue({total_push_count, 0, 0, 0}), + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetResetRepCountReply() const { + return GetReadRepCountReply(); +} + +std::vector<u8> RingController::GetSaveDataReply() const { + const StatusReply reply{ + .status = DataValid::Valid, + }; + + return GetDataVector(reply); +} + +std::vector<u8> RingController::GetErrorReply() const { + const ErrorReply reply{ + .status = DataValid::BadCRC, + }; + + return GetDataVector(reply); +} + +u8 RingController::GetCrcValue(const std::vector<u8>& data) const { + u8 crc = 0; + for (std::size_t index = 0; index < data.size(); index++) { + for (u8 i = 0x80; i > 0; i >>= 1) { + bool bit = (crc & 0x80) != 0; + if ((data[index] & i) != 0) { + bit = !bit; + } + crc <<= 1; + if (bit) { + crc ^= 0x8d; + } + } + } + return crc; +} + +template <typename T> +std::vector<u8> RingController::GetDataVector(const T& reply) const { + static_assert(std::is_trivially_copyable_v<T>); + std::vector<u8> data; + data.resize(sizeof(reply)); + std::memcpy(data.data(), &reply, sizeof(reply)); + return data; +} + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/ringcon.h b/src/core/hle/service/hid/hidbus/ringcon.h new file mode 100644 index 000000000..2dbc6150e --- /dev/null +++ b/src/core/hle/service/hid/hidbus/ringcon.h @@ -0,0 +1,254 @@ +// Copyright 2021 yuzu Emulator Project +// 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/hle/service/hid/hidbus/hidbus_base.h" + +namespace Core::HID { +class EmulatedDevices; +} // namespace Core::HID + +namespace Service::HID { + +class RingController final : public HidbusBase { +public: + explicit RingController(Core::HID::HIDCore& hid_core_, + KernelHelpers::ServiceContext& service_context_); + ~RingController() override; + + void OnInit() override; + + void OnRelease() override; + + // Updates ringcon transfer memory + void OnUpdate() override; + + // Returns the device ID of the joycon + u8 GetDeviceId() const override; + + // Assigns a command from data + bool SetCommand(const std::vector<u8>& data) override; + + // Returns a reply from a command + std::vector<u8> GetReply() const override; + +private: + // These values are obtained from a real ring controller + static constexpr s16 idle_value = 2280; + static constexpr s16 idle_deadzone = 120; + static constexpr s16 range = 2500; + + // Most missing command names are leftovers from other firmware versions + enum class RingConCommands : u32 { + GetFirmwareVersion = 0x00020000, + ReadId = 0x00020100, + JoyPolling = 0x00020101, + Unknown1 = 0x00020104, + c20105 = 0x00020105, + Unknown2 = 0x00020204, + Unknown3 = 0x00020304, + Unknown4 = 0x00020404, + ReadUnkCal = 0x00020504, + ReadFactoryCal = 0x00020A04, + Unknown5 = 0x00021104, + Unknown6 = 0x00021204, + Unknown7 = 0x00021304, + ReadUserCal = 0x00021A04, + ReadRepCount = 0x00023104, + ReadTotalPushCount = 0x00023204, + ResetRepCount = 0x04013104, + Unknown8 = 0x04011104, + Unknown9 = 0x04011204, + Unknown10 = 0x04011304, + SaveCalData = 0x10011A04, + Error = 0xFFFFFFFF, + }; + + enum class DataValid : u32 { + Valid, + BadCRC, + Cal, + }; + + struct FirmwareVersion { + u8 sub; + u8 main; + }; + static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size"); + + struct FactoryCalibration { + s32_le os_max; + s32_le hk_max; + s32_le zero_min; + s32_le zero_max; + }; + static_assert(sizeof(FactoryCalibration) == 0x10, "FactoryCalibration is an invalid size"); + + struct CalibrationValue { + s16 value; + u16 crc; + }; + static_assert(sizeof(CalibrationValue) == 0x4, "CalibrationValue is an invalid size"); + + struct UserCalibration { + CalibrationValue os_max; + CalibrationValue hk_max; + CalibrationValue zero; + }; + static_assert(sizeof(UserCalibration) == 0xC, "UserCalibration is an invalid size"); + + struct SaveCalData { + RingConCommands command; + UserCalibration calibration; + INSERT_PADDING_BYTES_NOINIT(4); + }; + static_assert(sizeof(SaveCalData) == 0x14, "SaveCalData is an invalid size"); + static_assert(std::is_trivially_copyable_v<SaveCalData>, + "SaveCalData must be trivially copyable"); + + struct FirmwareVersionReply { + DataValid status; + FirmwareVersion firmware; + INSERT_PADDING_BYTES(0x2); + }; + static_assert(sizeof(FirmwareVersionReply) == 0x8, "FirmwareVersionReply is an invalid size"); + + struct Cmd020105Reply { + DataValid status; + u8 data; + INSERT_PADDING_BYTES(0x3); + }; + static_assert(sizeof(Cmd020105Reply) == 0x8, "Cmd020105Reply is an invalid size"); + + struct StatusReply { + DataValid status; + }; + static_assert(sizeof(StatusReply) == 0x4, "StatusReply is an invalid size"); + + struct GetThreeByteReply { + DataValid status; + std::array<u8, 3> data; + u8 crc; + }; + static_assert(sizeof(GetThreeByteReply) == 0x8, "GetThreeByteReply is an invalid size"); + + struct ReadUnkCalReply { + DataValid status; + u16 data; + INSERT_PADDING_BYTES(0x2); + }; + static_assert(sizeof(ReadUnkCalReply) == 0x8, "ReadUnkCalReply is an invalid size"); + + struct ReadFactoryCalReply { + DataValid status; + FactoryCalibration calibration; + }; + static_assert(sizeof(ReadFactoryCalReply) == 0x14, "ReadFactoryCalReply is an invalid size"); + + struct ReadUserCalReply { + DataValid status; + UserCalibration calibration; + INSERT_PADDING_BYTES(0x4); + }; + static_assert(sizeof(ReadUserCalReply) == 0x14, "ReadUserCalReply is an invalid size"); + + struct ReadIdReply { + DataValid status; + u16 id_l_x0; + u16 id_l_x0_2; + u16 id_l_x4; + u16 id_h_x0; + u16 id_h_x0_2; + u16 id_h_x4; + }; + static_assert(sizeof(ReadIdReply) == 0x10, "ReadIdReply is an invalid size"); + + struct ErrorReply { + DataValid status; + INSERT_PADDING_BYTES(0x3); + }; + static_assert(sizeof(ErrorReply) == 0x8, "ErrorReply is an invalid size"); + + struct RingConData { + DataValid status; + s16_le data; + INSERT_PADDING_BYTES(0x2); + }; + static_assert(sizeof(RingConData) == 0x8, "RingConData is an invalid size"); + + // Returns RingConData struct with pressure sensor values + RingConData GetSensorValue() const; + + // Returns 8 byte reply with firmware version + std::vector<u8> GetFirmwareVersionReply() const; + + // Returns 16 byte reply with ID values + std::vector<u8> GetReadIdReply() const; + + // (STUBBED) Returns 8 byte reply + std::vector<u8> GetC020105Reply() const; + + // (STUBBED) Returns 8 byte empty reply + std::vector<u8> GetReadUnkCalReply() const; + + // Returns 20 byte reply with factory calibration values + std::vector<u8> GetReadFactoryCalReply() const; + + // Returns 20 byte reply with user calibration values + std::vector<u8> GetReadUserCalReply() const; + + // Returns 8 byte reply + std::vector<u8> GetReadRepCountReply() const; + + // Returns 8 byte reply + std::vector<u8> GetReadTotalPushCountReply() const; + + // Returns 8 byte reply + std::vector<u8> GetResetRepCountReply() const; + + // Returns 4 byte save data reply + std::vector<u8> GetSaveDataReply() const; + + // Returns 8 byte error reply + std::vector<u8> GetErrorReply() const; + + // Returns 8 bit redundancy check from provided data + u8 GetCrcValue(const std::vector<u8>& data) const; + + // Converts structs to an u8 vector equivalent + template <typename T> + std::vector<u8> GetDataVector(const T& reply) const; + + RingConCommands command{RingConCommands::Error}; + + // These counters are used in multitasking mode while the switch is sleeping + // Total steps taken + u8 total_rep_count = 0; + // Total times the ring was pushed + u8 total_push_count = 0; + + const u8 device_id = 0x20; + const FirmwareVersion version = { + .sub = 0x0, + .main = 0x2c, + }; + const FactoryCalibration factory_calibration = { + .os_max = idle_value + range + idle_deadzone, + .hk_max = idle_value - range - idle_deadzone, + .zero_min = idle_value - idle_deadzone, + .zero_max = idle_value + idle_deadzone, + }; + UserCalibration user_calibration = { + .os_max = {.value = range, .crc = 228}, + .hk_max = {.value = -range, .crc = 239}, + .zero = {.value = idle_value, .crc = 225}, + }; + + Core::HID::EmulatedDevices* input; +}; +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/starlink.cpp b/src/core/hle/service/hid/hidbus/starlink.cpp new file mode 100644 index 000000000..3175c48da --- /dev/null +++ b/src/core/hle/service/hid/hidbus/starlink.cpp @@ -0,0 +1,51 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hle/service/hid/hidbus/starlink.h" + +namespace Service::HID { +constexpr u8 DEVICE_ID = 0x28; + +Starlink::Starlink(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_) + : HidbusBase(service_context_) {} +Starlink::~Starlink() = default; + +void Starlink::OnInit() { + return; +} + +void Starlink::OnRelease() { + return; +}; + +void Starlink::OnUpdate() { + if (!is_activated) { + return; + } + if (!device_enabled) { + return; + } + if (!polling_mode_enabled || !is_transfer_memory_set) { + return; + } + + LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode); +} + +u8 Starlink::GetDeviceId() const { + return DEVICE_ID; +} + +std::vector<u8> Starlink::GetReply() const { + return {}; +} + +bool Starlink::SetCommand(const std::vector<u8>& data) { + LOG_ERROR(Service_HID, "Command not implemented"); + return false; +} + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/starlink.h b/src/core/hle/service/hid/hidbus/starlink.h new file mode 100644 index 000000000..79770b68e --- /dev/null +++ b/src/core/hle/service/hid/hidbus/starlink.h @@ -0,0 +1,39 @@ +// Copyright 2021 yuzu Emulator Project +// 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/hidbus/hidbus_base.h" + +namespace Core::HID { +class EmulatedController; +} // namespace Core::HID + +namespace Service::HID { + +class Starlink final : public HidbusBase { +public: + explicit Starlink(Core::HID::HIDCore& hid_core_, + KernelHelpers::ServiceContext& service_context_); + ~Starlink() override; + + void OnInit() override; + + void OnRelease() override; + + // Updates ringcon transfer memory + void OnUpdate() override; + + // Returns the device ID of the joycon + u8 GetDeviceId() const override; + + // Assigns a command from data + bool SetCommand(const std::vector<u8>& data) override; + + // Returns a reply from a command + std::vector<u8> GetReply() const override; +}; + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/stubbed.cpp b/src/core/hle/service/hid/hidbus/stubbed.cpp new file mode 100644 index 000000000..5474657be --- /dev/null +++ b/src/core/hle/service/hid/hidbus/stubbed.cpp @@ -0,0 +1,52 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hle/service/hid/hidbus/stubbed.h" + +namespace Service::HID { +constexpr u8 DEVICE_ID = 0xFF; + +HidbusStubbed::HidbusStubbed(Core::HID::HIDCore& hid_core_, + KernelHelpers::ServiceContext& service_context_) + : HidbusBase(service_context_) {} +HidbusStubbed::~HidbusStubbed() = default; + +void HidbusStubbed::OnInit() { + return; +} + +void HidbusStubbed::OnRelease() { + return; +}; + +void HidbusStubbed::OnUpdate() { + if (!is_activated) { + return; + } + if (!device_enabled) { + return; + } + if (!polling_mode_enabled || !is_transfer_memory_set) { + return; + } + + LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode); +} + +u8 HidbusStubbed::GetDeviceId() const { + return DEVICE_ID; +} + +std::vector<u8> HidbusStubbed::GetReply() const { + return {}; +} + +bool HidbusStubbed::SetCommand(const std::vector<u8>& data) { + LOG_ERROR(Service_HID, "Command not implemented"); + return false; +} + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/hidbus/stubbed.h b/src/core/hle/service/hid/hidbus/stubbed.h new file mode 100644 index 000000000..40acdfe8f --- /dev/null +++ b/src/core/hle/service/hid/hidbus/stubbed.h @@ -0,0 +1,39 @@ +// Copyright 2021 yuzu Emulator Project +// 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/hidbus/hidbus_base.h" + +namespace Core::HID { +class EmulatedController; +} // namespace Core::HID + +namespace Service::HID { + +class HidbusStubbed final : public HidbusBase { +public: + explicit HidbusStubbed(Core::HID::HIDCore& hid_core_, + KernelHelpers::ServiceContext& service_context_); + ~HidbusStubbed() override; + + void OnInit() override; + + void OnRelease() override; + + // Updates ringcon transfer memory + void OnUpdate() override; + + // Returns the device ID of the joycon + u8 GetDeviceId() const override; + + // Assigns a command from data + bool SetCommand(const std::vector<u8>& data) override; + + // Returns a reply from a command + std::vector<u8> GetReply() const override; +}; + +} // namespace Service::HID diff --git a/src/core/hle/service/jit/jit.cpp b/src/core/hle/service/jit/jit.cpp index c8ebd2e3f..0f9e33ef6 100644 --- a/src/core/hle/service/jit/jit.cpp +++ b/src/core/hle/service/jit/jit.cpp @@ -2,27 +2,256 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/arm/symbols.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/k_code_memory.h" +#include "core/hle/kernel/k_transfer_memory.h" #include "core/hle/result.h" #include "core/hle/service/jit/jit.h" +#include "core/hle/service/jit/jit_context.h" #include "core/hle/service/service.h" +#include "core/memory.h" namespace Service::JIT { +struct CodeRange { + u64 offset; + u64 size; +}; + class IJitEnvironment final : public ServiceFramework<IJitEnvironment> { public: - explicit IJitEnvironment(Core::System& system_) : ServiceFramework{system_, "IJitEnvironment"} { + explicit IJitEnvironment(Core::System& system_, CodeRange user_rx, CodeRange user_ro) + : ServiceFramework{system_, "IJitEnvironment", ServiceThreadType::CreateNew}, + context{system_.Memory()} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "GenerateCode"}, - {1, nullptr, "Control"}, - {1000, nullptr, "LoadPlugin"}, - {1001, nullptr, "GetCodeAddress"}, + {0, &IJitEnvironment::GenerateCode, "GenerateCode"}, + {1, &IJitEnvironment::Control, "Control"}, + {1000, &IJitEnvironment::LoadPlugin, "LoadPlugin"}, + {1001, &IJitEnvironment::GetCodeAddress, "GetCodeAddress"}, }; // clang-format on RegisterHandlers(functions); + + // Identity map user code range into sysmodule context + configuration.user_ro_memory = user_ro; + configuration.user_rx_memory = user_rx; + configuration.sys_ro_memory = user_ro; + configuration.sys_rx_memory = user_rx; } + + void GenerateCode(Kernel::HLERequestContext& ctx) { + struct Parameters { + u32 data_size; + u64 command; + CodeRange cr1; + CodeRange cr2; + Struct32 data; + }; + + IPC::RequestParser rp{ctx}; + const auto parameters{rp.PopRaw<Parameters>()}; + std::vector<u8> input_buffer{ctx.CanReadBuffer() ? ctx.ReadBuffer() : std::vector<u8>()}; + std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0); + + const VAddr return_ptr{context.AddHeap(0u)}; + const VAddr cr1_in_ptr{context.AddHeap(parameters.cr1)}; + const VAddr cr2_in_ptr{context.AddHeap(parameters.cr2)}; + const VAddr cr1_out_ptr{ + context.AddHeap(CodeRange{.offset = parameters.cr1.offset, .size = 0})}; + const VAddr cr2_out_ptr{ + context.AddHeap(CodeRange{.offset = parameters.cr2.offset, .size = 0})}; + const VAddr input_ptr{context.AddHeap(input_buffer.data(), input_buffer.size())}; + const VAddr output_ptr{context.AddHeap(output_buffer.data(), output_buffer.size())}; + const VAddr data_ptr{context.AddHeap(parameters.data)}; + const VAddr configuration_ptr{context.AddHeap(configuration)}; + + context.CallFunction(callbacks.GenerateCode, return_ptr, cr1_out_ptr, cr2_out_ptr, + configuration_ptr, parameters.command, input_ptr, input_buffer.size(), + cr1_in_ptr, cr2_in_ptr, data_ptr, parameters.data_size, output_ptr, + output_buffer.size()); + + const s32 return_value{context.GetHeap<s32>(return_ptr)}; + + if (return_value == 0) { + system.InvalidateCpuInstructionCacheRange(configuration.user_rx_memory.offset, + configuration.user_rx_memory.size); + + if (ctx.CanWriteBuffer()) { + context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size()); + ctx.WriteBuffer(output_buffer.data(), output_buffer.size()); + } + const auto cr1_out{context.GetHeap<CodeRange>(cr1_out_ptr)}; + const auto cr2_out{context.GetHeap<CodeRange>(cr2_out_ptr)}; + + IPC::ResponseBuilder rb{ctx, 8}; + rb.Push(ResultSuccess); + rb.Push<u64>(return_value); + rb.PushRaw(cr1_out); + rb.PushRaw(cr2_out); + } else { + LOG_WARNING(Service_JIT, "plugin GenerateCode callback failed"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + } + }; + + void Control(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto command{rp.PopRaw<u64>()}; + const auto input_buffer{ctx.ReadBuffer()}; + std::vector<u8> output_buffer(ctx.CanWriteBuffer() ? ctx.GetWriteBufferSize() : 0); + + const VAddr return_ptr{context.AddHeap(0u)}; + const VAddr configuration_ptr{context.AddHeap(configuration)}; + const VAddr input_ptr{context.AddHeap(input_buffer.data(), input_buffer.size())}; + const VAddr output_ptr{context.AddHeap(output_buffer.data(), output_buffer.size())}; + const u64 wrapper_value{ + context.CallFunction(callbacks.Control, return_ptr, configuration_ptr, command, + input_ptr, input_buffer.size(), output_ptr, output_buffer.size())}; + const s32 return_value{context.GetHeap<s32>(return_ptr)}; + + if (wrapper_value == 0 && return_value == 0) { + if (ctx.CanWriteBuffer()) { + context.GetHeap(output_ptr, output_buffer.data(), output_buffer.size()); + ctx.WriteBuffer(output_buffer.data(), output_buffer.size()); + } + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(return_value); + } else { + LOG_WARNING(Service_JIT, "plugin Control callback failed"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + } + } + + void LoadPlugin(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto tmem_size{rp.PopRaw<u64>()}; + if (tmem_size == 0) { + LOG_ERROR(Service_JIT, "attempted to load plugin with empty transfer memory"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + const auto tmem_handle{ctx.GetCopyHandle(0)}; + auto tmem{system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( + tmem_handle)}; + if (tmem.IsNull()) { + LOG_ERROR(Service_JIT, "attempted to load plugin with invalid transfer memory handle"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + configuration.work_memory.offset = tmem->GetSourceAddress(); + configuration.work_memory.size = tmem_size; + + const auto nro_plugin{ctx.ReadBuffer(1)}; + auto symbols{Core::Symbols::GetSymbols(nro_plugin, true)}; + const auto GetSymbol{[&](std::string name) { return symbols[name].first; }}; + + callbacks = + GuestCallbacks{.rtld_fini = GetSymbol("_fini"), + .rtld_init = GetSymbol("_init"), + .Control = GetSymbol("nnjitpluginControl"), + .ResolveBasicSymbols = GetSymbol("nnjitpluginResolveBasicSymbols"), + .SetupDiagnostics = GetSymbol("nnjitpluginSetupDiagnostics"), + .Configure = GetSymbol("nnjitpluginConfigure"), + .GenerateCode = GetSymbol("nnjitpluginGenerateCode"), + .GetVersion = GetSymbol("nnjitpluginGetVersion"), + .Keeper = GetSymbol("nnjitpluginKeeper"), + .OnPrepared = GetSymbol("nnjitpluginOnPrepared")}; + + if (callbacks.GetVersion == 0 || callbacks.Configure == 0 || callbacks.GenerateCode == 0 || + callbacks.OnPrepared == 0) { + LOG_ERROR(Service_JIT, "plugin does not implement all necessary functionality"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + if (!context.LoadNRO(nro_plugin)) { + LOG_ERROR(Service_JIT, "failed to load plugin"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + context.MapProcessMemory(configuration.sys_ro_memory.offset, + configuration.sys_ro_memory.size); + context.MapProcessMemory(configuration.sys_rx_memory.offset, + configuration.sys_rx_memory.size); + context.MapProcessMemory(configuration.work_memory.offset, configuration.work_memory.size); + + if (callbacks.rtld_init != 0) { + context.CallFunction(callbacks.rtld_init); + } + + const auto version{context.CallFunction(callbacks.GetVersion)}; + if (version != 1) { + LOG_ERROR(Service_JIT, "unknown plugin version {}", version); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + const auto resolve{context.GetHelper("_resolve")}; + if (callbacks.ResolveBasicSymbols != 0) { + context.CallFunction(callbacks.ResolveBasicSymbols, resolve); + } + const auto resolve_ptr{context.AddHeap(resolve)}; + if (callbacks.SetupDiagnostics != 0) { + context.CallFunction(callbacks.SetupDiagnostics, 0u, resolve_ptr); + } + + context.CallFunction(callbacks.Configure, 0u); + const auto configuration_ptr{context.AddHeap(configuration)}; + context.CallFunction(callbacks.OnPrepared, configuration_ptr); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void GetCodeAddress(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.Push(configuration.user_rx_memory.offset); + rb.Push(configuration.user_ro_memory.offset); + } + +private: + using Struct32 = std::array<u8, 32>; + + struct GuestCallbacks { + VAddr rtld_fini; + VAddr rtld_init; + VAddr Control; + VAddr ResolveBasicSymbols; + VAddr SetupDiagnostics; + VAddr Configure; + VAddr GenerateCode; + VAddr GetVersion; + VAddr Keeper; + VAddr OnPrepared; + }; + + struct JITConfiguration { + CodeRange user_rx_memory; + CodeRange user_ro_memory; + CodeRange work_memory; + CodeRange sys_rx_memory; + CodeRange sys_ro_memory; + }; + + GuestCallbacks callbacks; + JITConfiguration configuration; + JITContext context; }; class JITU final : public ServiceFramework<JITU> { @@ -40,9 +269,59 @@ public: void CreateJitEnvironment(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_JIT, "called"); + struct Parameters { + u64 rx_size; + u64 ro_size; + }; + + IPC::RequestParser rp{ctx}; + const auto parameters{rp.PopRaw<Parameters>()}; + const auto executable_mem_handle{ctx.GetCopyHandle(1)}; + const auto readable_mem_handle{ctx.GetCopyHandle(2)}; + + if (parameters.rx_size == 0 || parameters.ro_size == 0) { + LOG_ERROR(Service_JIT, "attempted to init with empty code regions"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + // The copy handle at index 0 is the process handle, but handle tables are + // per-process, so there is no point reading it here until we are multiprocess + const auto& process{*system.CurrentProcess()}; + + auto executable_mem{ + process.GetHandleTable().GetObject<Kernel::KCodeMemory>(executable_mem_handle)}; + if (executable_mem.IsNull()) { + LOG_ERROR(Service_JIT, "executable_mem is null for handle=0x{:08X}", + executable_mem_handle); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + auto readable_mem{ + process.GetHandleTable().GetObject<Kernel::KCodeMemory>(readable_mem_handle)}; + if (readable_mem.IsNull()) { + LOG_ERROR(Service_JIT, "readable_mem is null for handle=0x{:08X}", readable_mem_handle); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultUnknown); + return; + } + + const CodeRange user_rx{ + .offset = executable_mem->GetSourceAddress(), + .size = parameters.rx_size, + }; + + const CodeRange user_ro{ + .offset = readable_mem->GetSourceAddress(), + .size = parameters.ro_size, + }; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface<IJitEnvironment>(system); + rb.PushIpcInterface<IJitEnvironment>(system, user_rx, user_ro); } }; diff --git a/src/core/hle/service/jit/jit_context.cpp b/src/core/hle/service/jit/jit_context.cpp new file mode 100644 index 000000000..630368fb3 --- /dev/null +++ b/src/core/hle/service/jit/jit_context.cpp @@ -0,0 +1,424 @@ +// Copyright 2022 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <map> +#include <span> +#include <boost/icl/interval_set.hpp> +#include <dynarmic/interface/A64/a64.h> +#include <dynarmic/interface/A64/config.h> + +#include "common/alignment.h" +#include "common/common_funcs.h" +#include "common/div_ceil.h" +#include "common/logging/log.h" +#include "core/hle/service/jit/jit_context.h" +#include "core/memory.h" + +namespace Service::JIT { + +constexpr std::array<u8, 4> STOP_ARM64 = { + 0x01, 0x00, 0x00, 0xd4, // svc #0 +}; + +constexpr std::array<u8, 8> RESOLVE_ARM64 = { + 0x21, 0x00, 0x00, 0xd4, // svc #1 + 0xc0, 0x03, 0x5f, 0xd6, // ret +}; + +constexpr std::array<u8, 4> PANIC_ARM64 = { + 0x41, 0x00, 0x00, 0xd4, // svc #2 +}; + +constexpr std::array<u8, 60> MEMMOVE_ARM64 = { + 0x1f, 0x00, 0x01, 0xeb, // cmp x0, x1 + 0x83, 0x01, 0x00, 0x54, // b.lo #+34 + 0x42, 0x04, 0x00, 0xd1, // sub x2, x2, 1 + 0x22, 0x01, 0xf8, 0xb7, // tbnz x2, #63, #+36 + 0x23, 0x68, 0x62, 0x38, // ldrb w3, [x1, x2] + 0x03, 0x68, 0x22, 0x38, // strb w3, [x0, x2] + 0xfc, 0xff, 0xff, 0x17, // b #-16 + 0x24, 0x68, 0x63, 0x38, // ldrb w4, [x1, x3] + 0x04, 0x68, 0x23, 0x38, // strb w4, [x0, x3] + 0x63, 0x04, 0x00, 0x91, // add x3, x3, 1 + 0x7f, 0x00, 0x02, 0xeb, // cmp x3, x2 + 0x8b, 0xff, 0xff, 0x54, // b.lt #-16 + 0xc0, 0x03, 0x5f, 0xd6, // ret + 0x03, 0x00, 0x80, 0xd2, // mov x3, 0 + 0xfc, 0xff, 0xff, 0x17, // b #-16 +}; + +constexpr std::array<u8, 28> MEMSET_ARM64 = { + 0x03, 0x00, 0x80, 0xd2, // mov x3, 0 + 0x7f, 0x00, 0x02, 0xeb, // cmp x3, x2 + 0x4b, 0x00, 0x00, 0x54, // b.lt #+8 + 0xc0, 0x03, 0x5f, 0xd6, // ret + 0x01, 0x68, 0x23, 0x38, // strb w1, [x0, x3] + 0x63, 0x04, 0x00, 0x91, // add x3, x3, 1 + 0xfb, 0xff, 0xff, 0x17, // b #-20 +}; + +struct HelperFunction { + const char* name; + const std::span<const u8> data; +}; + +constexpr std::array<HelperFunction, 6> HELPER_FUNCTIONS{{ + {"_stop", STOP_ARM64}, + {"_resolve", RESOLVE_ARM64}, + {"_panic", PANIC_ARM64}, + {"memcpy", MEMMOVE_ARM64}, + {"memmove", MEMMOVE_ARM64}, + {"memset", MEMSET_ARM64}, +}}; + +struct Elf64_Dyn { + u64 d_tag; + u64 d_un; +}; + +struct Elf64_Rela { + u64 r_offset; + u64 r_info; + s64 r_addend; +}; + +static constexpr u32 Elf64_RelaType(const Elf64_Rela* rela) { + return static_cast<u32>(rela->r_info); +} + +constexpr int DT_RELA = 7; /* Address of Rela relocs */ +constexpr int DT_RELASZ = 8; /* Total size of Rela relocs */ +constexpr int R_AARCH64_RELATIVE = 1027; /* Adjust by program base. */ + +constexpr size_t STACK_ALIGN = 16; + +class JITContextImpl; + +using IntervalSet = boost::icl::interval_set<VAddr>::type; +using IntervalType = boost::icl::interval_set<VAddr>::interval_type; + +class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks { +public: + explicit DynarmicCallbacks64(Core::Memory::Memory& memory_, std::vector<u8>& local_memory_, + IntervalSet& mapped_ranges_, JITContextImpl& parent_) + : memory{memory_}, local_memory{local_memory_}, + mapped_ranges{mapped_ranges_}, parent{parent_} {} + + u8 MemoryRead8(u64 vaddr) override { + return ReadMemory<u8>(vaddr); + } + u16 MemoryRead16(u64 vaddr) override { + return ReadMemory<u16>(vaddr); + } + u32 MemoryRead32(u64 vaddr) override { + return ReadMemory<u32>(vaddr); + } + u64 MemoryRead64(u64 vaddr) override { + return ReadMemory<u64>(vaddr); + } + u128 MemoryRead128(u64 vaddr) override { + return ReadMemory<u128>(vaddr); + } + std::string MemoryReadCString(u64 vaddr) { + std::string result; + u8 next; + + while ((next = MemoryRead8(vaddr++)) != 0) { + result += next; + } + + return result; + } + + void MemoryWrite8(u64 vaddr, u8 value) override { + WriteMemory<u8>(vaddr, value); + } + void MemoryWrite16(u64 vaddr, u16 value) override { + WriteMemory<u16>(vaddr, value); + } + void MemoryWrite32(u64 vaddr, u32 value) override { + WriteMemory<u32>(vaddr, value); + } + void MemoryWrite64(u64 vaddr, u64 value) override { + WriteMemory<u64>(vaddr, value); + } + void MemoryWrite128(u64 vaddr, u128 value) override { + WriteMemory<u128>(vaddr, value); + } + + bool MemoryWriteExclusive8(u64 vaddr, u8 value, u8) override { + return WriteMemory<u8>(vaddr, value); + } + bool MemoryWriteExclusive16(u64 vaddr, u16 value, u16) override { + return WriteMemory<u16>(vaddr, value); + } + bool MemoryWriteExclusive32(u64 vaddr, u32 value, u32) override { + return WriteMemory<u32>(vaddr, value); + } + bool MemoryWriteExclusive64(u64 vaddr, u64 value, u64) override { + return WriteMemory<u64>(vaddr, value); + } + bool MemoryWriteExclusive128(u64 vaddr, u128 value, u128) override { + return WriteMemory<u128>(vaddr, value); + } + + void CallSVC(u32 swi) override; + void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override; + void InterpreterFallback(u64 pc, size_t num_instructions) override; + + void AddTicks(u64 ticks) override {} + u64 GetTicksRemaining() override { + return std::numeric_limits<u32>::max(); + } + u64 GetCNTPCT() override { + return 0; + } + + template <class T> + T ReadMemory(u64 vaddr) { + T ret{}; + if (boost::icl::contains(mapped_ranges, vaddr)) { + memory.ReadBlock(vaddr, &ret, sizeof(T)); + } else if (vaddr + sizeof(T) > local_memory.size()) { + LOG_CRITICAL(Service_JIT, "plugin: unmapped read @ 0x{:016x}", vaddr); + } else { + std::memcpy(&ret, local_memory.data() + vaddr, sizeof(T)); + } + return ret; + } + + template <class T> + bool WriteMemory(u64 vaddr, const T value) { + if (boost::icl::contains(mapped_ranges, vaddr)) { + memory.WriteBlock(vaddr, &value, sizeof(T)); + } else if (vaddr + sizeof(T) > local_memory.size()) { + LOG_CRITICAL(Service_JIT, "plugin: unmapped write @ 0x{:016x}", vaddr); + } else { + std::memcpy(local_memory.data() + vaddr, &value, sizeof(T)); + } + return true; + } + +private: + Core::Memory::Memory& memory; + std::vector<u8>& local_memory; + IntervalSet& mapped_ranges; + JITContextImpl& parent; +}; + +class JITContextImpl { +public: + explicit JITContextImpl(Core::Memory::Memory& memory_) : memory{memory_} { + callbacks = + std::make_unique<DynarmicCallbacks64>(memory, local_memory, mapped_ranges, *this); + user_config.callbacks = callbacks.get(); + jit = std::make_unique<Dynarmic::A64::Jit>(user_config); + } + + bool LoadNRO(std::span<const u8> data) { + local_memory.clear(); + local_memory.insert(local_memory.end(), data.begin(), data.end()); + + if (FixupRelocations()) { + InsertHelperFunctions(); + InsertStack(); + return true; + } else { + return false; + } + } + + bool FixupRelocations() { + const VAddr mod_offset{callbacks->MemoryRead32(4)}; + if (callbacks->MemoryRead32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) { + return false; + } + + VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)}; + VAddr rela_dyn = 0; + size_t num_rela = 0; + while (true) { + const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)}; + dynamic_offset += sizeof(Elf64_Dyn); + + if (!dyn.d_tag) { + break; + } + if (dyn.d_tag == DT_RELA) { + rela_dyn = dyn.d_un; + } + if (dyn.d_tag == DT_RELASZ) { + num_rela = dyn.d_un / sizeof(Elf64_Rela); + } + } + + for (size_t i = 0; i < num_rela; i++) { + const auto rela{callbacks->ReadMemory<Elf64_Rela>(rela_dyn + i * sizeof(Elf64_Rela))}; + if (Elf64_RelaType(&rela) != R_AARCH64_RELATIVE) { + continue; + } + const VAddr contents{callbacks->MemoryRead64(rela.r_offset)}; + callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend); + } + + return true; + } + + void InsertHelperFunctions() { + for (const auto& [name, contents] : HELPER_FUNCTIONS) { + helpers[name] = local_memory.size(); + local_memory.insert(local_memory.end(), contents.begin(), contents.end()); + } + } + + void InsertStack() { + const u64 pad_amount{Common::AlignUp(local_memory.size(), STACK_ALIGN) - + local_memory.size()}; + local_memory.insert(local_memory.end(), 0x10000 + pad_amount, 0); + top_of_stack = local_memory.size(); + heap_pointer = top_of_stack; + } + + void MapProcessMemory(VAddr dest_address, std::size_t size) { + mapped_ranges.add(IntervalType{dest_address, dest_address + size}); + } + + void PushArgument(const void* data, size_t size) { + const size_t num_words = Common::DivCeil(size, sizeof(u64)); + const size_t current_pos = argument_stack.size(); + argument_stack.insert(argument_stack.end(), num_words, 0); + std::memcpy(argument_stack.data() + current_pos, data, size); + } + + void SetupArguments() { + for (size_t i = 0; i < 8 && i < argument_stack.size(); i++) { + jit->SetRegister(i, argument_stack[i]); + } + if (argument_stack.size() > 8) { + const VAddr new_sp = Common::AlignDown( + top_of_stack - (argument_stack.size() - 8) * sizeof(u64), STACK_ALIGN); + for (size_t i = 8; i < argument_stack.size(); i++) { + callbacks->MemoryWrite64(new_sp + (i - 8) * sizeof(u64), argument_stack[i]); + } + jit->SetSP(new_sp); + } + argument_stack.clear(); + heap_pointer = top_of_stack; + } + + u64 CallFunction(VAddr func) { + jit->SetRegister(30, helpers["_stop"]); + jit->SetSP(top_of_stack); + SetupArguments(); + + jit->SetPC(func); + jit->Run(); + return jit->GetRegister(0); + } + + VAddr GetHelper(const std::string& name) { + return helpers[name]; + } + + VAddr AddHeap(const void* data, size_t size) { + const size_t num_bytes{Common::AlignUp(size, STACK_ALIGN)}; + if (heap_pointer + num_bytes > local_memory.size()) { + local_memory.insert(local_memory.end(), + (heap_pointer + num_bytes) - local_memory.size(), 0); + } + const VAddr location{heap_pointer}; + std::memcpy(local_memory.data() + location, data, size); + heap_pointer += num_bytes; + return location; + } + + void GetHeap(VAddr location, void* data, size_t size) { + std::memcpy(data, local_memory.data() + location, size); + } + + std::unique_ptr<DynarmicCallbacks64> callbacks; + std::vector<u8> local_memory; + std::vector<u64> argument_stack; + IntervalSet mapped_ranges; + Dynarmic::A64::UserConfig user_config; + std::unique_ptr<Dynarmic::A64::Jit> jit; + std::map<std::string, VAddr, std::less<>> helpers; + Core::Memory::Memory& memory; + VAddr top_of_stack; + VAddr heap_pointer; +}; + +void DynarmicCallbacks64::CallSVC(u32 swi) { + switch (swi) { + case 0: + parent.jit->HaltExecution(); + break; + + case 1: { + // X0 contains a char* for a symbol to resolve + std::string name{MemoryReadCString(parent.jit->GetRegister(0))}; + const auto helper{parent.helpers[name]}; + + if (helper != 0) { + parent.jit->SetRegister(0, helper); + } else { + LOG_WARNING(Service_JIT, "plugin requested unknown function {}", name); + parent.jit->SetRegister(0, parent.helpers["_panic"]); + } + break; + } + + case 2: + default: + LOG_CRITICAL(Service_JIT, "plugin panicked!"); + parent.jit->HaltExecution(); + break; + } +} + +void DynarmicCallbacks64::ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) { + LOG_CRITICAL(Service_JIT, "Illegal operation PC @ {:08x}", pc); + parent.jit->HaltExecution(); +} + +void DynarmicCallbacks64::InterpreterFallback(u64 pc, size_t num_instructions) { + LOG_CRITICAL(Service_JIT, "Unimplemented instruction PC @ {:08x}", pc); + parent.jit->HaltExecution(); +} + +JITContext::JITContext(Core::Memory::Memory& memory) + : impl{std::make_unique<JITContextImpl>(memory)} {} + +JITContext::~JITContext() {} + +bool JITContext::LoadNRO(std::span<const u8> data) { + return impl->LoadNRO(data); +} + +void JITContext::MapProcessMemory(VAddr dest_address, std::size_t size) { + impl->MapProcessMemory(dest_address, size); +} + +u64 JITContext::CallFunction(VAddr func) { + return impl->CallFunction(func); +} + +void JITContext::PushArgument(const void* data, size_t size) { + impl->PushArgument(data, size); +} + +VAddr JITContext::GetHelper(const std::string& name) { + return impl->GetHelper(name); +} + +VAddr JITContext::AddHeap(const void* data, size_t size) { + return impl->AddHeap(data, size); +} + +void JITContext::GetHeap(VAddr location, void* data, size_t size) { + impl->GetHeap(location, data, size); +} + +} // namespace Service::JIT diff --git a/src/core/hle/service/jit/jit_context.h b/src/core/hle/service/jit/jit_context.h new file mode 100644 index 000000000..d8bf76cff --- /dev/null +++ b/src/core/hle/service/jit/jit_context.h @@ -0,0 +1,65 @@ +// Copyright 2022 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <span> +#include <string> + +#include "common/common_types.h" + +namespace Core::Memory { +class Memory; +} + +namespace Service::JIT { + +class JITContextImpl; + +class JITContext { +public: + explicit JITContext(Core::Memory::Memory& memory); + ~JITContext(); + + [[nodiscard]] bool LoadNRO(std::span<const u8> data); + void MapProcessMemory(VAddr dest_address, std::size_t size); + + template <typename T, typename... Ts> + u64 CallFunction(VAddr func, T argument, Ts... rest) { + static_assert(std::is_trivially_copyable_v<T>); + PushArgument(&argument, sizeof(argument)); + + if constexpr (sizeof...(rest) > 0) { + return CallFunction(func, rest...); + } else { + return CallFunction(func); + } + } + + u64 CallFunction(VAddr func); + VAddr GetHelper(const std::string& name); + + template <typename T> + VAddr AddHeap(T argument) { + return AddHeap(&argument, sizeof(argument)); + } + VAddr AddHeap(const void* data, size_t size); + + template <typename T> + T GetHeap(VAddr location) { + static_assert(std::is_trivially_copyable_v<T>); + T result; + GetHeap(location, &result, sizeof(result)); + return result; + } + void GetHeap(VAddr location, void* data, size_t size); + +private: + std::unique_ptr<JITContextImpl> impl; + + void PushArgument(const void* data, size_t size); +}; + +} // namespace Service::JIT diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index cf727c167..42f9cf811 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -160,7 +160,8 @@ public: class RelocatableObject final : public ServiceFramework<RelocatableObject> { public: - explicit RelocatableObject(Core::System& system_) : ServiceFramework{system_, "ldr:ro"} { + explicit RelocatableObject(Core::System& system_) + : ServiceFramework{system_, "ldr:ro", ServiceThreadType::CreateNew} { // clang-format off static const FunctionInfo functions[] = { {0, &RelocatableObject::LoadModule, "LoadModule"}, diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index c78b2baeb..148265218 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -9,7 +9,6 @@ #include <string> #include <boost/container/flat_map.hpp> #include "common/common_types.h" -#include "common/spin_lock.h" #include "core/hle/kernel/hle_ipc.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -90,7 +89,7 @@ protected: using HandlerFnP = void (Self::*)(Kernel::HLERequestContext&); /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. - [[nodiscard]] std::scoped_lock<Common::SpinLock> LockService() { + [[nodiscard]] std::scoped_lock<std::mutex> LockService() { return std::scoped_lock{lock_service}; } @@ -135,7 +134,7 @@ private: boost::container::flat_map<u32, FunctionInfoBase> handlers_tipc; /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. - Common::SpinLock lock_service; + std::mutex lock_service; }; /** diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 21bfb76a4..3f2bf6294 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -1311,7 +1311,20 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) { const GPUVAddr gpu_addr_begin = array.StartAddress(); const GPUVAddr gpu_addr_end = limit.LimitAddress() + 1; const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr_begin); - const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); + u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin); + if (address_size >= 64_MiB) { + // Reported vertex buffer size is very large, cap to mapped buffer size + GPUVAddr submapped_addr_end = gpu_addr_begin; + + const auto ranges{gpu_memory.GetSubmappedRange(gpu_addr_begin, address_size)}; + if (ranges.size() > 0) { + const auto& [addr, size] = *ranges.begin(); + submapped_addr_end = addr + size; + } + + address_size = + std::min(address_size, static_cast<u32>(submapped_addr_end - gpu_addr_begin)); + } const u32 size = address_size; // TODO: Analyze stride and number of vertices if (array.enable == 0 || size == 0 || !cpu_addr) { vertex_buffers[index] = NULL_BINDING; diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 54a902f56..7399e760f 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -214,6 +214,11 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume regs.index_array.first = regs.small_index.first; dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; return DrawArrays(); + case MAXWELL3D_REG_INDEX(small_index_2): + regs.index_array.count = regs.small_index_2.count; + regs.index_array.first = regs.small_index_2.first; + dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + return DrawArrays(); case MAXWELL3D_REG_INDEX(topology_override): use_topology_override = true; return; diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 3f5b38e55..d36dc3daa 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -1220,7 +1220,12 @@ public: BitField<16, 16, u32> count; } small_index; - INSERT_PADDING_WORDS_NOINIT(0x6); + union { + BitField<0, 16, u32> first; + BitField<16, 16, u32> count; + } small_index_2; + + INSERT_PADDING_WORDS_NOINIT(0x5); INSERT_PADDING_WORDS_NOINIT(0x1F); diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index d12076358..f8c6e5c7e 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -182,6 +182,26 @@ GLenum AttachmentType(PixelFormat format) { } } +GLint ConvertA5B5G5R1_UNORM(SwizzleSource source) { + switch (source) { + case SwizzleSource::Zero: + return GL_ZERO; + case SwizzleSource::R: + return GL_ALPHA; + case SwizzleSource::G: + return GL_BLUE; + case SwizzleSource::B: + return GL_GREEN; + case SwizzleSource::A: + return GL_RED; + case SwizzleSource::OneInt: + case SwizzleSource::OneFloat: + return GL_ONE; + } + UNREACHABLE_MSG("Invalid swizzle source={}", source); + return GL_NONE; +} + void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4> swizzle) { switch (format) { case PixelFormat::D24_UNORM_S8_UINT: @@ -192,6 +212,12 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4 TextureMode(format, swizzle[0] == SwizzleSource::R)); std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed); break; + case PixelFormat::A5B5G5R1_UNORM: { + std::array<GLint, 4> gl_swizzle; + std::ranges::transform(swizzle, gl_swizzle.begin(), ConvertA5B5G5R1_UNORM); + glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data()); + return; + } default: break; } diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index db5bf1d30..03adf3d4c 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -30,6 +30,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10_UNORM {GL_RGB10_A2UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10_UINT {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // A1B5G5R5_UNORM + {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // A5B5G5R1_UNORM {GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8_UNORM {GL_R8_SNORM, GL_RED, GL_BYTE}, // R8_SNORM {GL_R8I, GL_RED_INTEGER, GL_BYTE}, // R8_SINT @@ -87,6 +88,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}, // BC3_SRGB {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM}, // BC7_SRGB {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4_REV}, // A4B4G4R4_UNORM + {GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R4G4_UNORM {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR}, // ASTC_2D_4X4_SRGB {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR}, // ASTC_2D_8X8_SRGB {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR}, // ASTC_2D_8X5_SRGB diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index 1c136c410..a2c6d0e6c 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -127,6 +127,7 @@ struct FormatTuple { {VK_FORMAT_A2B10G10R10_UNORM_PACK32, Attachable | Storage}, // A2B10G10R10_UNORM {VK_FORMAT_A2B10G10R10_UINT_PACK32, Attachable | Storage}, // A2B10G10R10_UINT {VK_FORMAT_A1R5G5B5_UNORM_PACK16, Attachable}, // A1B5G5R5_UNORM (flipped with swizzle) + {VK_FORMAT_R5G5B5A1_UNORM_PACK16}, // A5B5G5R1_UNORM (specially swizzled) {VK_FORMAT_R8_UNORM, Attachable | Storage}, // R8_UNORM {VK_FORMAT_R8_SNORM, Attachable | Storage}, // R8_SNORM {VK_FORMAT_R8_SINT, Attachable | Storage}, // R8_SINT @@ -184,6 +185,7 @@ struct FormatTuple { {VK_FORMAT_BC3_SRGB_BLOCK}, // BC3_SRGB {VK_FORMAT_BC7_SRGB_BLOCK}, // BC7_SRGB {VK_FORMAT_R4G4B4A4_UNORM_PACK16, Attachable}, // A4B4G4R4_UNORM + {VK_FORMAT_R4G4_UNORM_PACK8}, // R4G4_UNORM {VK_FORMAT_ASTC_4x4_SRGB_BLOCK}, // ASTC_2D_4X4_SRGB {VK_FORMAT_ASTC_8x8_SRGB_BLOCK}, // ASTC_2D_8X8_SRGB {VK_FORMAT_ASTC_8x5_SRGB_BLOCK}, // ASTC_2D_8X5_SRGB diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 2c2ccc7c6..49691ce0c 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -438,6 +438,32 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { } } +[[nodiscard]] SwizzleSource SwapGreenRed(SwizzleSource value) { + switch (value) { + case SwizzleSource::R: + return SwizzleSource::G; + case SwizzleSource::G: + return SwizzleSource::R; + default: + return value; + } +} + +[[nodiscard]] SwizzleSource SwapSpecial(SwizzleSource value) { + switch (value) { + case SwizzleSource::A: + return SwizzleSource::R; + case SwizzleSource::R: + return SwizzleSource::A; + case SwizzleSource::G: + return SwizzleSource::B; + case SwizzleSource::B: + return SwizzleSource::G; + default: + return value; + } +} + void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage image, VkImageAspectFlags aspect_mask, bool is_initialized, std::span<const VkBufferImageCopy> copies) { @@ -554,14 +580,25 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im }; } -[[nodiscard]] bool IsFormatFlipped(PixelFormat format, bool emulate_bgr565) { +void TryTransformSwizzleIfNeeded(PixelFormat format, std::array<SwizzleSource, 4>& swizzle, + bool emulate_bgr565) { switch (format) { case PixelFormat::A1B5G5R5_UNORM: - return true; + std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed); + break; case PixelFormat::B5G6R5_UNORM: - return emulate_bgr565; + if (emulate_bgr565) { + std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed); + } + break; + case PixelFormat::A5B5G5R1_UNORM: + std::ranges::transform(swizzle, swizzle.begin(), SwapSpecial); + break; + case PixelFormat::R4G4_UNORM: + std::ranges::transform(swizzle, swizzle.begin(), SwapGreenRed); + break; default: - return false; + break; } } @@ -1496,9 +1533,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI }; if (!info.IsRenderTarget()) { swizzle = info.Swizzle(); - if (IsFormatFlipped(format, device->MustEmulateBGR565())) { - std::ranges::transform(swizzle, swizzle.begin(), SwapBlueRed); - } + TryTransformSwizzleIfNeeded(format, swizzle, device->MustEmulateBGR565()); if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed); } diff --git a/src/video_core/surface.h b/src/video_core/surface.h index 5704cf16a..86fea61ae 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -25,6 +25,7 @@ enum class PixelFormat { A2B10G10R10_UNORM, A2B10G10R10_UINT, A1B5G5R5_UNORM, + A5B5G5R1_UNORM, R8_UNORM, R8_SNORM, R8_SINT, @@ -82,6 +83,7 @@ enum class PixelFormat { BC3_SRGB, BC7_SRGB, A4B4G4R4_UNORM, + R4G4_UNORM, ASTC_2D_4X4_SRGB, ASTC_2D_8X8_SRGB, ASTC_2D_8X5_SRGB, @@ -156,6 +158,7 @@ constexpr std::array<u32, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{ 1, // A2B10G10R10_UNORM 1, // A2B10G10R10_UINT 1, // A1B5G5R5_UNORM + 1, // A5B5G5R1_UNORM 1, // R8_UNORM 1, // R8_SNORM 1, // R8_SINT @@ -213,6 +216,7 @@ constexpr std::array<u32, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{ 4, // BC3_SRGB 4, // BC7_SRGB 1, // A4B4G4R4_UNORM + 1, // R4G4_UNORM 4, // ASTC_2D_4X4_SRGB 8, // ASTC_2D_8X8_SRGB 8, // ASTC_2D_8X5_SRGB @@ -256,6 +260,7 @@ constexpr std::array<u32, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{ 1, // A2B10G10R10_UNORM 1, // A2B10G10R10_UINT 1, // A1B5G5R5_UNORM + 1, // A5B5G5R1_UNORM 1, // R8_UNORM 1, // R8_SNORM 1, // R8_SINT @@ -313,6 +318,7 @@ constexpr std::array<u32, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{ 4, // BC3_SRGB 4, // BC7_SRGB 1, // A4B4G4R4_UNORM + 1, // R4G4_UNORM 4, // ASTC_2D_4X4_SRGB 8, // ASTC_2D_8X8_SRGB 5, // ASTC_2D_8X5_SRGB @@ -356,6 +362,7 @@ constexpr std::array<u32, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{ 32, // A2B10G10R10_UNORM 32, // A2B10G10R10_UINT 16, // A1B5G5R5_UNORM + 16, // A5B5G5R1_UNORM 8, // R8_UNORM 8, // R8_SNORM 8, // R8_SINT @@ -413,6 +420,7 @@ constexpr std::array<u32, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{ 128, // BC3_SRGB 128, // BC7_UNORM 16, // A4B4G4R4_UNORM + 8, // R4G4_UNORM 128, // ASTC_2D_4X4_SRGB 128, // ASTC_2D_8X8_SRGB 128, // ASTC_2D_8X5_SRGB diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp index afa807d5d..20e64a7c2 100644 --- a/src/video_core/texture_cache/format_lookup_table.cpp +++ b/src/video_core/texture_cache/format_lookup_table.cpp @@ -63,6 +63,10 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, return PixelFormat::A1B5G5R5_UNORM; case Hash(TextureFormat::A4B4G4R4, UNORM): return PixelFormat::A4B4G4R4_UNORM; + case Hash(TextureFormat::G4R4, UNORM): + return PixelFormat::R4G4_UNORM; + case Hash(TextureFormat::A5B5G5R1, UNORM): + return PixelFormat::A5B5G5R1_UNORM; case Hash(TextureFormat::R8, UNORM): return PixelFormat::R8_UNORM; case Hash(TextureFormat::R8, SNORM): @@ -143,6 +147,8 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, return PixelFormat::S8_UINT_D24_UNORM; case Hash(TextureFormat::R8G24, UINT, UNORM, UNORM, UNORM, LINEAR): return PixelFormat::S8_UINT_D24_UNORM; + case Hash(TextureFormat::D24S8, UNORM, UINT, UINT, UINT, LINEAR): + return PixelFormat::D24_UNORM_S8_UINT; case Hash(TextureFormat::D32S8, FLOAT, UINT, UNORM, UNORM, LINEAR): return PixelFormat::D32_FLOAT_S8_UINT; case Hash(TextureFormat::BC1_RGBA, UNORM, LINEAR): diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h index b2c81057b..6f5afc5a9 100644 --- a/src/video_core/texture_cache/formatter.h +++ b/src/video_core/texture_cache/formatter.h @@ -38,6 +38,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str return "A2B10G10R10_UINT"; case PixelFormat::A1B5G5R5_UNORM: return "A1B5G5R5_UNORM"; + case PixelFormat::A5B5G5R1_UNORM: + return "A5B5G5R1_UNORM"; case PixelFormat::R8_UNORM: return "R8_UNORM"; case PixelFormat::R8_SNORM: @@ -152,6 +154,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str return "BC7_SRGB"; case PixelFormat::A4B4G4R4_UNORM: return "A4B4G4R4_UNORM"; + case PixelFormat::R4G4_UNORM: + return "R4G4_UNORM"; case PixelFormat::ASTC_2D_4X4_SRGB: return "ASTC_2D_4X4_SRGB"; case PixelFormat::ASTC_2D_8X8_SRGB: diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index f3a05ada9..bd05a1f84 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -45,6 +45,12 @@ constexpr std::array B5G6R5_UNORM_PACK16{ VK_FORMAT_R5G6B5_UNORM_PACK16, VK_FORMAT_UNDEFINED, }; + +constexpr std::array R4G4_UNORM_PACK8{ + VK_FORMAT_R8_UNORM, + VK_FORMAT_UNDEFINED, +}; + } // namespace Alternatives enum class NvidiaArchitecture { @@ -95,6 +101,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) { return Alternatives::DEPTH16_UNORM_STENCIL8_UINT.data(); case VK_FORMAT_B5G6R5_UNORM_PACK16: return Alternatives::B5G6R5_UNORM_PACK16.data(); + case VK_FORMAT_R4G4_UNORM_PACK8: + return Alternatives::R4G4_UNORM_PACK8.data(); default: return nullptr; } @@ -122,6 +130,8 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica VK_FORMAT_A8B8G8R8_SRGB_PACK32, VK_FORMAT_R5G6B5_UNORM_PACK16, VK_FORMAT_B5G6R5_UNORM_PACK16, + VK_FORMAT_R5G5B5A1_UNORM_PACK16, + VK_FORMAT_B5G5R5A1_UNORM_PACK16, VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_FORMAT_A2B10G10R10_UINT_PACK32, VK_FORMAT_A1R5G5B5_UNORM_PACK16, @@ -160,7 +170,9 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SRGB, + VK_FORMAT_R4G4_UNORM_PACK8, VK_FORMAT_R4G4B4A4_UNORM_PACK16, + VK_FORMAT_B4G4R4A4_UNORM_PACK16, VK_FORMAT_D32_SFLOAT, VK_FORMAT_D16_UNORM, VK_FORMAT_S8_UINT, diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index b1467d016..2ee21f751 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -99,6 +99,9 @@ add_executable(yuzu configuration/configure_profile_manager.cpp configuration/configure_profile_manager.h configuration/configure_profile_manager.ui + configuration/configure_ringcon.cpp + configuration/configure_ringcon.h + configuration/configure_ringcon.ui configuration/configure_network.cpp configuration/configure_network.h configuration/configure_network.ui diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index d2e735f48..ac26b885b 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -60,6 +60,11 @@ const std::array<int, 2> Config::default_stick_mod = { 0, }; +const std::array<int, 2> Config::default_ringcon_analogs{{ + Qt::Key_A, + Qt::Key_D, +}}; + // This shouldn't have anything except static initializers (no functions). So // QKeySequence(...).toString() is NOT ALLOWED HERE. // This must be in alphabetical order according to action name as it must have the same order as @@ -346,6 +351,23 @@ void Config::ReadTouchscreenValues() { ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt(); } +void Config::ReadHidbusValues() { + Settings::values.enable_ring_controller = + ReadSetting(QStringLiteral("enable_ring_controller"), true).toBool(); + + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); + auto& ringcon_analogs = Settings::values.ringcon_analogs; + + ringcon_analogs = + qt_config->value(QStringLiteral("ring_controller"), QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (ringcon_analogs.empty()) { + ringcon_analogs = default_param; + } +} + void Config::ReadAudioValues() { qt_config->beginGroup(QStringLiteral("Audio")); @@ -369,6 +391,7 @@ void Config::ReadControlValues() { ReadMouseValues(); ReadTouchscreenValues(); ReadMotionTouchValues(); + ReadHidbusValues(); #ifdef _WIN32 ReadBasicSetting(Settings::values.enable_raw_input); @@ -962,6 +985,16 @@ void Config::SaveMotionTouchValues() { qt_config->endArray(); } +void Config::SaveHidbusValues() { + WriteBasicSetting(Settings::values.enable_ring_controller); + + const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + 0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f); + WriteSetting(QStringLiteral("ring_controller"), + QString::fromStdString(Settings::values.ringcon_analogs), + QString::fromStdString(default_param)); +} + void Config::SaveValues() { if (global) { SaveControlValues(); @@ -1002,6 +1035,7 @@ void Config::SaveControlValues() { SaveMouseValues(); SaveTouchscreenValues(); SaveMotionTouchValues(); + SaveHidbusValues(); WriteGlobalSetting(Settings::values.use_docked_mode); WriteGlobalSetting(Settings::values.vibration_enabled); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index ae3e36a11..f0ab6bdaa 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -42,6 +42,7 @@ public: static const std::array<int, Settings::NativeMotion::NumMotions> default_motions; static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs; static const std::array<int, 2> default_stick_mod; + static const std::array<int, 2> default_ringcon_analogs; static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> default_mouse_buttons; static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys; @@ -66,6 +67,7 @@ private: void ReadMouseValues(); void ReadTouchscreenValues(); void ReadMotionTouchValues(); + void ReadHidbusValues(); // Read functions bases off the respective config section names. void ReadAudioValues(); @@ -93,6 +95,7 @@ private: void SaveMouseValues(); void SaveTouchscreenValues(); void SaveMotionTouchValues(); + void SaveHidbusValues(); // Save functions based off the respective config section names. void SaveAudioValues(); diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 4ca74a5f7..73d7ba24b 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -20,6 +20,7 @@ #include "yuzu/configuration/configure_input_advanced.h" #include "yuzu/configuration/configure_input_player.h" #include "yuzu/configuration/configure_motion_touch.h" +#include "yuzu/configuration/configure_ringcon.h" #include "yuzu/configuration/configure_touchscreen_advanced.h" #include "yuzu/configuration/configure_vibration.h" #include "yuzu/configuration/input_profiles.h" @@ -158,6 +159,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, [this, input_subsystem] { CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem); }); + connect(advanced, &ConfigureInputAdvanced::CallRingControllerDialog, + [this, input_subsystem, &hid_core] { + CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core); + }); connect(ui->vibrationButton, &QPushButton::clicked, [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); }); diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index 20fc2599d..8fd1f4a38 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -79,13 +79,17 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent) &ConfigureInputAdvanced::UpdateUIEnabled); connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this, &ConfigureInputAdvanced::UpdateUIEnabled); + connect(ui->enable_ring_controller, &QCheckBox::stateChanged, this, + &ConfigureInputAdvanced::UpdateUIEnabled); connect(ui->debug_configure, &QPushButton::clicked, this, [this] { CallDebugControllerDialog(); }); connect(ui->touchscreen_advanced, &QPushButton::clicked, this, [this] { CallTouchscreenConfigDialog(); }); connect(ui->buttonMotionTouch, &QPushButton::clicked, this, - &ConfigureInputAdvanced::CallMotionTouchConfigDialog); + [this] { CallMotionTouchConfigDialog(); }); + connect(ui->ring_controller_configure, &QPushButton::clicked, this, + [this] { CallRingControllerDialog(); }); #ifndef _WIN32 ui->enable_raw_input->setVisible(false); @@ -132,6 +136,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() { Settings::values.enable_raw_input = ui->enable_raw_input->isChecked(); Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked(); Settings::values.controller_navigation = ui->controller_navigation->isChecked(); + Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked(); } void ConfigureInputAdvanced::LoadConfiguration() { @@ -164,6 +169,7 @@ void ConfigureInputAdvanced::LoadConfiguration() { ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue()); ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue()); ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue()); + ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue()); UpdateUIEnabled(); } @@ -185,4 +191,5 @@ void ConfigureInputAdvanced::UpdateUIEnabled() { ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked()); ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked()); ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked()); + ui->ring_controller_configure->setEnabled(ui->enable_ring_controller->isChecked()); } diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h index 3083d55c1..4472cb846 100644 --- a/src/yuzu/configuration/configure_input_advanced.h +++ b/src/yuzu/configuration/configure_input_advanced.h @@ -29,6 +29,7 @@ signals: void CallMouseConfigDialog(); void CallTouchscreenConfigDialog(); void CallMotionTouchConfigDialog(); + void CallRingControllerDialog(); private: void changeEvent(QEvent* event) override; diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index 66f2075f2..14403cb10 100644 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -2603,6 +2603,20 @@ </property> </widget> </item> + <item row="4" column="0"> + <widget class="QCheckBox" name="enable_ring_controller"> + <property name="text"> + <string>Ring Controller</string> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QPushButton" name="ring_controller_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 8ef3596dd..291e37acf 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -1407,10 +1407,10 @@ void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { } void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { - event->ignore(); if (!input_setter || !event) { return; } + event->ignore(); if (event->key() != Qt::Key_Escape) { input_subsystem->GetKeyboard()->PressKey(event->key()); } diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp new file mode 100644 index 000000000..f1cdd4fed --- /dev/null +++ b/src/yuzu/configuration/configure_ringcon.cpp @@ -0,0 +1,424 @@ +// Copyright 2022 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <QKeyEvent> +#include <QMenu> +#include <QTimer> + +#include "core/hid/emulated_devices.h" +#include "core/hid/hid_core.h" +#include "input_common/drivers/keyboard.h" +#include "input_common/drivers/mouse.h" +#include "input_common/main.h" +#include "ui_configure_ringcon.h" +#include "yuzu/bootmanager.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_ringcon.h" + +const std::array<std::string, ConfigureRingController::ANALOG_SUB_BUTTONS_NUM> + ConfigureRingController::analog_sub_buttons{{ + "left", + "right", + }}; + +namespace { + +QString GetKeyName(int key_code) { + switch (key_code) { + case Qt::Key_Shift: + return QObject::tr("Shift"); + case Qt::Key_Control: + return QObject::tr("Ctrl"); + case Qt::Key_Alt: + return QObject::tr("Alt"); + case Qt::Key_Meta: + return {}; + default: + return QKeySequence(key_code).toString(); + } +} + +QString GetButtonName(Common::Input::ButtonNames button_name) { + switch (button_name) { + case Common::Input::ButtonNames::ButtonLeft: + return QObject::tr("Left"); + case Common::Input::ButtonNames::ButtonRight: + return QObject::tr("Right"); + case Common::Input::ButtonNames::ButtonDown: + return QObject::tr("Down"); + case Common::Input::ButtonNames::ButtonUp: + return QObject::tr("Up"); + case Common::Input::ButtonNames::TriggerZ: + return QObject::tr("Z"); + case Common::Input::ButtonNames::TriggerR: + return QObject::tr("R"); + case Common::Input::ButtonNames::TriggerL: + return QObject::tr("L"); + case Common::Input::ButtonNames::ButtonA: + return QObject::tr("A"); + case Common::Input::ButtonNames::ButtonB: + return QObject::tr("B"); + case Common::Input::ButtonNames::ButtonX: + return QObject::tr("X"); + case Common::Input::ButtonNames::ButtonY: + return QObject::tr("Y"); + case Common::Input::ButtonNames::ButtonStart: + return QObject::tr("Start"); + case Common::Input::ButtonNames::L1: + return QObject::tr("L1"); + case Common::Input::ButtonNames::L2: + return QObject::tr("L2"); + case Common::Input::ButtonNames::L3: + return QObject::tr("L3"); + case Common::Input::ButtonNames::R1: + return QObject::tr("R1"); + case Common::Input::ButtonNames::R2: + return QObject::tr("R2"); + case Common::Input::ButtonNames::R3: + return QObject::tr("R3"); + case Common::Input::ButtonNames::Circle: + return QObject::tr("Circle"); + case Common::Input::ButtonNames::Cross: + return QObject::tr("Cross"); + case Common::Input::ButtonNames::Square: + return QObject::tr("Square"); + case Common::Input::ButtonNames::Triangle: + return QObject::tr("Triangle"); + case Common::Input::ButtonNames::Share: + return QObject::tr("Share"); + case Common::Input::ButtonNames::Options: + return QObject::tr("Options"); + default: + return QObject::tr("[undefined]"); + } +} + +void SetAnalogParam(const Common::ParamPackage& input_param, Common::ParamPackage& analog_param, + const std::string& button_name) { + // The poller returned a complete axis, so set all the buttons + if (input_param.Has("axis_x") && input_param.Has("axis_y")) { + analog_param = input_param; + return; + } + // Check if the current configuration has either no engine or an axis binding. + // Clears out the old binding and adds one with analog_from_button. + if (!analog_param.Has("engine") || analog_param.Has("axis_x") || analog_param.Has("axis_y")) { + analog_param = { + {"engine", "analog_from_button"}, + }; + } + analog_param.Set(button_name, input_param.Serialize()); +} +} // namespace + +ConfigureRingController::ConfigureRingController(QWidget* parent, + InputCommon::InputSubsystem* input_subsystem_, + Core::HID::HIDCore& hid_core_) + : QDialog(parent), timeout_timer(std::make_unique<QTimer>()), + poll_timer(std::make_unique<QTimer>()), input_subsystem{input_subsystem_}, + + ui(std::make_unique<Ui::ConfigureRingController>()) { + ui->setupUi(this); + + analog_map_buttons = { + ui->buttonRingAnalogPull, + ui->buttonRingAnalogPush, + }; + + emulated_device = hid_core_.GetEmulatedDevices(); + emulated_device->SaveCurrentConfig(); + emulated_device->EnableConfiguration(); + + LoadConfiguration(); + + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { + auto* const analog_button = analog_map_buttons[sub_button_id]; + + if (analog_button == nullptr) { + continue; + } + + connect(analog_button, &QPushButton::clicked, [=, this] { + HandleClick( + analog_map_buttons[sub_button_id], + [=, this](const Common::ParamPackage& params) { + Common::ParamPackage param = emulated_device->GetRingParam(); + SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]); + emulated_device->SetRingParam(param); + }, + InputCommon::Polling::InputType::Stick); + }); + + analog_button->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(analog_button, &QPushButton::customContextMenuRequested, + [=, this](const QPoint& menu_location) { + QMenu context_menu; + Common::ParamPackage param = emulated_device->GetRingParam(); + context_menu.addAction(tr("Clear"), [&] { + emulated_device->SetRingParam({}); + analog_map_buttons[sub_button_id]->setText(tr("[not set]")); + }); + context_menu.addAction(tr("Invert axis"), [&] { + const bool invert_value = param.Get("invert_x", "+") == "-"; + const std::string invert_str = invert_value ? "+" : "-"; + param.Set("invert_x", invert_str); + emulated_device->SetRingParam(param); + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; + ++sub_button_id) { + analog_map_buttons[sub_button_id]->setText( + AnalogToText(param, analog_sub_buttons[sub_button_id])); + } + }); + context_menu.exec( + analog_map_buttons[sub_button_id]->mapToGlobal(menu_location)); + }); + } + + connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] { + Common::ParamPackage param = emulated_device->GetRingParam(); + const auto slider_value = ui->sliderRingAnalogDeadzone->value(); + ui->labelRingAnalogDeadzone->setText(tr("Deadzone: %1%").arg(slider_value)); + param.Set("deadzone", slider_value / 100.0f); + emulated_device->SetRingParam(param); + }); + + connect(ui->restore_defaults_button, &QPushButton::clicked, this, + &ConfigureRingController::RestoreDefaults); + + timeout_timer->setSingleShot(true); + connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); }); + + connect(poll_timer.get(), &QTimer::timeout, [this] { + const auto& params = input_subsystem->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + }); + + resize(0, 0); +} + +ConfigureRingController::~ConfigureRingController() { + emulated_device->DisableConfiguration(); +}; + +void ConfigureRingController::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QDialog::changeEvent(event); +} + +void ConfigureRingController::RetranslateUI() { + ui->retranslateUi(this); +} + +void ConfigureRingController::UpdateUI() { + RetranslateUI(); + const Common::ParamPackage param = emulated_device->GetRingParam(); + + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { + auto* const analog_button = analog_map_buttons[sub_button_id]; + + if (analog_button == nullptr) { + continue; + } + + analog_button->setText(AnalogToText(param, analog_sub_buttons[sub_button_id])); + } + + const auto deadzone_label = ui->labelRingAnalogDeadzone; + const auto deadzone_slider = ui->sliderRingAnalogDeadzone; + + int slider_value = static_cast<int>(param.Get("deadzone", 0.15f) * 100); + deadzone_label->setText(tr("Deadzone: %1%").arg(slider_value)); + deadzone_slider->setValue(slider_value); +} + +void ConfigureRingController::ApplyConfiguration() { + emulated_device->DisableConfiguration(); + emulated_device->SaveCurrentConfig(); + emulated_device->EnableConfiguration(); +} + +void ConfigureRingController::LoadConfiguration() { + UpdateUI(); +} + +void ConfigureRingController::RestoreDefaults() { + const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys( + 0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f); + emulated_device->SetRingParam(Common::ParamPackage(default_ring_string)); + UpdateUI(); +} + +void ConfigureRingController::HandleClick( + QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::InputType type) { + button->setText(tr("[waiting]")); + button->setFocus(); + + input_setter = new_input_setter; + + input_subsystem->BeginMapping(type); + + QWidget::grabMouse(); + QWidget::grabKeyboard(); + + timeout_timer->start(2500); // Cancel after 2.5 seconds + poll_timer->start(25); // Check for new inputs every 25ms +} + +void ConfigureRingController::SetPollingResult(const Common::ParamPackage& params, bool abort) { + timeout_timer->stop(); + poll_timer->stop(); + input_subsystem->StopMapping(); + + QWidget::releaseMouse(); + QWidget::releaseKeyboard(); + + if (!abort) { + (*input_setter)(params); + } + + UpdateUI(); + + input_setter = std::nullopt; +} + +bool ConfigureRingController::IsInputAcceptable(const Common::ParamPackage& params) const { + return true; +} + +void ConfigureRingController::mousePressEvent(QMouseEvent* event) { + if (!input_setter || !event) { + return; + } + + const auto button = GRenderWindow::QtButtonToMouseButton(event->button()); + input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button); +} + +void ConfigureRingController::keyPressEvent(QKeyEvent* event) { + if (!input_setter || !event) { + return; + } + event->ignore(); + if (event->key() != Qt::Key_Escape) { + input_subsystem->GetKeyboard()->PressKey(event->key()); + } +} + +QString ConfigureRingController::ButtonToText(const Common::ParamPackage& param) { + if (!param.Has("engine")) { + return QObject::tr("[not set]"); + } + + const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); + const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); + const auto common_button_name = input_subsystem->GetButtonName(param); + + // Retrieve the names from Qt + if (param.Get("engine", "") == "keyboard") { + const QString button_str = GetKeyName(param.Get("code", 0)); + return QObject::tr("%1%2").arg(toggle, button_str); + } + + if (common_button_name == Common::Input::ButtonNames::Invalid) { + return QObject::tr("[invalid]"); + } + + if (common_button_name == Common::Input::ButtonNames::Engine) { + return QString::fromStdString(param.Get("engine", "")); + } + + if (common_button_name == Common::Input::ButtonNames::Value) { + if (param.Has("hat")) { + const QString hat = QString::fromStdString(param.Get("direction", "")); + return QObject::tr("%1%2Hat %3").arg(toggle, inverted, hat); + } + if (param.Has("axis")) { + const QString axis = QString::fromStdString(param.Get("axis", "")); + return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis); + } + if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { + const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); + const QString axis_y = QString::fromStdString(param.Get("axis_y", "")); + const QString axis_z = QString::fromStdString(param.Get("axis_z", "")); + return QObject::tr("%1%2Axis %3,%4,%5").arg(toggle, inverted, axis_x, axis_y, axis_z); + } + if (param.Has("motion")) { + const QString motion = QString::fromStdString(param.Get("motion", "")); + return QObject::tr("%1%2Motion %3").arg(toggle, inverted, motion); + } + if (param.Has("button")) { + const QString button = QString::fromStdString(param.Get("button", "")); + return QObject::tr("%1%2Button %3").arg(toggle, inverted, button); + } + } + + QString button_name = GetButtonName(common_button_name); + if (param.Has("hat")) { + return QObject::tr("%1%2Hat %3").arg(toggle, inverted, button_name); + } + if (param.Has("axis")) { + return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name); + } + if (param.Has("motion")) { + return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name); + } + if (param.Has("button")) { + return QObject::tr("%1%2Button %3").arg(toggle, inverted, button_name); + } + + return QObject::tr("[unknown]"); +} + +QString ConfigureRingController::AnalogToText(const Common::ParamPackage& param, + const std::string& dir) { + if (!param.Has("engine")) { + return QObject::tr("[not set]"); + } + + if (param.Get("engine", "") == "analog_from_button") { + return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); + } + + if (!param.Has("axis_x") || !param.Has("axis_y")) { + return QObject::tr("[unknown]"); + } + + const auto engine_str = param.Get("engine", ""); + const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); + const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); + const bool invert_x = param.Get("invert_x", "+") == "-"; + const bool invert_y = param.Get("invert_y", "+") == "-"; + + if (dir == "modifier") { + return QObject::tr("[unused]"); + } + + if (dir == "left") { + const QString invert_x_str = QString::fromStdString(invert_x ? "+" : "-"); + return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); + } + if (dir == "right") { + const QString invert_x_str = QString::fromStdString(invert_x ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_x_str, invert_x_str); + } + if (dir == "up") { + const QString invert_y_str = QString::fromStdString(invert_y ? "-" : "+"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); + } + if (dir == "down") { + const QString invert_y_str = QString::fromStdString(invert_y ? "+" : "-"); + return QObject::tr("Axis %1%2").arg(axis_y_str, invert_y_str); + } + + return QObject::tr("[unknown]"); +}
\ No newline at end of file diff --git a/src/yuzu/configuration/configure_ringcon.h b/src/yuzu/configuration/configure_ringcon.h new file mode 100644 index 000000000..cf9e54f09 --- /dev/null +++ b/src/yuzu/configuration/configure_ringcon.h @@ -0,0 +1,85 @@ +// Copyright 2022 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <memory> +#include <QDialog> + +namespace InputCommon { +class InputSubsystem; +} // namespace InputCommon + +namespace Core::HID { +class HIDCore; +class EmulatedDevices; +} // namespace Core::HID + +namespace Ui { +class ConfigureRingController; +} // namespace Ui + +class ConfigureRingController : public QDialog { + Q_OBJECT + +public: + explicit ConfigureRingController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_, + Core::HID::HIDCore& hid_core_); + ~ConfigureRingController() override; + + void ApplyConfiguration(); + +private: + void changeEvent(QEvent* event) override; + void RetranslateUI(); + + void UpdateUI(); + + /// Load configuration settings. + void LoadConfiguration(); + + /// Restore all buttons to their default values. + void RestoreDefaults(); + + /// Called when the button was pressed. + void HandleClick(QPushButton* button, + std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::InputType type); + + /// Finish polling and configure input using the input_setter. + void SetPollingResult(const Common::ParamPackage& params, bool abort); + + /// Checks whether a given input can be accepted. + bool IsInputAcceptable(const Common::ParamPackage& params) const; + + /// Handle mouse button press events. + void mousePressEvent(QMouseEvent* event) override; + + /// Handle key press events. + void keyPressEvent(QKeyEvent* event) override; + + QString ButtonToText(const Common::ParamPackage& param); + + QString AnalogToText(const Common::ParamPackage& param, const std::string& dir); + + static constexpr int ANALOG_SUB_BUTTONS_NUM = 2; + + // A group of four QPushButtons represent one analog input. The buttons each represent left, + // right, respectively. + std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM> analog_map_buttons; + + static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons; + + std::unique_ptr<QTimer> timeout_timer; + std::unique_ptr<QTimer> poll_timer; + + /// This will be the the setting function when an input is awaiting configuration. + std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; + + InputCommon::InputSubsystem* input_subsystem; + Core::HID::EmulatedDevices* emulated_device; + + std::unique_ptr<Ui::ConfigureRingController> ui; +}; diff --git a/src/yuzu/configuration/configure_ringcon.ui b/src/yuzu/configuration/configure_ringcon.ui new file mode 100644 index 000000000..9ec634dd4 --- /dev/null +++ b/src/yuzu/configuration/configure_ringcon.ui @@ -0,0 +1,278 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureRingController</class> + <widget class="QDialog" name="ConfigureRingController"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>298</width> + <height>339</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Ring Controller</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label_2"> + <property name="minimumSize"> + <size> + <width>280</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>If you want to use this controller configure player 1 as right controller and player 2 as dual joycon before starting the game to allow this controller to be detected properly.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>10</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="RingAnalog"> + <property name="title"> + <string>Ring Sensor Parameters</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>0</number> + </property> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="buttonRingAnalogPullHorizontaLayout"> + <property name="spacing"> + <number>3</number> + </property> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonRingAnalogPullGroup"> + <property name="title"> + <string>Pull</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonRingAnalogPullVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="buttonRingAnalogPull"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Pull</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item alignment="Qt::AlignHCenter"> + <widget class="QGroupBox" name="buttonRingAnalogPushGroup"> + <property name="title"> + <string>Push</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <layout class="QVBoxLayout" name="buttonRingAnalogPushVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>3</number> + </property> + <property name="topMargin"> + <number>3</number> + </property> + <property name="rightMargin"> + <number>3</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <widget class="QPushButton" name="buttonRingAnalogPush"> + <property name="minimumSize"> + <size> + <width>68</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>68</width> + <height>16777215</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">min-width: 68px;</string> + </property> + <property name="text"> + <string>Push</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>10</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>3</number> + </property> + <item> + <layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRingAnalogDeadzone"> + <property name="text"> + <string>Deadzone: 0%</string> + </property> + <property name="alignment"> + <set>Qt::AlignHCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QSlider" name="sliderRingAnalogDeadzone"> + <property name="maximum"> + <number>100</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="restore_defaults_button"> + <property name="text"> + <string>Restore Defaults</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureRingController</receiver> + <slot>accept()</slot> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureRingController</receiver> + <slot>reject()</slot> + </connection> + </connections> +</ui> diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 62d15f8cd..52879a989 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -3652,6 +3652,14 @@ void GMainWindow::UpdateUITheme() { setStyleSheet({}); } + QPalette new_pal(qApp->palette()); + if (UISettings::IsDarkTheme()) { + new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255)); + } else { + new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255)); + } + qApp->setPalette(new_pal); + QIcon::setThemeName(current_theme); QIcon::setThemeSearchPaths(theme_paths); } diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp index 21683576c..f683b80f7 100644 --- a/src/yuzu/uisettings.cpp +++ b/src/yuzu/uisettings.cpp @@ -15,6 +15,14 @@ const Themes themes{{ {"Midnight Blue Colorful", "colorful_midnight_blue"}, }}; +bool IsDarkTheme() { + const auto& theme = UISettings::values.theme; + return theme == QStringLiteral("qdarkstyle") || + theme == QStringLiteral("qdarkstyle_midnight_blue") || + theme == QStringLiteral("colorful_dark") || + theme == QStringLiteral("colorful_midnight_blue"); +} + Values values = {}; } // namespace UISettings diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index cc5aee382..15ba9ea17 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -17,6 +17,8 @@ namespace UISettings { +bool IsDarkTheme(); + struct ContextualShortcut { QString keyseq; QString controller_keyseq; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index 57f807826..ae2e62dc5 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -123,14 +123,15 @@ void EmuWindow_SDL2::ShowCursor(bool show_cursor) { } void EmuWindow_SDL2::Fullscreen() { + SDL_DisplayMode display_mode; switch (Settings::values.fullscreen_mode.GetValue()) { case Settings::FullscreenMode::Exclusive: - // Set window size to render size before entering fullscreen -- SDL does not resize to - // display dimensions in this mode. - // TODO: Multiply the window size by resolution_factor (for both docked modes) - if (Settings::values.use_docked_mode) { - SDL_SetWindowSize(render_window, Layout::ScreenDocked::Width, - Layout::ScreenDocked::Height); + // Set window size to render size before entering fullscreen -- SDL2 does not resize window + // to display dimensions automatically in this mode. + if (SDL_GetDesktopDisplayMode(0, &display_mode) == 0) { + SDL_SetWindowSize(render_window, display_mode.w, display_mode.h); + } else { + LOG_ERROR(Frontend, "SDL_GetDesktopDisplayMode failed: {}", SDL_GetError()); } if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN) == 0) { |