Просмотр исходного кода

Merge pull request #6558 from german77/ringcon2

hidbus: Implement hidbus and ringcon
Fernando S 4 лет назад
Родитель
Сommit
fd49b186fa
29 измененных файлов с 2608 добавлено и 28 удалено
  1. 3 0
      src/common/settings.h
  2. 1 0
      src/common/settings_input.h
  3. 10 0
      src/core/CMakeLists.txt
  4. 45 0
      src/core/hid/emulated_devices.cpp
  5. 34 0
      src/core/hid/emulated_devices.h
  6. 18 0
      src/core/hle/kernel/kernel.cpp
  7. 6 0
      src/core/hle/kernel/kernel.h
  8. 1 26
      src/core/hle/service/hid/hid.cpp
  9. 531 0
      src/core/hle/service/hid/hidbus.cpp
  10. 131 0
      src/core/hle/service/hid/hidbus.h
  11. 72 0
      src/core/hle/service/hid/hidbus/hidbus_base.cpp
  12. 179 0
      src/core/hle/service/hid/hidbus/hidbus_base.h
  13. 286 0
      src/core/hle/service/hid/hidbus/ringcon.cpp
  14. 254 0
      src/core/hle/service/hid/hidbus/ringcon.h
  15. 51 0
      src/core/hle/service/hid/hidbus/starlink.cpp
  16. 39 0
      src/core/hle/service/hid/hidbus/starlink.h
  17. 52 0
      src/core/hle/service/hid/hidbus/stubbed.cpp
  18. 39 0
      src/core/hle/service/hid/hidbus/stubbed.h
  19. 3 0
      src/yuzu/CMakeLists.txt
  20. 34 0
      src/yuzu/configuration/config.cpp
  21. 3 0
      src/yuzu/configuration/config.h
  22. 5 0
      src/yuzu/configuration/configure_input.cpp
  23. 8 1
      src/yuzu/configuration/configure_input_advanced.cpp
  24. 1 0
      src/yuzu/configuration/configure_input_advanced.h
  25. 14 0
      src/yuzu/configuration/configure_input_advanced.ui
  26. 1 1
      src/yuzu/configuration/configure_input_player.cpp
  27. 424 0
      src/yuzu/configuration/configure_ringcon.cpp
  28. 85 0
      src/yuzu/configuration/configure_ringcon.h
  29. 278 0
      src/yuzu/configuration/configure_ringcon.ui

+ 3 - 0
src/common/settings.h

@@ -590,6 +590,9 @@ struct Values {
     BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"};
     BasicSetting<int> touch_from_button_map_index{0, "touch_from_button_map"};
     std::vector<TouchFromButtonMap> touch_from_button_maps;
     std::vector<TouchFromButtonMap> touch_from_button_maps;
 
 
+    BasicSetting<bool> enable_ring_controller{true, "enable_ring_controller"};
+    RingconRaw ringcon_analogs;
+
     // Data Storage
     // Data Storage
     BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"};
     BasicSetting<bool> use_virtual_sd{true, "use_virtual_sd"};
     BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"};
     BasicSetting<bool> gamecard_inserted{false, "gamecard_inserted"};

+ 1 - 0
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 AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
 using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
 using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
 using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
 using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
+using RingconRaw = std::string;
 
 
 constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
 constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
 constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
 constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;

+ 10 - 0
src/core/CMakeLists.txt

@@ -434,6 +434,8 @@ add_library(core STATIC
     hle/service/grc/grc.h
     hle/service/grc/grc.h
     hle/service/hid/hid.cpp
     hle/service/hid/hid.cpp
     hle/service/hid/hid.h
     hle/service/hid/hid.h
+    hle/service/hid/hidbus.cpp
+    hle/service/hid/hidbus.h
     hle/service/hid/irs.cpp
     hle/service/hid/irs.cpp
     hle/service/hid/irs.h
     hle/service/hid/irs.h
     hle/service/hid/ring_lifo.h
     hle/service/hid/ring_lifo.h
@@ -460,6 +462,14 @@ add_library(core STATIC
     hle/service/hid/controllers/touchscreen.h
     hle/service/hid/controllers/touchscreen.h
     hle/service/hid/controllers/xpad.cpp
     hle/service/hid/controllers/xpad.cpp
     hle/service/hid/controllers/xpad.h
     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.cpp
     hle/service/jit/jit_context.h
     hle/service/jit/jit_context.h
     hle/service/jit/jit.cpp
     hle/service/jit/jit.cpp

+ 45 - 0
src/core/hid/emulated_devices.cpp

@@ -15,6 +15,7 @@ EmulatedDevices::EmulatedDevices() = default;
 EmulatedDevices::~EmulatedDevices() = default;
 EmulatedDevices::~EmulatedDevices() = default;
 
 
 void EmulatedDevices::ReloadFromSettings() {
 void EmulatedDevices::ReloadFromSettings() {
+    ring_params = Common::ParamPackage(Settings::values.ringcon_analogs);
     ReloadInput();
     ReloadInput();
 }
 }
 
 
@@ -66,6 +67,8 @@ void EmulatedDevices::ReloadInput() {
         key_index++;
         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) {
     for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
         if (!mouse_button_devices[index]) {
         if (!mouse_button_devices[index]) {
             continue;
             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() {
 void EmulatedDevices::UnloadInput() {
@@ -155,6 +165,7 @@ void EmulatedDevices::SaveCurrentConfig() {
     if (!is_configuring) {
     if (!is_configuring) {
         return;
         return;
     }
     }
+    Settings::values.ringcon_analogs = ring_params.Serialize();
 }
 }
 
 
 void EmulatedDevices::RestoreConfig() {
 void EmulatedDevices::RestoreConfig() {
@@ -164,6 +175,15 @@ void EmulatedDevices::RestoreConfig() {
     ReloadFromSettings();
     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,
 void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
                                         std::size_t index) {
                                         std::size_t index) {
     if (index >= device_status.keyboard_values.size()) {
     if (index >= device_status.keyboard_values.size()) {
@@ -410,6 +430,23 @@ void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callbac
     TriggerOnChange(DeviceTriggerType::Mouse);
     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 {
 KeyboardValues EmulatedDevices::GetKeyboardValues() const {
     std::scoped_lock lock{mutex};
     std::scoped_lock lock{mutex};
     return device_status.keyboard_values;
     return device_status.keyboard_values;
@@ -425,6 +462,10 @@ MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
     return device_status.mouse_button_values;
     return device_status.mouse_button_values;
 }
 }
 
 
+RingAnalogValue EmulatedDevices::GetRingSensorValues() const {
+    return device_status.ring_analog_value;
+}
+
 KeyboardKey EmulatedDevices::GetKeyboard() const {
 KeyboardKey EmulatedDevices::GetKeyboard() const {
     std::scoped_lock lock{mutex};
     std::scoped_lock lock{mutex};
     return device_status.keyboard_state;
     return device_status.keyboard_state;
@@ -450,6 +491,10 @@ AnalogStickState EmulatedDevices::GetMouseWheel() const {
     return device_status.mouse_wheel_state;
     return device_status.mouse_wheel_state;
 }
 }
 
 
+RingSensorForce EmulatedDevices::GetRingSensorForce() const {
+    return device_status.ring_analog_state;
+}
+
 void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
 void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
     std::scoped_lock lock{callback_mutex};
     std::scoped_lock lock{callback_mutex};
     for (const auto& poller_pair : callback_list) {
     for (const auto& poller_pair : callback_list) {

+ 34 - 0
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>,
 using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
                                       Settings::NativeMouseWheel::NumMouseWheels>;
                                       Settings::NativeMouseWheel::NumMouseWheels>;
 using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
 using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
+using RingAnalogDevice = std::unique_ptr<Common::Input::InputDevice>;
 
 
 using MouseButtonParams =
 using MouseButtonParams =
     std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
     std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
+using RingAnalogParams = Common::ParamPackage;
 
 
 using KeyboardValues =
 using KeyboardValues =
     std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
     std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
@@ -39,12 +41,17 @@ using MouseButtonValues =
 using MouseAnalogValues =
 using MouseAnalogValues =
     std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
     std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
 using MouseStickValue = Common::Input::TouchStatus;
 using MouseStickValue = Common::Input::TouchStatus;
+using RingAnalogValue = Common::Input::AnalogStatus;
 
 
 struct MousePosition {
 struct MousePosition {
     f32 x;
     f32 x;
     f32 y;
     f32 y;
 };
 };
 
 
+struct RingSensorForce {
+    f32 force;
+};
+
 struct DeviceStatus {
 struct DeviceStatus {
     // Data from input_common
     // Data from input_common
     KeyboardValues keyboard_values{};
     KeyboardValues keyboard_values{};
@@ -52,6 +59,7 @@ struct DeviceStatus {
     MouseButtonValues mouse_button_values{};
     MouseButtonValues mouse_button_values{};
     MouseAnalogValues mouse_analog_values{};
     MouseAnalogValues mouse_analog_values{};
     MouseStickValue mouse_stick_value{};
     MouseStickValue mouse_stick_value{};
+    RingAnalogValue ring_analog_value{};
 
 
     // Data for HID serices
     // Data for HID serices
     KeyboardKey keyboard_state{};
     KeyboardKey keyboard_state{};
@@ -59,12 +67,14 @@ struct DeviceStatus {
     MouseButton mouse_button_state{};
     MouseButton mouse_button_state{};
     MousePosition mouse_position_state{};
     MousePosition mouse_position_state{};
     AnalogStickState mouse_wheel_state{};
     AnalogStickState mouse_wheel_state{};
+    RingSensorForce ring_analog_state{};
 };
 };
 
 
 enum class DeviceTriggerType {
 enum class DeviceTriggerType {
     Keyboard,
     Keyboard,
     KeyboardModdifier,
     KeyboardModdifier,
     Mouse,
     Mouse,
+    RingController,
 };
 };
 
 
 struct InterfaceUpdateCallback {
 struct InterfaceUpdateCallback {
@@ -110,6 +120,15 @@ public:
     /// Reverts any mapped changes made that weren't saved
     /// Reverts any mapped changes made that weren't saved
     void RestoreConfig();
     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
     /// Returns the latest status of button input from the keyboard with parameters
     KeyboardValues GetKeyboardValues() const;
     KeyboardValues GetKeyboardValues() const;
 
 
@@ -119,6 +138,9 @@ public:
     /// Returns the latest status of button input from the mouse with parameters
     /// Returns the latest status of button input from the mouse with parameters
     MouseButtonValues GetMouseButtonsValues() const;
     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
     /// Returns the latest status of button input from the keyboard
     KeyboardKey GetKeyboard() const;
     KeyboardKey GetKeyboard() const;
 
 
@@ -134,6 +156,9 @@ public:
     /// Returns the latest mouse wheel change
     /// Returns the latest mouse wheel change
     AnalogStickState GetMouseWheel() const;
     AnalogStickState GetMouseWheel() const;
 
 
+    /// Returns the latest ringcon force sensor value
+    RingSensorForce GetRingSensorForce() const;
+
     /**
     /**
      * Adds a callback to the list of events
      * Adds a callback to the list of events
      * @param update_callback InterfaceUpdateCallback that will be triggered
      * @param update_callback InterfaceUpdateCallback that will be triggered
@@ -185,6 +210,12 @@ private:
      */
      */
     void SetMouseStick(const Common::Input::CallbackStatus& callback);
     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
      * Triggers a callback that something has changed on the device status
      * @param type Input type of the event to trigger
      * @param type Input type of the event to trigger
@@ -193,11 +224,14 @@ private:
 
 
     bool is_configuring{false};
     bool is_configuring{false};
 
 
+    RingAnalogParams ring_params;
+
     KeyboardDevices keyboard_devices;
     KeyboardDevices keyboard_devices;
     KeyboardModifierDevices keyboard_modifier_devices;
     KeyboardModifierDevices keyboard_modifier_devices;
     MouseButtonDevices mouse_button_devices;
     MouseButtonDevices mouse_button_devices;
     MouseAnalogDevices mouse_analog_devices;
     MouseAnalogDevices mouse_analog_devices;
     MouseStickDevice mouse_stick_device;
     MouseStickDevice mouse_stick_device;
+    RingAnalogDevice ring_analog_device;
 
 
     mutable std::mutex mutex;
     mutable std::mutex mutex;
     mutable std::mutex callback_mutex;
     mutable std::mutex callback_mutex;

+ 18 - 0
src/core/hle/kernel/kernel.cpp

@@ -140,6 +140,7 @@ struct KernelCore::Impl {
         CleanupObject(font_shared_mem);
         CleanupObject(font_shared_mem);
         CleanupObject(irs_shared_mem);
         CleanupObject(irs_shared_mem);
         CleanupObject(time_shared_mem);
         CleanupObject(time_shared_mem);
+        CleanupObject(hidbus_shared_mem);
         CleanupObject(system_resource_limit);
         CleanupObject(system_resource_limit);
 
 
         for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
         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 font_size{0x1100000};
         constexpr std::size_t irs_size{0x8000};
         constexpr std::size_t irs_size{0x8000};
         constexpr std::size_t time_size{0x1000};
         constexpr std::size_t time_size{0x1000};
+        constexpr std::size_t hidbus_size{0x1000};
 
 
         const PAddr hid_phys_addr{system_pool.GetAddress()};
         const PAddr hid_phys_addr{system_pool.GetAddress()};
         const PAddr font_phys_addr{system_pool.GetAddress() + hid_size};
         const PAddr font_phys_addr{system_pool.GetAddress() + hid_size};
         const PAddr irs_phys_addr{system_pool.GetAddress() + hid_size + font_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 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());
         hid_shared_mem = KSharedMemory::Create(system.Kernel());
         font_shared_mem = KSharedMemory::Create(system.Kernel());
         font_shared_mem = KSharedMemory::Create(system.Kernel());
         irs_shared_mem = KSharedMemory::Create(system.Kernel());
         irs_shared_mem = KSharedMemory::Create(system.Kernel());
         time_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_shared_mem->Initialize(system.DeviceMemory(), nullptr,
                                    {hid_phys_addr, hid_size / PageSize},
                                    {hid_phys_addr, hid_size / PageSize},
@@ -649,6 +654,10 @@ struct KernelCore::Impl {
                                     {time_phys_addr, time_size / PageSize},
                                     {time_phys_addr, time_size / PageSize},
                                     Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
                                     Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
                                     time_phys_addr, time_size, "Time:SharedMemory");
                                     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) {
     KClientPort* CreateNamedServicePort(std::string name) {
@@ -748,6 +757,7 @@ struct KernelCore::Impl {
     Kernel::KSharedMemory* font_shared_mem{};
     Kernel::KSharedMemory* font_shared_mem{};
     Kernel::KSharedMemory* irs_shared_mem{};
     Kernel::KSharedMemory* irs_shared_mem{};
     Kernel::KSharedMemory* time_shared_mem{};
     Kernel::KSharedMemory* time_shared_mem{};
+    Kernel::KSharedMemory* hidbus_shared_mem{};
 
 
     // Memory layout
     // Memory layout
     std::unique_ptr<KMemoryLayout> memory_layout;
     std::unique_ptr<KMemoryLayout> memory_layout;
@@ -1047,6 +1057,14 @@ const Kernel::KSharedMemory& KernelCore::GetTimeSharedMem() const {
     return *impl->time_shared_mem;
     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) {
 void KernelCore::Suspend(bool in_suspention) {
     const bool should_suspend = exception_exited || in_suspention;
     const bool should_suspend = exception_exited || in_suspention;
     {
     {

+ 6 - 0
src/core/hle/kernel/kernel.h

@@ -264,6 +264,12 @@ public:
     /// Gets the shared memory object for Time services.
     /// Gets the shared memory object for Time services.
     const Kernel::KSharedMemory& GetTimeSharedMem() const;
     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.
     /// Suspend/unsuspend the OS.
     void Suspend(bool in_suspention);
     void Suspend(bool in_suspention);
 
 

+ 1 - 26
src/core/hle/service/hid/hid.cpp

@@ -16,6 +16,7 @@
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/service/hid/errors.h"
 #include "core/hle/service/hid/errors.h"
 #include "core/hle/service/hid/hid.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/irs.h"
 #include "core/hle/service/hid/xcd.h"
 #include "core/hle/service/hid/xcd.h"
 #include "core/memory.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) {
 void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
     std::make_shared<Hid>(system)->InstallAsService(service_manager);
     std::make_shared<Hid>(system)->InstallAsService(service_manager);
     std::make_shared<HidBus>(system)->InstallAsService(service_manager);
     std::make_shared<HidBus>(system)->InstallAsService(service_manager);

+ 531 - 0
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

+ 131 - 0
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

+ 72 - 0
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

+ 179 - 0
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

+ 286 - 0
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

+ 254 - 0
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

+ 51 - 0
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

+ 39 - 0
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

+ 52 - 0
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

+ 39 - 0
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

+ 3 - 0
src/yuzu/CMakeLists.txt

@@ -99,6 +99,9 @@ add_executable(yuzu
     configuration/configure_profile_manager.cpp
     configuration/configure_profile_manager.cpp
     configuration/configure_profile_manager.h
     configuration/configure_profile_manager.h
     configuration/configure_profile_manager.ui
     configuration/configure_profile_manager.ui
+    configuration/configure_ringcon.cpp
+    configuration/configure_ringcon.h
+    configuration/configure_ringcon.ui
     configuration/configure_network.cpp
     configuration/configure_network.cpp
     configuration/configure_network.h
     configuration/configure_network.h
     configuration/configure_network.ui
     configuration/configure_network.ui

+ 34 - 0
src/yuzu/configuration/config.cpp

@@ -60,6 +60,11 @@ const std::array<int, 2> Config::default_stick_mod = {
     0,
     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
 // This shouldn't have anything except static initializers (no functions). So
 // QKeySequence(...).toString() is NOT ALLOWED HERE.
 // QKeySequence(...).toString() is NOT ALLOWED HERE.
 // This must be in alphabetical order according to action name as it must have the same order as
 // 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();
         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() {
 void Config::ReadAudioValues() {
     qt_config->beginGroup(QStringLiteral("Audio"));
     qt_config->beginGroup(QStringLiteral("Audio"));
 
 
@@ -369,6 +391,7 @@ void Config::ReadControlValues() {
     ReadMouseValues();
     ReadMouseValues();
     ReadTouchscreenValues();
     ReadTouchscreenValues();
     ReadMotionTouchValues();
     ReadMotionTouchValues();
+    ReadHidbusValues();
 
 
 #ifdef _WIN32
 #ifdef _WIN32
     ReadBasicSetting(Settings::values.enable_raw_input);
     ReadBasicSetting(Settings::values.enable_raw_input);
@@ -962,6 +985,16 @@ void Config::SaveMotionTouchValues() {
     qt_config->endArray();
     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() {
 void Config::SaveValues() {
     if (global) {
     if (global) {
         SaveControlValues();
         SaveControlValues();
@@ -1002,6 +1035,7 @@ void Config::SaveControlValues() {
     SaveMouseValues();
     SaveMouseValues();
     SaveTouchscreenValues();
     SaveTouchscreenValues();
     SaveMotionTouchValues();
     SaveMotionTouchValues();
+    SaveHidbusValues();
 
 
     WriteGlobalSetting(Settings::values.use_docked_mode);
     WriteGlobalSetting(Settings::values.use_docked_mode);
     WriteGlobalSetting(Settings::values.vibration_enabled);
     WriteGlobalSetting(Settings::values.vibration_enabled);

+ 3 - 0
src/yuzu/configuration/config.h

@@ -42,6 +42,7 @@ public:
     static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
     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<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_stick_mod;
+    static const std::array<int, 2> default_ringcon_analogs;
     static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
     static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
         default_mouse_buttons;
         default_mouse_buttons;
     static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
     static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
@@ -66,6 +67,7 @@ private:
     void ReadMouseValues();
     void ReadMouseValues();
     void ReadTouchscreenValues();
     void ReadTouchscreenValues();
     void ReadMotionTouchValues();
     void ReadMotionTouchValues();
+    void ReadHidbusValues();
 
 
     // Read functions bases off the respective config section names.
     // Read functions bases off the respective config section names.
     void ReadAudioValues();
     void ReadAudioValues();
@@ -93,6 +95,7 @@ private:
     void SaveMouseValues();
     void SaveMouseValues();
     void SaveTouchscreenValues();
     void SaveTouchscreenValues();
     void SaveMotionTouchValues();
     void SaveMotionTouchValues();
+    void SaveHidbusValues();
 
 
     // Save functions based off the respective config section names.
     // Save functions based off the respective config section names.
     void SaveAudioValues();
     void SaveAudioValues();

+ 5 - 0
src/yuzu/configuration/configure_input.cpp

@@ -20,6 +20,7 @@
 #include "yuzu/configuration/configure_input_advanced.h"
 #include "yuzu/configuration/configure_input_advanced.h"
 #include "yuzu/configuration/configure_input_player.h"
 #include "yuzu/configuration/configure_input_player.h"
 #include "yuzu/configuration/configure_motion_touch.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_touchscreen_advanced.h"
 #include "yuzu/configuration/configure_vibration.h"
 #include "yuzu/configuration/configure_vibration.h"
 #include "yuzu/configuration/input_profiles.h"
 #include "yuzu/configuration/input_profiles.h"
@@ -158,6 +159,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
             [this, input_subsystem] {
             [this, input_subsystem] {
                 CallConfigureDialog<ConfigureMotionTouch>(*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,
     connect(ui->vibrationButton, &QPushButton::clicked,
             [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });
             [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });

+ 8 - 1
src/yuzu/configuration/configure_input_advanced.cpp

@@ -79,13 +79,17 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
             &ConfigureInputAdvanced::UpdateUIEnabled);
             &ConfigureInputAdvanced::UpdateUIEnabled);
     connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
     connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
             &ConfigureInputAdvanced::UpdateUIEnabled);
             &ConfigureInputAdvanced::UpdateUIEnabled);
+    connect(ui->enable_ring_controller, &QCheckBox::stateChanged, this,
+            &ConfigureInputAdvanced::UpdateUIEnabled);
 
 
     connect(ui->debug_configure, &QPushButton::clicked, this,
     connect(ui->debug_configure, &QPushButton::clicked, this,
             [this] { CallDebugControllerDialog(); });
             [this] { CallDebugControllerDialog(); });
     connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
     connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
             [this] { CallTouchscreenConfigDialog(); });
             [this] { CallTouchscreenConfigDialog(); });
     connect(ui->buttonMotionTouch, &QPushButton::clicked, this,
     connect(ui->buttonMotionTouch, &QPushButton::clicked, this,
-            &ConfigureInputAdvanced::CallMotionTouchConfigDialog);
+            [this] { CallMotionTouchConfigDialog(); });
+    connect(ui->ring_controller_configure, &QPushButton::clicked, this,
+            [this] { CallRingControllerDialog(); });
 
 
 #ifndef _WIN32
 #ifndef _WIN32
     ui->enable_raw_input->setVisible(false);
     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_raw_input = ui->enable_raw_input->isChecked();
     Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
     Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
     Settings::values.controller_navigation = ui->controller_navigation->isChecked();
     Settings::values.controller_navigation = ui->controller_navigation->isChecked();
+    Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked();
 }
 }
 
 
 void ConfigureInputAdvanced::LoadConfiguration() {
 void ConfigureInputAdvanced::LoadConfiguration() {
@@ -164,6 +169,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
     ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue());
     ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue());
     ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
     ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
     ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
     ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
+    ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());
 
 
     UpdateUIEnabled();
     UpdateUIEnabled();
 }
 }
@@ -185,4 +191,5 @@ void ConfigureInputAdvanced::UpdateUIEnabled() {
     ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
     ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
     ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked());
     ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked());
     ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked());
     ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked());
+    ui->ring_controller_configure->setEnabled(ui->enable_ring_controller->isChecked());
 }
 }

+ 1 - 0
src/yuzu/configuration/configure_input_advanced.h

@@ -29,6 +29,7 @@ signals:
     void CallMouseConfigDialog();
     void CallMouseConfigDialog();
     void CallTouchscreenConfigDialog();
     void CallTouchscreenConfigDialog();
     void CallMotionTouchConfigDialog();
     void CallMotionTouchConfigDialog();
+    void CallRingControllerDialog();
 
 
 private:
 private:
     void changeEvent(QEvent* event) override;
     void changeEvent(QEvent* event) override;

+ 14 - 0
src/yuzu/configuration/configure_input_advanced.ui

@@ -2603,6 +2603,20 @@
                  </property>
                  </property>
                 </widget>
                 </widget>
                </item>
                </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>
               </layout>
              </widget>
              </widget>
             </item>
             </item>

+ 1 - 1
src/yuzu/configuration/configure_input_player.cpp

@@ -1407,10 +1407,10 @@ void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
 }
 }
 
 
 void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
 void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
-    event->ignore();
     if (!input_setter || !event) {
     if (!input_setter || !event) {
         return;
         return;
     }
     }
+    event->ignore();
     if (event->key() != Qt::Key_Escape) {
     if (event->key() != Qt::Key_Escape) {
         input_subsystem->GetKeyboard()->PressKey(event->key());
         input_subsystem->GetKeyboard()->PressKey(event->key());
     }
     }

+ 424 - 0
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]");
+}

+ 85 - 0
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;
+};

+ 278 - 0
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>