From d80e6c399bf8196646cca5ac1265d122638bb96b Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 20 Dec 2022 11:34:33 -0600
Subject: [PATCH 01/24] input_common: Initial skeleton for custom joycon driver

---
 src/common/input.h                            |  26 +-
 src/input_common/CMakeLists.txt               |   5 +
 src/input_common/drivers/joycon.cpp           | 615 ++++++++++++++++++
 src/input_common/drivers/joycon.h             | 107 +++
 src/input_common/helpers/joycon_driver.cpp    | 382 +++++++++++
 src/input_common/helpers/joycon_driver.h      | 146 +++++
 .../helpers/joycon_protocol/joycon_types.h    | 494 ++++++++++++++
 src/input_common/main.cpp                     |  14 +
 8 files changed, 1786 insertions(+), 3 deletions(-)
 create mode 100644 src/input_common/drivers/joycon.cpp
 create mode 100644 src/input_common/drivers/joycon.h
 create mode 100644 src/input_common/helpers/joycon_driver.cpp
 create mode 100644 src/input_common/helpers/joycon_driver.h
 create mode 100644 src/input_common/helpers/joycon_protocol/joycon_types.h

diff --git a/src/common/input.h b/src/common/input.h
index d27b1d7728..1e5ba038d8 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -51,6 +51,8 @@ enum class PollingMode {
     NFC,
     // Enable infrared camera polling
     IR,
+    // Enable ring controller polling
+    Ring,
 };
 
 enum class CameraFormat {
@@ -67,6 +69,7 @@ enum class VibrationError {
     None,
     NotSupported,
     Disabled,
+    InvalidHandle,
     Unknown,
 };
 
@@ -74,6 +77,7 @@ enum class VibrationError {
 enum class PollingError {
     None,
     NotSupported,
+    InvalidHandle,
     Unknown,
 };
 
@@ -190,6 +194,8 @@ struct TouchStatus {
 struct BodyColorStatus {
     u32 body{};
     u32 buttons{};
+    u32 left_grip{};
+    u32 right_grip{};
 };
 
 // HD rumble data
@@ -228,17 +234,31 @@ enum class ButtonNames {
     Engine,
     // This will display the button by value instead of the button name
     Value,
+
+    // Joycon button names
     ButtonLeft,
     ButtonRight,
     ButtonDown,
     ButtonUp,
-    TriggerZ,
-    TriggerR,
-    TriggerL,
     ButtonA,
     ButtonB,
     ButtonX,
     ButtonY,
+    ButtonPlus,
+    ButtonMinus,
+    ButtonHome,
+    ButtonCapture,
+    ButtonStickL,
+    ButtonStickR,
+    TriggerL,
+    TriggerZL,
+    TriggerSL,
+    TriggerR,
+    TriggerZR,
+    TriggerSR,
+
+    // GC button names
+    TriggerZ,
     ButtonStart,
 
     // DS4 button names
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index cef2c4d52f..41885d0d26 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -51,8 +51,13 @@ endif()
 
 if (ENABLE_SDL2)
     target_sources(input_common PRIVATE
+        drivers/joycon.cpp
+        drivers/joycon.h
         drivers/sdl_driver.cpp
         drivers/sdl_driver.h
+        helpers/joycon_driver.cpp
+        helpers/joycon_driver.h
+        helpers/joycon_protocol/joycon_types.h
     )
     target_link_libraries(input_common PRIVATE SDL2::SDL2)
     target_compile_definitions(input_common PRIVATE HAVE_SDL2)
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
new file mode 100644
index 0000000000..eab10d11c0
--- /dev/null
+++ b/src/input_common/drivers/joycon.cpp
@@ -0,0 +1,615 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <fmt/format.h>
+
+#include "common/param_package.h"
+#include "common/settings.h"
+#include "common/thread.h"
+#include "input_common/drivers/joycon.h"
+#include "input_common/helpers/joycon_driver.h"
+#include "input_common/helpers/joycon_protocol/joycon_types.h"
+
+namespace InputCommon {
+
+Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) {
+    LOG_INFO(Input, "Joycon driver Initialization started");
+    const int init_res = SDL_hid_init();
+    if (init_res == 0) {
+        Setup();
+    } else {
+        LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res);
+    }
+}
+
+Joycons::~Joycons() {
+    Reset();
+}
+
+void Joycons::Reset() {
+    scan_thread = {};
+    for (const auto& device : left_joycons) {
+        if (!device) {
+            continue;
+        }
+        device->Stop();
+    }
+    for (const auto& device : right_joycons) {
+        if (!device) {
+            continue;
+        }
+        device->Stop();
+    }
+    for (const auto& device : pro_joycons) {
+        if (!device) {
+            continue;
+        }
+        device->Stop();
+    }
+    SDL_hid_exit();
+}
+
+void Joycons::Setup() {
+    u32 port = 0;
+    for (auto& device : left_joycons) {
+        PreSetController(GetIdentifier(port, Joycon::ControllerType::Left));
+        device = std::make_shared<Joycon::JoyconDriver>(port++);
+    }
+    for (auto& device : right_joycons) {
+        PreSetController(GetIdentifier(port, Joycon::ControllerType::Right));
+        device = std::make_shared<Joycon::JoyconDriver>(port++);
+    }
+    for (auto& device : pro_joycons) {
+        PreSetController(GetIdentifier(port, Joycon::ControllerType::Pro));
+        device = std::make_shared<Joycon::JoyconDriver>(port++);
+    }
+
+    if (!scan_thread_running) {
+        scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
+    }
+}
+
+void Joycons::ScanThread(std::stop_token stop_token) {
+    constexpr u16 nintendo_vendor_id = 0x057e;
+    Common::SetCurrentThreadName("yuzu:input:JoyconScanThread");
+    scan_thread_running = true;
+    while (!stop_token.stop_requested()) {
+        SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0);
+        SDL_hid_device_info* cur_dev = devs;
+
+        while (cur_dev) {
+            if (IsDeviceNew(cur_dev)) {
+                LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id,
+                          cur_dev->product_id);
+                RegisterNewDevice(cur_dev);
+            }
+            cur_dev = cur_dev->next;
+        }
+
+        std::this_thread::sleep_for(std::chrono::seconds(5));
+    }
+    scan_thread_running = false;
+}
+
+bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const {
+    Joycon::ControllerType type{};
+    Joycon::SerialNumber serial_number{};
+
+    const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
+    if (result != Joycon::DriverResult::Success) {
+        return false;
+    }
+
+    const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number);
+    if (result2 != Joycon::DriverResult::Success) {
+        return false;
+    }
+
+    auto is_handle_identical = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
+        if (!device) {
+            return false;
+        }
+        if (!device->IsConnected()) {
+            return false;
+        }
+        if (device->GetHandleSerialNumber() != serial_number) {
+            return false;
+        }
+        return true;
+    };
+
+    // Check if device already exist
+    switch (type) {
+    case Joycon::ControllerType::Left:
+        for (const auto& device : left_joycons) {
+            if (is_handle_identical(device)) {
+                return false;
+            }
+        }
+        break;
+    case Joycon::ControllerType::Right:
+        for (const auto& device : right_joycons) {
+            if (is_handle_identical(device)) {
+                return false;
+            }
+        }
+        break;
+    case Joycon::ControllerType::Pro:
+    case Joycon::ControllerType::Grip:
+        for (const auto& device : pro_joycons) {
+            if (is_handle_identical(device)) {
+                return false;
+            }
+        }
+        break;
+    default:
+        return false;
+    }
+
+    return true;
+}
+
+void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
+    Joycon::ControllerType type{};
+    auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
+    auto handle = GetNextFreeHandle(type);
+    if (handle == nullptr) {
+        LOG_WARNING(Input, "No free handles available");
+        return;
+    }
+    if (result == Joycon::DriverResult::Success) {
+        result = handle->RequestDeviceAccess(device_info);
+    }
+    if (result == Joycon::DriverResult::Success) {
+        LOG_WARNING(Input, "Initialize device");
+
+        std::function<void(Joycon::Battery)> on_battery_data;
+        std::function<void(Joycon::Color)> on_button_data;
+        std::function<void(int, f32)> on_stick_data;
+        std::function<void(int, std::array<u8, 6>)> on_motion_data;
+        std::function<void(s16)> on_ring_data;
+        std::function<void(const std::vector<u8>&)> on_amiibo_data;
+
+        const std::size_t port = handle->GetDevicePort();
+        handle->on_battery_data = {
+            [this, port, type](Joycon::Battery value) { OnBatteryUpdate(port, type, value); }};
+        handle->on_color_data = {
+            [this, port, type](Joycon::Color value) { OnColorUpdate(port, type, value); }};
+        handle->on_button_data = {
+            [this, port, type](int id, bool value) { OnButtonUpdate(port, type, id, value); }};
+        handle->on_stick_data = {
+            [this, port, type](int id, f32 value) { OnStickUpdate(port, type, id, value); }};
+        handle->on_motion_data = {[this, port, type](int id, Joycon::MotionData value) {
+            OnMotionUpdate(port, type, id, value);
+        }};
+        handle->on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }};
+        handle->on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) {
+            OnAmiiboUpdate(port, amiibo_data);
+        }};
+        handle->InitializeDevice();
+    }
+}
+
+std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle(
+    Joycon::ControllerType type) const {
+
+    if (type == Joycon::ControllerType::Left) {
+        for (const auto& device : left_joycons) {
+            if (!device->IsConnected()) {
+                return device;
+            }
+        }
+    }
+    if (type == Joycon::ControllerType::Right) {
+        for (const auto& device : right_joycons) {
+            if (!device->IsConnected()) {
+                return device;
+            }
+        }
+    }
+    if (type == Joycon::ControllerType::Pro || type == Joycon::ControllerType::Grip) {
+        for (const auto& device : pro_joycons) {
+            if (!device->IsConnected()) {
+                return device;
+            }
+        }
+    }
+    return nullptr;
+}
+
+bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) {
+    const auto handle = GetHandle(identifier);
+    if (handle == nullptr) {
+        return false;
+    }
+    return handle->IsVibrationEnabled();
+}
+
+Common::Input::VibrationError Joycons::SetVibration(
+    const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
+    const Joycon::VibrationValue native_vibration{
+        .low_amplitude = vibration.low_amplitude,
+        .low_frequency = vibration.low_frequency,
+        .high_amplitude = vibration.high_amplitude,
+        .high_frequency = vibration.high_amplitude,
+    };
+    auto handle = GetHandle(identifier);
+    if (handle == nullptr) {
+        return Common::Input::VibrationError::InvalidHandle;
+    }
+
+    handle->SetVibration(native_vibration);
+    return Common::Input::VibrationError::None;
+}
+
+void Joycons::SetLeds(const PadIdentifier& identifier, const Common::Input::LedStatus& led_status) {
+    auto handle = GetHandle(identifier);
+    if (handle == nullptr) {
+        return;
+    }
+    int led_config = led_status.led_1 ? 1 : 0;
+    led_config += led_status.led_2 ? 2 : 0;
+    led_config += led_status.led_3 ? 4 : 0;
+    led_config += led_status.led_4 ? 8 : 0;
+
+    const auto result = handle->SetLedConfig(static_cast<u8>(led_config));
+    if (result != Joycon::DriverResult::Success) {
+        LOG_ERROR(Input, "Failed to set led config");
+    }
+}
+
+Common::Input::CameraError Joycons::SetCameraFormat(const PadIdentifier& identifier_,
+                                                    Common::Input::CameraFormat camera_format) {
+    return Common::Input::CameraError::NotSupported;
+};
+
+Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const {
+    return Common::Input::NfcState::Success;
+};
+
+Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_,
+                                              const std::vector<u8>& data) {
+    return Common::Input::NfcState::NotSupported;
+};
+
+Common::Input::PollingError Joycons::SetPollingMode(const PadIdentifier& identifier,
+                                                    const Common::Input::PollingMode polling_mode) {
+    auto handle = GetHandle(identifier);
+    if (handle == nullptr) {
+        LOG_ERROR(Input, "Invalid handle {}", identifier.port);
+        return Common::Input::PollingError::InvalidHandle;
+    }
+
+    switch (polling_mode) {
+    case Common::Input::PollingMode::NFC:
+        handle->SetNfcMode();
+        break;
+    case Common::Input::PollingMode::Active:
+        handle->SetActiveMode();
+        break;
+    case Common::Input::PollingMode::Pasive:
+        handle->SetPasiveMode();
+        break;
+    case Common::Input::PollingMode::Ring:
+        handle->SetRingConMode();
+        break;
+    default:
+        return Common::Input::PollingError::NotSupported;
+    }
+
+    return Common::Input::PollingError::None;
+}
+
+void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type,
+                              Joycon::Battery value) {
+    const auto identifier = GetIdentifier(port, type);
+    if (value.charging != 0) {
+        SetBattery(identifier, Common::Input::BatteryLevel::Charging);
+        return;
+    }
+
+    Common::Input::BatteryLevel battery{value.status.Value()};
+    switch (value.status) {
+    case 0:
+        battery = Common::Input::BatteryLevel::Empty;
+        break;
+    case 1:
+        battery = Common::Input::BatteryLevel::Critical;
+        break;
+    case 2:
+        battery = Common::Input::BatteryLevel::Low;
+        break;
+    case 3:
+        battery = Common::Input::BatteryLevel::Medium;
+        break;
+    case 4:
+    default:
+        battery = Common::Input::BatteryLevel::Full;
+        break;
+    }
+    SetBattery(identifier, battery);
+}
+
+void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type,
+                            const Joycon::Color& value) {}
+
+void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) {
+    const auto identifier = GetIdentifier(port, type);
+    SetButton(identifier, id, value);
+}
+
+void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) {
+    const auto identifier = GetIdentifier(port, type);
+    SetAxis(identifier, id, value);
+}
+
+void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
+                             const Joycon::MotionData& value) {
+    const auto identifier = GetIdentifier(port, type);
+    BasicMotion motion_data{
+        .gyro_x = value.gyro_x,
+        .gyro_y = value.gyro_y,
+        .gyro_z = value.gyro_z,
+        .accel_x = value.accel_x,
+        .accel_y = value.accel_y,
+        .accel_z = value.accel_z,
+        .delta_timestamp = 15000,
+    };
+    SetMotion(identifier, id, motion_data);
+}
+
+void Joycons::OnRingConUpdate(f32 ring_data) {
+    // To simplify ring detection it will always be mapped to an empty identifier for all
+    // controllers
+    constexpr PadIdentifier identifier = {
+        .guid = Common::UUID{},
+        .port = 0,
+        .pad = 0,
+    };
+    SetAxis(identifier, 100, ring_data);
+}
+
+void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) {
+    const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
+    SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_data});
+}
+
+std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const {
+    auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
+        if (!device) {
+            return false;
+        }
+        if (!device->IsConnected()) {
+            return false;
+        }
+        if (device->GetDevicePort() == identifier.port) {
+            return true;
+        }
+        return false;
+    };
+    const auto type = static_cast<Joycon::ControllerType>(identifier.pad);
+    if (type == Joycon::ControllerType::Left) {
+        for (const auto& device : left_joycons) {
+            if (is_handle_active(device)) {
+                return device;
+            }
+        }
+    }
+    if (type == Joycon::ControllerType::Right) {
+        for (const auto& device : right_joycons) {
+            if (is_handle_active(device)) {
+                return device;
+            }
+        }
+    }
+    if (type == Joycon::ControllerType::Pro || type == Joycon::ControllerType::Grip) {
+        for (const auto& device : pro_joycons) {
+            if (is_handle_active(device)) {
+                return device;
+            }
+        }
+    }
+    return nullptr;
+}
+
+PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const {
+    return {
+        .guid = Common::UUID{Common::InvalidUUID},
+        .port = port,
+        .pad = static_cast<std::size_t>(type),
+    };
+}
+
+std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
+    std::vector<Common::ParamPackage> devices{};
+
+    auto add_entry = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
+        if (!device) {
+            return;
+        }
+        if (!device->IsConnected()) {
+            return;
+        }
+        std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()),
+                                       device->GetDevicePort());
+        devices.emplace_back(Common::ParamPackage{
+            {"engine", GetEngineName()},
+            {"display", std::move(name)},
+            {"port", std::to_string(device->GetDevicePort())},
+            {"pad", std::to_string(static_cast<std::size_t>(device->GetHandleDeviceType()))},
+        });
+    };
+
+    for (const auto& controller : left_joycons) {
+        add_entry(controller);
+    }
+    for (const auto& controller : right_joycons) {
+        add_entry(controller);
+    }
+    for (const auto& controller : pro_joycons) {
+        add_entry(controller);
+    }
+
+    return devices;
+}
+
+ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) {
+    static constexpr std::array<std::pair<Settings::NativeButton::Values, Joycon::PadButton>, 20>
+        switch_to_joycon_button = {
+            std::pair{Settings::NativeButton::A, Joycon::PadButton::A},
+            {Settings::NativeButton::B, Joycon::PadButton::B},
+            {Settings::NativeButton::X, Joycon::PadButton::X},
+            {Settings::NativeButton::Y, Joycon::PadButton::Y},
+            {Settings::NativeButton::DLeft, Joycon::PadButton::Left},
+            {Settings::NativeButton::DUp, Joycon::PadButton::Up},
+            {Settings::NativeButton::DRight, Joycon::PadButton::Right},
+            {Settings::NativeButton::DDown, Joycon::PadButton::Down},
+            {Settings::NativeButton::SL, Joycon::PadButton::LeftSL},
+            {Settings::NativeButton::SR, Joycon::PadButton::LeftSR},
+            {Settings::NativeButton::L, Joycon::PadButton::L},
+            {Settings::NativeButton::R, Joycon::PadButton::R},
+            {Settings::NativeButton::ZL, Joycon::PadButton::ZL},
+            {Settings::NativeButton::ZR, Joycon::PadButton::ZR},
+            {Settings::NativeButton::Plus, Joycon::PadButton::Plus},
+            {Settings::NativeButton::Minus, Joycon::PadButton::Minus},
+            {Settings::NativeButton::Home, Joycon::PadButton::Home},
+            {Settings::NativeButton::Screenshot, Joycon::PadButton::Capture},
+            {Settings::NativeButton::LStick, Joycon::PadButton::StickL},
+            {Settings::NativeButton::RStick, Joycon::PadButton::StickR},
+        };
+
+    if (!params.Has("port")) {
+        return {};
+    }
+
+    ButtonMapping mapping{};
+    for (const auto& [switch_button, joycon_button] : switch_to_joycon_button) {
+        Common::ParamPackage button_params{};
+        button_params.Set("engine", GetEngineName());
+        button_params.Set("port", params.Get("port", 0));
+        button_params.Set("button", static_cast<int>(joycon_button));
+        mapping.insert_or_assign(switch_button, std::move(button_params));
+    }
+
+    return mapping;
+}
+
+AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
+    if (!params.Has("port")) {
+        return {};
+    }
+
+    AnalogMapping mapping = {};
+    Common::ParamPackage left_analog_params;
+    left_analog_params.Set("engine", GetEngineName());
+    left_analog_params.Set("port", params.Get("port", 0));
+    left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX));
+    left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY));
+    mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
+    Common::ParamPackage right_analog_params;
+    right_analog_params.Set("engine", GetEngineName());
+    right_analog_params.Set("port", params.Get("port", 0));
+    right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX));
+    right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY));
+    mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
+    return mapping;
+}
+
+MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) {
+    if (!params.Has("port")) {
+        return {};
+    }
+
+    MotionMapping mapping = {};
+    Common::ParamPackage left_motion_params;
+    left_motion_params.Set("engine", GetEngineName());
+    left_motion_params.Set("port", params.Get("port", 0));
+    left_motion_params.Set("motion", 0);
+    mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
+    Common::ParamPackage right_Motion_params;
+    right_Motion_params.Set("engine", GetEngineName());
+    right_Motion_params.Set("port", params.Get("port", 0));
+    right_Motion_params.Set("motion", 1);
+    mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params));
+    return mapping;
+}
+
+Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const {
+    const auto button = static_cast<Joycon::PadButton>(params.Get("button", 0));
+    switch (button) {
+    case Joycon::PadButton::Left:
+        return Common::Input::ButtonNames::ButtonLeft;
+    case Joycon::PadButton::Right:
+        return Common::Input::ButtonNames::ButtonRight;
+    case Joycon::PadButton::Down:
+        return Common::Input::ButtonNames::ButtonDown;
+    case Joycon::PadButton::Up:
+        return Common::Input::ButtonNames::ButtonUp;
+    case Joycon::PadButton::LeftSL:
+    case Joycon::PadButton::RightSL:
+        return Common::Input::ButtonNames::TriggerSL;
+    case Joycon::PadButton::LeftSR:
+    case Joycon::PadButton::RightSR:
+        return Common::Input::ButtonNames::TriggerSR;
+    case Joycon::PadButton::L:
+        return Common::Input::ButtonNames::TriggerL;
+    case Joycon::PadButton::R:
+        return Common::Input::ButtonNames::TriggerR;
+    case Joycon::PadButton::ZL:
+        return Common::Input::ButtonNames::TriggerZL;
+    case Joycon::PadButton::ZR:
+        return Common::Input::ButtonNames::TriggerZR;
+    case Joycon::PadButton::A:
+        return Common::Input::ButtonNames::ButtonA;
+    case Joycon::PadButton::B:
+        return Common::Input::ButtonNames::ButtonB;
+    case Joycon::PadButton::X:
+        return Common::Input::ButtonNames::ButtonX;
+    case Joycon::PadButton::Y:
+        return Common::Input::ButtonNames::ButtonY;
+    case Joycon::PadButton::Plus:
+        return Common::Input::ButtonNames::ButtonPlus;
+    case Joycon::PadButton::Minus:
+        return Common::Input::ButtonNames::ButtonMinus;
+    case Joycon::PadButton::Home:
+        return Common::Input::ButtonNames::ButtonHome;
+    case Joycon::PadButton::Capture:
+        return Common::Input::ButtonNames::ButtonCapture;
+    case Joycon::PadButton::StickL:
+        return Common::Input::ButtonNames::ButtonStickL;
+    case Joycon::PadButton::StickR:
+        return Common::Input::ButtonNames::ButtonStickR;
+    default:
+        return Common::Input::ButtonNames::Undefined;
+    }
+}
+
+Common::Input::ButtonNames Joycons::GetUIName(const Common::ParamPackage& params) const {
+    if (params.Has("button")) {
+        return GetUIButtonName(params);
+    }
+    if (params.Has("axis")) {
+        return Common::Input::ButtonNames::Value;
+    }
+    if (params.Has("motion")) {
+        return Common::Input::ButtonNames::Engine;
+    }
+
+    return Common::Input::ButtonNames::Invalid;
+}
+
+std::string Joycons::JoyconName(Joycon::ControllerType type) const {
+    switch (type) {
+    case Joycon::ControllerType::Left:
+        return "Left Joycon";
+    case Joycon::ControllerType::Right:
+        return "Right Joycon";
+    case Joycon::ControllerType::Pro:
+        return "Pro Controller";
+    case Joycon::ControllerType::Grip:
+        return "Grip Controller";
+    default:
+        return "Unknow Joycon";
+    }
+}
+} // namespace InputCommon
diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h
new file mode 100644
index 0000000000..56c1172701
--- /dev/null
+++ b/src/input_common/drivers/joycon.h
@@ -0,0 +1,107 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <span>
+#include <thread>
+#include <SDL_hidapi.h>
+
+#include "input_common/input_engine.h"
+
+namespace InputCommon::Joycon {
+using SerialNumber = std::array<u8, 15>;
+struct Battery;
+struct Color;
+struct MotionData;
+enum class ControllerType;
+enum class DriverResult;
+class JoyconDriver;
+} // namespace InputCommon::Joycon
+
+namespace InputCommon {
+
+class Joycons final : public InputCommon::InputEngine {
+public:
+    explicit Joycons(const std::string& input_engine_);
+
+    ~Joycons();
+
+    bool IsVibrationEnabled(const PadIdentifier& identifier) override;
+    Common::Input::VibrationError SetVibration(
+        const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
+
+    void SetLeds(const PadIdentifier& identifier,
+                 const Common::Input::LedStatus& led_status) override;
+
+    Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_,
+                                               Common::Input::CameraFormat camera_format) override;
+
+    Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
+    Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
+                                         const std::vector<u8>& data) override;
+
+    Common::Input::PollingError SetPollingMode(
+        const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override;
+
+    /// Used for automapping features
+    std::vector<Common::ParamPackage> GetInputDevices() const override;
+    ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
+    AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
+    MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
+    Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
+
+private:
+    static constexpr std::size_t MaxSupportedControllers = 8;
+
+    /// For shutting down, clear all data, join all threads, release usb devices
+    void Reset();
+
+    /// Registers controllers, clears all data and starts the scan thread
+    void Setup();
+
+    /// Actively searchs for new devices
+    void ScanThread(std::stop_token stop_token);
+
+    /// Returns true if device is valid and not registered
+    bool IsDeviceNew(SDL_hid_device_info* device_info) const;
+
+    /// Tries to connect to the new device
+    void RegisterNewDevice(SDL_hid_device_info* device_info);
+
+    /// Returns the next free handle
+    std::shared_ptr<Joycon::JoyconDriver> GetNextFreeHandle(Joycon::ControllerType type) const;
+
+    void OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, Joycon::Battery value);
+    void OnColorUpdate(std::size_t port, Joycon::ControllerType type, const Joycon::Color& value);
+    void OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value);
+    void OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value);
+    void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
+                        const Joycon::MotionData& value);
+    void OnRingConUpdate(f32 ring_data);
+    void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data);
+
+    /// Returns a JoyconHandle corresponding to a PadIdentifier
+    std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const;
+
+    /// Returns a PadIdentifier corresponding to the port number
+    PadIdentifier GetIdentifier(std::size_t port, Joycon::ControllerType type) const;
+
+    std::string JoyconName(std::size_t port) const;
+
+    Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
+
+    /// Returns the name of the device in text format
+    std::string JoyconName(Joycon::ControllerType type) const;
+
+    std::jthread scan_thread;
+    bool scan_thread_running{};
+
+    // Joycon types are split by type to ease supporting dualjoycon configurations
+    std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{};
+    std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> right_joycons{};
+    std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> pro_joycons{};
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
new file mode 100644
index 0000000000..a0a2a180b5
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -0,0 +1,382 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "common/swap.h"
+#include "common/thread.h"
+#include "input_common/helpers/joycon_driver.h"
+
+namespace InputCommon::Joycon {
+JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} {
+    hidapi_handle = std::make_shared<JoyconHandle>();
+}
+
+JoyconDriver::~JoyconDriver() {
+    Stop();
+}
+
+void JoyconDriver::Stop() {
+    is_connected = false;
+    input_thread = {};
+}
+
+DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) {
+    std::scoped_lock lock{mutex};
+
+    handle_device_type = ControllerType::None;
+    GetDeviceType(device_info, handle_device_type);
+    if (handle_device_type == ControllerType::None) {
+        return DriverResult::UnsupportedControllerType;
+    }
+
+    hidapi_handle->handle =
+        SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
+    std::memcpy(&handle_serial_number, device_info->serial_number, 15);
+    if (!hidapi_handle->handle) {
+        LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
+                  device_info->vendor_id, device_info->product_id);
+        return DriverResult::HandleInUse;
+    }
+    SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
+    return DriverResult::Success;
+}
+
+DriverResult JoyconDriver::InitializeDevice() {
+    if (!hidapi_handle->handle) {
+        return DriverResult::InvalidHandle;
+    }
+    std::scoped_lock lock{mutex};
+    disable_input_thread = true;
+
+    // Reset Counters
+    error_counter = 0;
+    hidapi_handle->packet_counter = 0;
+
+    // Set HW default configuration
+    vibration_enabled = true;
+    motion_enabled = true;
+    hidbus_enabled = false;
+    nfc_enabled = false;
+    passive_enabled = false;
+    gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
+    gyro_performance = Joycon::GyroPerformance::HZ833;
+    accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
+    accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
+
+    // Initialize HW Protocols
+
+    // Get fixed joycon info
+    supported_features = GetSupportedFeatures();
+
+    // Get Calibration data
+
+    // Set led status
+
+    // Apply HW configuration
+    SetPollingMode();
+
+    // Start pooling for data
+    is_connected = true;
+    if (!input_thread_running) {
+        input_thread =
+            std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
+    }
+
+    disable_input_thread = false;
+    return DriverResult::Success;
+}
+
+void JoyconDriver::InputThread(std::stop_token stop_token) {
+    LOG_INFO(Input, "JC Adapter input thread started");
+    Common::SetCurrentThreadName("JoyconInput");
+    input_thread_running = true;
+
+    // Max update rate is 5ms, ensure we are always able to read a bit faster
+    constexpr int ThreadDelay = 2;
+    std::vector<u8> buffer(MaxBufferSize);
+
+    while (!stop_token.stop_requested()) {
+        int status = 0;
+
+        if (!IsInputThreadValid()) {
+            input_thread.request_stop();
+            continue;
+        }
+
+        // By disabling the input thread we can ensure custom commands will succeed as no package is
+        // skipped
+        if (!disable_input_thread) {
+            status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(),
+                                          ThreadDelay);
+        } else {
+            std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay));
+        }
+
+        if (IsPayloadCorrect(status, buffer)) {
+            OnNewData(buffer);
+        }
+
+        std::this_thread::yield();
+    }
+
+    is_connected = false;
+    input_thread_running = false;
+    LOG_INFO(Input, "JC Adapter input thread stopped");
+}
+
+void JoyconDriver::OnNewData(std::span<u8> buffer) {
+    const auto report_mode = static_cast<InputReport>(buffer[0]);
+
+    switch (report_mode) {
+    case InputReport::STANDARD_FULL_60HZ:
+        ReadActiveMode(buffer);
+        break;
+    case InputReport::NFC_IR_MODE_60HZ:
+        ReadNfcIRMode(buffer);
+        break;
+    case InputReport::SIMPLE_HID_MODE:
+        ReadPassiveMode(buffer);
+        break;
+    default:
+        LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
+        break;
+    }
+}
+
+void JoyconDriver::SetPollingMode() {
+    disable_input_thread = true;
+    disable_input_thread = false;
+}
+
+JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
+    SupportedFeatures features{
+        .passive = true,
+        .motion = true,
+        .vibration = true,
+    };
+
+    if (device_type == ControllerType::Right) {
+        features.nfc = true;
+        features.irs = true;
+        features.hidbus = true;
+    }
+
+    if (device_type == ControllerType::Pro) {
+        features.nfc = true;
+    }
+    return features;
+}
+
+void JoyconDriver::ReadActiveMode(std::span<u8> buffer) {
+    InputReportActive data{};
+    memcpy(&data, buffer.data(), sizeof(InputReportActive));
+
+    // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
+    // experience
+    const auto now = std::chrono::steady_clock::now();
+    const auto new_delta_time =
+        std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count();
+    delta_time = static_cast<u64>((delta_time * 0.8f) + (new_delta_time * 0.2));
+    last_update = now;
+
+    switch (device_type) {
+    case Joycon::ControllerType::Left:
+        break;
+    case Joycon::ControllerType::Right:
+        break;
+    case Joycon::ControllerType::Pro:
+        break;
+    case Joycon::ControllerType::Grip:
+    case Joycon::ControllerType::Dual:
+    case Joycon::ControllerType::None:
+        break;
+    }
+
+    on_battery_data(data.battery_status);
+    on_color_data(color);
+}
+
+void JoyconDriver::ReadPassiveMode(std::span<u8> buffer) {
+    InputReportPassive data{};
+    memcpy(&data, buffer.data(), sizeof(InputReportPassive));
+
+    switch (device_type) {
+    case Joycon::ControllerType::Left:
+        break;
+    case Joycon::ControllerType::Right:
+        break;
+    case Joycon::ControllerType::Pro:
+        break;
+    case Joycon::ControllerType::Grip:
+    case Joycon::ControllerType::Dual:
+    case Joycon::ControllerType::None:
+        break;
+    }
+}
+
+void JoyconDriver::ReadNfcIRMode(std::span<u8> buffer) {
+    // This mode is compatible with the active mode
+    ReadActiveMode(buffer);
+
+    if (!nfc_enabled) {
+        return;
+    }
+}
+
+bool JoyconDriver::IsInputThreadValid() const {
+    if (!is_connected) {
+        return false;
+    }
+    if (hidapi_handle->handle == nullptr) {
+        return false;
+    }
+    // Controller is not responding. Terminate connection
+    if (error_counter > MaxErrorCount) {
+        return false;
+    }
+    return true;
+}
+
+bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
+    if (status <= -1) {
+        error_counter++;
+        return false;
+    }
+    // There's no new data
+    if (status == 0) {
+        return false;
+    }
+    // No reply ever starts with zero
+    if (buffer[0] == 0x00) {
+        error_counter++;
+        return false;
+    }
+    error_counter = 0;
+    return true;
+}
+
+DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
+    std::scoped_lock lock{mutex};
+    return DriverResult::NotSupported;
+}
+
+DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
+    std::scoped_lock lock{mutex};
+    return DriverResult::NotSupported;
+}
+
+DriverResult JoyconDriver::SetPasiveMode() {
+    motion_enabled = false;
+    hidbus_enabled = false;
+    nfc_enabled = false;
+    passive_enabled = true;
+    SetPollingMode();
+    return DriverResult::Success;
+}
+
+DriverResult JoyconDriver::SetActiveMode() {
+    motion_enabled = false;
+    hidbus_enabled = false;
+    nfc_enabled = false;
+    passive_enabled = false;
+    SetPollingMode();
+    return DriverResult::Success;
+}
+
+DriverResult JoyconDriver::SetNfcMode() {
+    motion_enabled = false;
+    hidbus_enabled = false;
+    nfc_enabled = true;
+    passive_enabled = false;
+    SetPollingMode();
+    return DriverResult::Success;
+}
+
+DriverResult JoyconDriver::SetRingConMode() {
+    motion_enabled = true;
+    hidbus_enabled = true;
+    nfc_enabled = false;
+    passive_enabled = false;
+    SetPollingMode();
+    return DriverResult::Success;
+}
+
+bool JoyconDriver::IsConnected() const {
+    std::scoped_lock lock{mutex};
+    return is_connected;
+}
+
+bool JoyconDriver::IsVibrationEnabled() const {
+    std::scoped_lock lock{mutex};
+    return vibration_enabled;
+}
+
+FirmwareVersion JoyconDriver::GetDeviceVersion() const {
+    std::scoped_lock lock{mutex};
+    return version;
+}
+
+Color JoyconDriver::GetDeviceColor() const {
+    std::scoped_lock lock{mutex};
+    return color;
+}
+
+std::size_t JoyconDriver::GetDevicePort() const {
+    std::scoped_lock lock{mutex};
+    return port;
+}
+
+ControllerType JoyconDriver::GetDeviceType() const {
+    std::scoped_lock lock{mutex};
+    return handle_device_type;
+}
+
+ControllerType JoyconDriver::GetHandleDeviceType() const {
+    std::scoped_lock lock{mutex};
+    return handle_device_type;
+}
+
+SerialNumber JoyconDriver::GetSerialNumber() const {
+    std::scoped_lock lock{mutex};
+    return serial_number;
+}
+
+SerialNumber JoyconDriver::GetHandleSerialNumber() const {
+    std::scoped_lock lock{mutex};
+    return handle_serial_number;
+}
+
+Joycon::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
+                                                 ControllerType& controller_type) {
+    std::array<std::pair<u32, Joycon::ControllerType>, 4> supported_devices{
+        std::pair<u32, Joycon::ControllerType>{0x2006, Joycon::ControllerType::Left},
+        {0x2007, Joycon::ControllerType::Right},
+        {0x2009, Joycon::ControllerType::Pro},
+        {0x200E, Joycon::ControllerType::Grip},
+    };
+    constexpr u16 nintendo_vendor_id = 0x057e;
+
+    controller_type = Joycon::ControllerType::None;
+    if (device_info->vendor_id != nintendo_vendor_id) {
+        return Joycon::DriverResult::UnsupportedControllerType;
+    }
+
+    for (const auto& [product_id, type] : supported_devices) {
+        if (device_info->product_id == static_cast<u16>(product_id)) {
+            controller_type = type;
+            return Joycon::DriverResult::Success;
+        }
+    }
+    return Joycon::DriverResult::UnsupportedControllerType;
+}
+
+Joycon::DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
+                                                   Joycon::SerialNumber& serial_number) {
+    if (device_info->serial_number == nullptr) {
+        return Joycon::DriverResult::Unknown;
+    }
+    std::memcpy(&serial_number, device_info->serial_number, 15);
+    return Joycon::DriverResult::Success;
+}
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
new file mode 100644
index 0000000000..be3053a7b4
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.h
@@ -0,0 +1,146 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <functional>
+#include <mutex>
+#include <span>
+#include <thread>
+
+#include "input_common/helpers/joycon_protocol/joycon_types.h"
+
+namespace InputCommon::Joycon {
+
+class JoyconDriver final {
+public:
+    explicit JoyconDriver(std::size_t port_);
+
+    ~JoyconDriver();
+
+    DriverResult RequestDeviceAccess(SDL_hid_device_info* device_info);
+    DriverResult InitializeDevice();
+    void Stop();
+
+    bool IsConnected() const;
+    bool IsVibrationEnabled() const;
+
+    FirmwareVersion GetDeviceVersion() const;
+    Color GetDeviceColor() const;
+    std::size_t GetDevicePort() const;
+    ControllerType GetDeviceType() const;
+    ControllerType GetHandleDeviceType() const;
+    SerialNumber GetSerialNumber() const;
+    SerialNumber GetHandleSerialNumber() const;
+
+    DriverResult SetVibration(const VibrationValue& vibration);
+    DriverResult SetLedConfig(u8 led_pattern);
+    DriverResult SetPasiveMode();
+    DriverResult SetActiveMode();
+    DriverResult SetNfcMode();
+    DriverResult SetRingConMode();
+
+    // Returns device type from hidapi handle
+    static Joycon::DriverResult GetDeviceType(SDL_hid_device_info* device_info,
+                                              Joycon::ControllerType& controller_type);
+
+    // Returns serial number from hidapi handle
+    static Joycon::DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
+                                                Joycon::SerialNumber& serial_number);
+
+    std::function<void(Battery)> on_battery_data;
+    std::function<void(Color)> on_color_data;
+    std::function<void(int, bool)> on_button_data;
+    std::function<void(int, f32)> on_stick_data;
+    std::function<void(int, MotionData)> on_motion_data;
+    std::function<void(f32)> on_ring_data;
+    std::function<void(const std::vector<u8>&)> on_amiibo_data;
+
+private:
+    struct SupportedFeatures {
+        bool passive{};
+        bool hidbus{};
+        bool irs{};
+        bool motion{};
+        bool nfc{};
+        bool vibration{};
+    };
+
+    /// Main thread, actively request new data from the handle
+    void InputThread(std::stop_token stop_token);
+
+    /// Called everytime a valid package arrives
+    void OnNewData(std::span<u8> buffer);
+
+    /// Updates device configuration to enable or disable features
+    void SetPollingMode();
+
+    /// Returns true if input thread is valid and doesn't need to be stopped
+    bool IsInputThreadValid() const;
+
+    /// Returns true if the data should be interpreted. Otherwise the error counter is incremented
+    bool IsPayloadCorrect(int status, std::span<const u8> buffer);
+
+    /// Returns a list of supported features that can be enabled on this device
+    SupportedFeatures GetSupportedFeatures();
+
+    /// Handles data from passive packages
+    void ReadPassiveMode(std::span<u8> buffer);
+
+    /// Handles data from active packages
+    void ReadActiveMode(std::span<u8> buffer);
+
+    /// Handles data from nfc or ir packages
+    void ReadNfcIRMode(std::span<u8> buffer);
+
+    // Protocol Features
+
+    // Connection status
+    bool is_connected{};
+    u64 delta_time;
+    std::size_t error_counter{};
+    std::shared_ptr<JoyconHandle> hidapi_handle = nullptr;
+    std::chrono::time_point<std::chrono::steady_clock> last_update;
+
+    // External device status
+    bool starlink_connected{};
+    bool ring_connected{};
+    bool amiibo_detected{};
+
+    // Harware configuration
+    u8 leds{};
+    ReportMode mode{};
+    bool passive_enabled{};   // Low power mode, Ideal for multiple controllers at the same time
+    bool hidbus_enabled{};    // External device support
+    bool irs_enabled{};       // Infrared camera input
+    bool motion_enabled{};    // Enables motion input
+    bool nfc_enabled{};       // Enables Amiibo detection
+    bool vibration_enabled{}; // Allows vibrations
+
+    // Calibration data
+    GyroSensitivity gyro_sensitivity{};
+    GyroPerformance gyro_performance{};
+    AccelerometerSensitivity accelerometer_sensitivity{};
+    AccelerometerPerformance accelerometer_performance{};
+    JoyStickCalibration left_stick_calibration{};
+    JoyStickCalibration right_stick_calibration{};
+    MotionCalibration motion_calibration{};
+
+    // Fixed joycon info
+    FirmwareVersion version{};
+    Color color{};
+    std::size_t port{};
+    ControllerType device_type{};        // Device type reported by controller
+    ControllerType handle_device_type{}; // Device type reported by hidapi
+    SerialNumber serial_number{};        // Serial number reported by controller
+    SerialNumber handle_serial_number{}; // Serial number type reported by hidapi
+    SupportedFeatures supported_features{};
+
+    // Thread related
+    mutable std::mutex mutex;
+    std::jthread input_thread;
+    bool input_thread_running{};
+    bool disable_input_thread{};
+};
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h
new file mode 100644
index 0000000000..de512fe639
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/joycon_types.h
@@ -0,0 +1,494 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
+// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
+// https://github.com/CTCaer/jc_toolkit
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
+
+#pragma once
+
+#include <array>
+#include <functional>
+#include <SDL_hidapi.h>
+
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace InputCommon::Joycon {
+constexpr u32 MaxErrorCount = 50;
+constexpr u32 MaxBufferSize = 60;
+constexpr u32 MaxResponseSize = 49;
+constexpr u32 MaxSubCommandResponseSize = 64;
+constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
+
+using MacAddress = std::array<u8, 6>;
+using SerialNumber = std::array<u8, 15>;
+
+enum class ControllerType {
+    None,
+    Left,
+    Right,
+    Pro,
+    Grip,
+    Dual,
+};
+
+enum class PadAxes {
+    LeftStickX,
+    LeftStickY,
+    RightStickX,
+    RightStickY,
+    Undefined,
+};
+
+enum class PadMotion {
+    LeftMotion,
+    RightMotion,
+    Undefined,
+};
+
+enum class PadButton : u32 {
+    Down = 0x000001,
+    Up = 0x000002,
+    Right = 0x000004,
+    Left = 0x000008,
+    LeftSR = 0x000010,
+    LeftSL = 0x000020,
+    L = 0x000040,
+    ZL = 0x000080,
+    Y = 0x000100,
+    X = 0x000200,
+    B = 0x000400,
+    A = 0x000800,
+    RightSR = 0x001000,
+    RightSL = 0x002000,
+    R = 0x004000,
+    ZR = 0x008000,
+    Minus = 0x010000,
+    Plus = 0x020000,
+    StickR = 0x040000,
+    StickL = 0x080000,
+    Home = 0x100000,
+    Capture = 0x200000,
+};
+
+enum class PasivePadButton : u32 {
+    Down_A = 0x0001,
+    Right_X = 0x0002,
+    Left_B = 0x0004,
+    Up_Y = 0x0008,
+    SL = 0x0010,
+    SR = 0x0020,
+    Minus = 0x0100,
+    Plus = 0x0200,
+    StickL = 0x0400,
+    StickR = 0x0800,
+    Home = 0x1000,
+    Capture = 0x2000,
+    L_R = 0x4000,
+    ZL_ZR = 0x8000,
+};
+
+enum class OutputReport : u8 {
+    RUMBLE_AND_SUBCMD = 0x01,
+    FW_UPDATE_PKT = 0x03,
+    RUMBLE_ONLY = 0x10,
+    MCU_DATA = 0x11,
+    USB_CMD = 0x80,
+};
+
+enum class InputReport : u8 {
+    SUBCMD_REPLY = 0x21,
+    STANDARD_FULL_60HZ = 0x30,
+    NFC_IR_MODE_60HZ = 0x31,
+    SIMPLE_HID_MODE = 0x3F,
+    INPUT_USB_RESPONSE = 0x81,
+};
+
+enum class FeatureReport : u8 {
+    Last_SUBCMD = 0x02,
+    OTA_GW_UPGRADE = 0x70,
+    SETUP_MEM_READ = 0x71,
+    MEM_READ = 0x72,
+    ERASE_MEM_SECTOR = 0x73,
+    MEM_WRITE = 0x74,
+    LAUNCH = 0x75,
+};
+
+enum class SubCommand : u8 {
+    STATE = 0x00,
+    MANUAL_BT_PAIRING = 0x01,
+    REQ_DEV_INFO = 0x02,
+    SET_REPORT_MODE = 0x03,
+    TRIGGERS_ELAPSED = 0x04,
+    GET_PAGE_LIST_STATE = 0x05,
+    SET_HCI_STATE = 0x06,
+    RESET_PAIRING_INFO = 0x07,
+    LOW_POWER_MODE = 0x08,
+    SPI_FLASH_READ = 0x10,
+    SPI_FLASH_WRITE = 0x11,
+    RESET_MCU = 0x20,
+    SET_MCU_CONFIG = 0x21,
+    SET_MCU_STATE = 0x22,
+    SET_PLAYER_LIGHTS = 0x30,
+    GET_PLAYER_LIGHTS = 0x31,
+    SET_HOME_LIGHT = 0x38,
+    ENABLE_IMU = 0x40,
+    SET_IMU_SENSITIVITY = 0x41,
+    WRITE_IMU_REG = 0x42,
+    READ_IMU_REG = 0x43,
+    ENABLE_VIBRATION = 0x48,
+    GET_REGULATED_VOLTAGE = 0x50,
+    SET_EXTERNAL_CONFIG = 0x58,
+    UNKNOWN_RINGCON = 0x59,
+    UNKNOWN_RINGCON2 = 0x5A,
+    UNKNOWN_RINGCON3 = 0x5C,
+};
+
+enum class UsbSubCommand : u8 {
+    CONN_STATUS = 0x01,
+    HADSHAKE = 0x02,
+    BAUDRATE_3M = 0x03,
+    NO_TIMEOUT = 0x04,
+    EN_TIMEOUT = 0x05,
+    RESET = 0x06,
+    PRE_HANDSHAKE = 0x91,
+    SEND_UART = 0x92,
+};
+
+enum class CalMagic : u8 {
+    USR_MAGIC_0 = 0xB2,
+    USR_MAGIC_1 = 0xA1,
+    USRR_MAGI_SIZE = 2,
+};
+
+enum class CalAddr {
+    SERIAL_NUMBER = 0X6000,
+    DEVICE_TYPE = 0X6012,
+    COLOR_EXIST = 0X601B,
+    FACT_LEFT_DATA = 0X603d,
+    FACT_RIGHT_DATA = 0X6046,
+    COLOR_DATA = 0X6050,
+    FACT_IMU_DATA = 0X6020,
+    USER_LEFT_MAGIC = 0X8010,
+    USER_LEFT_DATA = 0X8012,
+    USER_RIGHT_MAGIC = 0X801B,
+    USER_RIGHT_DATA = 0X801D,
+    USER_IMU_MAGIC = 0X8026,
+    USER_IMU_DATA = 0X8028,
+};
+
+enum class ReportMode : u8 {
+    ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00,
+    ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01,
+    ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02,
+    ACTIVE_POLLING_IR_CAMERA_DATA = 0x03,
+    MCU_UPDATE_STATE = 0x23,
+    STANDARD_FULL_60HZ = 0x30,
+    NFC_IR_MODE_60HZ = 0x31,
+    SIMPLE_HID_MODE = 0x3F,
+};
+
+enum class GyroSensitivity : u8 {
+    DPS250,
+    DPS500,
+    DPS1000,
+    DPS2000, // Default
+};
+
+enum class AccelerometerSensitivity : u8 {
+    G8, // Default
+    G4,
+    G2,
+    G16,
+};
+
+enum class GyroPerformance : u8 {
+    HZ833,
+    HZ208, // Default
+};
+
+enum class AccelerometerPerformance : u8 {
+    HZ200,
+    HZ100, // Default
+};
+
+enum class MCUCommand : u8 {
+    ConfigureMCU = 0x21,
+    ConfigureIR = 0x23,
+};
+
+enum class MCUSubCommand : u8 {
+    SetMCUMode = 0x0,
+    SetDeviceMode = 0x1,
+    ReadDeviceMode = 0x02,
+    WriteDeviceRegisters = 0x4,
+};
+
+enum class MCUMode : u8 {
+    Suspend = 0,
+    Standby = 1,
+    Ringcon = 3,
+    NFC = 4,
+    IR = 5,
+    MaybeFWUpdate = 6,
+};
+
+enum class MCURequest : u8 {
+    GetMCUStatus = 1,
+    GetNFCData = 2,
+    GetIRData = 3,
+};
+
+enum class MCUReport : u8 {
+    Empty = 0x00,
+    StateReport = 0x01,
+    IRData = 0x03,
+    BusyInitializing = 0x0b,
+    IRStatus = 0x13,
+    IRRegisters = 0x1b,
+    NFCState = 0x2a,
+    NFCReadData = 0x3a,
+    EmptyAwaitingCmd = 0xff,
+};
+
+enum class MCUPacketFlag : u8 {
+    MorePacketsRemaining = 0x00,
+    LastCommandPacket = 0x08,
+};
+
+enum class NFCReadCommand : u8 {
+    CancelAll = 0x00,
+    StartPolling = 0x01,
+    StopPolling = 0x02,
+    StartWaitingRecieve = 0x04,
+    Ntag = 0x06,
+    Mifare = 0x0F,
+};
+
+enum class NFCTagType : u8 {
+    AllTags = 0x00,
+    Ntag215 = 0x01,
+};
+
+enum class DriverResult {
+    Success,
+    WrongReply,
+    Timeout,
+    UnsupportedControllerType,
+    HandleInUse,
+    ErrorReadingData,
+    ErrorWritingData,
+    NoDeviceDetected,
+    InvalidHandle,
+    NotSupported,
+    Unknown,
+};
+
+struct MotionSensorCalibration {
+    s16 offset;
+    s16 scale;
+};
+
+struct MotionCalibration {
+    std::array<MotionSensorCalibration, 3> accelerometer;
+    std::array<MotionSensorCalibration, 3> gyro;
+};
+
+// Basic motion data containing data from the sensors and a timestamp in microseconds
+struct MotionData {
+    float gyro_x{};
+    float gyro_y{};
+    float gyro_z{};
+    float accel_x{};
+    float accel_y{};
+    float accel_z{};
+    u64 delta_timestamp{};
+};
+
+struct JoyStickAxisCalibration {
+    u16 max{1};
+    u16 min{1};
+    u16 center{0};
+};
+
+struct JoyStickCalibration {
+    JoyStickAxisCalibration x;
+    JoyStickAxisCalibration y;
+};
+
+struct RingCalibration {
+    s16 default_value;
+    s16 max_value;
+    s16 min_value;
+};
+
+struct Color {
+    u32 body;
+    u32 buttons;
+    u32 left_grip;
+    u32 right_grip;
+};
+
+struct Battery {
+    union {
+        u8 raw{};
+
+        BitField<0, 4, u8> unknown;
+        BitField<4, 1, u8> charging;
+        BitField<5, 3, u8> status;
+    };
+};
+
+struct VibrationValue {
+    f32 low_amplitude;
+    f32 low_frequency;
+    f32 high_amplitude;
+    f32 high_frequency;
+};
+
+struct JoyconHandle {
+    SDL_hid_device* handle = nullptr;
+    u8 packet_counter{};
+};
+
+struct MCUConfig {
+    MCUCommand command;
+    MCUSubCommand sub_command;
+    MCUMode mode;
+    INSERT_PADDING_BYTES(0x22);
+    u8 crc;
+};
+static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size");
+
+#pragma pack(push, 1)
+struct InputReportPassive {
+    InputReport report_mode;
+    u16 button_input;
+    u8 stick_state;
+    std::array<u8, 10> unknown_data;
+};
+static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size");
+
+struct InputReportActive {
+    InputReport report_mode;
+    u8 packet_id;
+    Battery battery_status;
+    std::array<u8, 3> button_input;
+    std::array<u8, 3> left_stick_state;
+    std::array<u8, 3> right_stick_state;
+    u8 vibration_code;
+    std::array<s16, 6 * 2> motion_input;
+    INSERT_PADDING_BYTES(0x2);
+    s16 ring_input;
+};
+static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size");
+
+struct InputReportNfcIr {
+    InputReport report_mode;
+    u8 packet_id;
+    Battery battery_status;
+    std::array<u8, 3> button_input;
+    std::array<u8, 3> left_stick_state;
+    std::array<u8, 3> right_stick_state;
+    u8 vibration_code;
+    std::array<s16, 6 * 2> motion_input;
+    INSERT_PADDING_BYTES(0x4);
+};
+static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size");
+#pragma pack(pop)
+
+struct IMUCalibration {
+    std::array<s16, 3> accelerometer_offset;
+    std::array<s16, 3> accelerometer_scale;
+    std::array<s16, 3> gyroscope_offset;
+    std::array<s16, 3> gyroscope_scale;
+};
+static_assert(sizeof(IMUCalibration) == 0x18, "IMUCalibration is an invalid size");
+
+struct NFCReadBlock {
+    u8 start;
+    u8 end;
+};
+static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size");
+
+struct NFCReadBlockCommand {
+    u8 block_count{};
+    std::array<NFCReadBlock, 4> blocks{};
+};
+static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size");
+
+struct NFCReadCommandData {
+    u8 unknown;
+    u8 uuid_length;
+    u8 unknown_2;
+    std::array<u8, 6> uid;
+    NFCTagType tag_type;
+    NFCReadBlockCommand read_block;
+};
+static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size");
+
+struct NFCPollingCommandData {
+    u8 enable_mifare;
+    u8 unknown_1;
+    u8 unknown_2;
+    u8 unknown_3;
+    u8 unknown_4;
+};
+static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size");
+
+struct NFCRequestState {
+    MCUSubCommand sub_command;
+    NFCReadCommand command_argument;
+    u8 packet_id;
+    INSERT_PADDING_BYTES(0x1);
+    MCUPacketFlag packet_flag;
+    u8 data_length;
+    union {
+        std::array<u8, 0x1F> raw_data;
+        NFCReadCommandData nfc_read;
+        NFCPollingCommandData nfc_polling;
+    };
+    u8 crc;
+};
+static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size");
+
+struct FirmwareVersion {
+    u8 major;
+    u8 minor;
+};
+static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
+
+struct DeviceInfo {
+    FirmwareVersion firmware;
+    MacAddress mac_address;
+};
+static_assert(sizeof(DeviceInfo) == 0x8, "DeviceInfo is an invalid size");
+
+struct MotionStatus {
+    bool is_enabled;
+    u64 delta_time;
+    GyroSensitivity gyro_sensitivity;
+    AccelerometerSensitivity accelerometer_sensitivity;
+};
+
+struct RingStatus {
+    bool is_enabled;
+    s16 default_value;
+    s16 max_value;
+    s16 min_value;
+};
+
+struct JoyconCallbacks {
+    std::function<void(Battery)> on_battery_data;
+    std::function<void(Color)> on_color_data;
+    std::function<void(int, bool)> on_button_data;
+    std::function<void(int, f32)> on_stick_data;
+    std::function<void(int, const MotionData&)> on_motion_data;
+    std::function<void(f32)> on_ring_data;
+    std::function<void(const std::vector<u8>&)> on_amiibo_data;
+};
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index e0b2131ed8..c77fc04ee6 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -23,6 +23,7 @@
 #include "input_common/drivers/gc_adapter.h"
 #endif
 #ifdef HAVE_SDL2
+#include "input_common/drivers/joycon.h"
 #include "input_common/drivers/sdl_driver.h"
 #endif
 
@@ -81,6 +82,7 @@ struct InputSubsystem::Impl {
         RegisterEngine("virtual_gamepad", virtual_gamepad);
 #ifdef HAVE_SDL2
         RegisterEngine("sdl", sdl);
+        RegisterEngine("joycon", joycon);
 #endif
 
         Common::Input::RegisterInputFactory("touch_from_button",
@@ -111,6 +113,7 @@ struct InputSubsystem::Impl {
         UnregisterEngine(virtual_gamepad);
 #ifdef HAVE_SDL2
         UnregisterEngine(sdl);
+        UnregisterEngine(joycon);
 #endif
 
         Common::Input::UnregisterInputFactory("touch_from_button");
@@ -133,6 +136,8 @@ struct InputSubsystem::Impl {
         auto udp_devices = udp_client->GetInputDevices();
         devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
 #ifdef HAVE_SDL2
+        auto joycon_devices = joycon->GetInputDevices();
+        devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
         auto sdl_devices = sdl->GetInputDevices();
         devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
 #endif
@@ -164,6 +169,9 @@ struct InputSubsystem::Impl {
         if (engine == sdl->GetEngineName()) {
             return sdl;
         }
+        if (engine == joycon->GetEngineName()) {
+            return joycon;
+        }
 #endif
         return nullptr;
     }
@@ -247,6 +255,9 @@ struct InputSubsystem::Impl {
         if (engine == sdl->GetEngineName()) {
             return true;
         }
+        if (engine == joycon->GetEngineName()) {
+            return true;
+        }
 #endif
         return false;
     }
@@ -260,6 +271,7 @@ struct InputSubsystem::Impl {
         udp_client->BeginConfiguration();
 #ifdef HAVE_SDL2
         sdl->BeginConfiguration();
+        joycon->BeginConfiguration();
 #endif
     }
 
@@ -272,6 +284,7 @@ struct InputSubsystem::Impl {
         udp_client->EndConfiguration();
 #ifdef HAVE_SDL2
         sdl->EndConfiguration();
+        joycon->EndConfiguration();
 #endif
     }
 
@@ -304,6 +317,7 @@ struct InputSubsystem::Impl {
 
 #ifdef HAVE_SDL2
     std::shared_ptr<SDLDriver> sdl;
+    std::shared_ptr<Joycons> joycon;
 #endif
 };
 

From 2d802893e706c4ce7fd6f320db0eed2bf90b2d45 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 20 Dec 2022 12:45:54 -0600
Subject: [PATCH 02/24] input_common: Disable SDL driver with switch
 controllers

---
 src/common/settings.h                         |  1 +
 src/input_common/drivers/joycon.cpp           |  4 ++++
 src/input_common/drivers/sdl_driver.cpp       | 19 +++++++++++++---
 src/yuzu/configuration/config.cpp             |  2 ++
 .../configure_input_advanced.cpp              |  2 ++
 .../configuration/configure_input_advanced.ui | 22 ++++++++++++++++---
 6 files changed, 44 insertions(+), 6 deletions(-)

diff --git a/src/common/settings.h b/src/common/settings.h
index 80b2eeabcd..4b4da4da29 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -483,6 +483,7 @@ struct Values {
 
     Setting<bool> enable_raw_input{false, "enable_raw_input"};
     Setting<bool> controller_navigation{true, "controller_navigation"};
+    Setting<bool> enable_joycon_driver{true, "enable_joycon_driver"};
 
     SwitchableSetting<bool> vibration_enabled{true, "vibration_enabled"};
     SwitchableSetting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index eab10d11c0..1fca11d34e 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -13,6 +13,10 @@
 namespace InputCommon {
 
 Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) {
+    // Avoid conflicting with SDL driver
+    if (!Settings::values.enable_joycon_driver) {
+        return;
+    }
     LOG_INFO(Input, "Joycon driver Initialization started");
     const int init_res = SDL_hid_init();
     if (init_res == 0) {
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index 4818bb7442..c9496a0d86 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -318,6 +318,14 @@ void SDLDriver::InitJoystick(int joystick_index) {
 
     const auto guid = GetGUID(sdl_joystick);
 
+    if (Settings::values.enable_joycon_driver) {
+        if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e) {
+            LOG_ERROR(Input, "Device black listed {}", joystick_index);
+            SDL_JoystickClose(sdl_joystick);
+            return;
+        }
+    }
+
     std::scoped_lock lock{joystick_map_mutex};
     if (joystick_map.find(guid) == joystick_map.end()) {
         auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
@@ -440,9 +448,14 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
     SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
     SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
 
-    // Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and
-    // not a generic one
-    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
+    // Disable hidapi drivers for switch controllers when the custom joycon driver is enabled
+    if (Settings::values.enable_joycon_driver) {
+        SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0");
+        SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "0");
+    } else {
+        SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
+        SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");
+    }
 
     // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
     // driver on Linux.
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 0db62baa32..d8b26ebd88 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -440,6 +440,7 @@ void Config::ReadControlValues() {
     ReadBasicSetting(Settings::values.emulate_analog_keyboard);
     Settings::values.mouse_panning = false;
     ReadBasicSetting(Settings::values.mouse_panning_sensitivity);
+    ReadBasicSetting(Settings::values.enable_joycon_driver);
 
     ReadBasicSetting(Settings::values.tas_enable);
     ReadBasicSetting(Settings::values.tas_loop);
@@ -1139,6 +1140,7 @@ void Config::SaveControlValues() {
     WriteGlobalSetting(Settings::values.enable_accurate_vibrations);
     WriteGlobalSetting(Settings::values.motion_enabled);
     WriteBasicSetting(Settings::values.enable_raw_input);
+    WriteBasicSetting(Settings::values.enable_joycon_driver);
     WriteBasicSetting(Settings::values.keyboard_enabled);
     WriteBasicSetting(Settings::values.emulate_analog_keyboard);
     WriteBasicSetting(Settings::values.mouse_panning_sensitivity);
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
index 235b813d97..77b976e740 100644
--- a/src/yuzu/configuration/configure_input_advanced.cpp
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -138,6 +138,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
     Settings::values.controller_navigation = ui->controller_navigation->isChecked();
     Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked();
     Settings::values.enable_ir_sensor = ui->enable_ir_sensor->isChecked();
+    Settings::values.enable_joycon_driver = ui->enable_joycon_driver->isChecked();
 }
 
 void ConfigureInputAdvanced::LoadConfiguration() {
@@ -172,6 +173,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
     ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
     ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());
     ui->enable_ir_sensor->setChecked(Settings::values.enable_ir_sensor.GetValue());
+    ui->enable_joycon_driver->setChecked(Settings::values.enable_joycon_driver.GetValue());
 
     UpdateUIEnabled();
 }
diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui
index fac8cf827d..75d96d3ab5 100644
--- a/src/yuzu/configuration/configure_input_advanced.ui
+++ b/src/yuzu/configuration/configure_input_advanced.ui
@@ -2696,6 +2696,22 @@
                      </widget>
                    </item>
                    <item row="5" column="0">
+                      <widget class="QCheckBox" name="enable_joycon_driver">
+                       <property name="toolTip">
+                         <string>Requires restarting yuzu</string>
+                       </property>
+                       <property name="minimumSize">
+                         <size>
+                           <width>0</width>
+                           <height>23</height>
+                         </size>
+                       </property>
+                       <property name="text">
+                         <string>Enable direct JoyCon driver</string>
+                       </property>
+                     </widget>
+                   </item>
+                   <item row="6" column="0">
                      <widget class="QCheckBox" name="mouse_panning">
                        <property name="minimumSize">
                          <size>
@@ -2708,7 +2724,7 @@
                        </property>
                      </widget>
                    </item>
-                   <item row="5" column="2">
+                   <item row="6" column="2">
                      <widget class="QSpinBox" name="mouse_panning_sensitivity">
                        <property name="toolTip">
                          <string>Mouse sensitivity</string>
@@ -2730,14 +2746,14 @@
                        </property>
                      </widget>
                    </item>
-                   <item row="6" column="0">
+                   <item row="7" column="0">
                      <widget class="QLabel" name="motion_touch">
                        <property name="text">
                          <string>Motion / Touch</string>
                        </property>
                      </widget>
                    </item>
-                   <item row="6" column="2">
+                   <item row="7" column="2">
                      <widget class="QPushButton" name="buttonMotionTouch">
                        <property name="text">
                          <string>Configure</string>

From 18c9f8eeed64196088969904d16095721ed66a3c Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 20 Dec 2022 12:49:43 -0600
Subject: [PATCH 03/24] yuzu: Update controller colors and button names

---
 .../configuration/configure_input_player.cpp  | 20 +++++++++++++++++++
 .../configure_input_player_widget.cpp         | 10 +++++++---
 2 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index c40d980c9a..4b7e3b01bf 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -66,6 +66,18 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {
         return QObject::tr("R");
     case Common::Input::ButtonNames::TriggerL:
         return QObject::tr("L");
+    case Common::Input::ButtonNames::TriggerZR:
+        return QObject::tr("ZR");
+    case Common::Input::ButtonNames::TriggerZL:
+        return QObject::tr("ZL");
+    case Common::Input::ButtonNames::TriggerSR:
+        return QObject::tr("SR");
+    case Common::Input::ButtonNames::TriggerSL:
+        return QObject::tr("SL");
+    case Common::Input::ButtonNames::ButtonStickL:
+        return QObject::tr("Stick L");
+    case Common::Input::ButtonNames::ButtonStickR:
+        return QObject::tr("Stick R");
     case Common::Input::ButtonNames::ButtonA:
         return QObject::tr("A");
     case Common::Input::ButtonNames::ButtonB:
@@ -76,6 +88,14 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {
         return QObject::tr("Y");
     case Common::Input::ButtonNames::ButtonStart:
         return QObject::tr("Start");
+    case Common::Input::ButtonNames::ButtonPlus:
+        return QObject::tr("Plus");
+    case Common::Input::ButtonNames::ButtonMinus:
+        return QObject::tr("Minus");
+    case Common::Input::ButtonNames::ButtonHome:
+        return QObject::tr("Home");
+    case Common::Input::ButtonNames::ButtonCapture:
+        return QObject::tr("Capture");
     case Common::Input::ButtonNames::L1:
         return QObject::tr("L1");
     case Common::Input::ButtonNames::L2:
diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
index 11390fec06..68af6c20c9 100644
--- a/src/yuzu/configuration/configure_input_player_widget.cpp
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -103,9 +103,13 @@ void PlayerControlPreview::UpdateColors() {
 
     colors.left = colors.primary;
     colors.right = colors.primary;
-    // Possible alternative to set colors from settings
-    // colors.left = QColor(controller->GetColors().left.body);
-    // colors.right = QColor(controller->GetColors().right.body);
+
+    const auto color_left = controller->GetColorsValues()[0].body;
+    const auto color_right = controller->GetColorsValues()[1].body;
+    if (color_left != 0 && color_right != 0) {
+        colors.left = QColor(color_left);
+        colors.right = QColor(color_right);
+    }
 }
 
 void PlayerControlPreview::ResetInputs() {

From a4074001fe2e8ed72c87093f57ec972815661b81 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 20 Dec 2022 13:09:10 -0600
Subject: [PATCH 04/24] core: hid: Migrate ring from emulated devices to
 emulated controller

---
 src/core/hid/emulated_controller.cpp         | 46 ++++++++++++++++++++
 src/core/hid/emulated_controller.h           | 35 ++++++++++++++-
 src/core/hid/emulated_devices.cpp            | 46 --------------------
 src/core/hid/emulated_devices.h              | 18 --------
 src/core/hle/service/hid/hidbus/ringcon.cpp  |  6 ++-
 src/core/hle/service/hid/hidbus/ringcon.h    |  4 +-
 src/yuzu/configuration/configure_ringcon.cpp | 34 +++++++--------
 src/yuzu/configuration/configure_ringcon.h   |  4 +-
 8 files changed, 105 insertions(+), 88 deletions(-)

diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 7a01f3f4c8..128101e8ca 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -139,6 +139,7 @@ void EmulatedController::LoadDevices() {
 
     camera_params = Common::ParamPackage{"engine:camera,camera:1"};
     nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
+    ring_params = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
 
     output_params[LeftIndex] = left_joycon;
     output_params[RightIndex] = right_joycon;
@@ -160,6 +161,7 @@ void EmulatedController::LoadDevices() {
     std::ranges::transform(battery_params, battery_devices.begin(),
                            Common::Input::CreateInputDevice);
     camera_devices = Common::Input::CreateInputDevice(camera_params);
+    ring_analog_device = Common::Input::CreateInputDevice(ring_params);
     nfc_devices = Common::Input::CreateInputDevice(nfc_params);
     std::ranges::transform(output_params, output_devices.begin(),
                            Common::Input::CreateOutputDevice);
@@ -343,6 +345,13 @@ void EmulatedController::ReloadInput() {
         camera_devices->ForceUpdate();
     }
 
+    if (ring_analog_device) {
+        ring_analog_device->SetCallback({
+            .on_change =
+                [this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
+        });
+    }
+
     if (nfc_devices) {
         if (npad_id_type == NpadIdType::Handheld || npad_id_type == NpadIdType::Player1) {
             nfc_devices->SetCallback({
@@ -436,6 +445,7 @@ void EmulatedController::UnloadInput() {
         stick.reset();
     }
     camera_devices.reset();
+    ring_analog_device.reset();
     nfc_devices.reset();
 }
 
@@ -501,6 +511,7 @@ void EmulatedController::SaveCurrentConfig() {
     for (std::size_t index = 0; index < player.motions.size(); ++index) {
         player.motions[index] = motion_params[index].Serialize();
     }
+    Settings::values.ringcon_analogs = ring_params.Serialize();
 }
 
 void EmulatedController::RestoreConfig() {
@@ -1005,6 +1016,24 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback
     TriggerOnChange(ControllerTriggerType::IrSensor, true);
 }
 
+void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
+    std::unique_lock lock{mutex};
+    const auto force_value = TransformToStick(callback);
+
+    controller.ring_analog_value = force_value.x;
+
+    if (is_configuring) {
+        lock.unlock();
+        TriggerOnChange(ControllerTriggerType::RingController, false);
+        return;
+    }
+
+    controller.ring_analog_state.force = force_value.x.value;
+
+    lock.unlock();
+    TriggerOnChange(ControllerTriggerType::RingController, true);
+}
+
 void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
     std::unique_lock lock{mutex};
     controller.nfc_values = TransformToNfc(callback);
@@ -1104,6 +1133,15 @@ bool EmulatedController::SetCameraFormat(
                camera_format)) == Common::Input::CameraError::None;
 }
 
+Common::ParamPackage EmulatedController::GetRingParam() const {
+    return ring_params;
+}
+
+void EmulatedController::SetRingParam(Common::ParamPackage param) {
+    ring_params = std::move(param);
+    ReloadInput();
+}
+
 bool EmulatedController::HasNfc() const {
     const auto& nfc_output_device = output_devices[3];
 
@@ -1395,6 +1433,10 @@ CameraValues EmulatedController::GetCameraValues() const {
     return controller.camera_values;
 }
 
+RingAnalogValue EmulatedController::GetRingSensorValues() const {
+    return controller.ring_analog_value;
+}
+
 HomeButtonState EmulatedController::GetHomeButtons() const {
     std::scoped_lock lock{mutex};
     if (is_configuring) {
@@ -1478,6 +1520,10 @@ const CameraState& EmulatedController::GetCamera() const {
     return controller.camera_state;
 }
 
+RingSensorForce EmulatedController::GetRingSensorForce() const {
+    return controller.ring_analog_state;
+}
+
 const NfcState& EmulatedController::GetNfc() const {
     std::scoped_lock lock{mutex};
     return controller.nfc_state;
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index a398543a64..aed331a1aa 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -38,6 +38,7 @@ using TriggerDevices =
 using BatteryDevices =
     std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
 using CameraDevices = std::unique_ptr<Common::Input::InputDevice>;
+using RingAnalogDevice = std::unique_ptr<Common::Input::InputDevice>;
 using NfcDevices = std::unique_ptr<Common::Input::InputDevice>;
 using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>;
 
@@ -47,6 +48,7 @@ using ControllerMotionParams = std::array<Common::ParamPackage, Settings::Native
 using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
 using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using CameraParams = Common::ParamPackage;
+using RingAnalogParams = Common::ParamPackage;
 using NfcParams = Common::ParamPackage;
 using OutputParams = std::array<Common::ParamPackage, output_devices_size>;
 
@@ -58,6 +60,7 @@ using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::Native
 using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
 using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
 using CameraValues = Common::Input::CameraStatus;
+using RingAnalogValue = Common::Input::AnalogStatus;
 using NfcValues = Common::Input::NfcStatus;
 using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
 
@@ -84,6 +87,10 @@ struct CameraState {
     std::size_t sample{};
 };
 
+struct RingSensorForce {
+    f32 force;
+};
+
 struct NfcState {
     Common::Input::NfcState state{};
     std::vector<u8> data{};
@@ -116,6 +123,7 @@ struct ControllerStatus {
     BatteryValues battery_values{};
     VibrationValues vibration_values{};
     CameraValues camera_values{};
+    RingAnalogValue ring_analog_value{};
     NfcValues nfc_values{};
 
     // Data for HID serices
@@ -129,6 +137,7 @@ struct ControllerStatus {
     ControllerColors colors_state{};
     BatteryLevelState battery_state{};
     CameraState camera_state{};
+    RingSensorForce ring_analog_state{};
     NfcState nfc_state{};
 };
 
@@ -141,6 +150,7 @@ enum class ControllerTriggerType {
     Battery,
     Vibration,
     IrSensor,
+    RingController,
     Nfc,
     Connected,
     Disconnected,
@@ -294,6 +304,9 @@ public:
     /// Returns the latest camera status from the controller with parameters
     CameraValues GetCameraValues() const;
 
+    /// Returns the latest status of analog input from the ring sensor with parameters
+    RingAnalogValue GetRingSensorValues() const;
+
     /// Returns the latest status of button input for the hid::HomeButton service
     HomeButtonState GetHomeButtons() const;
 
@@ -324,6 +337,9 @@ public:
     /// Returns the latest camera status from the controller
     const CameraState& GetCamera() const;
 
+    /// Returns the latest ringcon force sensor value
+    RingSensorForce GetRingSensorForce() const;
+
     /// Returns the latest ntag status from the controller
     const NfcState& GetNfc() const;
 
@@ -353,6 +369,15 @@ public:
      */
     bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format);
 
+    // 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 true if the device has nfc support
     bool HasNfc() const;
 
@@ -435,7 +460,7 @@ private:
     /**
      * Updates the battery status of the controller
      * @param callback A CallbackStatus containing the battery status
-     * @param index Button ID of the to be updated
+     * @param index battery ID of the to be updated
      */
     void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index);
 
@@ -445,6 +470,12 @@ private:
      */
     void SetCamera(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);
+
     /**
      * Updates the nfc status of the controller
      * @param callback A CallbackStatus containing the nfc status
@@ -485,6 +516,7 @@ private:
     TriggerParams trigger_params;
     BatteryParams battery_params;
     CameraParams camera_params;
+    RingAnalogParams ring_params;
     NfcParams nfc_params;
     OutputParams output_params;
 
@@ -494,6 +526,7 @@ private:
     TriggerDevices trigger_devices;
     BatteryDevices battery_devices;
     CameraDevices camera_devices;
+    RingAnalogDevice ring_analog_device;
     NfcDevices nfc_devices;
     OutputDevices output_devices;
 
diff --git a/src/core/hid/emulated_devices.cpp b/src/core/hid/emulated_devices.cpp
index e421828d25..836f32c0f8 100644
--- a/src/core/hid/emulated_devices.cpp
+++ b/src/core/hid/emulated_devices.cpp
@@ -14,7 +14,6 @@ EmulatedDevices::EmulatedDevices() = default;
 EmulatedDevices::~EmulatedDevices() = default;
 
 void EmulatedDevices::ReloadFromSettings() {
-    ring_params = Common::ParamPackage(Settings::values.ringcon_analogs);
     ReloadInput();
 }
 
@@ -66,8 +65,6 @@ void EmulatedDevices::ReloadInput() {
         key_index++;
     }
 
-    ring_analog_device = Common::Input::CreateInputDevice(ring_params);
-
     for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
         if (!mouse_button_devices[index]) {
             continue;
@@ -122,13 +119,6 @@ void EmulatedDevices::ReloadInput() {
                 },
         });
     }
-
-    if (ring_analog_device) {
-        ring_analog_device->SetCallback({
-            .on_change =
-                [this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
-        });
-    }
 }
 
 void EmulatedDevices::UnloadInput() {
@@ -145,7 +135,6 @@ void EmulatedDevices::UnloadInput() {
     for (auto& button : keyboard_modifier_devices) {
         button.reset();
     }
-    ring_analog_device.reset();
 }
 
 void EmulatedDevices::EnableConfiguration() {
@@ -165,7 +154,6 @@ void EmulatedDevices::SaveCurrentConfig() {
     if (!is_configuring) {
         return;
     }
-    Settings::values.ringcon_analogs = ring_params.Serialize();
 }
 
 void EmulatedDevices::RestoreConfig() {
@@ -175,15 +163,6 @@ void EmulatedDevices::RestoreConfig() {
     ReloadFromSettings();
 }
 
-Common::ParamPackage EmulatedDevices::GetRingParam() const {
-    return ring_params;
-}
-
-void EmulatedDevices::SetRingParam(Common::ParamPackage param) {
-    ring_params = std::move(param);
-    ReloadInput();
-}
-
 void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
                                         std::size_t index) {
     if (index >= device_status.keyboard_values.size()) {
@@ -430,23 +409,6 @@ void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callbac
     TriggerOnChange(DeviceTriggerType::Mouse);
 }
 
-void EmulatedDevices::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
-    std::lock_guard lock{mutex};
-    const auto force_value = TransformToStick(callback);
-
-    device_status.ring_analog_value = force_value.x;
-
-    if (is_configuring) {
-        device_status.ring_analog_value = {};
-        TriggerOnChange(DeviceTriggerType::RingController);
-        return;
-    }
-
-    device_status.ring_analog_state.force = force_value.x.value;
-
-    TriggerOnChange(DeviceTriggerType::RingController);
-}
-
 KeyboardValues EmulatedDevices::GetKeyboardValues() const {
     std::scoped_lock lock{mutex};
     return device_status.keyboard_values;
@@ -462,10 +424,6 @@ MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
     return device_status.mouse_button_values;
 }
 
-RingAnalogValue EmulatedDevices::GetRingSensorValues() const {
-    return device_status.ring_analog_value;
-}
-
 KeyboardKey EmulatedDevices::GetKeyboard() const {
     std::scoped_lock lock{mutex};
     return device_status.keyboard_state;
@@ -491,10 +449,6 @@ AnalogStickState EmulatedDevices::GetMouseWheel() const {
     return device_status.mouse_wheel_state;
 }
 
-RingSensorForce EmulatedDevices::GetRingSensorForce() const {
-    return device_status.ring_analog_state;
-}
-
 void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
     std::scoped_lock lock{callback_mutex};
     for (const auto& poller_pair : callback_list) {
diff --git a/src/core/hid/emulated_devices.h b/src/core/hid/emulated_devices.h
index 4cdbf9dc64..76f9150df4 100644
--- a/src/core/hid/emulated_devices.h
+++ b/src/core/hid/emulated_devices.h
@@ -26,11 +26,9 @@ using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice
 using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
                                       Settings::NativeMouseWheel::NumMouseWheels>;
 using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
-using RingAnalogDevice = std::unique_ptr<Common::Input::InputDevice>;
 
 using MouseButtonParams =
     std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
-using RingAnalogParams = Common::ParamPackage;
 
 using KeyboardValues =
     std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
@@ -41,17 +39,12 @@ using MouseButtonValues =
 using MouseAnalogValues =
     std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
 using MouseStickValue = Common::Input::TouchStatus;
-using RingAnalogValue = Common::Input::AnalogStatus;
 
 struct MousePosition {
     f32 x;
     f32 y;
 };
 
-struct RingSensorForce {
-    f32 force;
-};
-
 struct DeviceStatus {
     // Data from input_common
     KeyboardValues keyboard_values{};
@@ -59,7 +52,6 @@ struct DeviceStatus {
     MouseButtonValues mouse_button_values{};
     MouseAnalogValues mouse_analog_values{};
     MouseStickValue mouse_stick_value{};
-    RingAnalogValue ring_analog_value{};
 
     // Data for HID serices
     KeyboardKey keyboard_state{};
@@ -67,7 +59,6 @@ struct DeviceStatus {
     MouseButton mouse_button_state{};
     MousePosition mouse_position_state{};
     AnalogStickState mouse_wheel_state{};
-    RingSensorForce ring_analog_state{};
 };
 
 enum class DeviceTriggerType {
@@ -138,9 +129,6 @@ public:
     /// Returns the latest status of button input from the mouse with parameters
     MouseButtonValues GetMouseButtonsValues() const;
 
-    /// Returns the latest status of analog input from the ring sensor with parameters
-    RingAnalogValue GetRingSensorValues() const;
-
     /// Returns the latest status of button input from the keyboard
     KeyboardKey GetKeyboard() const;
 
@@ -156,9 +144,6 @@ public:
     /// Returns the latest mouse wheel change
     AnalogStickState GetMouseWheel() const;
 
-    /// Returns the latest ringcon force sensor value
-    RingSensorForce GetRingSensorForce() const;
-
     /**
      * Adds a callback to the list of events
      * @param update_callback InterfaceUpdateCallback that will be triggered
@@ -224,14 +209,11 @@ private:
 
     bool is_configuring{false};
 
-    RingAnalogParams ring_params;
-
     KeyboardDevices keyboard_devices;
     KeyboardModifierDevices keyboard_modifier_devices;
     MouseButtonDevices mouse_button_devices;
     MouseAnalogDevices mouse_analog_devices;
     MouseStickDevice mouse_stick_device;
-    RingAnalogDevice ring_analog_device;
 
     mutable std::mutex mutex;
     mutable std::mutex callback_mutex;
diff --git a/src/core/hle/service/hid/hidbus/ringcon.cpp b/src/core/hle/service/hid/hidbus/ringcon.cpp
index 57f1a2a26b..af776d5062 100644
--- a/src/core/hle/service/hid/hidbus/ringcon.cpp
+++ b/src/core/hle/service/hid/hidbus/ringcon.cpp
@@ -1,7 +1,7 @@
 // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
-#include "core/hid/emulated_devices.h"
+#include "core/hid/emulated_controller.h"
 #include "core/hid/hid_core.h"
 #include "core/hle/kernel/k_event.h"
 #include "core/hle/kernel/k_readable_event.h"
@@ -12,16 +12,18 @@ namespace Service::HID {
 RingController::RingController(Core::HID::HIDCore& hid_core_,
                                KernelHelpers::ServiceContext& service_context_)
     : HidbusBase(service_context_) {
-    input = hid_core_.GetEmulatedDevices();
+    input = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1);
 }
 
 RingController::~RingController() = default;
 
 void RingController::OnInit() {
+    input->SetPollingMode(Common::Input::PollingMode::Ring);
     return;
 }
 
 void RingController::OnRelease() {
+    input->SetPollingMode(Common::Input::PollingMode::Active);
     return;
 };
 
diff --git a/src/core/hle/service/hid/hidbus/ringcon.h b/src/core/hle/service/hid/hidbus/ringcon.h
index b37df50ac8..845ce85a5b 100644
--- a/src/core/hle/service/hid/hidbus/ringcon.h
+++ b/src/core/hle/service/hid/hidbus/ringcon.h
@@ -9,7 +9,7 @@
 #include "core/hle/service/hid/hidbus/hidbus_base.h"
 
 namespace Core::HID {
-class EmulatedDevices;
+class EmulatedController;
 } // namespace Core::HID
 
 namespace Service::HID {
@@ -248,6 +248,6 @@ private:
         .zero = {.value = idle_value, .crc = 225},
     };
 
-    Core::HID::EmulatedDevices* input;
+    Core::HID::EmulatedController* input;
 };
 } // namespace Service::HID
diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp
index 688c2dd389..0cfe3b60ef 100644
--- a/src/yuzu/configuration/configure_ringcon.cpp
+++ b/src/yuzu/configuration/configure_ringcon.cpp
@@ -6,7 +6,7 @@
 #include <QMenu>
 #include <QTimer>
 
-#include "core/hid/emulated_devices.h"
+#include "core/hid/emulated_controller.h"
 #include "core/hid/hid_core.h"
 #include "input_common/drivers/keyboard.h"
 #include "input_common/drivers/mouse.h"
@@ -126,9 +126,9 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
         ui->buttonRingAnalogPush,
     };
 
-    emulated_device = hid_core_.GetEmulatedDevices();
-    emulated_device->SaveCurrentConfig();
-    emulated_device->EnableConfiguration();
+    emulated_controller = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1);
+    emulated_controller->SaveCurrentConfig();
+    emulated_controller->EnableConfiguration();
 
     LoadConfiguration();
 
@@ -143,9 +143,9 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
             HandleClick(
                 analog_map_buttons[sub_button_id],
                 [=, this](const Common::ParamPackage& params) {
-                    Common::ParamPackage param = emulated_device->GetRingParam();
+                    Common::ParamPackage param = emulated_controller->GetRingParam();
                     SetAnalogParam(params, param, analog_sub_buttons[sub_button_id]);
-                    emulated_device->SetRingParam(param);
+                    emulated_controller->SetRingParam(param);
                 },
                 InputCommon::Polling::InputType::Stick);
         });
@@ -155,16 +155,16 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
         connect(analog_button, &QPushButton::customContextMenuRequested,
                 [=, this](const QPoint& menu_location) {
                     QMenu context_menu;
-                    Common::ParamPackage param = emulated_device->GetRingParam();
+                    Common::ParamPackage param = emulated_controller->GetRingParam();
                     context_menu.addAction(tr("Clear"), [&] {
-                        emulated_device->SetRingParam({});
+                        emulated_controller->SetRingParam(param);
                         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);
+                        emulated_controller->SetRingParam(param);
                         for (int sub_button_id2 = 0; sub_button_id2 < ANALOG_SUB_BUTTONS_NUM;
                              ++sub_button_id2) {
                             analog_map_buttons[sub_button_id2]->setText(
@@ -177,11 +177,11 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
     }
 
     connect(ui->sliderRingAnalogDeadzone, &QSlider::valueChanged, [=, this] {
-        Common::ParamPackage param = emulated_device->GetRingParam();
+        Common::ParamPackage param = emulated_controller->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);
+        emulated_controller->SetRingParam(param);
     });
 
     connect(ui->restore_defaults_button, &QPushButton::clicked, this,
@@ -202,7 +202,7 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
 }
 
 ConfigureRingController::~ConfigureRingController() {
-    emulated_device->DisableConfiguration();
+    emulated_controller->DisableConfiguration();
 };
 
 void ConfigureRingController::changeEvent(QEvent* event) {
@@ -219,7 +219,7 @@ void ConfigureRingController::RetranslateUI() {
 
 void ConfigureRingController::UpdateUI() {
     RetranslateUI();
-    const Common::ParamPackage param = emulated_device->GetRingParam();
+    const Common::ParamPackage param = emulated_controller->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];
@@ -240,9 +240,9 @@ void ConfigureRingController::UpdateUI() {
 }
 
 void ConfigureRingController::ApplyConfiguration() {
-    emulated_device->DisableConfiguration();
-    emulated_device->SaveCurrentConfig();
-    emulated_device->EnableConfiguration();
+    emulated_controller->DisableConfiguration();
+    emulated_controller->SaveCurrentConfig();
+    emulated_controller->EnableConfiguration();
 }
 
 void ConfigureRingController::LoadConfiguration() {
@@ -252,7 +252,7 @@ void ConfigureRingController::LoadConfiguration() {
 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));
+    emulated_controller->SetRingParam(Common::ParamPackage(default_ring_string));
     UpdateUI();
 }
 
diff --git a/src/yuzu/configuration/configure_ringcon.h b/src/yuzu/configuration/configure_ringcon.h
index 38a9cb7166..6e693e0dd2 100644
--- a/src/yuzu/configuration/configure_ringcon.h
+++ b/src/yuzu/configuration/configure_ringcon.h
@@ -13,7 +13,7 @@ class InputSubsystem;
 
 namespace Core::HID {
 class HIDCore;
-class EmulatedDevices;
+class EmulatedController;
 } // namespace Core::HID
 
 namespace Ui {
@@ -78,7 +78,7 @@ private:
     std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
 
     InputCommon::InputSubsystem* input_subsystem;
-    Core::HID::EmulatedDevices* emulated_device;
+    Core::HID::EmulatedController* emulated_controller;
 
     std::unique_ptr<Ui::ConfigureRingController> ui;
 };

From ed5fa10e9729cf55205533f62a428e646aa5ed7c Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 20 Dec 2022 13:23:31 -0600
Subject: [PATCH 05/24] core: hid: Enable pulling color data from controllers

---
 src/core/hid/emulated_controller.cpp | 80 ++++++++++++++++++++++++++++
 src/core/hid/emulated_controller.h   | 12 +++++
 src/core/hid/input_converter.cpp     | 14 +++++
 src/core/hid/input_converter.h       | 10 +++-
 src/input_common/drivers/joycon.cpp  | 11 +++-
 src/input_common/input_engine.cpp    | 37 +++++++++++++
 src/input_common/input_engine.h      |  6 +++
 src/input_common/input_poller.cpp    | 67 +++++++++++++++++++++++
 src/input_common/input_poller.h      | 11 ++++
 9 files changed, 246 insertions(+), 2 deletions(-)

diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 128101e8ca..89638cb856 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -93,6 +93,7 @@ void EmulatedController::ReloadFromSettings() {
         motion_params[index] = Common::ParamPackage(player.motions[index]);
     }
 
+    controller.color_values = {};
     controller.colors_state.fullkey = {
         .body = GetNpadColor(player.body_color_left),
         .button = GetNpadColor(player.button_color_left),
@@ -132,6 +133,11 @@ void EmulatedController::LoadDevices() {
     trigger_params[LeftIndex] = button_params[Settings::NativeButton::ZL];
     trigger_params[RightIndex] = button_params[Settings::NativeButton::ZR];
 
+    color_params[LeftIndex] = left_joycon;
+    color_params[RightIndex] = right_joycon;
+    color_params[LeftIndex].Set("color", true);
+    color_params[RightIndex].Set("color", true);
+
     battery_params[LeftIndex] = left_joycon;
     battery_params[RightIndex] = right_joycon;
     battery_params[LeftIndex].Set("battery", true);
@@ -160,6 +166,7 @@ void EmulatedController::LoadDevices() {
                            Common::Input::CreateInputDevice);
     std::ranges::transform(battery_params, battery_devices.begin(),
                            Common::Input::CreateInputDevice);
+    std::ranges::transform(color_params, color_devices.begin(), Common::Input::CreateInputDevice);
     camera_devices = Common::Input::CreateInputDevice(camera_params);
     ring_analog_device = Common::Input::CreateInputDevice(ring_params);
     nfc_devices = Common::Input::CreateInputDevice(nfc_params);
@@ -324,6 +331,19 @@ void EmulatedController::ReloadInput() {
         battery_devices[index]->ForceUpdate();
     }
 
+    for (std::size_t index = 0; index < color_devices.size(); ++index) {
+        if (!color_devices[index]) {
+            continue;
+        }
+        color_devices[index]->SetCallback({
+            .on_change =
+                [this, index](const Common::Input::CallbackStatus& callback) {
+                    SetColors(callback, index);
+                },
+        });
+        color_devices[index]->ForceUpdate();
+    }
+
     for (std::size_t index = 0; index < motion_devices.size(); ++index) {
         if (!motion_devices[index]) {
             continue;
@@ -429,6 +449,9 @@ void EmulatedController::UnloadInput() {
     for (auto& battery : battery_devices) {
         battery.reset();
     }
+    for (auto& color : color_devices) {
+        color.reset();
+    }
     for (auto& output : output_devices) {
         output.reset();
     }
@@ -458,6 +481,11 @@ void EmulatedController::EnableConfiguration() {
 void EmulatedController::DisableConfiguration() {
     is_configuring = false;
 
+    // Get Joycon colors before turning on the controller
+    for (const auto& color_device : color_devices) {
+        color_device->ForceUpdate();
+    }
+
     // Apply temporary npad type to the real controller
     if (tmp_npad_type != npad_type) {
         if (is_connected) {
@@ -926,6 +954,58 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
     TriggerOnChange(ControllerTriggerType::Motion, true);
 }
 
+void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback,
+                                   std::size_t index) {
+    if (index >= controller.color_values.size()) {
+        return;
+    }
+    std::unique_lock lock{mutex};
+    controller.color_values[index] = TransformToColor(callback);
+
+    if (is_configuring) {
+        lock.unlock();
+        TriggerOnChange(ControllerTriggerType::Color, false);
+        return;
+    }
+
+    if (controller.color_values[index].body == 0) {
+        return;
+    }
+
+    controller.colors_state.fullkey = {
+        .body = GetNpadColor(controller.color_values[index].body),
+        .button = GetNpadColor(controller.color_values[index].buttons),
+    };
+    if (npad_type == NpadStyleIndex::ProController) {
+        controller.colors_state.left = {
+            .body = GetNpadColor(controller.color_values[index].left_grip),
+            .button = GetNpadColor(controller.color_values[index].buttons),
+        };
+        controller.colors_state.right = {
+            .body = GetNpadColor(controller.color_values[index].right_grip),
+            .button = GetNpadColor(controller.color_values[index].buttons),
+        };
+    } else {
+        switch (index) {
+        case LeftIndex:
+            controller.colors_state.left = {
+                .body = GetNpadColor(controller.color_values[index].body),
+                .button = GetNpadColor(controller.color_values[index].buttons),
+            };
+            break;
+        case RightIndex:
+            controller.colors_state.right = {
+                .body = GetNpadColor(controller.color_values[index].body),
+                .button = GetNpadColor(controller.color_values[index].buttons),
+            };
+            break;
+        }
+    }
+
+    lock.unlock();
+    TriggerOnChange(ControllerTriggerType::Color, true);
+}
+
 void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback,
                                     std::size_t index) {
     if (index >= controller.battery_values.size()) {
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index aed331a1aa..d044cc36b8 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -35,6 +35,8 @@ using ControllerMotionDevices =
     std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>;
 using TriggerDevices =
     std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
+using ColorDevices =
+    std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
 using BatteryDevices =
     std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
 using CameraDevices = std::unique_ptr<Common::Input::InputDevice>;
@@ -46,6 +48,7 @@ using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::Nu
 using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
 using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
 using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
+using ColorParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using CameraParams = Common::ParamPackage;
 using RingAnalogParams = Common::ParamPackage;
@@ -457,6 +460,13 @@ private:
      */
     void SetMotion(const Common::Input::CallbackStatus& callback, std::size_t index);
 
+    /**
+     * Updates the color status of the controller
+     * @param callback A CallbackStatus containing the color status
+     * @param index color ID of the to be updated
+     */
+    void SetColors(const Common::Input::CallbackStatus& callback, std::size_t index);
+
     /**
      * Updates the battery status of the controller
      * @param callback A CallbackStatus containing the battery status
@@ -515,6 +525,7 @@ private:
     ControllerMotionParams motion_params;
     TriggerParams trigger_params;
     BatteryParams battery_params;
+    ColorParams color_params;
     CameraParams camera_params;
     RingAnalogParams ring_params;
     NfcParams nfc_params;
@@ -525,6 +536,7 @@ private:
     ControllerMotionDevices motion_devices;
     TriggerDevices trigger_devices;
     BatteryDevices battery_devices;
+    ColorDevices color_devices;
     CameraDevices camera_devices;
     RingAnalogDevice ring_analog_device;
     NfcDevices nfc_devices;
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
index 5026928753..d7e253044a 100644
--- a/src/core/hid/input_converter.cpp
+++ b/src/core/hid/input_converter.cpp
@@ -304,6 +304,20 @@ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& cal
     return nfc;
 }
 
+Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback) {
+    Common::Input::BodyColorStatus color{};
+    switch (callback.type) {
+    case Common::Input::InputType::Color:
+        color = callback.color_status;
+        break;
+    default:
+        LOG_ERROR(Input, "Conversion from type {} to color not implemented", callback.type);
+        break;
+    }
+
+    return color;
+}
+
 void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
     const auto& properties = analog.properties;
     float& raw_value = analog.raw_value;
diff --git a/src/core/hid/input_converter.h b/src/core/hid/input_converter.h
index b7eb6e660c..c51c03e57e 100644
--- a/src/core/hid/input_converter.h
+++ b/src/core/hid/input_converter.h
@@ -88,10 +88,18 @@ Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatu
  * Converts raw input data into a valid nfc status.
  *
  * @param callback Supported callbacks: Nfc.
- * @return A valid CameraObject object.
+ * @return A valid data tag vector.
  */
 Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback);
 
+/**
+ * Converts raw input data into a valid color status.
+ *
+ * @param callback Supported callbacks: Color.
+ * @return A valid Color object.
+ */
+Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback);
+
 /**
  * Converts raw analog data into a valid analog value
  * @param analog An analog object containing raw data and properties
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index 1fca11d34e..c6f78c9893 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -335,7 +335,16 @@ void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type,
 }
 
 void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type,
-                            const Joycon::Color& value) {}
+                            const Joycon::Color& value) {
+    const auto identifier = GetIdentifier(port, type);
+    Common::Input::BodyColorStatus color{
+        .body = value.body,
+        .buttons = value.buttons,
+        .left_grip = value.left_grip,
+        .right_grip = value.right_grip,
+    };
+    SetColor(identifier, color);
+}
 
 void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) {
     const auto identifier = GetIdentifier(port, type);
diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp
index 61cfd0911c..91aa96aa73 100644
--- a/src/input_common/input_engine.cpp
+++ b/src/input_common/input_engine.cpp
@@ -79,6 +79,17 @@ void InputEngine::SetBattery(const PadIdentifier& identifier, Common::Input::Bat
     TriggerOnBatteryChange(identifier, value);
 }
 
+void InputEngine::SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value) {
+    {
+        std::scoped_lock lock{mutex};
+        ControllerData& controller = controller_list.at(identifier);
+        if (!configuring) {
+            controller.color = value;
+        }
+    }
+    TriggerOnColorChange(identifier, value);
+}
+
 void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) {
     {
         std::scoped_lock lock{mutex};
@@ -176,6 +187,18 @@ Common::Input::BatteryLevel InputEngine::GetBattery(const PadIdentifier& identif
     return controller.battery;
 }
 
+Common::Input::BodyColorStatus InputEngine::GetColor(const PadIdentifier& identifier) const {
+    std::scoped_lock lock{mutex};
+    const auto controller_iter = controller_list.find(identifier);
+    if (controller_iter == controller_list.cend()) {
+        LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
+                  identifier.pad, identifier.port);
+        return {};
+    }
+    const ControllerData& controller = controller_iter->second;
+    return controller.color;
+}
+
 BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const {
     std::scoped_lock lock{mutex};
     const auto controller_iter = controller_list.find(identifier);
@@ -328,6 +351,20 @@ void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
     }
 }
 
+void InputEngine::TriggerOnColorChange(const PadIdentifier& identifier,
+                                       [[maybe_unused]] Common::Input::BodyColorStatus value) {
+    std::scoped_lock lock{mutex_callback};
+    for (const auto& poller_pair : callback_list) {
+        const InputIdentifier& poller = poller_pair.second;
+        if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Color, 0)) {
+            continue;
+        }
+        if (poller.callback.on_change) {
+            poller.callback.on_change();
+        }
+    }
+}
+
 void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
                                         const BasicMotion& value) {
     std::scoped_lock lock{mutex_callback};
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h
index 6cbcf5207e..6301c5719a 100644
--- a/src/input_common/input_engine.h
+++ b/src/input_common/input_engine.h
@@ -40,6 +40,7 @@ enum class EngineInputType {
     Battery,
     Button,
     Camera,
+    Color,
     HatButton,
     Motion,
     Nfc,
@@ -199,6 +200,7 @@ public:
     bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const;
     f32 GetAxis(const PadIdentifier& identifier, int axis) const;
     Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const;
+    Common::Input::BodyColorStatus GetColor(const PadIdentifier& identifier) const;
     BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
     Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const;
     Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const;
@@ -212,6 +214,7 @@ protected:
     void SetHatButton(const PadIdentifier& identifier, int button, u8 value);
     void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
     void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
+    void SetColor(const PadIdentifier& identifier, Common::Input::BodyColorStatus value);
     void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value);
     void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value);
     void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value);
@@ -227,6 +230,7 @@ private:
         std::unordered_map<int, float> axes;
         std::unordered_map<int, BasicMotion> motions;
         Common::Input::BatteryLevel battery{};
+        Common::Input::BodyColorStatus color{};
         Common::Input::CameraStatus camera{};
         Common::Input::NfcStatus nfc{};
     };
@@ -235,6 +239,8 @@ private:
     void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value);
     void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value);
     void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
+    void TriggerOnColorChange(const PadIdentifier& identifier,
+                              Common::Input::BodyColorStatus value);
     void TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
                                const BasicMotion& value);
     void TriggerOnCameraChange(const PadIdentifier& identifier,
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index fb8be42e2e..368ffbdd5a 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -498,6 +498,58 @@ private:
     InputEngine* input_engine;
 };
 
+class InputFromColor final : public Common::Input::InputDevice {
+public:
+    explicit InputFromColor(PadIdentifier identifier_, InputEngine* input_engine_)
+        : identifier(identifier_), input_engine(input_engine_) {
+        UpdateCallback engine_callback{[this]() { OnChange(); }};
+        const InputIdentifier input_identifier{
+            .identifier = identifier,
+            .type = EngineInputType::Color,
+            .index = 0,
+            .callback = engine_callback,
+        };
+        last_color_value = {};
+        callback_key = input_engine->SetCallback(input_identifier);
+    }
+
+    ~InputFromColor() override {
+        input_engine->DeleteCallback(callback_key);
+    }
+
+    Common::Input::BodyColorStatus GetStatus() const {
+        return input_engine->GetColor(identifier);
+    }
+
+    void ForceUpdate() override {
+        const Common::Input::CallbackStatus status{
+            .type = Common::Input::InputType::Color,
+            .color_status = GetStatus(),
+        };
+
+        last_color_value = status.color_status;
+        TriggerOnChange(status);
+    }
+
+    void OnChange() {
+        const Common::Input::CallbackStatus status{
+            .type = Common::Input::InputType::Color,
+            .color_status = GetStatus(),
+        };
+
+        if (status.color_status.body != last_color_value.body) {
+            last_color_value = status.color_status;
+            TriggerOnChange(status);
+        }
+    }
+
+private:
+    const PadIdentifier identifier;
+    int callback_key;
+    Common::Input::BodyColorStatus last_color_value;
+    InputEngine* input_engine;
+};
+
 class InputFromMotion final : public Common::Input::InputDevice {
 public:
     explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, float gyro_threshold_,
@@ -966,6 +1018,18 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice(
     return std::make_unique<InputFromBattery>(identifier, input_engine.get());
 }
 
+std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateColorDevice(
+    const Common::ParamPackage& params) {
+    const PadIdentifier identifier = {
+        .guid = Common::UUID{params.Get("guid", "")},
+        .port = static_cast<std::size_t>(params.Get("port", 0)),
+        .pad = static_cast<std::size_t>(params.Get("pad", 0)),
+    };
+
+    input_engine->PreSetController(identifier);
+    return std::make_unique<InputFromColor>(identifier, input_engine.get());
+}
+
 std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
     Common::ParamPackage params) {
     const PadIdentifier identifier = {
@@ -1053,6 +1117,9 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
     if (params.Has("battery")) {
         return CreateBatteryDevice(params);
     }
+    if (params.Has("color")) {
+        return CreateColorDevice(params);
+    }
     if (params.Has("camera")) {
         return CreateCameraDevice(params);
     }
diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h
index d7db13ce42..e097e254c8 100644
--- a/src/input_common/input_poller.h
+++ b/src/input_common/input_poller.h
@@ -190,6 +190,17 @@ private:
     std::unique_ptr<Common::Input::InputDevice> CreateBatteryDevice(
         const Common::ParamPackage& params);
 
+    /**
+     * Creates a color device from the parameters given.
+     * @param params contains parameters for creating the device:
+     *               - "guid": text string for identifying controllers
+     *               - "port": port of the connected device
+     *               - "pad": slot of the connected controller
+     * @returns a unique input device with the parameters specified
+     */
+    std::unique_ptr<Common::Input::InputDevice> CreateColorDevice(
+        const Common::ParamPackage& params);
+
     /**
      * Creates a motion device from the parameters given.
      * @param params contains parameters for creating the device:

From 36d5e0a41151ae39b5fb827c5085142ab4b296a5 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 20 Dec 2022 13:33:45 -0600
Subject: [PATCH 06/24] service: hid: Set led pattern and fix color detection

---
 src/core/hle/service/hid/controllers/npad.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 2f871de310..83b3680911 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -272,6 +272,8 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
         }
         break;
     case Core::HID::NpadStyleIndex::JoyconLeft:
+        shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
+        shared_memory->fullkey_color.fullkey = body_colors.left;
         shared_memory->joycon_color.attribute = ColorAttribute::Ok;
         shared_memory->joycon_color.left = body_colors.left;
         shared_memory->battery_level_dual = battery_level.left.battery_level;
@@ -285,6 +287,8 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
         shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1);
         break;
     case Core::HID::NpadStyleIndex::JoyconRight:
+        shared_memory->fullkey_color.attribute = ColorAttribute::Ok;
+        shared_memory->fullkey_color.fullkey = body_colors.right;
         shared_memory->joycon_color.attribute = ColorAttribute::Ok;
         shared_memory->joycon_color.right = body_colors.right;
         shared_memory->battery_level_right = battery_level.right.battery_level;
@@ -332,6 +336,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
 
     controller.is_connected = true;
     controller.device->Connect();
+    controller.device->SetLedPattern();
     SignalStyleSetChangedEvent(npad_id);
     WriteEmptyEntry(controller.shared_memory);
 }

From 6aa6301acdf1c29b80ebfc7fca2aac326400760f Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 20 Dec 2022 13:54:17 -0600
Subject: [PATCH 07/24] input_common: Add joycon low level functions

---
 src/input_common/CMakeLists.txt               |   2 +
 .../joycon_protocol/common_protocol.cpp       | 286 ++++++++++++++++++
 .../helpers/joycon_protocol/common_protocol.h | 146 +++++++++
 3 files changed, 434 insertions(+)
 create mode 100644 src/input_common/helpers/joycon_protocol/common_protocol.cpp
 create mode 100644 src/input_common/helpers/joycon_protocol/common_protocol.h

diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 41885d0d26..566be9f90c 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -57,6 +57,8 @@ if (ENABLE_SDL2)
         drivers/sdl_driver.h
         helpers/joycon_driver.cpp
         helpers/joycon_driver.h
+        helpers/joycon_protocol/common_protocol.cpp
+        helpers/joycon_protocol/common_protocol.h
         helpers/joycon_protocol/joycon_types.h
     )
     target_link_libraries(input_common PRIVATE SDL2::SDL2)
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
new file mode 100644
index 0000000000..43a036e027
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -0,0 +1,286 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "input_common/helpers/joycon_protocol/common_protocol.h"
+
+namespace InputCommon::Joycon {
+JoyconCommonProtocol::JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_)
+    : hidapi_handle{hidapi_handle_} {}
+
+u8 JoyconCommonProtocol::GetCounter() {
+    hidapi_handle->packet_counter = (hidapi_handle->packet_counter + 1) & 0x0F;
+    return hidapi_handle->packet_counter;
+}
+
+void JoyconCommonProtocol::SetBlocking() {
+    SDL_hid_set_nonblocking(hidapi_handle->handle, 0);
+}
+
+void JoyconCommonProtocol::SetNonBlocking() {
+    SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
+}
+
+DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType& controller_type) {
+    std::vector<u8> buffer;
+    const auto result = ReadSPI(CalAddr::DEVICE_TYPE, 1, buffer);
+    controller_type = ControllerType::None;
+
+    if (result == DriverResult::Success) {
+        controller_type = static_cast<ControllerType>(buffer[0]);
+        // Fallback to 3rd party pro controllers
+        if (controller_type == ControllerType::None) {
+            controller_type = ControllerType::Pro;
+        }
+    }
+
+    return result;
+}
+
+DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device_info) {
+    ControllerType controller_type{ControllerType::None};
+    const auto result = GetDeviceType(controller_type);
+    if (result != DriverResult::Success || controller_type == ControllerType::None) {
+        return DriverResult::UnsupportedControllerType;
+    }
+
+    hidapi_handle->handle =
+        SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
+
+    if (!hidapi_handle->handle) {
+        LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
+                  device_info->vendor_id, device_info->product_id);
+        return DriverResult::HandleInUse;
+    }
+
+    SetNonBlocking();
+    return DriverResult::Success;
+}
+
+DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) {
+    const std::vector<u8> buffer{static_cast<u8>(report_mode)};
+    std::vector<u8> output;
+    return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer, output);
+}
+
+DriverResult JoyconCommonProtocol::SendData(std::span<const u8> buffer) {
+    const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size());
+
+    if (result == -1) {
+        return DriverResult::ErrorWritingData;
+    }
+
+    return DriverResult::Success;
+}
+
+DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, std::vector<u8>& output) {
+    constexpr int timeout_mili = 100;
+    constexpr int MaxTries = 10;
+    int tries = 0;
+    output.resize(MaxSubCommandResponseSize);
+
+    do {
+        int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(),
+                                          MaxSubCommandResponseSize, timeout_mili);
+
+        if (result < 1) {
+            LOG_ERROR(Input, "No response from joycon");
+        }
+        if (tries++ > MaxTries) {
+            return DriverResult::Timeout;
+        }
+    } while (output[0] != 0x21 && output[14] != static_cast<u8>(sc));
+
+    if (output[0] != 0x21 && output[14] != static_cast<u8>(sc)) {
+        return DriverResult::WrongReply;
+    }
+
+    return DriverResult::Success;
+}
+
+DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer,
+                                                  std::vector<u8>& output) {
+    std::vector<u8> local_buffer(MaxResponseSize);
+
+    local_buffer[0] = static_cast<u8>(OutputReport::RUMBLE_AND_SUBCMD);
+    local_buffer[1] = GetCounter();
+    local_buffer[10] = static_cast<u8>(sc);
+    for (std::size_t i = 0; i < buffer.size(); ++i) {
+        local_buffer[11 + i] = buffer[i];
+    }
+
+    auto result = SendData(local_buffer);
+
+    if (result != DriverResult::Success) {
+        return result;
+    }
+
+    result = GetSubCommandResponse(sc, output);
+
+    return DriverResult::Success;
+}
+
+DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) {
+    std::vector<u8> local_buffer(MaxResponseSize);
+
+    local_buffer[0] = static_cast<u8>(Joycon::OutputReport::RUMBLE_ONLY);
+    local_buffer[1] = GetCounter();
+
+    memcpy(local_buffer.data() + 2, buffer.data(), buffer.size());
+
+    return SendData(local_buffer);
+}
+
+DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output) {
+    constexpr std::size_t MaxTries = 10;
+    std::size_t tries = 0;
+    std::vector<u8> buffer = {0x00, 0x00, 0x00, 0x00, size};
+    std::vector<u8> local_buffer(size + 20);
+
+    buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF);
+    buffer[1] = static_cast<u8>((static_cast<u16>(addr) & 0xFF00) >> 8);
+    do {
+        const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, local_buffer);
+        if (result != DriverResult::Success) {
+            return result;
+        }
+
+        if (tries++ > MaxTries) {
+            return DriverResult::Timeout;
+        }
+    } while (local_buffer[15] != buffer[0] || local_buffer[16] != buffer[1]);
+
+    // Remove header from output
+    output = std::vector<u8>(local_buffer.begin() + 20, local_buffer.begin() + 20 + size);
+    return DriverResult::Success;
+}
+
+DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
+    std::vector<u8> output;
+
+    const std::vector<u8> mcu_state{static_cast<u8>(enable ? 1 : 0)};
+    const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state, output);
+
+    if (result != DriverResult::Success) {
+        LOG_ERROR(Input, "SendMCUData failed with error {}", result);
+    }
+
+    return result;
+}
+
+DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) {
+    LOG_DEBUG(Input, "ConfigureMCU");
+    std::vector<u8> output;
+
+    std::array<u8, sizeof(MCUConfig)> config_buffer;
+    memcpy(config_buffer.data(), &config, sizeof(MCUConfig));
+    config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36);
+
+    const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer, output);
+
+    if (result != DriverResult::Success) {
+        LOG_ERROR(Input, "Set MCU config failed with error {}", result);
+    }
+
+    return result;
+}
+
+DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode_,
+                                                      std::vector<u8>& output) {
+    const int report_mode = static_cast<u8>(report_mode_);
+    constexpr int TimeoutMili = 200;
+    constexpr int MaxTries = 9;
+    int tries = 0;
+    output.resize(0x170);
+
+    do {
+        int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(), 0x170, TimeoutMili);
+
+        if (result < 1) {
+            LOG_ERROR(Input, "No response from joycon attempt {}", tries);
+        }
+        if (tries++ > MaxTries) {
+            return DriverResult::Timeout;
+        }
+    } while (output[0] != report_mode || output[49] == 0xFF);
+
+    if (output[0] != report_mode || output[49] == 0xFF) {
+        return DriverResult::WrongReply;
+    }
+
+    return DriverResult::Success;
+}
+
+DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc,
+                                               std::span<const u8> buffer,
+                                               std::vector<u8>& output) {
+    std::vector<u8> local_buffer(MaxResponseSize);
+
+    local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
+    local_buffer[1] = GetCounter();
+    local_buffer[9] = static_cast<u8>(sc);
+    for (std::size_t i = 0; i < buffer.size(); ++i) {
+        local_buffer[10 + i] = buffer[i];
+    }
+
+    auto result = SendData(local_buffer);
+
+    if (result != DriverResult::Success) {
+        return result;
+    }
+
+    result = GetMCUDataResponse(report_mode, output);
+
+    return DriverResult::Success;
+}
+
+DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) {
+    std::vector<u8> output;
+    constexpr std::size_t MaxTries{8};
+    std::size_t tries{};
+
+    do {
+        const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)};
+        const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output);
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+
+        if (tries++ > MaxTries) {
+            return DriverResult::WrongReply;
+        }
+    } while (output[49] != 1 || output[56] != static_cast<u8>(mode));
+
+    return DriverResult::Success;
+}
+
+// crc-8-ccitt / polynomial 0x07 look up table
+static constexpr uint8_t mcu_crc8_table[256] = {
+    0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
+    0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
+    0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
+    0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
+    0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
+    0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
+    0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
+    0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
+    0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
+    0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
+    0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
+    0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
+    0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
+    0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
+    0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
+    0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3};
+
+u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const {
+    u8 crc8 = 0x0;
+
+    for (int i = 0; i < size; ++i) {
+        crc8 = mcu_crc8_table[(u8)(crc8 ^ buffer[i])];
+    }
+    return crc8;
+}
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h
new file mode 100644
index 0000000000..a65e4aa76a
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.h
@@ -0,0 +1,146 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
+// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
+// https://github.com/CTCaer/jc_toolkit
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
+
+#pragma once
+
+#include <memory>
+#include <span>
+#include <vector>
+
+#include "common/common_types.h"
+#include "input_common/helpers/joycon_protocol/joycon_types.h"
+
+namespace InputCommon::Joycon {
+
+/// Joycon driver functions that handle low level communication
+class JoyconCommonProtocol {
+public:
+    explicit JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_);
+
+    /**
+     * Sets handle to blocking. In blocking mode, SDL_hid_read() will wait (block) until there is
+     * data to read before returning.
+     */
+    void SetBlocking();
+
+    /**
+     * Sets handle to non blocking. In non-blocking mode calls to SDL_hid_read() will return
+     * immediately with a value of 0 if there is no data to be read
+     */
+    void SetNonBlocking();
+
+    /**
+     * Sends a request to obtain the joycon type from device
+     * @returns controller type of the joycon
+     */
+    DriverResult GetDeviceType(ControllerType& controller_type);
+
+    /**
+     * Verifies and sets the joycon_handle if device is valid
+     * @param device info from the driver
+     * @returns success if the device is valid
+     */
+    DriverResult CheckDeviceAccess(SDL_hid_device_info* device);
+
+    /**
+     * Sends a request to set the polling mode of the joycon
+     * @param report_mode polling mode to be set
+     */
+    DriverResult SetReportMode(Joycon::ReportMode report_mode);
+
+    /**
+     * Sends data to the joycon device
+     * @param buffer data to be send
+     */
+    DriverResult SendData(std::span<const u8> buffer);
+
+    /**
+     * Waits for incoming data of the joycon device that matchs the subcommand
+     * @param sub_command type of data to be returned
+     * @returns a buffer containing the responce
+     */
+    DriverResult GetSubCommandResponse(SubCommand sub_command, std::vector<u8>& output);
+
+    /**
+     * Sends a sub command to the device and waits for it's reply
+     * @param sc sub command to be send
+     * @param buffer data to be send
+     * @returns output buffer containing the responce
+     */
+    DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, std::vector<u8>& output);
+
+    /**
+     * Sends vibration data to the joycon
+     * @param buffer data to be send
+     */
+    DriverResult SendVibrationReport(std::span<const u8> buffer);
+
+    /**
+     * Reads the SPI memory stored on the joycon
+     * @param Initial address location
+     * @param size in bytes to be read
+     * @returns output buffer containing the responce
+     */
+    DriverResult ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output);
+
+    /**
+     * Enables MCU chip on the joycon
+     * @param enable if true the chip will be enabled
+     */
+    DriverResult EnableMCU(bool enable);
+
+    /**
+     * Configures the MCU to the correspoinding mode
+     * @param MCUConfig configuration
+     */
+    DriverResult ConfigureMCU(const MCUConfig& config);
+
+    /**
+     * Waits until there's MCU data available. On timeout returns error
+     * @param report mode of the expected reply
+     * @returns a buffer containing the responce
+     */
+    DriverResult GetMCUDataResponse(ReportMode report_mode_, std::vector<u8>& output);
+
+    /**
+     * Sends data to the MCU chip and waits for it's reply
+     * @param report mode of the expected reply
+     * @param sub command to be send
+     * @param buffer data to be send
+     * @returns output buffer containing the responce
+     */
+    DriverResult SendMCUData(ReportMode report_mode, SubCommand sc, std::span<const u8> buffer,
+                             std::vector<u8>& output);
+
+    /**
+     * Wait's until the MCU chip is on the specified mode
+     * @param report mode of the expected reply
+     * @param MCUMode configuration
+     */
+    DriverResult WaitSetMCUMode(ReportMode report_mode, MCUMode mode);
+
+    /**
+     * Calculates the checksum from the MCU data
+     * @param buffer containing the data to be send
+     * @param size of the buffer in bytes
+     * @returns byte with the correct checksum
+     */
+    u8 CalculateMCU_CRC8(u8* buffer, u8 size) const;
+
+private:
+    /**
+     * Increments and returns the packet counter of the handle
+     * @param joycon_handle device to send the data
+     * @returns packet counter value
+     */
+    u8 GetCounter();
+
+    std::shared_ptr<JoyconHandle> hidapi_handle;
+};
+
+} // namespace InputCommon::Joycon

From 594b2ade6d8d829c65166aebe12f5eb3463a6fe9 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 20 Dec 2022 14:30:03 -0600
Subject: [PATCH 08/24] input_common: Add support for joycon generic functions

---
 src/input_common/CMakeLists.txt               |   2 +
 src/input_common/helpers/joycon_driver.cpp    |  54 ++++++-
 src/input_common/helpers/joycon_driver.h      |   2 +
 .../joycon_protocol/generic_functions.cpp     | 147 ++++++++++++++++++
 .../joycon_protocol/generic_functions.h       | 108 +++++++++++++
 5 files changed, 310 insertions(+), 3 deletions(-)
 create mode 100644 src/input_common/helpers/joycon_protocol/generic_functions.cpp
 create mode 100644 src/input_common/helpers/joycon_protocol/generic_functions.h

diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 566be9f90c..a60cecaf47 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -59,6 +59,8 @@ if (ENABLE_SDL2)
         helpers/joycon_driver.h
         helpers/joycon_protocol/common_protocol.cpp
         helpers/joycon_protocol/common_protocol.h
+        helpers/joycon_protocol/generic_functions.cpp
+        helpers/joycon_protocol/generic_functions.h
         helpers/joycon_protocol/joycon_types.h
     )
     target_link_libraries(input_common PRIVATE SDL2::SDL2)
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index a0a2a180b5..0de55578b2 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -64,13 +64,24 @@ DriverResult JoyconDriver::InitializeDevice() {
     accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
 
     // Initialize HW Protocols
+    generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
 
     // Get fixed joycon info
+    generic_protocol->GetVersionNumber(version);
+    generic_protocol->GetColor(color);
+    if (handle_device_type == ControllerType::Pro) {
+        // Some 3rd party controllers aren't pro controllers
+        generic_protocol->GetControllerType(device_type);
+    } else {
+        device_type = handle_device_type;
+    }
+    generic_protocol->GetSerialNumber(serial_number);
     supported_features = GetSupportedFeatures();
 
     // Get Calibration data
 
     // Set led status
+    generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port));
 
     // Apply HW configuration
     SetPollingMode();
@@ -137,6 +148,9 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
     case InputReport::SIMPLE_HID_MODE:
         ReadPassiveMode(buffer);
         break;
+    case InputReport::SUBCMD_REPLY:
+        LOG_DEBUG(Input, "Unhandled command reply");
+        break;
     default:
         LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
         break;
@@ -145,6 +159,30 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
 
 void JoyconDriver::SetPollingMode() {
     disable_input_thread = true;
+
+    if (motion_enabled && supported_features.motion) {
+        generic_protocol->EnableImu(true);
+        generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance,
+                                       accelerometer_sensitivity, accelerometer_performance);
+    } else {
+        generic_protocol->EnableImu(false);
+    }
+
+    if (passive_enabled && supported_features.passive) {
+        const auto result = generic_protocol->EnablePassiveMode();
+        if (result == DriverResult::Success) {
+            disable_input_thread = false;
+            return;
+        }
+        LOG_ERROR(Input, "Error enabling passive mode");
+    }
+
+    // Default Mode
+    const auto result = generic_protocol->EnableActiveMode();
+    if (result != DriverResult::Success) {
+        LOG_ERROR(Input, "Error enabling active mode");
+    }
+
     disable_input_thread = false;
 }
 
@@ -257,15 +295,22 @@ bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
 
 DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
     std::scoped_lock lock{mutex};
+    if (disable_input_thread) {
+        return DriverResult::HandleInUse;
+    }
     return DriverResult::NotSupported;
 }
 
 DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
     std::scoped_lock lock{mutex};
-    return DriverResult::NotSupported;
+    if (disable_input_thread) {
+        return DriverResult::HandleInUse;
+    }
+    return generic_protocol->SetLedPattern(led_pattern);
 }
 
 DriverResult JoyconDriver::SetPasiveMode() {
+    std::scoped_lock lock{mutex};
     motion_enabled = false;
     hidbus_enabled = false;
     nfc_enabled = false;
@@ -275,7 +320,8 @@ DriverResult JoyconDriver::SetPasiveMode() {
 }
 
 DriverResult JoyconDriver::SetActiveMode() {
-    motion_enabled = false;
+    std::scoped_lock lock{mutex};
+    motion_enabled = true;
     hidbus_enabled = false;
     nfc_enabled = false;
     passive_enabled = false;
@@ -284,6 +330,7 @@ DriverResult JoyconDriver::SetActiveMode() {
 }
 
 DriverResult JoyconDriver::SetNfcMode() {
+    std::scoped_lock lock{mutex};
     motion_enabled = false;
     hidbus_enabled = false;
     nfc_enabled = true;
@@ -293,6 +340,7 @@ DriverResult JoyconDriver::SetNfcMode() {
 }
 
 DriverResult JoyconDriver::SetRingConMode() {
+    std::scoped_lock lock{mutex};
     motion_enabled = true;
     hidbus_enabled = true;
     nfc_enabled = false;
@@ -328,7 +376,7 @@ std::size_t JoyconDriver::GetDevicePort() const {
 
 ControllerType JoyconDriver::GetDeviceType() const {
     std::scoped_lock lock{mutex};
-    return handle_device_type;
+    return device_type;
 }
 
 ControllerType JoyconDriver::GetHandleDeviceType() const {
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index be3053a7b4..deb50ec772 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -8,6 +8,7 @@
 #include <span>
 #include <thread>
 
+#include "input_common/helpers/joycon_protocol/generic_functions.h"
 #include "input_common/helpers/joycon_protocol/joycon_types.h"
 
 namespace InputCommon::Joycon {
@@ -94,6 +95,7 @@ private:
     void ReadNfcIRMode(std::span<u8> buffer);
 
     // Protocol Features
+    std::unique_ptr<GenericProtocol> generic_protocol = nullptr;
 
     // Connection status
     bool is_connected{};
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.cpp b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
new file mode 100644
index 0000000000..829f7625d6
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
@@ -0,0 +1,147 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "input_common/helpers/joycon_protocol/generic_functions.h"
+
+namespace InputCommon::Joycon {
+
+GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle)
+    : JoyconCommonProtocol(handle) {}
+
+DriverResult GenericProtocol::EnablePassiveMode() {
+    SetBlocking();
+    const auto result = SetReportMode(ReportMode::SIMPLE_HID_MODE);
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult GenericProtocol::EnableActiveMode() {
+    SetBlocking();
+    const auto result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) {
+    std::vector<u8> output;
+    SetBlocking();
+
+    const auto result = SendSubCommand(SubCommand::REQ_DEV_INFO, {}, output);
+
+    device_info = {};
+    if (result == DriverResult::Success) {
+        memcpy(&device_info, output.data(), sizeof(DeviceInfo));
+    }
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type) {
+    return GetDeviceType(controller_type);
+}
+
+DriverResult GenericProtocol::EnableImu(bool enable) {
+    const std::vector<u8> buffer{static_cast<u8>(enable ? 1 : 0)};
+    std::vector<u8> output;
+    SetBlocking();
+    const auto result = SendSubCommand(SubCommand::ENABLE_IMU, buffer, output);
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
+                                           AccelerometerSensitivity asen,
+                                           AccelerometerPerformance afrec) {
+    const std::vector<u8> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
+                                 static_cast<u8>(gfrec), static_cast<u8>(afrec)};
+    std::vector<u8> output;
+    SetBlocking();
+    const auto result = SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer, output);
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult GenericProtocol::GetBattery(u32& battery_level) {
+    battery_level = 0;
+    return DriverResult::NotSupported;
+}
+
+DriverResult GenericProtocol::GetColor(Color& color) {
+    std::vector<u8> buffer;
+    SetBlocking();
+    const auto result = ReadSPI(CalAddr::COLOR_DATA, 12, buffer);
+    SetNonBlocking();
+
+    color = {};
+    if (result == DriverResult::Success) {
+        color.body = static_cast<u32>((buffer[0] << 16) | (buffer[1] << 8) | buffer[2]);
+        color.buttons = static_cast<u32>((buffer[3] << 16) | (buffer[4] << 8) | buffer[5]);
+        color.left_grip = static_cast<u32>((buffer[6] << 16) | (buffer[7] << 8) | buffer[8]);
+        color.right_grip = static_cast<u32>((buffer[9] << 16) | (buffer[10] << 8) | buffer[11]);
+    }
+
+    return result;
+}
+
+DriverResult GenericProtocol::GetSerialNumber(SerialNumber& serial_number) {
+    std::vector<u8> buffer;
+    SetBlocking();
+    const auto result = ReadSPI(CalAddr::SERIAL_NUMBER, 16, buffer);
+    SetNonBlocking();
+
+    serial_number = {};
+    if (result == DriverResult::Success) {
+        memcpy(serial_number.data(), buffer.data() + 1, sizeof(SerialNumber));
+    }
+
+    return result;
+}
+
+DriverResult GenericProtocol::GetTemperature(u32& temperature) {
+    // Not all devices have temperature sensor
+    temperature = 25;
+    return DriverResult::NotSupported;
+}
+
+DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) {
+    DeviceInfo device_info{};
+
+    const auto result = GetDeviceInfo(device_info);
+    version = device_info.firmware;
+
+    return result;
+}
+
+DriverResult GenericProtocol::SetHomeLight() {
+    const std::vector<u8> buffer{0x0f, 0xf0, 0x00};
+    std::vector<u8> output;
+    SetBlocking();
+
+    const auto result = SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer, output);
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult GenericProtocol::SetLedBusy() {
+    return DriverResult::NotSupported;
+}
+
+DriverResult GenericProtocol::SetLedPattern(u8 leds) {
+    const std::vector<u8> buffer{leds};
+    std::vector<u8> output;
+    SetBlocking();
+
+    const auto result = SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer, output);
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult GenericProtocol::SetLedBlinkPattern(u8 leds) {
+    return SetLedPattern(static_cast<u8>(leds << 4));
+}
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.h b/src/input_common/helpers/joycon_protocol/generic_functions.h
new file mode 100644
index 0000000000..c3e2ccadc0
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.h
@@ -0,0 +1,108 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
+// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
+// https://github.com/CTCaer/jc_toolkit
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
+
+#pragma once
+
+#include "input_common/helpers/joycon_protocol/common_protocol.h"
+#include "input_common/helpers/joycon_protocol/joycon_types.h"
+
+namespace InputCommon::Joycon {
+
+/// Joycon driver functions that easily implemented
+class GenericProtocol final : private JoyconCommonProtocol {
+public:
+    GenericProtocol(std::shared_ptr<JoyconHandle> handle);
+
+    /// Enables passive mode. This mode only sends button data on change. Sticks will return digital
+    /// data instead of analog. Motion will be disabled
+    DriverResult EnablePassiveMode();
+
+    /// Enables active mode. This mode will return the current status every 5-15ms
+    DriverResult EnableActiveMode();
+
+    /**
+     * Sends a request to obtain the joycon firmware and mac from handle
+     * @returns controller device info
+     */
+    DriverResult GetDeviceInfo(DeviceInfo& controller_type);
+
+    /**
+     * Sends a request to obtain the joycon type from handle
+     * @returns controller type of the joycon
+     */
+    DriverResult GetControllerType(ControllerType& controller_type);
+
+    /**
+     * Enables motion input
+     * @param enable if true motion data will be enabled
+     */
+    DriverResult EnableImu(bool enable);
+
+    /**
+     * Configures the motion sensor with the specified parameters
+     * @param gsen gyroscope sensor sensitvity in degrees per second
+     * @param gfrec gyroscope sensor frequency in hertz
+     * @param asen accelerometer sensitivity in G force
+     * @param afrec accelerometer frequency in hertz
+     */
+    DriverResult SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
+                              AccelerometerSensitivity asen, AccelerometerPerformance afrec);
+
+    /**
+     * Request battery level from the device
+     * @returns battery level
+     */
+    DriverResult GetBattery(u32& battery_level);
+
+    /**
+     * Request joycon colors from the device
+     * @returns colors of the body and buttons
+     */
+    DriverResult GetColor(Color& color);
+
+    /**
+     * Request joycon serial number from the device
+     * @returns 16 byte serial number
+     */
+    DriverResult GetSerialNumber(SerialNumber& serial_number);
+
+    /**
+     * Request joycon serial number from the device
+     * @returns 16 byte serial number
+     */
+    DriverResult GetTemperature(u32& temperature);
+
+    /**
+     * Request joycon serial number from the device
+     * @returns 16 byte serial number
+     */
+    DriverResult GetVersionNumber(FirmwareVersion& version);
+
+    /**
+     * Sets home led behaviour
+     */
+    DriverResult SetHomeLight();
+
+    /**
+     * Sets home led into a slow breathing state
+     */
+    DriverResult SetLedBusy();
+
+    /**
+     * Sets the 4 player leds on the joycon on a solid state
+     * @params bit flag containing the led state
+     */
+    DriverResult SetLedPattern(u8 leds);
+
+    /**
+     * Sets the 4 player leds on the joycon on a blinking state
+     * @returns bit flag containing the led state
+     */
+    DriverResult SetLedBlinkPattern(u8 leds);
+};
+} // namespace InputCommon::Joycon

From 5676c2e17fe895e450e185029991fc20bdf56ec5 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 20 Dec 2022 18:09:59 -0600
Subject: [PATCH 09/24] input_common: Use calibration from joycon

---
 src/input_common/CMakeLists.txt               |   2 +
 src/input_common/helpers/joycon_driver.cpp    |   4 +
 src/input_common/helpers/joycon_driver.h      |   2 +
 .../helpers/joycon_protocol/calibration.cpp   | 169 ++++++++++++++++++
 .../helpers/joycon_protocol/calibration.h     |  54 ++++++
 5 files changed, 231 insertions(+)
 create mode 100644 src/input_common/helpers/joycon_protocol/calibration.cpp
 create mode 100644 src/input_common/helpers/joycon_protocol/calibration.h

diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index a60cecaf47..d4307351c9 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -57,6 +57,8 @@ if (ENABLE_SDL2)
         drivers/sdl_driver.h
         helpers/joycon_driver.cpp
         helpers/joycon_driver.h
+        helpers/joycon_protocol/calibration.cpp
+        helpers/joycon_protocol/calibration.h
         helpers/joycon_protocol/common_protocol.cpp
         helpers/joycon_protocol/common_protocol.h
         helpers/joycon_protocol/generic_functions.cpp
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index 0de55578b2..ac11be1c17 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -64,6 +64,7 @@ DriverResult JoyconDriver::InitializeDevice() {
     accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
 
     // Initialize HW Protocols
+    calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
     generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
 
     // Get fixed joycon info
@@ -79,6 +80,9 @@ DriverResult JoyconDriver::InitializeDevice() {
     supported_features = GetSupportedFeatures();
 
     // Get Calibration data
+    calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration);
+    calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration);
+    calibration_protocol->GetImuCalibration(motion_calibration);
 
     // Set led status
     generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port));
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index deb50ec772..275c97b913 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -8,6 +8,7 @@
 #include <span>
 #include <thread>
 
+#include "input_common/helpers/joycon_protocol/calibration.h"
 #include "input_common/helpers/joycon_protocol/generic_functions.h"
 #include "input_common/helpers/joycon_protocol/joycon_types.h"
 
@@ -95,6 +96,7 @@ private:
     void ReadNfcIRMode(std::span<u8> buffer);
 
     // Protocol Features
+    std::unique_ptr<CalibrationProtocol> calibration_protocol = nullptr;
     std::unique_ptr<GenericProtocol> generic_protocol = nullptr;
 
     // Connection status
diff --git a/src/input_common/helpers/joycon_protocol/calibration.cpp b/src/input_common/helpers/joycon_protocol/calibration.cpp
new file mode 100644
index 0000000000..5c29af545b
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/calibration.cpp
@@ -0,0 +1,169 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <cstring>
+
+#include "input_common/helpers/joycon_protocol/calibration.h"
+#include "input_common/helpers/joycon_protocol/joycon_types.h"
+
+namespace InputCommon::Joycon {
+
+CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle)
+    : JoyconCommonProtocol(handle) {}
+
+DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) {
+    std::vector<u8> buffer;
+    DriverResult result{DriverResult::Success};
+    calibration = {};
+    SetBlocking();
+
+    result = ReadSPI(CalAddr::USER_LEFT_MAGIC, sizeof(u16), buffer);
+
+    if (result == DriverResult::Success) {
+        const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
+        if (has_user_calibration) {
+            result = ReadSPI(CalAddr::USER_LEFT_DATA, 9, buffer);
+        } else {
+            result = ReadSPI(CalAddr::FACT_LEFT_DATA, 9, buffer);
+        }
+    }
+
+    if (result == DriverResult::Success) {
+        calibration.x.max = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
+        calibration.y.max = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
+        calibration.x.center = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
+        calibration.y.center = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
+        calibration.x.min = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
+        calibration.y.min = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
+    }
+
+    // Nintendo fix for drifting stick
+    // result = ReadSPI(0x60, 0x86 ,buffer, 16);
+    // calibration.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
+
+    // Set a valid default calibration if data is missing
+    ValidateCalibration(calibration);
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibration& calibration) {
+    std::vector<u8> buffer;
+    DriverResult result{DriverResult::Success};
+    calibration = {};
+    SetBlocking();
+
+    result = ReadSPI(CalAddr::USER_RIGHT_MAGIC, sizeof(u16), buffer);
+
+    if (result == DriverResult::Success) {
+        const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
+        if (has_user_calibration) {
+            result = ReadSPI(CalAddr::USER_RIGHT_DATA, 9, buffer);
+        } else {
+            result = ReadSPI(CalAddr::FACT_RIGHT_DATA, 9, buffer);
+        }
+    }
+
+    if (result == DriverResult::Success) {
+        calibration.x.center = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
+        calibration.y.center = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
+        calibration.x.min = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
+        calibration.y.min = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
+        calibration.x.max = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
+        calibration.y.max = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
+    }
+
+    // Nintendo fix for drifting stick
+    // buffer = ReadSPI(0x60, 0x98 , 16);
+    // joystick.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
+
+    // Set a valid default calibration if data is missing
+    ValidateCalibration(calibration);
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) {
+    std::vector<u8> buffer;
+    DriverResult result{DriverResult::Success};
+    calibration = {};
+    SetBlocking();
+
+    result = ReadSPI(CalAddr::USER_IMU_MAGIC, sizeof(u16), buffer);
+
+    if (result == DriverResult::Success) {
+        const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
+        if (has_user_calibration) {
+            result = ReadSPI(CalAddr::USER_IMU_DATA, sizeof(IMUCalibration), buffer);
+        } else {
+            result = ReadSPI(CalAddr::FACT_IMU_DATA, sizeof(IMUCalibration), buffer);
+        }
+    }
+
+    if (result == DriverResult::Success) {
+        IMUCalibration device_calibration{};
+        memcpy(&device_calibration, buffer.data(), sizeof(IMUCalibration));
+        calibration.accelerometer[0].offset = device_calibration.accelerometer_offset[0];
+        calibration.accelerometer[1].offset = device_calibration.accelerometer_offset[1];
+        calibration.accelerometer[2].offset = device_calibration.accelerometer_offset[2];
+
+        calibration.accelerometer[0].scale = device_calibration.accelerometer_scale[0];
+        calibration.accelerometer[1].scale = device_calibration.accelerometer_scale[1];
+        calibration.accelerometer[2].scale = device_calibration.accelerometer_scale[2];
+
+        calibration.gyro[0].offset = device_calibration.gyroscope_offset[0];
+        calibration.gyro[1].offset = device_calibration.gyroscope_offset[1];
+        calibration.gyro[2].offset = device_calibration.gyroscope_offset[2];
+
+        calibration.gyro[0].scale = device_calibration.gyroscope_scale[0];
+        calibration.gyro[1].scale = device_calibration.gyroscope_scale[1];
+        calibration.gyro[2].scale = device_calibration.gyroscope_scale[2];
+    }
+
+    ValidateCalibration(calibration);
+
+    SetNonBlocking();
+    return result;
+}
+
+void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) {
+    constexpr u16 DefaultStickCenter{2048};
+    constexpr u16 DefaultStickRange{1740};
+
+    if (calibration.x.center == 0xFFF || calibration.x.center == 0) {
+        calibration.x.center = DefaultStickCenter;
+    }
+    if (calibration.x.max == 0xFFF || calibration.x.max == 0) {
+        calibration.x.max = DefaultStickRange;
+    }
+    if (calibration.x.min == 0xFFF || calibration.x.min == 0) {
+        calibration.x.min = DefaultStickRange;
+    }
+
+    if (calibration.y.center == 0xFFF || calibration.y.center == 0) {
+        calibration.y.center = DefaultStickCenter;
+    }
+    if (calibration.y.max == 0xFFF || calibration.y.max == 0) {
+        calibration.y.max = DefaultStickRange;
+    }
+    if (calibration.y.min == 0xFFF || calibration.y.min == 0) {
+        calibration.y.min = DefaultStickRange;
+    }
+}
+
+void CalibrationProtocol::ValidateCalibration(MotionCalibration& calibration) {
+    for (auto& sensor : calibration.accelerometer) {
+        if (sensor.scale == 0) {
+            sensor.scale = 0x4000;
+        }
+    }
+    for (auto& sensor : calibration.gyro) {
+        if (sensor.scale == 0) {
+            sensor.scale = 0x3be7;
+        }
+    }
+}
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/calibration.h b/src/input_common/helpers/joycon_protocol/calibration.h
new file mode 100644
index 0000000000..38214eed41
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/calibration.h
@@ -0,0 +1,54 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
+// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
+// https://github.com/CTCaer/jc_toolkit
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
+
+#pragma once
+
+#include <vector>
+
+#include "input_common/helpers/joycon_protocol/common_protocol.h"
+
+namespace InputCommon::Joycon {
+enum class DriverResult;
+struct JoyStickCalibration;
+struct IMUCalibration;
+struct JoyconHandle;
+} // namespace InputCommon::Joycon
+
+namespace InputCommon::Joycon {
+
+/// Driver functions related to retrieving calibration data from the device
+class CalibrationProtocol final : private JoyconCommonProtocol {
+public:
+    CalibrationProtocol(std::shared_ptr<JoyconHandle> handle);
+
+    /**
+     * Sends a request to obtain the left stick calibration from memory
+     * @param is_factory_calibration if true factory values will be returned
+     * @returns JoyStickCalibration of the left joystick
+     */
+    DriverResult GetLeftJoyStickCalibration(JoyStickCalibration& calibration);
+
+    /**
+     * Sends a request to obtain the right stick calibration from memory
+     * @param is_factory_calibration if true factory values will be returned
+     * @returns JoyStickCalibration of the right joystick
+     */
+    DriverResult GetRightJoyStickCalibration(JoyStickCalibration& calibration);
+
+    /**
+     * Sends a request to obtain the motion calibration from memory
+     * @returns ImuCalibration of the motion sensor
+     */
+    DriverResult GetImuCalibration(MotionCalibration& calibration);
+
+private:
+    void ValidateCalibration(JoyStickCalibration& calibration);
+    void ValidateCalibration(MotionCalibration& calibration);
+};
+
+} // namespace InputCommon::Joycon

From f09a023292e659af46d551b9b134d94d000a57c7 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 20 Dec 2022 20:27:34 -0600
Subject: [PATCH 10/24] input_common: Add support for joycon input reports

---
 src/input_common/CMakeLists.txt               |   4 +
 src/input_common/drivers/joycon.cpp           |  47 +--
 src/input_common/helpers/joycon_driver.cpp    | 100 +++---
 src/input_common/helpers/joycon_driver.h      |  23 +-
 .../helpers/joycon_protocol/poller.cpp        | 315 ++++++++++++++++++
 .../helpers/joycon_protocol/poller.h          |  77 +++++
 .../helpers/joycon_protocol/rumble.cpp        | 299 +++++++++++++++++
 .../helpers/joycon_protocol/rumble.h          |  33 ++
 8 files changed, 798 insertions(+), 100 deletions(-)
 create mode 100644 src/input_common/helpers/joycon_protocol/poller.cpp
 create mode 100644 src/input_common/helpers/joycon_protocol/poller.h
 create mode 100644 src/input_common/helpers/joycon_protocol/rumble.cpp
 create mode 100644 src/input_common/helpers/joycon_protocol/rumble.h

diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index d4307351c9..4ab1ccbfbe 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -64,6 +64,10 @@ if (ENABLE_SDL2)
         helpers/joycon_protocol/generic_functions.cpp
         helpers/joycon_protocol/generic_functions.h
         helpers/joycon_protocol/joycon_types.h
+        helpers/joycon_protocol/poller.cpp
+        helpers/joycon_protocol/poller.h
+        helpers/joycon_protocol/rumble.cpp
+        helpers/joycon_protocol/rumble.h
     )
     target_link_libraries(input_common PRIVATE SDL2::SDL2)
     target_compile_definitions(input_common PRIVATE HAVE_SDL2)
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index c6f78c9893..dbe730e1a3 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -167,30 +167,31 @@ void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
     if (result == Joycon::DriverResult::Success) {
         LOG_WARNING(Input, "Initialize device");
 
-        std::function<void(Joycon::Battery)> on_battery_data;
-        std::function<void(Joycon::Color)> on_button_data;
-        std::function<void(int, f32)> on_stick_data;
-        std::function<void(int, std::array<u8, 6>)> on_motion_data;
-        std::function<void(s16)> on_ring_data;
-        std::function<void(const std::vector<u8>&)> on_amiibo_data;
-
         const std::size_t port = handle->GetDevicePort();
-        handle->on_battery_data = {
-            [this, port, type](Joycon::Battery value) { OnBatteryUpdate(port, type, value); }};
-        handle->on_color_data = {
-            [this, port, type](Joycon::Color value) { OnColorUpdate(port, type, value); }};
-        handle->on_button_data = {
-            [this, port, type](int id, bool value) { OnButtonUpdate(port, type, id, value); }};
-        handle->on_stick_data = {
-            [this, port, type](int id, f32 value) { OnStickUpdate(port, type, id, value); }};
-        handle->on_motion_data = {[this, port, type](int id, Joycon::MotionData value) {
-            OnMotionUpdate(port, type, id, value);
-        }};
-        handle->on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }};
-        handle->on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) {
-            OnAmiiboUpdate(port, amiibo_data);
-        }};
+        const Joycon::JoyconCallbacks callbacks{
+            .on_battery_data = {[this, port, type](Joycon::Battery value) {
+                OnBatteryUpdate(port, type, value);
+            }},
+            .on_color_data = {[this, port, type](Joycon::Color value) {
+                OnColorUpdate(port, type, value);
+            }},
+            .on_button_data = {[this, port, type](int id, bool value) {
+                OnButtonUpdate(port, type, id, value);
+            }},
+            .on_stick_data = {[this, port, type](int id, f32 value) {
+                OnStickUpdate(port, type, id, value);
+            }},
+            .on_motion_data = {[this, port, type](int id, const Joycon::MotionData& value) {
+                OnMotionUpdate(port, type, id, value);
+            }},
+            .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }},
+            .on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) {
+                OnAmiiboUpdate(port, amiibo_data);
+            }},
+        };
+
         handle->InitializeDevice();
+        handle->SetCallbacks(callbacks);
     }
 }
 
@@ -235,7 +236,7 @@ Common::Input::VibrationError Joycons::SetVibration(
         .low_amplitude = vibration.low_amplitude,
         .low_frequency = vibration.low_frequency,
         .high_amplitude = vibration.high_amplitude,
-        .high_frequency = vibration.high_amplitude,
+        .high_frequency = vibration.high_frequency,
     };
     auto handle = GetHandle(identifier);
     if (handle == nullptr) {
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index ac11be1c17..5d0aeabf5f 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -66,6 +66,7 @@ DriverResult JoyconDriver::InitializeDevice() {
     // Initialize HW Protocols
     calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
     generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
+    rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
 
     // Get fixed joycon info
     generic_protocol->GetVersionNumber(version);
@@ -90,6 +91,10 @@ DriverResult JoyconDriver::InitializeDevice() {
     // Apply HW configuration
     SetPollingMode();
 
+    // Initialize joycon poller
+    joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration,
+                                                   right_stick_calibration, motion_calibration);
+
     // Start pooling for data
     is_connected = true;
     if (!input_thread_running) {
@@ -142,15 +147,40 @@ void JoyconDriver::InputThread(std::stop_token stop_token) {
 void JoyconDriver::OnNewData(std::span<u8> buffer) {
     const auto report_mode = static_cast<InputReport>(buffer[0]);
 
+    // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
+    // experience
     switch (report_mode) {
     case InputReport::STANDARD_FULL_60HZ:
-        ReadActiveMode(buffer);
+    case InputReport::NFC_IR_MODE_60HZ:
+    case InputReport::SIMPLE_HID_MODE: {
+        const auto now = std::chrono::steady_clock::now();
+        const auto new_delta_time = static_cast<u64>(
+            std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
+        delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10;
+        last_update = now;
+        joycon_poller->UpdateColor(color);
+        break;
+    }
+    default:
+        break;
+    }
+
+    const MotionStatus motion_status{
+        .is_enabled = motion_enabled,
+        .delta_time = delta_time,
+        .gyro_sensitivity = gyro_sensitivity,
+        .accelerometer_sensitivity = accelerometer_sensitivity,
+    };
+
+    switch (report_mode) {
+    case InputReport::STANDARD_FULL_60HZ:
+        joycon_poller->ReadActiveMode(buffer, motion_status);
         break;
     case InputReport::NFC_IR_MODE_60HZ:
-        ReadNfcIRMode(buffer);
+        joycon_poller->ReadNfcIRMode(buffer, motion_status);
         break;
     case InputReport::SIMPLE_HID_MODE:
-        ReadPassiveMode(buffer);
+        joycon_poller->ReadPassiveMode(buffer);
         break;
     case InputReport::SUBCMD_REPLY:
         LOG_DEBUG(Input, "Unhandled command reply");
@@ -164,6 +194,8 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
 void JoyconDriver::SetPollingMode() {
     disable_input_thread = true;
 
+    rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
+
     if (motion_enabled && supported_features.motion) {
         generic_protocol->EnableImu(true);
         generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance,
@@ -209,62 +241,6 @@ JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
     return features;
 }
 
-void JoyconDriver::ReadActiveMode(std::span<u8> buffer) {
-    InputReportActive data{};
-    memcpy(&data, buffer.data(), sizeof(InputReportActive));
-
-    // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
-    // experience
-    const auto now = std::chrono::steady_clock::now();
-    const auto new_delta_time =
-        std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count();
-    delta_time = static_cast<u64>((delta_time * 0.8f) + (new_delta_time * 0.2));
-    last_update = now;
-
-    switch (device_type) {
-    case Joycon::ControllerType::Left:
-        break;
-    case Joycon::ControllerType::Right:
-        break;
-    case Joycon::ControllerType::Pro:
-        break;
-    case Joycon::ControllerType::Grip:
-    case Joycon::ControllerType::Dual:
-    case Joycon::ControllerType::None:
-        break;
-    }
-
-    on_battery_data(data.battery_status);
-    on_color_data(color);
-}
-
-void JoyconDriver::ReadPassiveMode(std::span<u8> buffer) {
-    InputReportPassive data{};
-    memcpy(&data, buffer.data(), sizeof(InputReportPassive));
-
-    switch (device_type) {
-    case Joycon::ControllerType::Left:
-        break;
-    case Joycon::ControllerType::Right:
-        break;
-    case Joycon::ControllerType::Pro:
-        break;
-    case Joycon::ControllerType::Grip:
-    case Joycon::ControllerType::Dual:
-    case Joycon::ControllerType::None:
-        break;
-    }
-}
-
-void JoyconDriver::ReadNfcIRMode(std::span<u8> buffer) {
-    // This mode is compatible with the active mode
-    ReadActiveMode(buffer);
-
-    if (!nfc_enabled) {
-        return;
-    }
-}
-
 bool JoyconDriver::IsInputThreadValid() const {
     if (!is_connected) {
         return false;
@@ -302,7 +278,7 @@ DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
     if (disable_input_thread) {
         return DriverResult::HandleInUse;
     }
-    return DriverResult::NotSupported;
+    return rumble_protocol->SendVibration(vibration);
 }
 
 DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
@@ -398,6 +374,10 @@ SerialNumber JoyconDriver::GetHandleSerialNumber() const {
     return handle_serial_number;
 }
 
+void JoyconDriver::SetCallbacks(const Joycon::JoyconCallbacks& callbacks) {
+    joycon_poller->SetCallbacks(callbacks);
+}
+
 Joycon::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
                                                  ControllerType& controller_type) {
     std::array<std::pair<u32, Joycon::ControllerType>, 4> supported_devices{
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index 275c97b913..48ba859f4e 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -11,6 +11,8 @@
 #include "input_common/helpers/joycon_protocol/calibration.h"
 #include "input_common/helpers/joycon_protocol/generic_functions.h"
 #include "input_common/helpers/joycon_protocol/joycon_types.h"
+#include "input_common/helpers/joycon_protocol/poller.h"
+#include "input_common/helpers/joycon_protocol/rumble.h"
 
 namespace InputCommon::Joycon {
 
@@ -42,6 +44,8 @@ public:
     DriverResult SetNfcMode();
     DriverResult SetRingConMode();
 
+    void SetCallbacks(const Joycon::JoyconCallbacks& callbacks);
+
     // Returns device type from hidapi handle
     static Joycon::DriverResult GetDeviceType(SDL_hid_device_info* device_info,
                                               Joycon::ControllerType& controller_type);
@@ -50,14 +54,6 @@ public:
     static Joycon::DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
                                                 Joycon::SerialNumber& serial_number);
 
-    std::function<void(Battery)> on_battery_data;
-    std::function<void(Color)> on_color_data;
-    std::function<void(int, bool)> on_button_data;
-    std::function<void(int, f32)> on_stick_data;
-    std::function<void(int, MotionData)> on_motion_data;
-    std::function<void(f32)> on_ring_data;
-    std::function<void(const std::vector<u8>&)> on_amiibo_data;
-
 private:
     struct SupportedFeatures {
         bool passive{};
@@ -86,18 +82,11 @@ private:
     /// Returns a list of supported features that can be enabled on this device
     SupportedFeatures GetSupportedFeatures();
 
-    /// Handles data from passive packages
-    void ReadPassiveMode(std::span<u8> buffer);
-
-    /// Handles data from active packages
-    void ReadActiveMode(std::span<u8> buffer);
-
-    /// Handles data from nfc or ir packages
-    void ReadNfcIRMode(std::span<u8> buffer);
-
     // Protocol Features
     std::unique_ptr<CalibrationProtocol> calibration_protocol = nullptr;
     std::unique_ptr<GenericProtocol> generic_protocol = nullptr;
+    std::unique_ptr<JoyconPoller> joycon_poller = nullptr;
+    std::unique_ptr<RumbleProtocol> rumble_protocol = nullptr;
 
     // Connection status
     bool is_connected{};
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
new file mode 100644
index 0000000000..341479c0ca
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -0,0 +1,315 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "input_common/helpers/joycon_protocol/poller.h"
+
+namespace InputCommon::Joycon {
+
+JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
+                           JoyStickCalibration right_stick_calibration_,
+                           MotionCalibration motion_calibration_)
+    : device_type{device_type_}, left_stick_calibration{left_stick_calibration_},
+      right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {}
+
+void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) {
+    callbacks = std::move(callbacks_);
+}
+
+void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status) {
+    InputReportActive data{};
+    memcpy(&data, buffer.data(), sizeof(InputReportActive));
+
+    switch (device_type) {
+    case Joycon::ControllerType::Left:
+        UpdateActiveLeftPadInput(data, motion_status);
+        break;
+    case Joycon::ControllerType::Right:
+        UpdateActiveRightPadInput(data, motion_status);
+        break;
+    case Joycon::ControllerType::Pro:
+        UpdateActiveProPadInput(data, motion_status);
+        break;
+    case Joycon::ControllerType::Grip:
+    case Joycon::ControllerType::Dual:
+    case Joycon::ControllerType::None:
+        break;
+    }
+
+    callbacks.on_battery_data(data.battery_status);
+}
+
+void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) {
+    InputReportPassive data{};
+    memcpy(&data, buffer.data(), sizeof(InputReportPassive));
+
+    switch (device_type) {
+    case Joycon::ControllerType::Left:
+        UpdatePasiveLeftPadInput(data);
+        break;
+    case Joycon::ControllerType::Right:
+        UpdatePasiveRightPadInput(data);
+        break;
+    case Joycon::ControllerType::Pro:
+        UpdatePasiveProPadInput(data);
+        break;
+    case Joycon::ControllerType::Grip:
+    case Joycon::ControllerType::Dual:
+    case Joycon::ControllerType::None:
+        break;
+    }
+}
+
+void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) {
+    // This mode is compatible with the active mode
+    ReadActiveMode(buffer, motion_status);
+}
+
+void JoyconPoller::UpdateColor(const Color& color) {
+    callbacks.on_color_data(color);
+}
+
+void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input,
+                                            const MotionStatus& motion_status) {
+    static constexpr std::array<Joycon::PadButton, 11> left_buttons{
+        Joycon::PadButton::Down,    Joycon::PadButton::Up,     Joycon::PadButton::Right,
+        Joycon::PadButton::Left,    Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR,
+        Joycon::PadButton::L,       Joycon::PadButton::ZL,     Joycon::PadButton::Minus,
+        Joycon::PadButton::Capture, Joycon::PadButton::StickL,
+    };
+
+    const u32 raw_button =
+        static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16));
+    for (std::size_t i = 0; i < left_buttons.size(); ++i) {
+        const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0;
+        const int button = static_cast<int>(left_buttons[i]);
+        callbacks.on_button_data(button, button_status);
+    }
+
+    const u16 raw_left_axis_x =
+        static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
+    const u16 raw_left_axis_y =
+        static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
+    const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
+    const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
+
+    if (motion_status.is_enabled) {
+        auto left_motion = GetMotionInput(input, motion_status);
+        // Rotate motion axis to the correct direction
+        left_motion.accel_y = -left_motion.accel_y;
+        left_motion.accel_z = -left_motion.accel_z;
+        left_motion.gyro_x = -left_motion.gyro_x;
+        callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion);
+    }
+}
+
+void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input,
+                                             const MotionStatus& motion_status) {
+    static constexpr std::array<Joycon::PadButton, 11> right_buttons{
+        Joycon::PadButton::Y,    Joycon::PadButton::X,       Joycon::PadButton::B,
+        Joycon::PadButton::A,    Joycon::PadButton::RightSL, Joycon::PadButton::RightSR,
+        Joycon::PadButton::R,    Joycon::PadButton::ZR,      Joycon::PadButton::Plus,
+        Joycon::PadButton::Home, Joycon::PadButton::StickR,
+    };
+
+    const u32 raw_button =
+        static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16));
+    for (std::size_t i = 0; i < right_buttons.size(); ++i) {
+        const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0;
+        const int button = static_cast<int>(right_buttons[i]);
+        callbacks.on_button_data(button, button_status);
+    }
+
+    const u16 raw_right_axis_x =
+        static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
+    const u16 raw_right_axis_y =
+        static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
+    const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
+    const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
+
+    if (motion_status.is_enabled) {
+        auto right_motion = GetMotionInput(input, motion_status);
+        // Rotate motion axis to the correct direction
+        right_motion.accel_x = -right_motion.accel_x;
+        right_motion.accel_y = -right_motion.accel_y;
+        right_motion.gyro_z = -right_motion.gyro_z;
+        callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion);
+    }
+}
+
+void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input,
+                                           const MotionStatus& motion_status) {
+    static constexpr std::array<Joycon::PadButton, 18> pro_buttons{
+        Joycon::PadButton::Down,  Joycon::PadButton::Up,      Joycon::PadButton::Right,
+        Joycon::PadButton::Left,  Joycon::PadButton::L,       Joycon::PadButton::ZL,
+        Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y,
+        Joycon::PadButton::X,     Joycon::PadButton::B,       Joycon::PadButton::A,
+        Joycon::PadButton::R,     Joycon::PadButton::ZR,      Joycon::PadButton::Plus,
+        Joycon::PadButton::Home,  Joycon::PadButton::StickL,  Joycon::PadButton::StickR,
+    };
+
+    const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) |
+                                            (input.button_input[1] << 16));
+    for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
+        const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0;
+        const int button = static_cast<int>(pro_buttons[i]);
+        callbacks.on_button_data(button, button_status);
+    }
+
+    const u16 raw_left_axis_x =
+        static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
+    const u16 raw_left_axis_y =
+        static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
+    const u16 raw_right_axis_x =
+        static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
+    const u16 raw_right_axis_y =
+        static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
+
+    const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
+    const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
+    const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
+    const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
+
+    if (motion_status.is_enabled) {
+        auto pro_motion = GetMotionInput(input, motion_status);
+        pro_motion.gyro_x = -pro_motion.gyro_x;
+        pro_motion.accel_y = -pro_motion.accel_y;
+        pro_motion.accel_z = -pro_motion.accel_z;
+        callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion);
+        callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion);
+    }
+}
+
+void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) {
+    static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{
+        Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
+        Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
+        Joycon::PasivePadButton::SL,     Joycon::PasivePadButton::SR,
+        Joycon::PasivePadButton::L_R,    Joycon::PasivePadButton::ZL_ZR,
+        Joycon::PasivePadButton::Minus,  Joycon::PasivePadButton::Capture,
+        Joycon::PasivePadButton::StickL,
+    };
+
+    for (std::size_t i = 0; i < left_buttons.size(); ++i) {
+        const bool button_status = (input.button_input & static_cast<u32>(left_buttons[i])) != 0;
+        const int button = static_cast<int>(left_buttons[i]);
+        callbacks.on_button_data(button, button_status);
+    }
+}
+
+void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) {
+    static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{
+        Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
+        Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
+        Joycon::PasivePadButton::SL,     Joycon::PasivePadButton::SR,
+        Joycon::PasivePadButton::L_R,    Joycon::PasivePadButton::ZL_ZR,
+        Joycon::PasivePadButton::Plus,   Joycon::PasivePadButton::Home,
+        Joycon::PasivePadButton::StickR,
+    };
+
+    for (std::size_t i = 0; i < right_buttons.size(); ++i) {
+        const bool button_status = (input.button_input & static_cast<u32>(right_buttons[i])) != 0;
+        const int button = static_cast<int>(right_buttons[i]);
+        callbacks.on_button_data(button, button_status);
+    }
+}
+
+void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) {
+    static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{
+        Joycon::PasivePadButton::Down_A,  Joycon::PasivePadButton::Right_X,
+        Joycon::PasivePadButton::Left_B,  Joycon::PasivePadButton::Up_Y,
+        Joycon::PasivePadButton::SL,      Joycon::PasivePadButton::SR,
+        Joycon::PasivePadButton::L_R,     Joycon::PasivePadButton::ZL_ZR,
+        Joycon::PasivePadButton::Minus,   Joycon::PasivePadButton::Plus,
+        Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home,
+        Joycon::PasivePadButton::StickL,  Joycon::PasivePadButton::StickR,
+    };
+
+    for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
+        const bool button_status = (input.button_input & static_cast<u32>(pro_buttons[i])) != 0;
+        const int button = static_cast<int>(pro_buttons[i]);
+        callbacks.on_button_data(button, button_status);
+    }
+}
+
+f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const {
+    const f32 value = static_cast<f32>(raw_value - calibration.center);
+    if (value > 0.0f) {
+        return value / calibration.max;
+    }
+    return value / calibration.min;
+}
+
+f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
+                                        AccelerometerSensitivity sensitivity) const {
+    const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
+    switch (sensitivity) {
+    case Joycon::AccelerometerSensitivity::G2:
+        return value / 4.0f;
+    case Joycon::AccelerometerSensitivity::G4:
+        return value / 2.0f;
+    case Joycon::AccelerometerSensitivity::G8:
+        return value;
+    case Joycon::AccelerometerSensitivity::G16:
+        return value * 2.0f;
+    }
+    return value;
+}
+
+f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal,
+                               GyroSensitivity sensitivity) const {
+    const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
+    switch (sensitivity) {
+    case Joycon::GyroSensitivity::DPS250:
+        return value / 8.0f;
+    case Joycon::GyroSensitivity::DPS500:
+        return value / 4.0f;
+    case Joycon::GyroSensitivity::DPS1000:
+        return value / 2.0f;
+    case Joycon::GyroSensitivity::DPS2000:
+        return value;
+    }
+    return value;
+}
+
+s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis,
+                                  const InputReportActive& input) const {
+    return input.motion_input[(sensor * 3) + axis];
+}
+
+MotionData JoyconPoller::GetMotionInput(const InputReportActive& input,
+                                        const MotionStatus& motion_status) const {
+    MotionData motion{};
+    const auto& accel_cal = motion_calibration.accelerometer;
+    const auto& gyro_cal = motion_calibration.gyro;
+    const s16 raw_accel_x = input.motion_input[1];
+    const s16 raw_accel_y = input.motion_input[0];
+    const s16 raw_accel_z = input.motion_input[2];
+    const s16 raw_gyro_x = input.motion_input[4];
+    const s16 raw_gyro_y = input.motion_input[3];
+    const s16 raw_gyro_z = input.motion_input[5];
+
+    motion.delta_timestamp = motion_status.delta_time;
+    motion.accel_x =
+        GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity);
+    motion.accel_y =
+        GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity);
+    motion.accel_z =
+        GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity);
+    motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity);
+    motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity);
+    motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity);
+
+    // TODO(German77): Return all three samples data
+    return motion;
+}
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h
new file mode 100644
index 0000000000..fff681d0a1
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.h
@@ -0,0 +1,77 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
+// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
+// https://github.com/CTCaer/jc_toolkit
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
+
+#pragma once
+
+#include <functional>
+#include <span>
+
+#include "input_common/helpers/joycon_protocol/joycon_types.h"
+
+namespace InputCommon::Joycon {
+
+// Handles input packages and triggers the corresponding input events
+class JoyconPoller {
+public:
+    JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
+                 JoyStickCalibration right_stick_calibration_,
+                 MotionCalibration motion_calibration_);
+
+    void SetCallbacks(const Joycon::JoyconCallbacks& callbacks_);
+
+    /// Handles data from passive packages
+    void ReadPassiveMode(std::span<u8> buffer);
+
+    /// Handles data from active packages
+    void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status);
+
+    /// Handles data from nfc or ir packages
+    void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status);
+
+    void UpdateColor(const Color& color);
+
+private:
+    void UpdateActiveLeftPadInput(const InputReportActive& input,
+                                  const MotionStatus& motion_status);
+    void UpdateActiveRightPadInput(const InputReportActive& input,
+                                   const MotionStatus& motion_status);
+    void UpdateActiveProPadInput(const InputReportActive& input, const MotionStatus& motion_status);
+
+    void UpdatePasiveLeftPadInput(const InputReportPassive& buffer);
+    void UpdatePasiveRightPadInput(const InputReportPassive& buffer);
+    void UpdatePasiveProPadInput(const InputReportPassive& buffer);
+
+    /// Returns a calibrated joystick axis from raw axis data
+    f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const;
+
+    /// Returns a calibrated accelerometer axis from raw motion data
+    f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
+                              AccelerometerSensitivity sensitivity) const;
+
+    /// Returns a calibrated gyro axis from raw motion data
+    f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal,
+                     GyroSensitivity sensitivity) const;
+
+    /// Returns a raw motion value from a buffer
+    s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const;
+
+    /// Returns motion data from a buffer
+    MotionData GetMotionInput(const InputReportActive& input,
+                              const MotionStatus& motion_status) const;
+
+    ControllerType device_type{};
+
+    // Device calibration
+    JoyStickCalibration left_stick_calibration{};
+    JoyStickCalibration right_stick_calibration{};
+    MotionCalibration motion_calibration{};
+
+    Joycon::JoyconCallbacks callbacks{};
+};
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/rumble.cpp b/src/input_common/helpers/joycon_protocol/rumble.cpp
new file mode 100644
index 0000000000..17ee388634
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/rumble.cpp
@@ -0,0 +1,299 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "input_common/helpers/joycon_protocol/rumble.h"
+
+namespace InputCommon::Joycon {
+
+RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle)
+    : JoyconCommonProtocol(handle) {}
+
+DriverResult RumbleProtocol::EnableRumble(bool is_enabled) {
+    LOG_DEBUG(Input, "Enable Rumble");
+    const std::vector<u8> buffer{static_cast<u8>(is_enabled ? 1 : 0)};
+    std::vector<u8> output;
+    SetBlocking();
+    const auto result = SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer, output);
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) {
+    std::vector<u8> buffer(sizeof(DefaultVibrationBuffer));
+
+    if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) {
+        return SendVibrationReport(DefaultVibrationBuffer);
+    }
+
+    // Protect joycons from damage from strong vibrations
+    const f32 clamp_amplitude =
+        1.0f / std::max(1.0f, vibration.high_amplitude + vibration.low_amplitude);
+
+    const u16 encoded_high_frequency = EncodeHighFrequency(vibration.high_frequency);
+    const u8 encoded_high_amplitude =
+        EncodeHighAmplitude(vibration.high_amplitude * clamp_amplitude);
+    const u8 encoded_low_frequency = EncodeLowFrequency(vibration.low_frequency);
+    const u16 encoded_low_amplitude = EncodeLowAmplitude(vibration.low_amplitude * clamp_amplitude);
+
+    buffer[0] = static_cast<u8>(encoded_high_frequency & 0xFF);
+    buffer[1] = static_cast<u8>(encoded_high_amplitude | ((encoded_high_frequency >> 8) & 0x01));
+    buffer[2] = static_cast<u8>(encoded_low_frequency | ((encoded_low_amplitude >> 8) & 0x80));
+    buffer[3] = static_cast<u8>(encoded_low_amplitude & 0xFF);
+
+    // Duplicate rumble for now
+    buffer[4] = buffer[0];
+    buffer[5] = buffer[1];
+    buffer[6] = buffer[2];
+    buffer[7] = buffer[3];
+
+    return SendVibrationReport(buffer);
+}
+
+u16 RumbleProtocol::EncodeHighFrequency(f32 frequency) const {
+    const u8 new_frequency =
+        static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
+    return static_cast<u16>((new_frequency - 0x60) * 4);
+}
+
+u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const {
+    const u8 new_frequency =
+        static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
+    return static_cast<u8>(new_frequency - 0x40);
+}
+
+u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
+    /* More information about these values can be found here:
+     * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
+     */
+    constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
+        std::pair<f32, int>{0.0f, 0x0},
+        {0.01f, 0x2},
+        {0.012f, 0x4},
+        {0.014f, 0x6},
+        {0.017f, 0x8},
+        {0.02f, 0x0a},
+        {0.024f, 0x0c},
+        {0.028f, 0x0e},
+        {0.033f, 0x10},
+        {0.04f, 0x12},
+        {0.047f, 0x14},
+        {0.056f, 0x16},
+        {0.067f, 0x18},
+        {0.08f, 0x1a},
+        {0.095f, 0x1c},
+        {0.112f, 0x1e},
+        {0.117f, 0x20},
+        {0.123f, 0x22},
+        {0.128f, 0x24},
+        {0.134f, 0x26},
+        {0.14f, 0x28},
+        {0.146f, 0x2a},
+        {0.152f, 0x2c},
+        {0.159f, 0x2e},
+        {0.166f, 0x30},
+        {0.173f, 0x32},
+        {0.181f, 0x34},
+        {0.189f, 0x36},
+        {0.198f, 0x38},
+        {0.206f, 0x3a},
+        {0.215f, 0x3c},
+        {0.225f, 0x3e},
+        {0.23f, 0x40},
+        {0.235f, 0x42},
+        {0.24f, 0x44},
+        {0.245f, 0x46},
+        {0.251f, 0x48},
+        {0.256f, 0x4a},
+        {0.262f, 0x4c},
+        {0.268f, 0x4e},
+        {0.273f, 0x50},
+        {0.279f, 0x52},
+        {0.286f, 0x54},
+        {0.292f, 0x56},
+        {0.298f, 0x58},
+        {0.305f, 0x5a},
+        {0.311f, 0x5c},
+        {0.318f, 0x5e},
+        {0.325f, 0x60},
+        {0.332f, 0x62},
+        {0.34f, 0x64},
+        {0.347f, 0x66},
+        {0.355f, 0x68},
+        {0.362f, 0x6a},
+        {0.37f, 0x6c},
+        {0.378f, 0x6e},
+        {0.387f, 0x70},
+        {0.395f, 0x72},
+        {0.404f, 0x74},
+        {0.413f, 0x76},
+        {0.422f, 0x78},
+        {0.431f, 0x7a},
+        {0.44f, 0x7c},
+        {0.45f, 0x7e},
+        {0.46f, 0x80},
+        {0.47f, 0x82},
+        {0.48f, 0x84},
+        {0.491f, 0x86},
+        {0.501f, 0x88},
+        {0.512f, 0x8a},
+        {0.524f, 0x8c},
+        {0.535f, 0x8e},
+        {0.547f, 0x90},
+        {0.559f, 0x92},
+        {0.571f, 0x94},
+        {0.584f, 0x96},
+        {0.596f, 0x98},
+        {0.609f, 0x9a},
+        {0.623f, 0x9c},
+        {0.636f, 0x9e},
+        {0.65f, 0xa0},
+        {0.665f, 0xa2},
+        {0.679f, 0xa4},
+        {0.694f, 0xa6},
+        {0.709f, 0xa8},
+        {0.725f, 0xaa},
+        {0.741f, 0xac},
+        {0.757f, 0xae},
+        {0.773f, 0xb0},
+        {0.79f, 0xb2},
+        {0.808f, 0xb4},
+        {0.825f, 0xb6},
+        {0.843f, 0xb8},
+        {0.862f, 0xba},
+        {0.881f, 0xbc},
+        {0.9f, 0xbe},
+        {0.92f, 0xc0},
+        {0.94f, 0xc2},
+        {0.96f, 0xc4},
+        {0.981f, 0xc6},
+        {1.003f, 0xc8},
+    };
+
+    for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
+        if (amplitude <= amplitude_value) {
+            return static_cast<u8>(code);
+        }
+    }
+
+    return static_cast<u8>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
+}
+
+u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const {
+    /* More information about these values can be found here:
+     * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
+     */
+    constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
+        std::pair<f32, int>{0.0f, 0x0040},
+        {0.01f, 0x8040},
+        {0.012f, 0x0041},
+        {0.014f, 0x8041},
+        {0.017f, 0x0042},
+        {0.02f, 0x8042},
+        {0.024f, 0x0043},
+        {0.028f, 0x8043},
+        {0.033f, 0x0044},
+        {0.04f, 0x8044},
+        {0.047f, 0x0045},
+        {0.056f, 0x8045},
+        {0.067f, 0x0046},
+        {0.08f, 0x8046},
+        {0.095f, 0x0047},
+        {0.112f, 0x8047},
+        {0.117f, 0x0048},
+        {0.123f, 0x8048},
+        {0.128f, 0x0049},
+        {0.134f, 0x8049},
+        {0.14f, 0x004a},
+        {0.146f, 0x804a},
+        {0.152f, 0x004b},
+        {0.159f, 0x804b},
+        {0.166f, 0x004c},
+        {0.173f, 0x804c},
+        {0.181f, 0x004d},
+        {0.189f, 0x804d},
+        {0.198f, 0x004e},
+        {0.206f, 0x804e},
+        {0.215f, 0x004f},
+        {0.225f, 0x804f},
+        {0.23f, 0x0050},
+        {0.235f, 0x8050},
+        {0.24f, 0x0051},
+        {0.245f, 0x8051},
+        {0.251f, 0x0052},
+        {0.256f, 0x8052},
+        {0.262f, 0x0053},
+        {0.268f, 0x8053},
+        {0.273f, 0x0054},
+        {0.279f, 0x8054},
+        {0.286f, 0x0055},
+        {0.292f, 0x8055},
+        {0.298f, 0x0056},
+        {0.305f, 0x8056},
+        {0.311f, 0x0057},
+        {0.318f, 0x8057},
+        {0.325f, 0x0058},
+        {0.332f, 0x8058},
+        {0.34f, 0x0059},
+        {0.347f, 0x8059},
+        {0.355f, 0x005a},
+        {0.362f, 0x805a},
+        {0.37f, 0x005b},
+        {0.378f, 0x805b},
+        {0.387f, 0x005c},
+        {0.395f, 0x805c},
+        {0.404f, 0x005d},
+        {0.413f, 0x805d},
+        {0.422f, 0x005e},
+        {0.431f, 0x805e},
+        {0.44f, 0x005f},
+        {0.45f, 0x805f},
+        {0.46f, 0x0060},
+        {0.47f, 0x8060},
+        {0.48f, 0x0061},
+        {0.491f, 0x8061},
+        {0.501f, 0x0062},
+        {0.512f, 0x8062},
+        {0.524f, 0x0063},
+        {0.535f, 0x8063},
+        {0.547f, 0x0064},
+        {0.559f, 0x8064},
+        {0.571f, 0x0065},
+        {0.584f, 0x8065},
+        {0.596f, 0x0066},
+        {0.609f, 0x8066},
+        {0.623f, 0x0067},
+        {0.636f, 0x8067},
+        {0.65f, 0x0068},
+        {0.665f, 0x8068},
+        {0.679f, 0x0069},
+        {0.694f, 0x8069},
+        {0.709f, 0x006a},
+        {0.725f, 0x806a},
+        {0.741f, 0x006b},
+        {0.757f, 0x806b},
+        {0.773f, 0x006c},
+        {0.79f, 0x806c},
+        {0.808f, 0x006d},
+        {0.825f, 0x806d},
+        {0.843f, 0x006e},
+        {0.862f, 0x806e},
+        {0.881f, 0x006f},
+        {0.9f, 0x806f},
+        {0.92f, 0x0070},
+        {0.94f, 0x8070},
+        {0.96f, 0x0071},
+        {0.981f, 0x8071},
+        {1.003f, 0x0072},
+    };
+
+    for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
+        if (amplitude <= amplitude_value) {
+            return static_cast<u16>(code);
+        }
+    }
+
+    return static_cast<u16>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
+}
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/rumble.h b/src/input_common/helpers/joycon_protocol/rumble.h
new file mode 100644
index 0000000000..7d0329f039
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/rumble.h
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
+// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
+// https://github.com/CTCaer/jc_toolkit
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
+
+#pragma once
+
+#include <vector>
+
+#include "input_common/helpers/joycon_protocol/common_protocol.h"
+#include "input_common/helpers/joycon_protocol/joycon_types.h"
+
+namespace InputCommon::Joycon {
+
+class RumbleProtocol final : private JoyconCommonProtocol {
+public:
+    RumbleProtocol(std::shared_ptr<JoyconHandle> handle);
+
+    DriverResult EnableRumble(bool is_enabled);
+
+    DriverResult SendVibration(const VibrationValue& vibration);
+
+private:
+    u16 EncodeHighFrequency(f32 frequency) const;
+    u8 EncodeLowFrequency(f32 frequency) const;
+    u8 EncodeHighAmplitude(f32 amplitude) const;
+    u16 EncodeLowAmplitude(f32 amplitude) const;
+};
+
+} // namespace InputCommon::Joycon

From 751d36e7392b0b1637f17988cfc1ef0d7cd95753 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 20 Dec 2022 19:10:42 -0600
Subject: [PATCH 11/24] input_common: Add support for joycon ring controller

---
 src/input_common/CMakeLists.txt               |   2 +
 src/input_common/helpers/joycon_driver.cpp    |  43 +++++-
 src/input_common/helpers/joycon_driver.h      |   3 +
 .../helpers/joycon_protocol/calibration.cpp   |  22 +++
 .../helpers/joycon_protocol/calibration.h     |  10 ++
 .../helpers/joycon_protocol/poller.cpp        |  22 ++-
 .../helpers/joycon_protocol/poller.h          |   4 +-
 .../helpers/joycon_protocol/ringcon.cpp       | 132 ++++++++++++++++++
 .../helpers/joycon_protocol/ringcon.h         |  38 +++++
 9 files changed, 272 insertions(+), 4 deletions(-)
 create mode 100644 src/input_common/helpers/joycon_protocol/ringcon.cpp
 create mode 100644 src/input_common/helpers/joycon_protocol/ringcon.h

diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 4ab1ccbfbe..addecc9b3b 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -66,6 +66,8 @@ if (ENABLE_SDL2)
         helpers/joycon_protocol/joycon_types.h
         helpers/joycon_protocol/poller.cpp
         helpers/joycon_protocol/poller.h
+        helpers/joycon_protocol/ringcon.cpp
+        helpers/joycon_protocol/ringcon.h
         helpers/joycon_protocol/rumble.cpp
         helpers/joycon_protocol/rumble.h
     )
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index 5d0aeabf5f..c0a03fe2e1 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -52,12 +52,18 @@ DriverResult JoyconDriver::InitializeDevice() {
     error_counter = 0;
     hidapi_handle->packet_counter = 0;
 
+    // Reset external device status
+    starlink_connected = false;
+    ring_connected = false;
+    amiibo_detected = false;
+
     // Set HW default configuration
     vibration_enabled = true;
     motion_enabled = true;
     hidbus_enabled = false;
     nfc_enabled = false;
     passive_enabled = false;
+    irs_enabled = false;
     gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
     gyro_performance = Joycon::GyroPerformance::HZ833;
     accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
@@ -66,6 +72,7 @@ DriverResult JoyconDriver::InitializeDevice() {
     // Initialize HW Protocols
     calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
     generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
+    ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle);
     rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
 
     // Get fixed joycon info
@@ -172,9 +179,23 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
         .accelerometer_sensitivity = accelerometer_sensitivity,
     };
 
+    // TODO: Remove this when calibration is properly loaded and not calculated
+    if (ring_connected && report_mode == InputReport::STANDARD_FULL_60HZ) {
+        InputReportActive data{};
+        memcpy(&data, buffer.data(), sizeof(InputReportActive));
+        calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input);
+    }
+
+    const RingStatus ring_status{
+        .is_enabled = ring_connected,
+        .default_value = ring_calibration.default_value,
+        .max_value = ring_calibration.max_value,
+        .min_value = ring_calibration.min_value,
+    };
+
     switch (report_mode) {
     case InputReport::STANDARD_FULL_60HZ:
-        joycon_poller->ReadActiveMode(buffer, motion_status);
+        joycon_poller->ReadActiveMode(buffer, motion_status, ring_status);
         break;
     case InputReport::NFC_IR_MODE_60HZ:
         joycon_poller->ReadNfcIRMode(buffer, motion_status);
@@ -204,6 +225,26 @@ void JoyconDriver::SetPollingMode() {
         generic_protocol->EnableImu(false);
     }
 
+    if (ring_protocol->IsEnabled()) {
+        ring_connected = false;
+        ring_protocol->DisableRingCon();
+    }
+
+    if (hidbus_enabled && supported_features.hidbus) {
+        auto result = ring_protocol->EnableRingCon();
+        if (result == DriverResult::Success) {
+            result = ring_protocol->StartRingconPolling();
+        }
+        if (result == DriverResult::Success) {
+            ring_connected = true;
+            disable_input_thread = false;
+            return;
+        }
+        ring_connected = false;
+        ring_protocol->DisableRingCon();
+        LOG_ERROR(Input, "Error enabling Ringcon");
+    }
+
     if (passive_enabled && supported_features.passive) {
         const auto result = generic_protocol->EnablePassiveMode();
         if (result == DriverResult::Success) {
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index 48ba859f4e..dc5d60221a 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -12,6 +12,7 @@
 #include "input_common/helpers/joycon_protocol/generic_functions.h"
 #include "input_common/helpers/joycon_protocol/joycon_types.h"
 #include "input_common/helpers/joycon_protocol/poller.h"
+#include "input_common/helpers/joycon_protocol/ringcon.h"
 #include "input_common/helpers/joycon_protocol/rumble.h"
 
 namespace InputCommon::Joycon {
@@ -86,6 +87,7 @@ private:
     std::unique_ptr<CalibrationProtocol> calibration_protocol = nullptr;
     std::unique_ptr<GenericProtocol> generic_protocol = nullptr;
     std::unique_ptr<JoyconPoller> joycon_poller = nullptr;
+    std::unique_ptr<RingConProtocol> ring_protocol = nullptr;
     std::unique_ptr<RumbleProtocol> rumble_protocol = nullptr;
 
     // Connection status
@@ -118,6 +120,7 @@ private:
     JoyStickCalibration left_stick_calibration{};
     JoyStickCalibration right_stick_calibration{};
     MotionCalibration motion_calibration{};
+    RingCalibration ring_calibration{};
 
     // Fixed joycon info
     FirmwareVersion version{};
diff --git a/src/input_common/helpers/joycon_protocol/calibration.cpp b/src/input_common/helpers/joycon_protocol/calibration.cpp
index 5c29af545b..ce1ff7061d 100644
--- a/src/input_common/helpers/joycon_protocol/calibration.cpp
+++ b/src/input_common/helpers/joycon_protocol/calibration.cpp
@@ -128,6 +128,28 @@ DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibrati
     return result;
 }
 
+DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibration,
+                                                     s16 current_value) {
+    // TODO: Get default calibration form ring itself
+    if (ring_data_max == 0 && ring_data_min == 0) {
+        ring_data_max = current_value + 800;
+        ring_data_min = current_value - 800;
+        ring_data_default = current_value;
+    }
+    if (ring_data_max < current_value) {
+        ring_data_max = current_value;
+    }
+    if (ring_data_min > current_value) {
+        ring_data_min = current_value;
+    }
+    calibration = {
+        .default_value = ring_data_default,
+        .max_value = ring_data_max,
+        .min_value = ring_data_min,
+    };
+    return DriverResult::Success;
+}
+
 void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) {
     constexpr u16 DefaultStickCenter{2048};
     constexpr u16 DefaultStickRange{1740};
diff --git a/src/input_common/helpers/joycon_protocol/calibration.h b/src/input_common/helpers/joycon_protocol/calibration.h
index 38214eed41..32ddef4b85 100644
--- a/src/input_common/helpers/joycon_protocol/calibration.h
+++ b/src/input_common/helpers/joycon_protocol/calibration.h
@@ -46,9 +46,19 @@ public:
      */
     DriverResult GetImuCalibration(MotionCalibration& calibration);
 
+    /**
+     * Calculates on run time the proper calibration of the ring controller
+     * @returns RingCalibration of the ring sensor
+     */
+    DriverResult GetRingCalibration(RingCalibration& calibration, s16 current_value);
+
 private:
     void ValidateCalibration(JoyStickCalibration& calibration);
     void ValidateCalibration(MotionCalibration& calibration);
+
+    s16 ring_data_max = 0;
+    s16 ring_data_default = 0;
+    s16 ring_data_min = 0;
 };
 
 } // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
index 341479c0ca..cb76e1e06f 100644
--- a/src/input_common/helpers/joycon_protocol/poller.cpp
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -16,7 +16,8 @@ void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) {
     callbacks = std::move(callbacks_);
 }
 
-void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status) {
+void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
+                                  const RingStatus& ring_status) {
     InputReportActive data{};
     memcpy(&data, buffer.data(), sizeof(InputReportActive));
 
@@ -36,6 +37,10 @@ void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& moti
         break;
     }
 
+    if (ring_status.is_enabled) {
+        UpdateRing(data.ring_input, ring_status);
+    }
+
     callbacks.on_battery_data(data.battery_status);
 }
 
@@ -62,13 +67,26 @@ void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) {
 
 void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) {
     // This mode is compatible with the active mode
-    ReadActiveMode(buffer, motion_status);
+    ReadActiveMode(buffer, motion_status, {});
 }
 
 void JoyconPoller::UpdateColor(const Color& color) {
     callbacks.on_color_data(color);
 }
 
+void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) {
+    float normalized_value = static_cast<float>(value - ring_status.default_value);
+    if (normalized_value > 0) {
+        normalized_value = normalized_value /
+                           static_cast<float>(ring_status.max_value - ring_status.default_value);
+    }
+    if (normalized_value < 0) {
+        normalized_value = normalized_value /
+                           static_cast<float>(ring_status.default_value - ring_status.min_value);
+    }
+    callbacks.on_ring_data(normalized_value);
+}
+
 void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input,
                                             const MotionStatus& motion_status) {
     static constexpr std::array<Joycon::PadButton, 11> left_buttons{
diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h
index fff681d0a1..68b14b8ba0 100644
--- a/src/input_common/helpers/joycon_protocol/poller.h
+++ b/src/input_common/helpers/joycon_protocol/poller.h
@@ -28,12 +28,14 @@ public:
     void ReadPassiveMode(std::span<u8> buffer);
 
     /// Handles data from active packages
-    void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status);
+    void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
+                        const RingStatus& ring_status);
 
     /// Handles data from nfc or ir packages
     void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status);
 
     void UpdateColor(const Color& color);
+    void UpdateRing(s16 value, const RingStatus& ring_status);
 
 private:
     void UpdateActiveLeftPadInput(const InputReportActive& input,
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.cpp b/src/input_common/helpers/joycon_protocol/ringcon.cpp
new file mode 100644
index 0000000000..2d137b85d3
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/ringcon.cpp
@@ -0,0 +1,132 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "input_common/helpers/joycon_protocol/ringcon.h"
+
+namespace InputCommon::Joycon {
+
+RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle)
+    : JoyconCommonProtocol(handle) {}
+
+DriverResult RingConProtocol::EnableRingCon() {
+    LOG_DEBUG(Input, "Enable Ringcon");
+    DriverResult result{DriverResult::Success};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
+    }
+    if (result == DriverResult::Success) {
+        result = EnableMCU(true);
+    }
+    if (result == DriverResult::Success) {
+        const MCUConfig config{
+            .command = MCUCommand::ConfigureMCU,
+            .sub_command = MCUSubCommand::SetDeviceMode,
+            .mode = MCUMode::Standby,
+            .crc = {},
+        };
+        result = ConfigureMCU(config);
+    }
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult RingConProtocol::DisableRingCon() {
+    LOG_DEBUG(Input, "Disable RingCon");
+    DriverResult result{DriverResult::Success};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = EnableMCU(false);
+    }
+
+    is_enabled = false;
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult RingConProtocol::StartRingconPolling() {
+    LOG_DEBUG(Input, "Enable Ringcon");
+    bool is_connected = false;
+    DriverResult result{DriverResult::Success};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = WaitSetMCUMode(ReportMode::STANDARD_FULL_60HZ, MCUMode::Standby);
+    }
+    if (result == DriverResult::Success) {
+        result = IsRingConnected(is_connected);
+    }
+    if (result == DriverResult::Success && is_connected) {
+        LOG_INFO(Input, "Ringcon detected");
+        result = ConfigureRing();
+    }
+    if (result == DriverResult::Success) {
+        is_enabled = true;
+    }
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
+    LOG_DEBUG(Input, "IsRingConnected");
+    constexpr std::size_t max_tries = 28;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+    is_connected = false;
+
+    do {
+        std::vector<u8> empty_data(0);
+        const auto result = SendSubCommand(SubCommand::UNKNOWN_RINGCON, empty_data, output);
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+
+        if (tries++ >= max_tries) {
+            return DriverResult::NoDeviceDetected;
+        }
+    } while (output[14] != 0x59 || output[16] != 0x20);
+
+    is_connected = true;
+    return DriverResult::Success;
+}
+
+DriverResult RingConProtocol::ConfigureRing() {
+    LOG_DEBUG(Input, "ConfigureRing");
+    constexpr std::size_t max_tries = 28;
+    DriverResult result{DriverResult::Success};
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    do {
+        std::vector<u8> ring_config{0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16,
+                                    0xED, 0x34, 0x36, 0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6,
+                                    0xA9, 0x22, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                    0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36};
+        result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config, output);
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ >= max_tries) {
+            return DriverResult::NoDeviceDetected;
+        }
+    } while (output[14] != 0x5C);
+
+    std::vector<u8> ringcon_data{0x04, 0x01, 0x01, 0x02};
+    result = SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data, output);
+
+    return result;
+}
+
+bool RingConProtocol::IsEnabled() {
+    return is_enabled;
+}
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.h b/src/input_common/helpers/joycon_protocol/ringcon.h
new file mode 100644
index 0000000000..0c25de23ee
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/ringcon.h
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
+// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
+// https://github.com/CTCaer/jc_toolkit
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
+
+#pragma once
+
+#include <vector>
+
+#include "input_common/helpers/joycon_protocol/common_protocol.h"
+#include "input_common/helpers/joycon_protocol/joycon_types.h"
+
+namespace InputCommon::Joycon {
+
+class RingConProtocol final : private JoyconCommonProtocol {
+public:
+    RingConProtocol(std::shared_ptr<JoyconHandle> handle);
+
+    DriverResult EnableRingCon();
+
+    DriverResult DisableRingCon();
+
+    DriverResult StartRingconPolling();
+
+    bool IsEnabled();
+
+private:
+    DriverResult IsRingConnected(bool& is_connected);
+
+    DriverResult ConfigureRing();
+
+    bool is_enabled{};
+};
+
+} // namespace InputCommon::Joycon

From 6e33731f29bb870ad28416aff8db32854f4a4fa5 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Wed, 21 Dec 2022 10:44:23 -0600
Subject: [PATCH 12/24] input_common: Add dual joycon support

---
 src/input_common/drivers/joycon.cpp | 125 ++++++++++++++++++++++------
 1 file changed, 101 insertions(+), 24 deletions(-)

diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index dbe730e1a3..049ecc4f24 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -55,14 +55,17 @@ void Joycons::Reset() {
 
 void Joycons::Setup() {
     u32 port = 0;
+    PreSetController(GetIdentifier(0, Joycon::ControllerType::None));
     for (auto& device : left_joycons) {
         PreSetController(GetIdentifier(port, Joycon::ControllerType::Left));
         device = std::make_shared<Joycon::JoyconDriver>(port++);
     }
+    port = 0;
     for (auto& device : right_joycons) {
         PreSetController(GetIdentifier(port, Joycon::ControllerType::Right));
         device = std::make_shared<Joycon::JoyconDriver>(port++);
     }
+    port = 0;
     for (auto& device : pro_joycons) {
         PreSetController(GetIdentifier(port, Joycon::ControllerType::Pro));
         device = std::make_shared<Joycon::JoyconDriver>(port++);
@@ -109,7 +112,7 @@ bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const {
         return false;
     }
 
-    auto is_handle_identical = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
+    auto is_handle_identical = [serial_number](std::shared_ptr<Joycon::JoyconDriver> device) {
         if (!device) {
             return false;
         }
@@ -445,7 +448,7 @@ std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
             return;
         }
         std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()),
-                                       device->GetDevicePort());
+                                       device->GetDevicePort() + 1);
         devices.emplace_back(Common::ParamPackage{
             {"engine", GetEngineName()},
             {"display", std::move(name)},
@@ -464,32 +467,49 @@ std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
         add_entry(controller);
     }
 
+    // List dual joycon pairs
+    for (std::size_t i = 0; i < MaxSupportedControllers; i++) {
+        if (!left_joycons[i] || !right_joycons[i]) {
+            continue;
+        }
+        if (!left_joycons[i]->IsConnected() || !right_joycons[i]->IsConnected()) {
+            continue;
+        }
+        constexpr auto type = Joycon::ControllerType::Dual;
+        std::string name = fmt::format("{} {}", JoyconName(type), i + 1);
+        devices.emplace_back(Common::ParamPackage{
+            {"engine", GetEngineName()},
+            {"display", std::move(name)},
+            {"port", std::to_string(i)},
+            {"pad", std::to_string(static_cast<std::size_t>(type))},
+        });
+    }
+
     return devices;
 }
 
 ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) {
-    static constexpr std::array<std::pair<Settings::NativeButton::Values, Joycon::PadButton>, 20>
+    static constexpr std::array<std::tuple<Settings::NativeButton::Values, Joycon::PadButton, bool>,
+                                18>
         switch_to_joycon_button = {
-            std::pair{Settings::NativeButton::A, Joycon::PadButton::A},
-            {Settings::NativeButton::B, Joycon::PadButton::B},
-            {Settings::NativeButton::X, Joycon::PadButton::X},
-            {Settings::NativeButton::Y, Joycon::PadButton::Y},
-            {Settings::NativeButton::DLeft, Joycon::PadButton::Left},
-            {Settings::NativeButton::DUp, Joycon::PadButton::Up},
-            {Settings::NativeButton::DRight, Joycon::PadButton::Right},
-            {Settings::NativeButton::DDown, Joycon::PadButton::Down},
-            {Settings::NativeButton::SL, Joycon::PadButton::LeftSL},
-            {Settings::NativeButton::SR, Joycon::PadButton::LeftSR},
-            {Settings::NativeButton::L, Joycon::PadButton::L},
-            {Settings::NativeButton::R, Joycon::PadButton::R},
-            {Settings::NativeButton::ZL, Joycon::PadButton::ZL},
-            {Settings::NativeButton::ZR, Joycon::PadButton::ZR},
-            {Settings::NativeButton::Plus, Joycon::PadButton::Plus},
-            {Settings::NativeButton::Minus, Joycon::PadButton::Minus},
-            {Settings::NativeButton::Home, Joycon::PadButton::Home},
-            {Settings::NativeButton::Screenshot, Joycon::PadButton::Capture},
-            {Settings::NativeButton::LStick, Joycon::PadButton::StickL},
-            {Settings::NativeButton::RStick, Joycon::PadButton::StickR},
+            std::tuple{Settings::NativeButton::A, Joycon::PadButton::A, true},
+            {Settings::NativeButton::B, Joycon::PadButton::B, true},
+            {Settings::NativeButton::X, Joycon::PadButton::X, true},
+            {Settings::NativeButton::Y, Joycon::PadButton::Y, true},
+            {Settings::NativeButton::DLeft, Joycon::PadButton::Left, false},
+            {Settings::NativeButton::DUp, Joycon::PadButton::Up, false},
+            {Settings::NativeButton::DRight, Joycon::PadButton::Right, false},
+            {Settings::NativeButton::DDown, Joycon::PadButton::Down, false},
+            {Settings::NativeButton::L, Joycon::PadButton::L, false},
+            {Settings::NativeButton::R, Joycon::PadButton::R, true},
+            {Settings::NativeButton::ZL, Joycon::PadButton::ZL, false},
+            {Settings::NativeButton::ZR, Joycon::PadButton::ZR, true},
+            {Settings::NativeButton::Plus, Joycon::PadButton::Plus, true},
+            {Settings::NativeButton::Minus, Joycon::PadButton::Minus, false},
+            {Settings::NativeButton::Home, Joycon::PadButton::Home, true},
+            {Settings::NativeButton::Screenshot, Joycon::PadButton::Capture, false},
+            {Settings::NativeButton::LStick, Joycon::PadButton::StickL, false},
+            {Settings::NativeButton::RStick, Joycon::PadButton::StickR, true},
         };
 
     if (!params.Has("port")) {
@@ -497,14 +517,51 @@ ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& par
     }
 
     ButtonMapping mapping{};
-    for (const auto& [switch_button, joycon_button] : switch_to_joycon_button) {
+    for (const auto& [switch_button, joycon_button, side] : switch_to_joycon_button) {
+        int pad = params.Get("pad", 0);
+        if (pad == static_cast<int>(Joycon::ControllerType::Dual)) {
+            pad = side ? static_cast<int>(Joycon::ControllerType::Right)
+                       : static_cast<int>(Joycon::ControllerType::Left);
+        }
+
         Common::ParamPackage button_params{};
         button_params.Set("engine", GetEngineName());
         button_params.Set("port", params.Get("port", 0));
+        button_params.Set("pad", pad);
         button_params.Set("button", static_cast<int>(joycon_button));
         mapping.insert_or_assign(switch_button, std::move(button_params));
     }
 
+    // Map SL and SR buttons for left joycons
+    if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Left)) {
+        Common::ParamPackage button_params{};
+        button_params.Set("engine", GetEngineName());
+        button_params.Set("port", params.Get("port", 0));
+        button_params.Set("pad", static_cast<int>(Joycon::ControllerType::Left));
+
+        Common::ParamPackage sl_button_params = button_params;
+        Common::ParamPackage sr_button_params = button_params;
+        sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSL));
+        sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::LeftSR));
+        mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
+        mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
+    }
+
+    // Map SL and SR buttons for right joycons
+    if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Right)) {
+        Common::ParamPackage button_params{};
+        button_params.Set("engine", GetEngineName());
+        button_params.Set("port", params.Get("port", 0));
+        button_params.Set("pad", static_cast<int>(Joycon::ControllerType::Right));
+
+        Common::ParamPackage sl_button_params = button_params;
+        Common::ParamPackage sr_button_params = button_params;
+        sl_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSL));
+        sr_button_params.Set("button", static_cast<int>(Joycon::PadButton::RightSR));
+        mapping.insert_or_assign(Settings::NativeButton::SL, std::move(sl_button_params));
+        mapping.insert_or_assign(Settings::NativeButton::SR, std::move(sr_button_params));
+    }
+
     return mapping;
 }
 
@@ -513,16 +570,25 @@ AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& par
         return {};
     }
 
+    int pad_left = params.Get("pad", 0);
+    int pad_right = pad_left;
+    if (pad_left == static_cast<int>(Joycon::ControllerType::Dual)) {
+        pad_left = static_cast<int>(Joycon::ControllerType::Left);
+        pad_right = static_cast<int>(Joycon::ControllerType::Right);
+    }
+
     AnalogMapping mapping = {};
     Common::ParamPackage left_analog_params;
     left_analog_params.Set("engine", GetEngineName());
     left_analog_params.Set("port", params.Get("port", 0));
+    left_analog_params.Set("pad", pad_left);
     left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX));
     left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY));
     mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
     Common::ParamPackage right_analog_params;
     right_analog_params.Set("engine", GetEngineName());
     right_analog_params.Set("port", params.Get("port", 0));
+    right_analog_params.Set("pad", pad_right);
     right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX));
     right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY));
     mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
@@ -534,15 +600,24 @@ MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& par
         return {};
     }
 
+    int pad_left = params.Get("pad", 0);
+    int pad_right = pad_left;
+    if (pad_left == static_cast<int>(Joycon::ControllerType::Dual)) {
+        pad_left = static_cast<int>(Joycon::ControllerType::Left);
+        pad_right = static_cast<int>(Joycon::ControllerType::Right);
+    }
+
     MotionMapping mapping = {};
     Common::ParamPackage left_motion_params;
     left_motion_params.Set("engine", GetEngineName());
     left_motion_params.Set("port", params.Get("port", 0));
+    left_motion_params.Set("pad", pad_left);
     left_motion_params.Set("motion", 0);
     mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
     Common::ParamPackage right_Motion_params;
     right_Motion_params.Set("engine", GetEngineName());
     right_Motion_params.Set("port", params.Get("port", 0));
+    right_Motion_params.Set("pad", pad_right);
     right_Motion_params.Set("motion", 1);
     mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params));
     return mapping;
@@ -622,6 +697,8 @@ std::string Joycons::JoyconName(Joycon::ControllerType type) const {
         return "Pro Controller";
     case Joycon::ControllerType::Grip:
         return "Grip Controller";
+    case Joycon::ControllerType::Dual:
+        return "Dual Joycon";
     default:
         return "Unknow Joycon";
     }

From 6d6b7bdbc327528d155f0422ef096846559844c0 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Thu, 22 Dec 2022 01:07:46 -0600
Subject: [PATCH 13/24] input_common: Implement joycon nfc

---
 src/core/hid/emulated_controller.cpp          |   3 +-
 src/input_common/CMakeLists.txt               |   2 +
 src/input_common/drivers/joycon.cpp           |   4 +-
 src/input_common/helpers/joycon_driver.cpp    |  44 ++
 src/input_common/helpers/joycon_driver.h      |  24 +-
 .../helpers/joycon_protocol/nfc.cpp           | 414 ++++++++++++++++++
 .../helpers/joycon_protocol/nfc.h             |  61 +++
 .../helpers/joycon_protocol/poller.cpp        |   4 +
 .../helpers/joycon_protocol/poller.h          |   1 +
 9 files changed, 544 insertions(+), 13 deletions(-)
 create mode 100644 src/input_common/helpers/joycon_protocol/nfc.cpp
 create mode 100644 src/input_common/helpers/joycon_protocol/nfc.h

diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 89638cb856..1e4ec4addc 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -144,7 +144,8 @@ void EmulatedController::LoadDevices() {
     battery_params[RightIndex].Set("battery", true);
 
     camera_params = Common::ParamPackage{"engine:camera,camera:1"};
-    nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
+    nfc_params = right_joycon;
+    nfc_params.Set("nfc", true);
     ring_params = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
 
     output_params[LeftIndex] = left_joycon;
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index addecc9b3b..9c901af2ab 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -64,6 +64,8 @@ if (ENABLE_SDL2)
         helpers/joycon_protocol/generic_functions.cpp
         helpers/joycon_protocol/generic_functions.h
         helpers/joycon_protocol/joycon_types.h
+        helpers/joycon_protocol/nfc.cpp
+        helpers/joycon_protocol/nfc.h
         helpers/joycon_protocol/poller.cpp
         helpers/joycon_protocol/poller.h
         helpers/joycon_protocol/ringcon.cpp
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index 049ecc4f24..29f0dc0c89 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -388,7 +388,9 @@ void Joycons::OnRingConUpdate(f32 ring_data) {
 
 void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) {
     const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
-    SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_data});
+    const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved
+                                               : Common::Input::NfcState::NewAmiibo;
+    SetNfc(identifier, {nfc_state, amiibo_data});
 }
 
 std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const {
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index c0a03fe2e1..c3debffd11 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -5,6 +5,12 @@
 #include "common/swap.h"
 #include "common/thread.h"
 #include "input_common/helpers/joycon_driver.h"
+#include "input_common/helpers/joycon_protocol/calibration.h"
+#include "input_common/helpers/joycon_protocol/generic_functions.h"
+#include "input_common/helpers/joycon_protocol/nfc.h"
+#include "input_common/helpers/joycon_protocol/poller.h"
+#include "input_common/helpers/joycon_protocol/ringcon.h"
+#include "input_common/helpers/joycon_protocol/rumble.h"
 
 namespace InputCommon::Joycon {
 JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} {
@@ -72,6 +78,7 @@ DriverResult JoyconDriver::InitializeDevice() {
     // Initialize HW Protocols
     calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
     generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
+    nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle);
     ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle);
     rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
 
@@ -193,6 +200,25 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
         .min_value = ring_calibration.min_value,
     };
 
+    if (nfc_protocol->IsEnabled()) {
+        if (amiibo_detected) {
+            if (!nfc_protocol->HasAmiibo()) {
+                joycon_poller->updateAmiibo({});
+                amiibo_detected = false;
+                return;
+            }
+        }
+
+        if (!amiibo_detected) {
+            std::vector<u8> data(0x21C);
+            const auto result = nfc_protocol->ScanAmiibo(data);
+            if (result == DriverResult::Success) {
+                joycon_poller->updateAmiibo(data);
+                amiibo_detected = true;
+            }
+        }
+    }
+
     switch (report_mode) {
     case InputReport::STANDARD_FULL_60HZ:
         joycon_poller->ReadActiveMode(buffer, motion_status, ring_status);
@@ -225,6 +251,24 @@ void JoyconDriver::SetPollingMode() {
         generic_protocol->EnableImu(false);
     }
 
+    if (nfc_protocol->IsEnabled()) {
+        amiibo_detected = false;
+        nfc_protocol->DisableNfc();
+    }
+
+    if (nfc_enabled && supported_features.nfc) {
+        auto result = nfc_protocol->EnableNfc();
+        if (result == DriverResult::Success) {
+            result = nfc_protocol->StartNFCPollingMode();
+        }
+        if (result == DriverResult::Success) {
+            disable_input_thread = false;
+            return;
+        }
+        nfc_protocol->DisableNfc();
+        LOG_ERROR(Input, "Error enabling NFC");
+    }
+
     if (ring_protocol->IsEnabled()) {
         ring_connected = false;
         ring_protocol->DisableRingCon();
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index dc5d60221a..c9118ee939 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -8,14 +8,15 @@
 #include <span>
 #include <thread>
 
-#include "input_common/helpers/joycon_protocol/calibration.h"
-#include "input_common/helpers/joycon_protocol/generic_functions.h"
 #include "input_common/helpers/joycon_protocol/joycon_types.h"
-#include "input_common/helpers/joycon_protocol/poller.h"
-#include "input_common/helpers/joycon_protocol/ringcon.h"
-#include "input_common/helpers/joycon_protocol/rumble.h"
 
 namespace InputCommon::Joycon {
+class CalibrationProtocol;
+class GenericProtocol;
+class NfcProtocol;
+class JoyconPoller;
+class RingConProtocol;
+class RumbleProtocol;
 
 class JoyconDriver final {
 public:
@@ -84,17 +85,18 @@ private:
     SupportedFeatures GetSupportedFeatures();
 
     // Protocol Features
-    std::unique_ptr<CalibrationProtocol> calibration_protocol = nullptr;
-    std::unique_ptr<GenericProtocol> generic_protocol = nullptr;
-    std::unique_ptr<JoyconPoller> joycon_poller = nullptr;
-    std::unique_ptr<RingConProtocol> ring_protocol = nullptr;
-    std::unique_ptr<RumbleProtocol> rumble_protocol = nullptr;
+    std::unique_ptr<CalibrationProtocol> calibration_protocol;
+    std::unique_ptr<GenericProtocol> generic_protocol;
+    std::unique_ptr<NfcProtocol> nfc_protocol;
+    std::unique_ptr<JoyconPoller> joycon_poller;
+    std::unique_ptr<RingConProtocol> ring_protocol;
+    std::unique_ptr<RumbleProtocol> rumble_protocol;
 
     // Connection status
     bool is_connected{};
     u64 delta_time;
     std::size_t error_counter{};
-    std::shared_ptr<JoyconHandle> hidapi_handle = nullptr;
+    std::shared_ptr<JoyconHandle> hidapi_handle;
     std::chrono::time_point<std::chrono::steady_clock> last_update;
 
     // External device status
diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp
new file mode 100644
index 0000000000..69b2bfe050
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/nfc.cpp
@@ -0,0 +1,414 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <thread>
+#include "common/logging/log.h"
+#include "input_common/helpers/joycon_protocol/nfc.h"
+
+namespace InputCommon::Joycon {
+
+NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle) : JoyconCommonProtocol(handle) {}
+
+DriverResult NfcProtocol::EnableNfc() {
+    LOG_INFO(Input, "Enable NFC");
+    DriverResult result{DriverResult::Success};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
+    }
+    if (result == DriverResult::Success) {
+        result = EnableMCU(true);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
+    }
+    if (result == DriverResult::Success) {
+        const MCUConfig config{
+            .command = MCUCommand::ConfigureMCU,
+            .sub_command = MCUSubCommand::SetMCUMode,
+            .mode = MCUMode::NFC,
+            .crc = {},
+        };
+
+        result = ConfigureMCU(config);
+    }
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult NfcProtocol::DisableNfc() {
+    LOG_DEBUG(Input, "Disable NFC");
+    DriverResult result{DriverResult::Success};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = EnableMCU(false);
+    }
+
+    is_enabled = false;
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult NfcProtocol::StartNFCPollingMode() {
+    LOG_DEBUG(Input, "Start NFC pooling Mode");
+    DriverResult result{DriverResult::Success};
+    TagFoundData tag_data{};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitUntilNfcIsReady();
+    }
+    if (result == DriverResult::Success) {
+        is_enabled = true;
+    }
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
+    LOG_DEBUG(Input, "Start NFC pooling Mode");
+    DriverResult result{DriverResult::Success};
+    TagFoundData tag_data{};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = StartPolling(tag_data);
+    }
+    if (result == DriverResult::Success) {
+        result = ReadTag(tag_data);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitUntilNfcIsReady();
+    }
+    if (result == DriverResult::Success) {
+        result = StartPolling(tag_data);
+    }
+    if (result == DriverResult::Success) {
+        result = GetAmiiboData(data);
+    }
+
+    SetNonBlocking();
+    return result;
+}
+
+bool NfcProtocol::HasAmiibo() {
+    DriverResult result{DriverResult::Success};
+    TagFoundData tag_data{};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = StartPolling(tag_data);
+    }
+
+    SetNonBlocking();
+    return result == DriverResult::Success;
+}
+
+DriverResult NfcProtocol::WaitUntilNfcIsReady() {
+    constexpr std::size_t timeout_limit = 10;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    do {
+        auto result = SendStartWaitingRecieveRequest(output);
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ > timeout_limit) {
+            return DriverResult::Timeout;
+        }
+    } while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[55] != 0x31 ||
+             output[56] != 0x00);
+
+    return DriverResult::Success;
+}
+
+DriverResult NfcProtocol::StartPolling(TagFoundData& data) {
+    LOG_DEBUG(Input, "Start Polling for tag");
+    constexpr std::size_t timeout_limit = 7;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    do {
+        const auto result = SendStartPollingRequest(output);
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ > timeout_limit) {
+            return DriverResult::Timeout;
+        }
+    } while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[56] != 0x09);
+
+    data.type = output[62];
+    data.uuid.resize(output[64]);
+    memcpy(data.uuid.data(), output.data() + 65, data.uuid.size());
+
+    return DriverResult::Success;
+}
+
+DriverResult NfcProtocol::ReadTag(const TagFoundData& data) {
+    constexpr std::size_t timeout_limit = 10;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    std::string uuid_string = "";
+    for (auto& content : data.uuid) {
+        uuid_string += " " + fmt::format("{:02x}", content);
+    }
+
+    LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string);
+
+    tries = 0;
+    std::size_t ntag_pages = 0;
+    // Read Tag data
+loop1:
+    while (true) {
+        auto result = SendReadAmiiboRequest(output, ntag_pages);
+
+        int attempt = 0;
+        while (1) {
+            if (attempt != 0) {
+                result = GetMCUDataResponse(ReportMode::NFC_IR_MODE_60HZ, output);
+            }
+            if ((output[49] == 0x3a || output[49] == 0x2a) && output[56] == 0x07) {
+                return DriverResult::ErrorReadingData;
+            }
+            if (output[49] == 0x3a && output[51] == 0x07 && output[52] == 0x01) {
+                if (data.type != 2) {
+                    goto loop1;
+                }
+                switch (output[74]) {
+                case 0:
+                    ntag_pages = 135;
+                    break;
+                case 3:
+                    ntag_pages = 45;
+                    break;
+                case 4:
+                    ntag_pages = 231;
+                    break;
+                default:
+                    return DriverResult::ErrorReadingData;
+                }
+                goto loop1;
+            }
+            if (output[49] == 0x2a && output[56] == 0x04) {
+                // finished
+                SendStopPollingRequest(output);
+                return DriverResult::Success;
+            }
+            if (output[49] == 0x2a) {
+                goto loop1;
+            }
+            if (attempt++ > 6) {
+                goto loop1;
+            }
+        }
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ > timeout_limit) {
+            return DriverResult::Timeout;
+        }
+    }
+
+    return DriverResult::Success;
+}
+
+DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) {
+    constexpr std::size_t timeout_limit = 10;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    std::size_t ntag_pages = 135;
+    std::size_t ntag_buffer_pos = 0;
+    // Read Tag data
+loop1:
+    while (true) {
+        auto result = SendReadAmiiboRequest(output, ntag_pages);
+
+        int attempt = 0;
+        while (1) {
+            if (attempt != 0) {
+                result = GetMCUDataResponse(ReportMode::NFC_IR_MODE_60HZ, output);
+            }
+            if ((output[49] == 0x3a || output[49] == 0x2a) && output[56] == 0x07) {
+                return DriverResult::ErrorReadingData;
+            }
+            if (output[49] == 0x3a && output[51] == 0x07) {
+                std::size_t payload_size = (output[54] << 8 | output[55]) & 0x7FF;
+                if (output[52] == 0x01) {
+                    memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 116,
+                           payload_size - 60);
+                    ntag_buffer_pos += payload_size - 60;
+                } else {
+                    memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 56, payload_size);
+                }
+                goto loop1;
+            }
+            if (output[49] == 0x2a && output[56] == 0x04) {
+                LOG_INFO(Input, "Finished reading amiibo");
+                return DriverResult::Success;
+            }
+            if (output[49] == 0x2a) {
+                goto loop1;
+            }
+            if (attempt++ > 4) {
+                goto loop1;
+            }
+        }
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ > timeout_limit) {
+            return DriverResult::Timeout;
+        }
+    }
+
+    return DriverResult::Success;
+}
+
+DriverResult NfcProtocol::SendStartPollingRequest(std::vector<u8>& output) {
+    NFCRequestState request{
+        .sub_command = MCUSubCommand::ReadDeviceMode,
+        .command_argument = NFCReadCommand::StartPolling,
+        .packet_id = 0x0,
+        .packet_flag = MCUPacketFlag::LastCommandPacket,
+        .data_length = sizeof(NFCPollingCommandData),
+        .nfc_polling =
+            {
+                .enable_mifare = 0x01,
+                .unknown_1 = 0x00,
+                .unknown_2 = 0x00,
+                .unknown_3 = 0x2c,
+                .unknown_4 = 0x01,
+            },
+        .crc = {},
+    };
+
+    std::vector<u8> request_data(sizeof(NFCRequestState));
+    memcpy(request_data.data(), &request, sizeof(NFCRequestState));
+    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
+    return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
+}
+
+DriverResult NfcProtocol::SendStopPollingRequest(std::vector<u8>& output) {
+    NFCRequestState request{
+        .sub_command = MCUSubCommand::ReadDeviceMode,
+        .command_argument = NFCReadCommand::StopPolling,
+        .packet_id = 0x0,
+        .packet_flag = MCUPacketFlag::LastCommandPacket,
+        .data_length = 0,
+        .raw_data = {},
+        .crc = {},
+    };
+
+    std::vector<u8> request_data(sizeof(NFCRequestState));
+    memcpy(request_data.data(), &request, sizeof(NFCRequestState));
+    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
+    return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
+}
+
+DriverResult NfcProtocol::SendStartWaitingRecieveRequest(std::vector<u8>& output) {
+    NFCRequestState request{
+        .sub_command = MCUSubCommand::ReadDeviceMode,
+        .command_argument = NFCReadCommand::StartWaitingRecieve,
+        .packet_id = 0x0,
+        .packet_flag = MCUPacketFlag::LastCommandPacket,
+        .data_length = 0,
+        .raw_data = {},
+        .crc = {},
+    };
+
+    std::vector<u8> request_data(sizeof(NFCRequestState));
+    memcpy(request_data.data(), &request, sizeof(NFCRequestState));
+    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
+    return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
+}
+
+DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, std::size_t ntag_pages) {
+    NFCRequestState request{
+        .sub_command = MCUSubCommand::ReadDeviceMode,
+        .command_argument = NFCReadCommand::Ntag,
+        .packet_id = 0x0,
+        .packet_flag = MCUPacketFlag::LastCommandPacket,
+        .data_length = sizeof(NFCReadCommandData),
+        .nfc_read =
+            {
+                .unknown = 0xd0,
+                .uuid_length = 0x07,
+                .unknown_2 = 0x00,
+                .uid = {},
+                .tag_type = NFCTagType::AllTags,
+                .read_block = GetReadBlockCommand(ntag_pages),
+            },
+        .crc = {},
+    };
+
+    std::vector<u8> request_data(sizeof(NFCRequestState));
+    memcpy(request_data.data(), &request, sizeof(NFCRequestState));
+    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
+    return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
+}
+
+NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(std::size_t pages) const {
+    if (pages == 0) {
+        return {
+            .block_count = 1,
+        };
+    }
+
+    if (pages == 45) {
+        return {
+            .block_count = 1,
+            .blocks =
+                {
+                    NFCReadBlock{0x00, 0x2C},
+                },
+        };
+    }
+
+    if (pages == 135) {
+        return {
+            .block_count = 3,
+            .blocks =
+                {
+                    NFCReadBlock{0x00, 0x3b},
+                    {0x3c, 0x77},
+                    {0x78, 0x86},
+                },
+        };
+    }
+
+    if (pages == 231) {
+        return {
+            .block_count = 4,
+            .blocks =
+                {
+                    NFCReadBlock{0x00, 0x3b},
+                    {0x3c, 0x77},
+                    {0x78, 0x83},
+                    {0xb4, 0xe6},
+                },
+        };
+    }
+
+    return {};
+}
+
+bool NfcProtocol::IsEnabled() {
+    return is_enabled;
+}
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h
new file mode 100644
index 0000000000..0ede03d505
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/nfc.h
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
+// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
+// https://github.com/CTCaer/jc_toolkit
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
+
+#pragma once
+
+#include <vector>
+
+#include "input_common/helpers/joycon_protocol/common_protocol.h"
+#include "input_common/helpers/joycon_protocol/joycon_types.h"
+
+namespace InputCommon::Joycon {
+
+class NfcProtocol final : private JoyconCommonProtocol {
+public:
+    NfcProtocol(std::shared_ptr<JoyconHandle> handle);
+
+    DriverResult EnableNfc();
+
+    DriverResult DisableNfc();
+
+    DriverResult StartNFCPollingMode();
+
+    DriverResult ScanAmiibo(std::vector<u8>& data);
+
+    bool HasAmiibo();
+
+    bool IsEnabled();
+
+private:
+    struct TagFoundData {
+        u8 type;
+        std::vector<u8> uuid;
+    };
+
+    DriverResult WaitUntilNfcIsReady();
+
+    DriverResult StartPolling(TagFoundData& data);
+
+    DriverResult ReadTag(const TagFoundData& data);
+
+    DriverResult GetAmiiboData(std::vector<u8>& data);
+
+    DriverResult SendStartPollingRequest(std::vector<u8>& output);
+
+    DriverResult SendStopPollingRequest(std::vector<u8>& output);
+
+    DriverResult SendStartWaitingRecieveRequest(std::vector<u8>& output);
+
+    DriverResult SendReadAmiiboRequest(std::vector<u8>& output, std::size_t ntag_pages);
+
+    NFCReadBlockCommand GetReadBlockCommand(std::size_t pages) const;
+
+    bool is_enabled{};
+};
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
index cb76e1e06f..fd05d98f38 100644
--- a/src/input_common/helpers/joycon_protocol/poller.cpp
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -74,6 +74,10 @@ void JoyconPoller::UpdateColor(const Color& color) {
     callbacks.on_color_data(color);
 }
 
+void JoyconPoller::updateAmiibo(const std::vector<u8>& amiibo_data) {
+    callbacks.on_amiibo_data(amiibo_data);
+}
+
 void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) {
     float normalized_value = static_cast<float>(value - ring_status.default_value);
     if (normalized_value > 0) {
diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h
index 68b14b8ba0..c40fc7bca8 100644
--- a/src/input_common/helpers/joycon_protocol/poller.h
+++ b/src/input_common/helpers/joycon_protocol/poller.h
@@ -36,6 +36,7 @@ public:
 
     void UpdateColor(const Color& color);
     void UpdateRing(s16 value, const RingStatus& ring_status);
+    void updateAmiibo(const std::vector<u8>& amiibo_data);
 
 private:
     void UpdateActiveLeftPadInput(const InputReportActive& input,

From 1c08d532e059fab603facb43f758f37fe148c1fc Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Thu, 22 Dec 2022 20:47:51 -0600
Subject: [PATCH 14/24] core: hid: Fix input regressions

---
 src/core/hid/emulated_controller.cpp          | 55 ++++++++++++-------
 src/core/hid/emulated_controller.h            | 12 ++--
 src/core/hle/service/hid/controllers/npad.cpp |  1 +
 src/core/hle/service/hid/hidbus.cpp           | 24 ++++----
 src/input_common/helpers/joycon_driver.cpp    |  2 +-
 .../helpers/joycon_protocol/ringcon.cpp       |  3 -
 6 files changed, 56 insertions(+), 41 deletions(-)

diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 1e4ec4addc..1ed57f9491 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -107,6 +107,8 @@ void EmulatedController::ReloadFromSettings() {
         .button = GetNpadColor(player.button_color_right),
     };
 
+    ring_params[0] = Common::ParamPackage(Settings::values.ringcon_analogs);
+
     // Other or debug controller should always be a pro controller
     if (npad_id_type != NpadIdType::Other) {
         SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
@@ -144,14 +146,15 @@ void EmulatedController::LoadDevices() {
     battery_params[RightIndex].Set("battery", true);
 
     camera_params = Common::ParamPackage{"engine:camera,camera:1"};
-    nfc_params = right_joycon;
-    nfc_params.Set("nfc", true);
-    ring_params = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
+    ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
+    nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
+    nfc_params[1] = right_joycon;
+    nfc_params[1].Set("nfc", true);
 
     output_params[LeftIndex] = left_joycon;
     output_params[RightIndex] = right_joycon;
     output_params[2] = camera_params;
-    output_params[3] = nfc_params;
+    output_params[3] = nfc_params[0];
     output_params[LeftIndex].Set("output", true);
     output_params[RightIndex].Set("output", true);
     output_params[2].Set("output", true);
@@ -169,8 +172,9 @@ void EmulatedController::LoadDevices() {
                            Common::Input::CreateInputDevice);
     std::ranges::transform(color_params, color_devices.begin(), Common::Input::CreateInputDevice);
     camera_devices = Common::Input::CreateInputDevice(camera_params);
-    ring_analog_device = Common::Input::CreateInputDevice(ring_params);
-    nfc_devices = Common::Input::CreateInputDevice(nfc_params);
+    std::ranges::transform(ring_params, ring_analog_devices.begin(),
+                           Common::Input::CreateInputDevice);
+    std::ranges::transform(nfc_params, nfc_devices.begin(), Common::Input::CreateInputDevice);
     std::ranges::transform(output_params, output_devices.begin(),
                            Common::Input::CreateOutputDevice);
 
@@ -366,21 +370,26 @@ void EmulatedController::ReloadInput() {
         camera_devices->ForceUpdate();
     }
 
-    if (ring_analog_device) {
-        ring_analog_device->SetCallback({
+    for (std::size_t index = 0; index < ring_analog_devices.size(); ++index) {
+        if (!ring_analog_devices[index]) {
+            continue;
+        }
+        ring_analog_devices[index]->SetCallback({
             .on_change =
                 [this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
         });
+        ring_analog_devices[index]->ForceUpdate();
     }
 
-    if (nfc_devices) {
-        if (npad_id_type == NpadIdType::Handheld || npad_id_type == NpadIdType::Player1) {
-            nfc_devices->SetCallback({
-                .on_change =
-                    [this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); },
-            });
-            nfc_devices->ForceUpdate();
+    for (std::size_t index = 0; index < nfc_devices.size(); ++index) {
+        if (!nfc_devices[index]) {
+            continue;
         }
+        nfc_devices[index]->SetCallback({
+            .on_change =
+                [this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); },
+        });
+        nfc_devices[index]->ForceUpdate();
     }
 
     // Register TAS devices. No need to force update
@@ -469,8 +478,12 @@ void EmulatedController::UnloadInput() {
         stick.reset();
     }
     camera_devices.reset();
-    ring_analog_device.reset();
-    nfc_devices.reset();
+    for (auto& ring : ring_analog_devices) {
+        ring.reset();
+    }
+    for (auto& nfc : nfc_devices) {
+        nfc.reset();
+    }
 }
 
 void EmulatedController::EnableConfiguration() {
@@ -540,7 +553,9 @@ void EmulatedController::SaveCurrentConfig() {
     for (std::size_t index = 0; index < player.motions.size(); ++index) {
         player.motions[index] = motion_params[index].Serialize();
     }
-    Settings::values.ringcon_analogs = ring_params.Serialize();
+    if (npad_id_type == NpadIdType::Player1) {
+        Settings::values.ringcon_analogs = ring_params[0].Serialize();
+    }
 }
 
 void EmulatedController::RestoreConfig() {
@@ -1215,11 +1230,11 @@ bool EmulatedController::SetCameraFormat(
 }
 
 Common::ParamPackage EmulatedController::GetRingParam() const {
-    return ring_params;
+    return ring_params[0];
 }
 
 void EmulatedController::SetRingParam(Common::ParamPackage param) {
-    ring_params = std::move(param);
+    ring_params[0] = std::move(param);
     ReloadInput();
 }
 
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index d044cc36b8..c517aa5d72 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -40,8 +40,10 @@ using ColorDevices =
 using BatteryDevices =
     std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
 using CameraDevices = std::unique_ptr<Common::Input::InputDevice>;
-using RingAnalogDevice = std::unique_ptr<Common::Input::InputDevice>;
-using NfcDevices = std::unique_ptr<Common::Input::InputDevice>;
+using RingAnalogDevices =
+    std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
+using NfcDevices =
+    std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
 using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>;
 
 using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
@@ -51,8 +53,8 @@ using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::
 using ColorParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using CameraParams = Common::ParamPackage;
-using RingAnalogParams = Common::ParamPackage;
-using NfcParams = Common::ParamPackage;
+using RingAnalogParams = std::array<Common::ParamPackage, max_emulated_controllers>;
+using NfcParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using OutputParams = std::array<Common::ParamPackage, output_devices_size>;
 
 using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
@@ -538,7 +540,7 @@ private:
     BatteryDevices battery_devices;
     ColorDevices color_devices;
     CameraDevices camera_devices;
-    RingAnalogDevice ring_analog_device;
+    RingAnalogDevices ring_analog_devices;
     NfcDevices nfc_devices;
     OutputDevices output_devices;
 
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 83b3680911..fe5bf94d27 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -337,6 +337,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
     controller.is_connected = true;
     controller.device->Connect();
     controller.device->SetLedPattern();
+    controller.device->SetPollingMode(Common::Input::PollingMode::Active);
     SignalStyleSetChangedEvent(npad_id);
     WriteEmptyEntry(controller.shared_memory);
 }
diff --git a/src/core/hle/service/hid/hidbus.cpp b/src/core/hle/service/hid/hidbus.cpp
index e5e50845f8..17252a84ae 100644
--- a/src/core/hle/service/hid/hidbus.cpp
+++ b/src/core/hle/service/hid/hidbus.cpp
@@ -297,13 +297,13 @@ void HidBus::EnableExternalDevice(Kernel::HLERequestContext& ctx) {
 
     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);
+    LOG_DEBUG(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);
 
@@ -326,11 +326,11 @@ 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);
+    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_);
 
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index c3debffd11..db9ff4875e 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -396,7 +396,7 @@ DriverResult JoyconDriver::SetActiveMode() {
 
 DriverResult JoyconDriver::SetNfcMode() {
     std::scoped_lock lock{mutex};
-    motion_enabled = false;
+    motion_enabled = true;
     hidbus_enabled = false;
     nfc_enabled = true;
     passive_enabled = false;
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.cpp b/src/input_common/helpers/joycon_protocol/ringcon.cpp
index 2d137b85d3..47769f3441 100644
--- a/src/input_common/helpers/joycon_protocol/ringcon.cpp
+++ b/src/input_common/helpers/joycon_protocol/ringcon.cpp
@@ -55,9 +55,6 @@ DriverResult RingConProtocol::StartRingconPolling() {
     DriverResult result{DriverResult::Success};
     SetBlocking();
 
-    if (result == DriverResult::Success) {
-        result = WaitSetMCUMode(ReportMode::STANDARD_FULL_60HZ, MCUMode::Standby);
-    }
     if (result == DriverResult::Success) {
         result = IsRingConnected(is_connected);
     }

From e1a3bda4d9881cb99c36b64733b814a3bb437f13 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Fri, 23 Dec 2022 08:32:02 -0600
Subject: [PATCH 15/24] Address review comments

---
 src/core/hid/input_converter.cpp                 |  6 ++----
 src/input_common/drivers/joycon.cpp              |  2 +-
 src/input_common/helpers/joycon_driver.cpp       |  2 +-
 .../helpers/joycon_protocol/calibration.cpp      | 10 +++-------
 .../helpers/joycon_protocol/calibration.h        |  2 +-
 .../helpers/joycon_protocol/common_protocol.cpp  |  6 +++---
 .../joycon_protocol/generic_functions.cpp        | 12 ++++++------
 .../helpers/joycon_protocol/generic_functions.h  |  2 +-
 src/input_common/helpers/joycon_protocol/nfc.cpp |  9 +++++----
 src/input_common/helpers/joycon_protocol/nfc.h   |  4 ++--
 .../helpers/joycon_protocol/ringcon.cpp          | 16 ++++++++--------
 .../helpers/joycon_protocol/ringcon.h            |  4 ++--
 .../helpers/joycon_protocol/rumble.cpp           | 13 ++++++++-----
 .../helpers/joycon_protocol/rumble.h             |  2 +-
 14 files changed, 44 insertions(+), 46 deletions(-)

diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
index d7e253044a..3f7b8c0904 100644
--- a/src/core/hid/input_converter.cpp
+++ b/src/core/hid/input_converter.cpp
@@ -305,17 +305,15 @@ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& cal
 }
 
 Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback) {
-    Common::Input::BodyColorStatus color{};
     switch (callback.type) {
     case Common::Input::InputType::Color:
-        color = callback.color_status;
+        return callback.color_status;
         break;
     default:
         LOG_ERROR(Input, "Conversion from type {} to color not implemented", callback.type);
+        return {};
         break;
     }
-
-    return color;
 }
 
 void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index 29f0dc0c89..696a6db393 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -316,7 +316,7 @@ void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type,
         return;
     }
 
-    Common::Input::BatteryLevel battery{value.status.Value()};
+    Common::Input::BatteryLevel battery{};
     switch (value.status) {
     case 0:
         battery = Common::Input::BatteryLevel::Empty;
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index db9ff4875e..8982a2397f 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -465,7 +465,7 @@ void JoyconDriver::SetCallbacks(const Joycon::JoyconCallbacks& callbacks) {
 
 Joycon::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
                                                  ControllerType& controller_type) {
-    std::array<std::pair<u32, Joycon::ControllerType>, 4> supported_devices{
+    static constexpr std::array<std::pair<u32, Joycon::ControllerType>, 4> supported_devices{
         std::pair<u32, Joycon::ControllerType>{0x2006, Joycon::ControllerType::Left},
         {0x2007, Joycon::ControllerType::Right},
         {0x2009, Joycon::ControllerType::Pro},
diff --git a/src/input_common/helpers/joycon_protocol/calibration.cpp b/src/input_common/helpers/joycon_protocol/calibration.cpp
index ce1ff7061d..cd30ab8698 100644
--- a/src/input_common/helpers/joycon_protocol/calibration.cpp
+++ b/src/input_common/helpers/joycon_protocol/calibration.cpp
@@ -9,7 +9,7 @@
 namespace InputCommon::Joycon {
 
 CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle)
-    : JoyconCommonProtocol(handle) {}
+    : JoyconCommonProtocol(std::move(handle)) {}
 
 DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) {
     std::vector<u8> buffer;
@@ -136,12 +136,8 @@ DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibratio
         ring_data_min = current_value - 800;
         ring_data_default = current_value;
     }
-    if (ring_data_max < current_value) {
-        ring_data_max = current_value;
-    }
-    if (ring_data_min > current_value) {
-        ring_data_min = current_value;
-    }
+    ring_data_max = std::max(ring_data_max, current_value);
+    ring_data_min = std::min(ring_data_min, current_value);
     calibration = {
         .default_value = ring_data_default,
         .max_value = ring_data_max,
diff --git a/src/input_common/helpers/joycon_protocol/calibration.h b/src/input_common/helpers/joycon_protocol/calibration.h
index 32ddef4b85..afb52a36a4 100644
--- a/src/input_common/helpers/joycon_protocol/calibration.h
+++ b/src/input_common/helpers/joycon_protocol/calibration.h
@@ -24,7 +24,7 @@ namespace InputCommon::Joycon {
 /// Driver functions related to retrieving calibration data from the device
 class CalibrationProtocol final : private JoyconCommonProtocol {
 public:
-    CalibrationProtocol(std::shared_ptr<JoyconHandle> handle);
+    explicit CalibrationProtocol(std::shared_ptr<JoyconHandle> handle);
 
     /**
      * Sends a request to obtain the left stick calibration from memory
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
index 43a036e027..a4d08fdafa 100644
--- a/src/input_common/helpers/joycon_protocol/common_protocol.cpp
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -6,7 +6,7 @@
 
 namespace InputCommon::Joycon {
 JoyconCommonProtocol::JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_)
-    : hidapi_handle{hidapi_handle_} {}
+    : hidapi_handle{std::move(hidapi_handle_)} {}
 
 u8 JoyconCommonProtocol::GetCounter() {
     hidapi_handle->packet_counter = (hidapi_handle->packet_counter + 1) & 0x0F;
@@ -256,7 +256,7 @@ DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMod
 }
 
 // crc-8-ccitt / polynomial 0x07 look up table
-static constexpr uint8_t mcu_crc8_table[256] = {
+constexpr std::array<u8, 256> mcu_crc8_table = {
     0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
     0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
     0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
@@ -278,7 +278,7 @@ u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const {
     u8 crc8 = 0x0;
 
     for (int i = 0; i < size; ++i) {
-        crc8 = mcu_crc8_table[(u8)(crc8 ^ buffer[i])];
+        crc8 = mcu_crc8_table[static_cast<u8>(crc8 ^ buffer[i])];
     }
     return crc8;
 }
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.cpp b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
index 829f7625d6..cbd9ff4f8b 100644
--- a/src/input_common/helpers/joycon_protocol/generic_functions.cpp
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
@@ -7,7 +7,7 @@
 namespace InputCommon::Joycon {
 
 GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle)
-    : JoyconCommonProtocol(handle) {}
+    : JoyconCommonProtocol(std::move(handle)) {}
 
 DriverResult GenericProtocol::EnablePassiveMode() {
     SetBlocking();
@@ -43,7 +43,7 @@ DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type)
 }
 
 DriverResult GenericProtocol::EnableImu(bool enable) {
-    const std::vector<u8> buffer{static_cast<u8>(enable ? 1 : 0)};
+    const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)};
     std::vector<u8> output;
     SetBlocking();
     const auto result = SendSubCommand(SubCommand::ENABLE_IMU, buffer, output);
@@ -54,8 +54,8 @@ DriverResult GenericProtocol::EnableImu(bool enable) {
 DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
                                            AccelerometerSensitivity asen,
                                            AccelerometerPerformance afrec) {
-    const std::vector<u8> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
-                                 static_cast<u8>(gfrec), static_cast<u8>(afrec)};
+    const std::array<u8, 4> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
+                                   static_cast<u8>(gfrec), static_cast<u8>(afrec)};
     std::vector<u8> output;
     SetBlocking();
     const auto result = SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer, output);
@@ -115,7 +115,7 @@ DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) {
 }
 
 DriverResult GenericProtocol::SetHomeLight() {
-    const std::vector<u8> buffer{0x0f, 0xf0, 0x00};
+    static constexpr std::array<u8, 3> buffer{0x0f, 0xf0, 0x00};
     std::vector<u8> output;
     SetBlocking();
 
@@ -130,7 +130,7 @@ DriverResult GenericProtocol::SetLedBusy() {
 }
 
 DriverResult GenericProtocol::SetLedPattern(u8 leds) {
-    const std::vector<u8> buffer{leds};
+    const std::array<u8, 1> buffer{leds};
     std::vector<u8> output;
     SetBlocking();
 
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.h b/src/input_common/helpers/joycon_protocol/generic_functions.h
index c3e2ccadc0..239bb7dbf9 100644
--- a/src/input_common/helpers/joycon_protocol/generic_functions.h
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.h
@@ -16,7 +16,7 @@ namespace InputCommon::Joycon {
 /// Joycon driver functions that easily implemented
 class GenericProtocol final : private JoyconCommonProtocol {
 public:
-    GenericProtocol(std::shared_ptr<JoyconHandle> handle);
+    explicit GenericProtocol(std::shared_ptr<JoyconHandle> handle);
 
     /// Enables passive mode. This mode only sends button data on change. Sticks will return digital
     /// data instead of analog. Motion will be disabled
diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp
index 69b2bfe050..8755e310b8 100644
--- a/src/input_common/helpers/joycon_protocol/nfc.cpp
+++ b/src/input_common/helpers/joycon_protocol/nfc.cpp
@@ -7,7 +7,8 @@
 
 namespace InputCommon::Joycon {
 
-NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle) : JoyconCommonProtocol(handle) {}
+NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle)
+    : JoyconCommonProtocol(std::move(handle)) {}
 
 DriverResult NfcProtocol::EnableNfc() {
     LOG_INFO(Input, "Enable NFC");
@@ -160,9 +161,9 @@ DriverResult NfcProtocol::ReadTag(const TagFoundData& data) {
     std::vector<u8> output;
     std::size_t tries = 0;
 
-    std::string uuid_string = "";
+    std::string uuid_string;
     for (auto& content : data.uuid) {
-        uuid_string += " " + fmt::format("{:02x}", content);
+        uuid_string += fmt::format(" {:02x}", content);
     }
 
     LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string);
@@ -407,7 +408,7 @@ NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(std::size_t pages) const {
     return {};
 }
 
-bool NfcProtocol::IsEnabled() {
+bool NfcProtocol::IsEnabled() const {
     return is_enabled;
 }
 
diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h
index 0ede03d505..5cb0e5a652 100644
--- a/src/input_common/helpers/joycon_protocol/nfc.h
+++ b/src/input_common/helpers/joycon_protocol/nfc.h
@@ -17,7 +17,7 @@ namespace InputCommon::Joycon {
 
 class NfcProtocol final : private JoyconCommonProtocol {
 public:
-    NfcProtocol(std::shared_ptr<JoyconHandle> handle);
+    explicit NfcProtocol(std::shared_ptr<JoyconHandle> handle);
 
     DriverResult EnableNfc();
 
@@ -29,7 +29,7 @@ public:
 
     bool HasAmiibo();
 
-    bool IsEnabled();
+    bool IsEnabled() const;
 
 private:
     struct TagFoundData {
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.cpp b/src/input_common/helpers/joycon_protocol/ringcon.cpp
index 47769f3441..8adad57dd6 100644
--- a/src/input_common/helpers/joycon_protocol/ringcon.cpp
+++ b/src/input_common/helpers/joycon_protocol/ringcon.cpp
@@ -7,7 +7,7 @@
 namespace InputCommon::Joycon {
 
 RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle)
-    : JoyconCommonProtocol(handle) {}
+    : JoyconCommonProtocol(std::move(handle)) {}
 
 DriverResult RingConProtocol::EnableRingCon() {
     LOG_DEBUG(Input, "Enable Ringcon");
@@ -78,7 +78,7 @@ DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
     is_connected = false;
 
     do {
-        std::vector<u8> empty_data(0);
+        std::array<u8, 1> empty_data{};
         const auto result = SendSubCommand(SubCommand::UNKNOWN_RINGCON, empty_data, output);
 
         if (result != DriverResult::Success) {
@@ -101,11 +101,11 @@ DriverResult RingConProtocol::ConfigureRing() {
     std::vector<u8> output;
     std::size_t tries = 0;
 
+    static constexpr std::array<u8, 37> ring_config{
+        0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16, 0xED, 0x34, 0x36,
+        0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6, 0xA9, 0x22, 0x00, 0x00, 0x04, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36};
     do {
-        std::vector<u8> ring_config{0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16,
-                                    0xED, 0x34, 0x36, 0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6,
-                                    0xA9, 0x22, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
-                                    0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36};
         result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config, output);
 
         if (result != DriverResult::Success) {
@@ -116,13 +116,13 @@ DriverResult RingConProtocol::ConfigureRing() {
         }
     } while (output[14] != 0x5C);
 
-    std::vector<u8> ringcon_data{0x04, 0x01, 0x01, 0x02};
+    static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02};
     result = SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data, output);
 
     return result;
 }
 
-bool RingConProtocol::IsEnabled() {
+bool RingConProtocol::IsEnabled() const {
     return is_enabled;
 }
 
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.h b/src/input_common/helpers/joycon_protocol/ringcon.h
index 0c25de23ee..6e858f3fcb 100644
--- a/src/input_common/helpers/joycon_protocol/ringcon.h
+++ b/src/input_common/helpers/joycon_protocol/ringcon.h
@@ -17,7 +17,7 @@ namespace InputCommon::Joycon {
 
 class RingConProtocol final : private JoyconCommonProtocol {
 public:
-    RingConProtocol(std::shared_ptr<JoyconHandle> handle);
+    explicit RingConProtocol(std::shared_ptr<JoyconHandle> handle);
 
     DriverResult EnableRingCon();
 
@@ -25,7 +25,7 @@ public:
 
     DriverResult StartRingconPolling();
 
-    bool IsEnabled();
+    bool IsEnabled() const;
 
 private:
     DriverResult IsRingConnected(bool& is_connected);
diff --git a/src/input_common/helpers/joycon_protocol/rumble.cpp b/src/input_common/helpers/joycon_protocol/rumble.cpp
index 17ee388634..fad67a94ba 100644
--- a/src/input_common/helpers/joycon_protocol/rumble.cpp
+++ b/src/input_common/helpers/joycon_protocol/rumble.cpp
@@ -1,17 +1,20 @@
 // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include <algorithm>
+#include <cmath>
+
 #include "common/logging/log.h"
 #include "input_common/helpers/joycon_protocol/rumble.h"
 
 namespace InputCommon::Joycon {
 
 RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle)
-    : JoyconCommonProtocol(handle) {}
+    : JoyconCommonProtocol(std::move(handle)) {}
 
 DriverResult RumbleProtocol::EnableRumble(bool is_enabled) {
     LOG_DEBUG(Input, "Enable Rumble");
-    const std::vector<u8> buffer{static_cast<u8>(is_enabled ? 1 : 0)};
+    const std::array<u8, 1> buffer{static_cast<u8>(is_enabled ? 1 : 0)};
     std::vector<u8> output;
     SetBlocking();
     const auto result = SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer, output);
@@ -20,7 +23,7 @@ DriverResult RumbleProtocol::EnableRumble(bool is_enabled) {
 }
 
 DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) {
-    std::vector<u8> buffer(sizeof(DefaultVibrationBuffer));
+    std::array<u8, sizeof(DefaultVibrationBuffer)> buffer{};
 
     if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) {
         return SendVibrationReport(DefaultVibrationBuffer);
@@ -66,7 +69,7 @@ u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
     /* More information about these values can be found here:
      * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
      */
-    constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
+    static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
         std::pair<f32, int>{0.0f, 0x0},
         {0.01f, 0x2},
         {0.012f, 0x4},
@@ -183,7 +186,7 @@ u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const {
     /* More information about these values can be found here:
      * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
      */
-    constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
+    static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
         std::pair<f32, int>{0.0f, 0x0040},
         {0.01f, 0x8040},
         {0.012f, 0x0041},
diff --git a/src/input_common/helpers/joycon_protocol/rumble.h b/src/input_common/helpers/joycon_protocol/rumble.h
index 7d0329f039..6c12b79258 100644
--- a/src/input_common/helpers/joycon_protocol/rumble.h
+++ b/src/input_common/helpers/joycon_protocol/rumble.h
@@ -17,7 +17,7 @@ namespace InputCommon::Joycon {
 
 class RumbleProtocol final : private JoyconCommonProtocol {
 public:
-    RumbleProtocol(std::shared_ptr<JoyconHandle> handle);
+    explicit RumbleProtocol(std::shared_ptr<JoyconHandle> handle);
 
     DriverResult EnableRumble(bool is_enabled);
 

From 527dad70976a158e94defc51707347e064a31099 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Mon, 26 Dec 2022 11:11:01 -0600
Subject: [PATCH 16/24] input_common: Use DriverResult on all engines

---
 src/common/input.h                            | 46 ++++++++-----------
 src/core/hid/emulated_controller.cpp          | 10 ++--
 src/input_common/drivers/camera.cpp           |  4 +-
 src/input_common/drivers/camera.h             |  4 +-
 src/input_common/drivers/gc_adapter.cpp       |  6 +--
 src/input_common/drivers/gc_adapter.h         |  2 +-
 src/input_common/drivers/joycon.cpp           | 41 ++++++++---------
 src/input_common/drivers/joycon.h             | 12 ++---
 src/input_common/drivers/sdl_driver.cpp       |  4 +-
 src/input_common/drivers/sdl_driver.h         |  2 +-
 src/input_common/drivers/virtual_amiibo.cpp   |  4 +-
 src/input_common/drivers/virtual_amiibo.h     |  2 +-
 src/input_common/helpers/joycon_driver.cpp    | 26 +++++------
 src/input_common/helpers/joycon_driver.h      | 10 ++--
 .../helpers/joycon_protocol/joycon_types.h    |  1 +
 src/input_common/input_engine.h               | 19 ++++----
 src/input_common/input_poller.cpp             | 11 +++--
 17 files changed, 100 insertions(+), 104 deletions(-)

diff --git a/src/common/input.h b/src/common/input.h
index 1e5ba038d8..d61cd7ca88 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -64,20 +64,19 @@ enum class CameraFormat {
     None,
 };
 
-// Vibration reply from the controller
-enum class VibrationError {
-    None,
+// Different results that can happen from a device request
+enum class DriverResult {
+    Success,
+    WrongReply,
+    Timeout,
+    UnsupportedControllerType,
+    HandleInUse,
+    ErrorReadingData,
+    ErrorWritingData,
+    NoDeviceDetected,
+    InvalidHandle,
     NotSupported,
     Disabled,
-    InvalidHandle,
-    Unknown,
-};
-
-// Polling mode reply from the controller
-enum class PollingError {
-    None,
-    NotSupported,
-    InvalidHandle,
     Unknown,
 };
 
@@ -94,13 +93,6 @@ enum class NfcState {
     Unknown,
 };
 
-// Ir camera reply from the controller
-enum class CameraError {
-    None,
-    NotSupported,
-    Unknown,
-};
-
 // Hint for amplification curve to be used
 enum class VibrationAmplificationType {
     Linear,
@@ -336,22 +328,24 @@ class OutputDevice {
 public:
     virtual ~OutputDevice() = default;
 
-    virtual void SetLED([[maybe_unused]] const LedStatus& led_status) {}
+    virtual DriverResult SetLED([[maybe_unused]] const LedStatus& led_status) {
+        return DriverResult::NotSupported;
+    }
 
-    virtual VibrationError SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) {
-        return VibrationError::NotSupported;
+    virtual DriverResult SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) {
+        return DriverResult::NotSupported;
     }
 
     virtual bool IsVibrationEnabled() {
         return false;
     }
 
-    virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
-        return PollingError::NotSupported;
+    virtual DriverResult SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
+        return DriverResult::NotSupported;
     }
 
-    virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
-        return CameraError::NotSupported;
+    virtual DriverResult SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
+        return DriverResult::NotSupported;
     }
 
     virtual NfcState SupportsNfc() const {
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 1ed57f9491..62da5be6ca 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -1178,7 +1178,7 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v
         .type = type,
     };
     return output_devices[device_index]->SetVibration(status) ==
-           Common::Input::VibrationError::None;
+           Common::Input::DriverResult::Success;
 }
 
 bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
@@ -1208,8 +1208,8 @@ bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode)
     const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
     const auto mapped_nfc_result = output_device->SetPollingMode(polling_mode);
 
-    return virtual_nfc_result == Common::Input::PollingError::None ||
-           mapped_nfc_result == Common::Input::PollingError::None;
+    return virtual_nfc_result == Common::Input::DriverResult::Success ||
+           mapped_nfc_result == Common::Input::DriverResult::Success;
 }
 
 bool EmulatedController::SetCameraFormat(
@@ -1220,13 +1220,13 @@ bool EmulatedController::SetCameraFormat(
     auto& camera_output_device = output_devices[2];
 
     if (right_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
-            camera_format)) == Common::Input::CameraError::None) {
+            camera_format)) == Common::Input::DriverResult::Success) {
         return true;
     }
 
     // Fallback to Qt camera if native device doesn't have support
     return camera_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
-               camera_format)) == Common::Input::CameraError::None;
+               camera_format)) == Common::Input::DriverResult::Success;
 }
 
 Common::ParamPackage EmulatedController::GetRingParam() const {
diff --git a/src/input_common/drivers/camera.cpp b/src/input_common/drivers/camera.cpp
index fad9177dcb..04970f635d 100644
--- a/src/input_common/drivers/camera.cpp
+++ b/src/input_common/drivers/camera.cpp
@@ -72,11 +72,11 @@ std::size_t Camera::getImageHeight() const {
     }
 }
 
-Common::Input::CameraError Camera::SetCameraFormat(
+Common::Input::DriverResult Camera::SetCameraFormat(
     [[maybe_unused]] const PadIdentifier& identifier_,
     const Common::Input::CameraFormat camera_format) {
     status.format = camera_format;
-    return Common::Input::CameraError::None;
+    return Common::Input::DriverResult::Success;
 }
 
 } // namespace InputCommon
diff --git a/src/input_common/drivers/camera.h b/src/input_common/drivers/camera.h
index ead3e0fdee..24b27e3258 100644
--- a/src/input_common/drivers/camera.h
+++ b/src/input_common/drivers/camera.h
@@ -22,8 +22,8 @@ public:
     std::size_t getImageWidth() const;
     std::size_t getImageHeight() const;
 
-    Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_,
-                                               Common::Input::CameraFormat camera_format) override;
+    Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier_,
+                                                Common::Input::CameraFormat camera_format) override;
 
 private:
     Common::Input::CameraStatus status{};
diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp
index 826fa21097..ecb3e9dc29 100644
--- a/src/input_common/drivers/gc_adapter.cpp
+++ b/src/input_common/drivers/gc_adapter.cpp
@@ -324,7 +324,7 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) {
     return true;
 }
 
-Common::Input::VibrationError GCAdapter::SetVibration(
+Common::Input::DriverResult GCAdapter::SetVibration(
     const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
     const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
     const auto processed_amplitude =
@@ -333,9 +333,9 @@ Common::Input::VibrationError GCAdapter::SetVibration(
     pads[identifier.port].rumble_amplitude = processed_amplitude;
 
     if (!rumble_enabled) {
-        return Common::Input::VibrationError::Disabled;
+        return Common::Input::DriverResult::Disabled;
     }
-    return Common::Input::VibrationError::None;
+    return Common::Input::DriverResult::Success;
 }
 
 bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) {
diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h
index b5270fd0bb..3c2eb376dc 100644
--- a/src/input_common/drivers/gc_adapter.h
+++ b/src/input_common/drivers/gc_adapter.h
@@ -25,7 +25,7 @@ public:
     explicit GCAdapter(std::string input_engine_);
     ~GCAdapter() override;
 
-    Common::Input::VibrationError SetVibration(
+    Common::Input::DriverResult SetVibration(
         const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
 
     bool IsVibrationEnabled(const PadIdentifier& identifier) override;
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index 696a6db393..cf54f1b533 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -233,8 +233,8 @@ bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) {
     return handle->IsVibrationEnabled();
 }
 
-Common::Input::VibrationError Joycons::SetVibration(
-    const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
+Common::Input::DriverResult Joycons::SetVibration(const PadIdentifier& identifier,
+                                                  const Common::Input::VibrationStatus& vibration) {
     const Joycon::VibrationValue native_vibration{
         .low_amplitude = vibration.low_amplitude,
         .low_frequency = vibration.low_frequency,
@@ -243,32 +243,31 @@ Common::Input::VibrationError Joycons::SetVibration(
     };
     auto handle = GetHandle(identifier);
     if (handle == nullptr) {
-        return Common::Input::VibrationError::InvalidHandle;
+        return Common::Input::DriverResult::InvalidHandle;
     }
 
     handle->SetVibration(native_vibration);
-    return Common::Input::VibrationError::None;
+    return Common::Input::DriverResult::Success;
 }
 
-void Joycons::SetLeds(const PadIdentifier& identifier, const Common::Input::LedStatus& led_status) {
+Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier,
+                                             const Common::Input::LedStatus& led_status) {
     auto handle = GetHandle(identifier);
     if (handle == nullptr) {
-        return;
+        return Common::Input::DriverResult::InvalidHandle;
     }
     int led_config = led_status.led_1 ? 1 : 0;
     led_config += led_status.led_2 ? 2 : 0;
     led_config += led_status.led_3 ? 4 : 0;
     led_config += led_status.led_4 ? 8 : 0;
 
-    const auto result = handle->SetLedConfig(static_cast<u8>(led_config));
-    if (result != Joycon::DriverResult::Success) {
-        LOG_ERROR(Input, "Failed to set led config");
-    }
+    return static_cast<Common::Input::DriverResult>(
+        handle->SetLedConfig(static_cast<u8>(led_config)));
 }
 
-Common::Input::CameraError Joycons::SetCameraFormat(const PadIdentifier& identifier_,
-                                                    Common::Input::CameraFormat camera_format) {
-    return Common::Input::CameraError::NotSupported;
+Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier_,
+                                                     Common::Input::CameraFormat camera_format) {
+    return Common::Input::DriverResult::NotSupported;
 };
 
 Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const {
@@ -280,32 +279,30 @@ Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_,
     return Common::Input::NfcState::NotSupported;
 };
 
-Common::Input::PollingError Joycons::SetPollingMode(const PadIdentifier& identifier,
+Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier,
                                                     const Common::Input::PollingMode polling_mode) {
     auto handle = GetHandle(identifier);
     if (handle == nullptr) {
         LOG_ERROR(Input, "Invalid handle {}", identifier.port);
-        return Common::Input::PollingError::InvalidHandle;
+        return Common::Input::DriverResult::InvalidHandle;
     }
 
     switch (polling_mode) {
     case Common::Input::PollingMode::NFC:
-        handle->SetNfcMode();
+        return static_cast<Common::Input::DriverResult>(handle->SetNfcMode());
         break;
     case Common::Input::PollingMode::Active:
-        handle->SetActiveMode();
+        return static_cast<Common::Input::DriverResult>(handle->SetActiveMode());
         break;
     case Common::Input::PollingMode::Pasive:
-        handle->SetPasiveMode();
+        return static_cast<Common::Input::DriverResult>(handle->SetPasiveMode());
         break;
     case Common::Input::PollingMode::Ring:
-        handle->SetRingConMode();
+        return static_cast<Common::Input::DriverResult>(handle->SetRingConMode());
         break;
     default:
-        return Common::Input::PollingError::NotSupported;
+        return Common::Input::DriverResult::NotSupported;
     }
-
-    return Common::Input::PollingError::None;
 }
 
 void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type,
diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h
index 56c1172701..1a04c19fd3 100644
--- a/src/input_common/drivers/joycon.h
+++ b/src/input_common/drivers/joycon.h
@@ -29,20 +29,20 @@ public:
     ~Joycons();
 
     bool IsVibrationEnabled(const PadIdentifier& identifier) override;
-    Common::Input::VibrationError SetVibration(
+    Common::Input::DriverResult SetVibration(
         const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
 
-    void SetLeds(const PadIdentifier& identifier,
-                 const Common::Input::LedStatus& led_status) override;
+    Common::Input::DriverResult SetLeds(const PadIdentifier& identifier,
+                                        const Common::Input::LedStatus& led_status) override;
 
-    Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_,
-                                               Common::Input::CameraFormat camera_format) override;
+    Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier_,
+                                                Common::Input::CameraFormat camera_format) override;
 
     Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
     Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
                                          const std::vector<u8>& data) override;
 
-    Common::Input::PollingError SetPollingMode(
+    Common::Input::DriverResult SetPollingMode(
         const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override;
 
     /// Used for automapping features
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index c9496a0d86..51a9d8962e 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -545,7 +545,7 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
     return devices;
 }
 
-Common::Input::VibrationError SDLDriver::SetVibration(
+Common::Input::DriverResult SDLDriver::SetVibration(
     const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
     const auto joystick =
         GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port));
@@ -579,7 +579,7 @@ Common::Input::VibrationError SDLDriver::SetVibration(
         .vibration = new_vibration,
     });
 
-    return Common::Input::VibrationError::None;
+    return Common::Input::DriverResult::Success;
 }
 
 bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) {
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h
index 366bcc4965..ffde169b35 100644
--- a/src/input_common/drivers/sdl_driver.h
+++ b/src/input_common/drivers/sdl_driver.h
@@ -63,7 +63,7 @@ public:
 
     bool IsStickInverted(const Common::ParamPackage& params) override;
 
-    Common::Input::VibrationError SetVibration(
+    Common::Input::DriverResult SetVibration(
         const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
 
     bool IsVibrationEnabled(const PadIdentifier& identifier) override;
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp
index 63ffaca67b..29e129d3cc 100644
--- a/src/input_common/drivers/virtual_amiibo.cpp
+++ b/src/input_common/drivers/virtual_amiibo.cpp
@@ -22,7 +22,7 @@ VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move(
 
 VirtualAmiibo::~VirtualAmiibo() = default;
 
-Common::Input::PollingError VirtualAmiibo::SetPollingMode(
+Common::Input::DriverResult VirtualAmiibo::SetPollingMode(
     [[maybe_unused]] const PadIdentifier& identifier_,
     const Common::Input::PollingMode polling_mode_) {
     polling_mode = polling_mode_;
@@ -37,7 +37,7 @@ Common::Input::PollingError VirtualAmiibo::SetPollingMode(
         }
     }
 
-    return Common::Input::PollingError::None;
+    return Common::Input::DriverResult::Success;
 }
 
 Common::Input::NfcState VirtualAmiibo::SupportsNfc(
diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h
index 0f9dad3338..13cacfc0af 100644
--- a/src/input_common/drivers/virtual_amiibo.h
+++ b/src/input_common/drivers/virtual_amiibo.h
@@ -36,7 +36,7 @@ public:
     ~VirtualAmiibo() override;
 
     // Sets polling mode to a controller
-    Common::Input::PollingError SetPollingMode(
+    Common::Input::DriverResult SetPollingMode(
         const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override;
 
     Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index 8982a2397f..b00b6110b5 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -459,23 +459,23 @@ SerialNumber JoyconDriver::GetHandleSerialNumber() const {
     return handle_serial_number;
 }
 
-void JoyconDriver::SetCallbacks(const Joycon::JoyconCallbacks& callbacks) {
+void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) {
     joycon_poller->SetCallbacks(callbacks);
 }
 
-Joycon::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
-                                                 ControllerType& controller_type) {
-    static constexpr std::array<std::pair<u32, Joycon::ControllerType>, 4> supported_devices{
-        std::pair<u32, Joycon::ControllerType>{0x2006, Joycon::ControllerType::Left},
-        {0x2007, Joycon::ControllerType::Right},
-        {0x2009, Joycon::ControllerType::Pro},
-        {0x200E, Joycon::ControllerType::Grip},
+DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
+                                         ControllerType& controller_type) {
+    static constexpr std::array<std::pair<u32, ControllerType>, 4> supported_devices{
+        std::pair<u32, ControllerType>{0x2006, ControllerType::Left},
+        {0x2007, ControllerType::Right},
+        {0x2009, ControllerType::Pro},
+        {0x200E, ControllerType::Grip},
     };
     constexpr u16 nintendo_vendor_id = 0x057e;
 
-    controller_type = Joycon::ControllerType::None;
+    controller_type = ControllerType::None;
     if (device_info->vendor_id != nintendo_vendor_id) {
-        return Joycon::DriverResult::UnsupportedControllerType;
+        return DriverResult::UnsupportedControllerType;
     }
 
     for (const auto& [product_id, type] : supported_devices) {
@@ -487,10 +487,10 @@ Joycon::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_inf
     return Joycon::DriverResult::UnsupportedControllerType;
 }
 
-Joycon::DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
-                                                   Joycon::SerialNumber& serial_number) {
+DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
+                                           SerialNumber& serial_number) {
     if (device_info->serial_number == nullptr) {
-        return Joycon::DriverResult::Unknown;
+        return DriverResult::Unknown;
     }
     std::memcpy(&serial_number, device_info->serial_number, 15);
     return Joycon::DriverResult::Success;
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index c9118ee939..bf38a30090 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -46,15 +46,15 @@ public:
     DriverResult SetNfcMode();
     DriverResult SetRingConMode();
 
-    void SetCallbacks(const Joycon::JoyconCallbacks& callbacks);
+    void SetCallbacks(const JoyconCallbacks& callbacks);
 
     // Returns device type from hidapi handle
-    static Joycon::DriverResult GetDeviceType(SDL_hid_device_info* device_info,
-                                              Joycon::ControllerType& controller_type);
+    static DriverResult GetDeviceType(SDL_hid_device_info* device_info,
+                                      ControllerType& controller_type);
 
     // Returns serial number from hidapi handle
-    static Joycon::DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
-                                                Joycon::SerialNumber& serial_number);
+    static DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
+                                        SerialNumber& serial_number);
 
 private:
     struct SupportedFeatures {
diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h
index de512fe639..36c00a8d70 100644
--- a/src/input_common/helpers/joycon_protocol/joycon_types.h
+++ b/src/input_common/helpers/joycon_protocol/joycon_types.h
@@ -284,6 +284,7 @@ enum class DriverResult {
     NoDeviceDetected,
     InvalidHandle,
     NotSupported,
+    Disabled,
     Unknown,
 };
 
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h
index 6301c5719a..50b5a3dc8c 100644
--- a/src/input_common/input_engine.h
+++ b/src/input_common/input_engine.h
@@ -105,14 +105,17 @@ public:
     void EndConfiguration();
 
     // Sets a led pattern for a controller
-    virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier,
-                         [[maybe_unused]] const Common::Input::LedStatus& led_status) {}
+    virtual Common::Input::DriverResult SetLeds(
+        [[maybe_unused]] const PadIdentifier& identifier,
+        [[maybe_unused]] const Common::Input::LedStatus& led_status) {
+        return Common::Input::DriverResult::NotSupported;
+    }
 
     // Sets rumble to a controller
-    virtual Common::Input::VibrationError SetVibration(
+    virtual Common::Input::DriverResult SetVibration(
         [[maybe_unused]] const PadIdentifier& identifier,
         [[maybe_unused]] const Common::Input::VibrationStatus& vibration) {
-        return Common::Input::VibrationError::NotSupported;
+        return Common::Input::DriverResult::NotSupported;
     }
 
     // Returns true if device supports vibrations
@@ -121,17 +124,17 @@ public:
     }
 
     // Sets polling mode to a controller
-    virtual Common::Input::PollingError SetPollingMode(
+    virtual Common::Input::DriverResult SetPollingMode(
         [[maybe_unused]] const PadIdentifier& identifier,
         [[maybe_unused]] const Common::Input::PollingMode polling_mode) {
-        return Common::Input::PollingError::NotSupported;
+        return Common::Input::DriverResult::NotSupported;
     }
 
     // Sets camera format to a controller
-    virtual Common::Input::CameraError SetCameraFormat(
+    virtual Common::Input::DriverResult SetCameraFormat(
         [[maybe_unused]] const PadIdentifier& identifier,
         [[maybe_unused]] Common::Input::CameraFormat camera_format) {
-        return Common::Input::CameraError::NotSupported;
+        return Common::Input::DriverResult::NotSupported;
     }
 
     // Returns success if nfc is supported
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index 368ffbdd5a..15cbf7e5f0 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -806,11 +806,11 @@ public:
     explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
         : identifier(identifier_), input_engine(input_engine_) {}
 
-    void SetLED(const Common::Input::LedStatus& led_status) override {
-        input_engine->SetLeds(identifier, led_status);
+    Common::Input::DriverResult SetLED(const Common::Input::LedStatus& led_status) override {
+        return input_engine->SetLeds(identifier, led_status);
     }
 
-    Common::Input::VibrationError SetVibration(
+    Common::Input::DriverResult SetVibration(
         const Common::Input::VibrationStatus& vibration_status) override {
         return input_engine->SetVibration(identifier, vibration_status);
     }
@@ -819,11 +819,12 @@ public:
         return input_engine->IsVibrationEnabled(identifier);
     }
 
-    Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override {
+    Common::Input::DriverResult SetPollingMode(Common::Input::PollingMode polling_mode) override {
         return input_engine->SetPollingMode(identifier, polling_mode);
     }
 
-    Common::Input::CameraError SetCameraFormat(Common::Input::CameraFormat camera_format) override {
+    Common::Input::DriverResult SetCameraFormat(
+        Common::Input::CameraFormat camera_format) override {
         return input_engine->SetCameraFormat(identifier, camera_format);
     }
 

From 5cb437703fa441a08db295f8a916caedc3a581f2 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Mon, 26 Dec 2022 12:49:49 -0600
Subject: [PATCH 17/24] yuzu: Add ring controller test button

---
 src/core/hid/emulated_controller.cpp         |  10 +-
 src/core/hid/emulated_controller.h           |   4 +-
 src/core/hle/service/nfc/nfc_device.cpp      |   3 +-
 src/core/hle/service/nfp/nfp_device.cpp      |   3 +-
 src/input_common/drivers/virtual_amiibo.cpp  |   9 +-
 src/input_common/helpers/joycon_driver.cpp   |  38 +-
 src/input_common/helpers/joycon_driver.h     |   2 +-
 src/yuzu/configuration/configure_ringcon.cpp |  69 +++
 src/yuzu/configuration/configure_ringcon.h   |  10 +
 src/yuzu/configuration/configure_ringcon.ui  | 420 ++++++++++++-------
 10 files changed, 382 insertions(+), 186 deletions(-)

diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 62da5be6ca..915ffa4906 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -1200,7 +1200,8 @@ bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
     return output_devices[device_index]->IsVibrationEnabled();
 }
 
-bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) {
+Common::Input::DriverResult EmulatedController::SetPollingMode(
+    Common::Input::PollingMode polling_mode) {
     LOG_INFO(Service_HID, "Set polling mode {}", polling_mode);
     auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
     auto& nfc_output_device = output_devices[3];
@@ -1208,8 +1209,11 @@ bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode)
     const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
     const auto mapped_nfc_result = output_device->SetPollingMode(polling_mode);
 
-    return virtual_nfc_result == Common::Input::DriverResult::Success ||
-           mapped_nfc_result == Common::Input::DriverResult::Success;
+    if (virtual_nfc_result == Common::Input::DriverResult::Success) {
+        return virtual_nfc_result;
+    }
+
+    return mapped_nfc_result;
 }
 
 bool EmulatedController::SetCameraFormat(
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index c517aa5d72..fb931fc0a8 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -363,9 +363,9 @@ public:
     /**
      * Sets the desired data to be polled from a controller
      * @param polling_mode type of input desired buttons, gyro, nfc, ir, etc.
-     * @return true if SetPollingMode was successfull
+     * @return driver result from this command
      */
-    bool SetPollingMode(Common::Input::PollingMode polling_mode);
+    Common::Input::DriverResult SetPollingMode(Common::Input::PollingMode polling_mode);
 
     /**
      * Sets the desired camera format to be polled from a controller
diff --git a/src/core/hle/service/nfc/nfc_device.cpp b/src/core/hle/service/nfc/nfc_device.cpp
index 78578f7238..c9815edbc6 100644
--- a/src/core/hle/service/nfc/nfc_device.cpp
+++ b/src/core/hle/service/nfc/nfc_device.cpp
@@ -130,7 +130,8 @@ Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) {
         return WrongDeviceState;
     }
 
-    if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) {
+    if (npad_device->SetPollingMode(Common::Input::PollingMode::NFC) !=
+        Common::Input::DriverResult::Success) {
         LOG_ERROR(Service_NFC, "Nfc not supported");
         return NfcDisabled;
     }
diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp
index c860fd1a10..7b80139616 100644
--- a/src/core/hle/service/nfp/nfp_device.cpp
+++ b/src/core/hle/service/nfp/nfp_device.cpp
@@ -152,7 +152,8 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {
         return WrongDeviceState;
     }
 
-    if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) {
+    if (npad_device->SetPollingMode(Common::Input::PollingMode::NFC) !=
+        Common::Input::DriverResult::Success) {
         LOG_ERROR(Service_NFP, "Nfc not supported");
         return NfcDisabled;
     }
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp
index 29e129d3cc..4a0268a4dc 100644
--- a/src/input_common/drivers/virtual_amiibo.cpp
+++ b/src/input_common/drivers/virtual_amiibo.cpp
@@ -27,17 +27,18 @@ Common::Input::DriverResult VirtualAmiibo::SetPollingMode(
     const Common::Input::PollingMode polling_mode_) {
     polling_mode = polling_mode_;
 
-    if (polling_mode == Common::Input::PollingMode::NFC) {
+    switch (polling_mode) {
+    case Common::Input::PollingMode::NFC:
         if (state == State::Initialized) {
             state = State::WaitingForAmiibo;
         }
-    } else {
+        return Common::Input::DriverResult::Success;
+    default:
         if (state == State::AmiiboIsOpen) {
             CloseAmiibo();
         }
+        return Common::Input::DriverResult::NotSupported;
     }
-
-    return Common::Input::DriverResult::Success;
 }
 
 Common::Input::NfcState VirtualAmiibo::SupportsNfc(
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index b00b6110b5..8217ba7f61 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -238,7 +238,7 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
     }
 }
 
-void JoyconDriver::SetPollingMode() {
+DriverResult JoyconDriver::SetPollingMode() {
     disable_input_thread = true;
 
     rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
@@ -263,7 +263,7 @@ void JoyconDriver::SetPollingMode() {
         }
         if (result == DriverResult::Success) {
             disable_input_thread = false;
-            return;
+            return result;
         }
         nfc_protocol->DisableNfc();
         LOG_ERROR(Input, "Error enabling NFC");
@@ -282,7 +282,7 @@ void JoyconDriver::SetPollingMode() {
         if (result == DriverResult::Success) {
             ring_connected = true;
             disable_input_thread = false;
-            return;
+            return result;
         }
         ring_connected = false;
         ring_protocol->DisableRingCon();
@@ -293,7 +293,7 @@ void JoyconDriver::SetPollingMode() {
         const auto result = generic_protocol->EnablePassiveMode();
         if (result == DriverResult::Success) {
             disable_input_thread = false;
-            return;
+            return result;
         }
         LOG_ERROR(Input, "Error enabling passive mode");
     }
@@ -305,6 +305,7 @@ void JoyconDriver::SetPollingMode() {
     }
 
     disable_input_thread = false;
+    return result;
 }
 
 JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
@@ -380,8 +381,7 @@ DriverResult JoyconDriver::SetPasiveMode() {
     hidbus_enabled = false;
     nfc_enabled = false;
     passive_enabled = true;
-    SetPollingMode();
-    return DriverResult::Success;
+    return SetPollingMode();
 }
 
 DriverResult JoyconDriver::SetActiveMode() {
@@ -390,28 +390,42 @@ DriverResult JoyconDriver::SetActiveMode() {
     hidbus_enabled = false;
     nfc_enabled = false;
     passive_enabled = false;
-    SetPollingMode();
-    return DriverResult::Success;
+    return SetPollingMode();
 }
 
 DriverResult JoyconDriver::SetNfcMode() {
     std::scoped_lock lock{mutex};
+
+    if (!supported_features.nfc) {
+        return DriverResult::NotSupported;
+    }
+
     motion_enabled = true;
     hidbus_enabled = false;
     nfc_enabled = true;
     passive_enabled = false;
-    SetPollingMode();
-    return DriverResult::Success;
+    return SetPollingMode();
 }
 
 DriverResult JoyconDriver::SetRingConMode() {
     std::scoped_lock lock{mutex};
+
+    if (!supported_features.hidbus) {
+        return DriverResult::NotSupported;
+    }
+
     motion_enabled = true;
     hidbus_enabled = true;
     nfc_enabled = false;
     passive_enabled = false;
-    SetPollingMode();
-    return DriverResult::Success;
+
+    const auto result = SetPollingMode();
+
+    if (!ring_connected) {
+        return DriverResult::NoDeviceDetected;
+    }
+
+    return result;
 }
 
 bool JoyconDriver::IsConnected() const {
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index bf38a30090..5ff15c7841 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -73,7 +73,7 @@ private:
     void OnNewData(std::span<u8> buffer);
 
     /// Updates device configuration to enable or disable features
-    void SetPollingMode();
+    DriverResult SetPollingMode();
 
     /// Returns true if input thread is valid and doesn't need to be stopped
     bool IsInputThreadValid() const;
diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp
index 0cfe3b60ef..697c36fb4a 100644
--- a/src/yuzu/configuration/configure_ringcon.cpp
+++ b/src/yuzu/configuration/configure_ringcon.cpp
@@ -4,7 +4,9 @@
 #include <memory>
 #include <QKeyEvent>
 #include <QMenu>
+#include <QMessageBox>
 #include <QTimer>
+#include <fmt/format.h>
 
 #include "core/hid/emulated_controller.h"
 #include "core/hid/hid_core.h"
@@ -130,6 +132,13 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
     emulated_controller->SaveCurrentConfig();
     emulated_controller->EnableConfiguration();
 
+    Core::HID::ControllerUpdateCallback engine_callback{
+        .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); },
+        .is_npad_service = false,
+    };
+    callback_key = emulated_controller->SetCallback(engine_callback);
+    is_controller_set = true;
+
     LoadConfiguration();
 
     for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
@@ -187,6 +196,9 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
     connect(ui->restore_defaults_button, &QPushButton::clicked, this,
             &ConfigureRingController::RestoreDefaults);
 
+    connect(ui->enable_ring_controller_button, &QPushButton::clicked, this,
+            &ConfigureRingController::EnableRingController);
+
     timeout_timer->setSingleShot(true);
     connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
 
@@ -202,7 +214,13 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
 }
 
 ConfigureRingController::~ConfigureRingController() {
+    emulated_controller->SetPollingMode(Common::Input::PollingMode::Active);
     emulated_controller->DisableConfiguration();
+
+    if (is_controller_set) {
+        emulated_controller->DeleteCallback(callback_key);
+        is_controller_set = false;
+    }
 };
 
 void ConfigureRingController::changeEvent(QEvent* event) {
@@ -256,6 +274,57 @@ void ConfigureRingController::RestoreDefaults() {
     UpdateUI();
 }
 
+void ConfigureRingController::EnableRingController() {
+    const auto dialog_title = tr("Error enabling ring input");
+
+    is_ring_enabled = false;
+    ui->ring_controller_sensor_value->setText(tr("Not connected"));
+
+    if (!Settings::values.enable_joycon_driver) {
+        QMessageBox::warning(this, dialog_title, tr("Direct Joycon driver is not enabled"));
+        return;
+    }
+
+    ui->enable_ring_controller_button->setEnabled(false);
+    ui->enable_ring_controller_button->setText(tr("Configuring"));
+    // SetPollingMode is blocking. Allow to update the button status before calling the command
+    repaint();
+
+    const auto result = emulated_controller->SetPollingMode(Common::Input::PollingMode::Ring);
+    switch (result) {
+    case Common::Input::DriverResult::Success:
+        is_ring_enabled = true;
+        break;
+    case Common::Input::DriverResult::NotSupported:
+        QMessageBox::warning(this, dialog_title,
+                             tr("The current mapped device doesn't support the ring controller"));
+        break;
+    case Common::Input::DriverResult::NoDeviceDetected:
+        QMessageBox::warning(this, dialog_title,
+                             tr("The current mapped device doesn't have a ring attached"));
+        break;
+    default:
+        QMessageBox::warning(this, dialog_title,
+                             tr("Unexpected driver result %1").arg(static_cast<int>(result)));
+        break;
+    }
+    ui->enable_ring_controller_button->setEnabled(true);
+    ui->enable_ring_controller_button->setText(tr("Enable"));
+}
+
+void ConfigureRingController::ControllerUpdate(Core::HID::ControllerTriggerType type) {
+    if (!is_ring_enabled) {
+        return;
+    }
+    if (type != Core::HID::ControllerTriggerType::RingController) {
+        return;
+    }
+
+    const auto value = emulated_controller->GetRingSensorValues();
+    const auto tex_value = QString::fromStdString(fmt::format("{:.3f}", value.raw_value));
+    ui->ring_controller_sensor_value->setText(tex_value);
+}
+
 void ConfigureRingController::HandleClick(
     QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
     InputCommon::Polling::InputType type) {
diff --git a/src/yuzu/configuration/configure_ringcon.h b/src/yuzu/configuration/configure_ringcon.h
index 6e693e0dd2..b23c279061 100644
--- a/src/yuzu/configuration/configure_ringcon.h
+++ b/src/yuzu/configuration/configure_ringcon.h
@@ -42,6 +42,12 @@ private:
     /// Restore all buttons to their default values.
     void RestoreDefaults();
 
+    /// Sets current polling mode to ring input
+    void EnableRingController();
+
+    // Handles emulated controller events
+    void ControllerUpdate(Core::HID::ControllerTriggerType type);
+
     /// Called when the button was pressed.
     void HandleClick(QPushButton* button,
                      std::function<void(const Common::ParamPackage&)> new_input_setter,
@@ -80,5 +86,9 @@ private:
     InputCommon::InputSubsystem* input_subsystem;
     Core::HID::EmulatedController* emulated_controller;
 
+    bool is_ring_enabled{};
+    bool is_controller_set{};
+    int callback_key;
+
     std::unique_ptr<Ui::ConfigureRingController> ui;
 };
diff --git a/src/yuzu/configuration/configure_ringcon.ui b/src/yuzu/configuration/configure_ringcon.ui
index 9ec634dd49..514dff372a 100644
--- a/src/yuzu/configuration/configure_ringcon.ui
+++ b/src/yuzu/configuration/configure_ringcon.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>298</width>
-    <height>339</height>
+    <width>315</width>
+    <height>400</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -46,187 +46,283 @@
      </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">
+   <item>
+    <widget class="QGroupBox" name="RingAnalog">
+     <property name="title">
+      <string>Virtual Ring Sensor Parameters</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_1">
       <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>
+       <number>0</number>
       </property>
       <property name="sizeConstraint">
-        <enum>QLayout::SetDefaultConstraint</enum>
+       <enum>QLayout::SetDefaultConstraint</enum>
       </property>
       <property name="leftMargin">
-        <number>0</number>
+       <number>3</number>
       </property>
       <property name="topMargin">
-        <number>10</number>
+       <number>6</number>
       </property>
       <property name="rightMargin">
-        <number>0</number>
+       <number>3</number>
       </property>
       <property name="bottomMargin">
-        <number>3</number>
+       <number>0</number>
       </property>
       <item>
-        <layout class="QHBoxLayout" name="sliderRingAnalogDeadzoneHorizontalLayout">
-        <item>
-          <widget class="QLabel" name="labelRingAnalogDeadzone">
-          <property name="text">
-            <string>Deadzone: 0%</string>
+       <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::AlignHCenter</set>
+           <set>Qt::AlignCenter</set>
           </property>
-          </widget>
+          <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>70</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>
-        </layout>
+        <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>70</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>
-        <widget class="QSlider" name="sliderRingAnalogDeadzone">
-        <property name="maximum">
-          <number>100</number>
+       <layout class="QVBoxLayout" name="sliderRingAnalogDeadzoneVerticalLayout">
+        <property name="spacing">
+         <number>3</number>
         </property>
-        <property name="orientation">
-          <enum>Qt::Horizontal</enum>
+        <property name="sizeConstraint">
+         <enum>QLayout::SetDefaultConstraint</enum>
         </property>
-        </widget>
+        <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>
-    </item>
-    </layout>
-  </widget>
-  </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="RingDriver">
+     <property name="title">
+      <string>Direct Joycon Driver</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_2">
+      <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>10</number>
+      </property>
+      <item>
+       <layout class="QGridLayout" name="gridLayout">
+        <property name="leftMargin">
+         <number>10</number>
+        </property>
+        <property name="topMargin">
+         <number>6</number>
+        </property>
+        <property name="rightMargin">
+         <number>10</number>
+        </property>
+        <property name="bottomMargin">
+         <number>10</number>
+        </property>
+        <property name="verticalSpacing">
+         <number>10</number>
+        </property>
+        <item row="0" column="1">
+         <spacer name="horizontalSpacer">
+          <property name="orientation">
+           <enum>Qt::Horizontal</enum>
+          </property>
+          <property name="sizeType">
+           <enum>QSizePolicy::Fixed</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>76</width>
+            <height>20</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+        <item row="0" column="0">
+         <widget class="QLabel" name="enable_ring_controller_label">
+          <property name="text">
+           <string>Enable Ring Input</string>
+          </property>
+         </widget>
+        </item>
+        <item row="0" column="2">
+         <widget class="QPushButton" name="enable_ring_controller_button">
+          <property name="text">
+           <string>Enable</string>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="0">
+         <widget class="QLabel" name="ring_controller_sensor_label">
+          <property name="text">
+           <string>Ring Sensor Value</string>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="2">
+         <widget class="QLabel" name="ring_controller_sensor_value">
+          <property name="text">
+           <string>Not connected</string>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
    <item>
     <spacer name="verticalSpacer">
      <property name="orientation">
@@ -273,6 +369,6 @@
    <signal>rejected()</signal>
    <receiver>ConfigureRingController</receiver>
    <slot>reject()</slot>
-   </connection>
+  </connection>
  </connections>
 </ui>

From 459fb2b21337bae60194a2a99ce68c87aaed522d Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Wed, 28 Dec 2022 15:21:12 -0600
Subject: [PATCH 18/24] input_common: Implement joycon ir camera

---
 src/core/hid/emulated_controller.cpp          |  21 +-
 src/core/hid/emulated_controller.h            |   5 +-
 src/core/hle/service/hid/irs.cpp              |  11 +
 src/input_common/CMakeLists.txt               |   2 +
 src/input_common/drivers/joycon.cpp           |  29 +-
 src/input_common/drivers/joycon.h             |   5 +-
 src/input_common/helpers/joycon_driver.cpp    |  55 +++-
 src/input_common/helpers/joycon_driver.h      |   4 +
 .../joycon_protocol/common_protocol.cpp       |  13 +
 .../helpers/joycon_protocol/common_protocol.h |   7 +
 .../helpers/joycon_protocol/irs.cpp           | 300 ++++++++++++++++++
 .../helpers/joycon_protocol/irs.h             |  63 ++++
 .../helpers/joycon_protocol/joycon_types.h    | 107 ++++++-
 .../helpers/joycon_protocol/poller.cpp        |   6 +-
 .../helpers/joycon_protocol/poller.h          |   3 +-
 15 files changed, 608 insertions(+), 23 deletions(-)
 create mode 100644 src/input_common/helpers/joycon_protocol/irs.cpp
 create mode 100644 src/input_common/helpers/joycon_protocol/irs.h

diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 915ffa4906..faf9e7c4e5 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -145,7 +145,9 @@ void EmulatedController::LoadDevices() {
     battery_params[LeftIndex].Set("battery", true);
     battery_params[RightIndex].Set("battery", true);
 
-    camera_params = Common::ParamPackage{"engine:camera,camera:1"};
+    camera_params[0] = right_joycon;
+    camera_params[0].Set("camera", true);
+    camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
     ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
     nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
     nfc_params[1] = right_joycon;
@@ -153,7 +155,7 @@ void EmulatedController::LoadDevices() {
 
     output_params[LeftIndex] = left_joycon;
     output_params[RightIndex] = right_joycon;
-    output_params[2] = camera_params;
+    output_params[2] = camera_params[1];
     output_params[3] = nfc_params[0];
     output_params[LeftIndex].Set("output", true);
     output_params[RightIndex].Set("output", true);
@@ -171,7 +173,7 @@ void EmulatedController::LoadDevices() {
     std::ranges::transform(battery_params, battery_devices.begin(),
                            Common::Input::CreateInputDevice);
     std::ranges::transform(color_params, color_devices.begin(), Common::Input::CreateInputDevice);
-    camera_devices = Common::Input::CreateInputDevice(camera_params);
+    std::ranges::transform(camera_params, camera_devices.begin(), Common::Input::CreateInputDevice);
     std::ranges::transform(ring_params, ring_analog_devices.begin(),
                            Common::Input::CreateInputDevice);
     std::ranges::transform(nfc_params, nfc_devices.begin(), Common::Input::CreateInputDevice);
@@ -362,12 +364,15 @@ void EmulatedController::ReloadInput() {
         motion_devices[index]->ForceUpdate();
     }
 
-    if (camera_devices) {
-        camera_devices->SetCallback({
+    for (std::size_t index = 0; index < camera_devices.size(); ++index) {
+        if (!camera_devices[index]) {
+            continue;
+        }
+        camera_devices[index]->SetCallback({
             .on_change =
                 [this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); },
         });
-        camera_devices->ForceUpdate();
+        camera_devices[index]->ForceUpdate();
     }
 
     for (std::size_t index = 0; index < ring_analog_devices.size(); ++index) {
@@ -477,7 +482,9 @@ void EmulatedController::UnloadInput() {
     for (auto& stick : virtual_stick_devices) {
         stick.reset();
     }
-    camera_devices.reset();
+    for (auto& camera : camera_devices) {
+        camera.reset();
+    }
     for (auto& ring : ring_analog_devices) {
         ring.reset();
     }
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index fb931fc0a8..edebfc15c2 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -39,7 +39,8 @@ using ColorDevices =
     std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
 using BatteryDevices =
     std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
-using CameraDevices = std::unique_ptr<Common::Input::InputDevice>;
+using CameraDevices =
+    std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
 using RingAnalogDevices =
     std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
 using NfcDevices =
@@ -52,7 +53,7 @@ using ControllerMotionParams = std::array<Common::ParamPackage, Settings::Native
 using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
 using ColorParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
-using CameraParams = Common::ParamPackage;
+using CameraParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using RingAnalogParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using NfcParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using OutputParams = std::array<Common::ParamPackage, output_devices_size>;
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index 6a3453457f..3c1fa2274f 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -74,6 +74,8 @@ void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) {
     LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}",
                 applet_resource_user_id);
 
+    npad_device->SetPollingMode(Common::Input::PollingMode::Active);
+
     IPC::ResponseBuilder rb{ctx, 2};
     rb.Push(ResultSuccess);
 }
@@ -108,6 +110,7 @@ void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) {
     auto result = IsIrCameraHandleValid(parameters.camera_handle);
     if (result.IsSuccess()) {
         // TODO: Stop Image processor
+        npad_device->SetPollingMode(Common::Input::PollingMode::Active);
         result = ResultSuccess;
     }
 
@@ -139,6 +142,7 @@ void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) {
         MakeProcessor<MomentProcessor>(parameters.camera_handle, device);
         auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -170,6 +174,7 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
         auto& image_transfer_processor =
             GetProcessor<ClusteringProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -219,6 +224,7 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {
             GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
         image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -294,6 +300,7 @@ void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) {
         auto& image_transfer_processor =
             GetProcessor<TeraPluginProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -343,6 +350,7 @@ void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) {
         MakeProcessor<PointingProcessor>(camera_handle, device);
         auto& image_transfer_processor = GetProcessor<PointingProcessor>(camera_handle);
         image_transfer_processor.SetConfig(processor_config);
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -453,6 +461,7 @@ void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) {
             GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
         image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -479,6 +488,7 @@ void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) {
         MakeProcessor<IrLedProcessor>(camera_handle, device);
         auto& image_transfer_processor = GetProcessor<IrLedProcessor>(camera_handle);
         image_transfer_processor.SetConfig(processor_config);
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -504,6 +514,7 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {
     auto result = IsIrCameraHandleValid(parameters.camera_handle);
     if (result.IsSuccess()) {
         // TODO: Stop image processor async
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
         result = ResultSuccess;
     }
 
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 9c901af2ab..e3b627e4ff 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -64,6 +64,8 @@ if (ENABLE_SDL2)
         helpers/joycon_protocol/generic_functions.cpp
         helpers/joycon_protocol/generic_functions.h
         helpers/joycon_protocol/joycon_types.h
+        helpers/joycon_protocol/irs.cpp
+        helpers/joycon_protocol/irs.h
         helpers/joycon_protocol/nfc.cpp
         helpers/joycon_protocol/nfc.h
         helpers/joycon_protocol/poller.cpp
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index cf54f1b533..6c03e09537 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -191,6 +191,10 @@ void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
             .on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) {
                 OnAmiiboUpdate(port, amiibo_data);
             }},
+            .on_camera_data = {[this, port](const std::vector<u8>& camera_data,
+                                            Joycon::IrsResolution format) {
+                OnCameraUpdate(port, camera_data, format);
+            }},
         };
 
         handle->InitializeDevice();
@@ -265,9 +269,14 @@ Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier,
         handle->SetLedConfig(static_cast<u8>(led_config)));
 }
 
-Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier_,
+Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier,
                                                      Common::Input::CameraFormat camera_format) {
-    return Common::Input::DriverResult::NotSupported;
+    auto handle = GetHandle(identifier);
+    if (handle == nullptr) {
+        return Common::Input::DriverResult::InvalidHandle;
+    }
+    return static_cast<Common::Input::DriverResult>(handle->SetIrsConfig(
+        Joycon::IrsMode::ImageTransfer, static_cast<Joycon::IrsResolution>(camera_format)));
 };
 
 Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const {
@@ -288,18 +297,16 @@ Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identif
     }
 
     switch (polling_mode) {
-    case Common::Input::PollingMode::NFC:
-        return static_cast<Common::Input::DriverResult>(handle->SetNfcMode());
-        break;
     case Common::Input::PollingMode::Active:
         return static_cast<Common::Input::DriverResult>(handle->SetActiveMode());
-        break;
     case Common::Input::PollingMode::Pasive:
         return static_cast<Common::Input::DriverResult>(handle->SetPasiveMode());
-        break;
+    case Common::Input::PollingMode::IR:
+        return static_cast<Common::Input::DriverResult>(handle->SetIrMode());
+    case Common::Input::PollingMode::NFC:
+        return static_cast<Common::Input::DriverResult>(handle->SetNfcMode());
     case Common::Input::PollingMode::Ring:
         return static_cast<Common::Input::DriverResult>(handle->SetRingConMode());
-        break;
     default:
         return Common::Input::DriverResult::NotSupported;
     }
@@ -390,6 +397,12 @@ void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_dat
     SetNfc(identifier, {nfc_state, amiibo_data});
 }
 
+void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
+                             Joycon::IrsResolution format) {
+    const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
+    SetCamera(identifier, {static_cast<Common::Input::CameraFormat>(format), camera_data});
+}
+
 std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const {
     auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
         if (!device) {
diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h
index 1a04c19fd3..f180b74783 100644
--- a/src/input_common/drivers/joycon.h
+++ b/src/input_common/drivers/joycon.h
@@ -17,6 +17,7 @@ struct Color;
 struct MotionData;
 enum class ControllerType;
 enum class DriverResult;
+enum class IrsResolution;
 class JoyconDriver;
 } // namespace InputCommon::Joycon
 
@@ -35,7 +36,7 @@ public:
     Common::Input::DriverResult SetLeds(const PadIdentifier& identifier,
                                         const Common::Input::LedStatus& led_status) override;
 
-    Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier_,
+    Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier,
                                                 Common::Input::CameraFormat camera_format) override;
 
     Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
@@ -81,6 +82,8 @@ private:
                         const Joycon::MotionData& value);
     void OnRingConUpdate(f32 ring_data);
     void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data);
+    void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
+                        Joycon::IrsResolution format);
 
     /// Returns a JoyconHandle corresponding to a PadIdentifier
     std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const;
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index 8217ba7f61..040832a4ba 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -7,6 +7,7 @@
 #include "input_common/helpers/joycon_driver.h"
 #include "input_common/helpers/joycon_protocol/calibration.h"
 #include "input_common/helpers/joycon_protocol/generic_functions.h"
+#include "input_common/helpers/joycon_protocol/irs.h"
 #include "input_common/helpers/joycon_protocol/nfc.h"
 #include "input_common/helpers/joycon_protocol/poller.h"
 #include "input_common/helpers/joycon_protocol/ringcon.h"
@@ -78,6 +79,7 @@ DriverResult JoyconDriver::InitializeDevice() {
     // Initialize HW Protocols
     calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
     generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
+    irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle);
     nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle);
     ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle);
     rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
@@ -200,10 +202,15 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
         .min_value = ring_calibration.min_value,
     };
 
+    if (irs_protocol->IsEnabled()) {
+        irs_protocol->RequestImage(buffer);
+        joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat());
+    }
+
     if (nfc_protocol->IsEnabled()) {
         if (amiibo_detected) {
             if (!nfc_protocol->HasAmiibo()) {
-                joycon_poller->updateAmiibo({});
+                joycon_poller->UpdateAmiibo({});
                 amiibo_detected = false;
                 return;
             }
@@ -213,7 +220,7 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
             std::vector<u8> data(0x21C);
             const auto result = nfc_protocol->ScanAmiibo(data);
             if (result == DriverResult::Success) {
-                joycon_poller->updateAmiibo(data);
+                joycon_poller->UpdateAmiibo(data);
                 amiibo_detected = true;
             }
         }
@@ -251,6 +258,20 @@ DriverResult JoyconDriver::SetPollingMode() {
         generic_protocol->EnableImu(false);
     }
 
+    if (irs_protocol->IsEnabled()) {
+        irs_protocol->DisableIrs();
+    }
+
+    if (irs_enabled && supported_features.irs) {
+        auto result = irs_protocol->EnableIrs();
+        if (result == DriverResult::Success) {
+            disable_input_thread = false;
+            return result;
+        }
+        irs_protocol->DisableIrs();
+        LOG_ERROR(Input, "Error enabling IRS");
+    }
+
     if (nfc_protocol->IsEnabled()) {
         amiibo_detected = false;
         nfc_protocol->DisableNfc();
@@ -375,12 +396,24 @@ DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
     return generic_protocol->SetLedPattern(led_pattern);
 }
 
+DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) {
+    std::scoped_lock lock{mutex};
+    if (disable_input_thread) {
+        return DriverResult::HandleInUse;
+    }
+    disable_input_thread = true;
+    const auto result = irs_protocol->SetIrsConfig(mode_, format_);
+    disable_input_thread = false;
+    return result;
+}
+
 DriverResult JoyconDriver::SetPasiveMode() {
     std::scoped_lock lock{mutex};
     motion_enabled = false;
     hidbus_enabled = false;
     nfc_enabled = false;
     passive_enabled = true;
+    irs_enabled = false;
     return SetPollingMode();
 }
 
@@ -390,6 +423,22 @@ DriverResult JoyconDriver::SetActiveMode() {
     hidbus_enabled = false;
     nfc_enabled = false;
     passive_enabled = false;
+    irs_enabled = false;
+    return SetPollingMode();
+}
+
+DriverResult JoyconDriver::SetIrMode() {
+    std::scoped_lock lock{mutex};
+
+    if (!supported_features.irs) {
+        return DriverResult::NotSupported;
+    }
+
+    motion_enabled = false;
+    hidbus_enabled = false;
+    nfc_enabled = false;
+    passive_enabled = false;
+    irs_enabled = true;
     return SetPollingMode();
 }
 
@@ -404,6 +453,7 @@ DriverResult JoyconDriver::SetNfcMode() {
     hidbus_enabled = false;
     nfc_enabled = true;
     passive_enabled = false;
+    irs_enabled = false;
     return SetPollingMode();
 }
 
@@ -418,6 +468,7 @@ DriverResult JoyconDriver::SetRingConMode() {
     hidbus_enabled = true;
     nfc_enabled = false;
     passive_enabled = false;
+    irs_enabled = false;
 
     const auto result = SetPollingMode();
 
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index 5ff15c7841..61ecf4a6c2 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -13,6 +13,7 @@
 namespace InputCommon::Joycon {
 class CalibrationProtocol;
 class GenericProtocol;
+class IrsProtocol;
 class NfcProtocol;
 class JoyconPoller;
 class RingConProtocol;
@@ -41,8 +42,10 @@ public:
 
     DriverResult SetVibration(const VibrationValue& vibration);
     DriverResult SetLedConfig(u8 led_pattern);
+    DriverResult SetIrsConfig(IrsMode mode_, IrsResolution format_);
     DriverResult SetPasiveMode();
     DriverResult SetActiveMode();
+    DriverResult SetIrMode();
     DriverResult SetNfcMode();
     DriverResult SetRingConMode();
 
@@ -87,6 +90,7 @@ private:
     // Protocol Features
     std::unique_ptr<CalibrationProtocol> calibration_protocol;
     std::unique_ptr<GenericProtocol> generic_protocol;
+    std::unique_ptr<IrsProtocol> irs_protocol;
     std::unique_ptr<NfcProtocol> nfc_protocol;
     std::unique_ptr<JoyconPoller> joycon_poller;
     std::unique_ptr<RingConProtocol> ring_protocol;
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
index a4d08fdafa..a329db1070 100644
--- a/src/input_common/helpers/joycon_protocol/common_protocol.cpp
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -120,6 +120,19 @@ DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const
     return DriverResult::Success;
 }
 
+DriverResult JoyconCommonProtocol::SendMcuCommand(SubCommand sc, std::span<const u8> buffer) {
+    std::vector<u8> local_buffer(MaxResponseSize);
+
+    local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
+    local_buffer[1] = GetCounter();
+    local_buffer[10] = static_cast<u8>(sc);
+    for (std::size_t i = 0; i < buffer.size(); ++i) {
+        local_buffer[11 + i] = buffer[i];
+    }
+
+    return SendData(local_buffer);
+}
+
 DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) {
     std::vector<u8> local_buffer(MaxResponseSize);
 
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h
index a65e4aa76a..2a3feaf598 100644
--- a/src/input_common/helpers/joycon_protocol/common_protocol.h
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.h
@@ -74,6 +74,13 @@ public:
      */
     DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, std::vector<u8>& output);
 
+    /**
+     * Sends a mcu command to the device
+     * @param sc sub command to be send
+     * @param buffer data to be send
+     */
+    DriverResult SendMcuCommand(SubCommand sc, std::span<const u8> buffer);
+
     /**
      * Sends vibration data to the joycon
      * @param buffer data to be send
diff --git a/src/input_common/helpers/joycon_protocol/irs.cpp b/src/input_common/helpers/joycon_protocol/irs.cpp
new file mode 100644
index 0000000000..9dfa503c23
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/irs.cpp
@@ -0,0 +1,300 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <thread>
+#include "common/logging/log.h"
+#include "input_common/helpers/joycon_protocol/irs.h"
+
+namespace InputCommon::Joycon {
+
+IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle)
+    : JoyconCommonProtocol(std::move(handle)) {}
+
+DriverResult IrsProtocol::EnableIrs() {
+    LOG_INFO(Input, "Enable IRS");
+    DriverResult result{DriverResult::Success};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
+    }
+    if (result == DriverResult::Success) {
+        result = EnableMCU(true);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
+    }
+    if (result == DriverResult::Success) {
+        const MCUConfig config{
+            .command = MCUCommand::ConfigureMCU,
+            .sub_command = MCUSubCommand::SetMCUMode,
+            .mode = MCUMode::IR,
+            .crc = {},
+        };
+
+        result = ConfigureMCU(config);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::IR);
+    }
+    if (result == DriverResult::Success) {
+        result = ConfigureIrs();
+    }
+    if (result == DriverResult::Success) {
+        result = WriteRegistersStep1();
+    }
+    if (result == DriverResult::Success) {
+        result = WriteRegistersStep2();
+    }
+
+    is_enabled = true;
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult IrsProtocol::DisableIrs() {
+    LOG_DEBUG(Input, "Disable IRS");
+    DriverResult result{DriverResult::Success};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = EnableMCU(false);
+    }
+
+    is_enabled = false;
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult IrsProtocol::SetIrsConfig(IrsMode mode, IrsResolution format) {
+    irs_mode = mode;
+    switch (format) {
+    case IrsResolution::Size320x240:
+        resolution_code = IrsResolutionCode::Size320x240;
+        fragments = IrsFragments::Size320x240;
+        resolution = IrsResolution::Size320x240;
+        break;
+    case IrsResolution::Size160x120:
+        resolution_code = IrsResolutionCode::Size160x120;
+        fragments = IrsFragments::Size160x120;
+        resolution = IrsResolution::Size160x120;
+        break;
+    case IrsResolution::Size80x60:
+        resolution_code = IrsResolutionCode::Size80x60;
+        fragments = IrsFragments::Size80x60;
+        resolution = IrsResolution::Size80x60;
+        break;
+    case IrsResolution::Size20x15:
+        resolution_code = IrsResolutionCode::Size20x15;
+        fragments = IrsFragments::Size20x15;
+        resolution = IrsResolution::Size20x15;
+        break;
+    case IrsResolution::Size40x30:
+    default:
+        resolution_code = IrsResolutionCode::Size40x30;
+        fragments = IrsFragments::Size40x30;
+        resolution = IrsResolution::Size40x30;
+        break;
+    }
+
+    // Restart feature
+    if (is_enabled) {
+        DisableIrs();
+        return EnableIrs();
+    }
+
+    return DriverResult::Success;
+}
+
+DriverResult IrsProtocol::RequestImage(std::span<u8> buffer) {
+    const u8 next_packet_fragment =
+        static_cast<u8>((packet_fragment + 1) % (static_cast<u8>(fragments) + 1));
+
+    if (buffer[0] == 0x31 && buffer[49] == 0x03) {
+        u8 new_packet_fragment = buffer[52];
+        if (new_packet_fragment == next_packet_fragment) {
+            packet_fragment = next_packet_fragment;
+            memcpy(buf_image.data() + (300 * packet_fragment), buffer.data() + 59, 300);
+
+            return RequestFrame(packet_fragment);
+        }
+
+        if (new_packet_fragment == packet_fragment) {
+            return RequestFrame(packet_fragment);
+        }
+
+        return ResendFrame(next_packet_fragment);
+    }
+
+    return RequestFrame(packet_fragment);
+}
+
+DriverResult IrsProtocol::ConfigureIrs() {
+    LOG_DEBUG(Input, "Configure IRS");
+    constexpr std::size_t max_tries = 28;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    const IrsConfigure irs_configuration{
+        .command = MCUCommand::ConfigureIR,
+        .sub_command = MCUSubCommand::SetDeviceMode,
+        .irs_mode = IrsMode::ImageTransfer,
+        .number_of_fragments = fragments,
+        .mcu_major_version = 0x0500,
+        .mcu_minor_version = 0x1800,
+        .crc = {},
+    };
+    buf_image.resize((static_cast<u8>(fragments) + 1) * 300);
+
+    std::vector<u8> request_data(sizeof(IrsConfigure));
+    memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure));
+    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
+    do {
+        const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ >= max_tries) {
+            return DriverResult::WrongReply;
+        }
+    } while (output[15] != 0x0b);
+
+    return DriverResult::Success;
+}
+
+DriverResult IrsProtocol::WriteRegistersStep1() {
+    LOG_DEBUG(Input, "WriteRegistersStep1");
+    DriverResult result{DriverResult::Success};
+    constexpr std::size_t max_tries = 28;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    const IrsWriteRegisters irs_registers{
+        .command = MCUCommand::ConfigureIR,
+        .sub_command = MCUSubCommand::WriteDeviceRegisters,
+        .number_of_registers = 0x9,
+        .registers =
+            {
+                IrsRegister{IrRegistersAddress::Resolution, static_cast<u8>(resolution_code)},
+                {IrRegistersAddress::ExposureLSB, static_cast<u8>(exposure & 0xff)},
+                {IrRegistersAddress::ExposureMSB, static_cast<u8>(exposure >> 8)},
+                {IrRegistersAddress::ExposureTime, 0x00},
+                {IrRegistersAddress::Leds, static_cast<u8>(leds)},
+                {IrRegistersAddress::DigitalGainLSB, static_cast<u8>((digital_gain & 0x0f) << 4)},
+                {IrRegistersAddress::DigitalGainMSB, static_cast<u8>((digital_gain & 0xf0) >> 4)},
+                {IrRegistersAddress::LedFilter, static_cast<u8>(led_filter)},
+                {IrRegistersAddress::WhitePixelThreshold, 0xc8},
+            },
+        .crc = {},
+    };
+
+    std::vector<u8> request_data(sizeof(IrsWriteRegisters));
+    memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
+    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
+
+    std::array<u8, 38> mcu_request{0x02};
+    mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
+    mcu_request[37] = 0xFF;
+
+    if (result != DriverResult::Success) {
+        return result;
+    }
+
+    do {
+        result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
+
+        // First time we need to set the report mode
+        if (result == DriverResult::Success && tries == 0) {
+            result = SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request);
+        }
+        if (result == DriverResult::Success && tries == 0) {
+            GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output);
+        }
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ >= max_tries) {
+            return DriverResult::WrongReply;
+        }
+    } while (!(output[15] == 0x13 && output[17] == 0x07) && output[15] != 0x23);
+
+    return DriverResult::Success;
+}
+
+DriverResult IrsProtocol::WriteRegistersStep2() {
+    LOG_DEBUG(Input, "WriteRegistersStep2");
+    constexpr std::size_t max_tries = 28;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    const IrsWriteRegisters irs_registers{
+        .command = MCUCommand::ConfigureIR,
+        .sub_command = MCUSubCommand::WriteDeviceRegisters,
+        .number_of_registers = 0x8,
+        .registers =
+            {
+                IrsRegister{IrRegistersAddress::LedIntensitiyMSB,
+                            static_cast<u8>(led_intensity >> 8)},
+                {IrRegistersAddress::LedIntensitiyLSB, static_cast<u8>(led_intensity & 0xff)},
+                {IrRegistersAddress::ImageFlip, static_cast<u8>(image_flip)},
+                {IrRegistersAddress::DenoiseSmoothing, static_cast<u8>((denoise >> 16) & 0xff)},
+                {IrRegistersAddress::DenoiseEdge, static_cast<u8>((denoise >> 8) & 0xff)},
+                {IrRegistersAddress::DenoiseColor, static_cast<u8>(denoise & 0xff)},
+                {IrRegistersAddress::UpdateTime, 0x2d},
+                {IrRegistersAddress::FinalizeConfig, 0x01},
+            },
+        .crc = {},
+    };
+
+    std::vector<u8> request_data(sizeof(IrsWriteRegisters));
+    memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
+    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
+    do {
+        const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ >= max_tries) {
+            return DriverResult::WrongReply;
+        }
+    } while (output[15] != 0x13 && output[15] != 0x23);
+
+    return DriverResult::Success;
+}
+
+DriverResult IrsProtocol::RequestFrame(u8 frame) {
+    std::array<u8, 38> mcu_request{};
+    mcu_request[3] = frame;
+    mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
+    mcu_request[37] = 0xFF;
+    return SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request);
+}
+
+DriverResult IrsProtocol::ResendFrame(u8 frame) {
+    std::array<u8, 38> mcu_request{};
+    mcu_request[1] = 0x1;
+    mcu_request[2] = frame;
+    mcu_request[3] = 0x0;
+    mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
+    mcu_request[37] = 0xFF;
+    return SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request);
+}
+
+std::vector<u8> IrsProtocol::GetImage() const {
+    return buf_image;
+}
+
+IrsResolution IrsProtocol::GetIrsFormat() const {
+    return resolution;
+}
+
+bool IrsProtocol::IsEnabled() const {
+    return is_enabled;
+}
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/irs.h b/src/input_common/helpers/joycon_protocol/irs.h
new file mode 100644
index 0000000000..76dfa02ea5
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/irs.h
@@ -0,0 +1,63 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
+// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
+// https://github.com/CTCaer/jc_toolkit
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
+
+#pragma once
+
+#include <vector>
+
+#include "input_common/helpers/joycon_protocol/common_protocol.h"
+#include "input_common/helpers/joycon_protocol/joycon_types.h"
+
+namespace InputCommon::Joycon {
+
+class IrsProtocol final : private JoyconCommonProtocol {
+public:
+    explicit IrsProtocol(std::shared_ptr<JoyconHandle> handle);
+
+    DriverResult EnableIrs();
+
+    DriverResult DisableIrs();
+
+    DriverResult SetIrsConfig(IrsMode mode, IrsResolution format);
+
+    DriverResult RequestImage(std::span<u8> buffer);
+
+    std::vector<u8> GetImage() const;
+
+    IrsResolution GetIrsFormat() const;
+
+    bool IsEnabled() const;
+
+private:
+    DriverResult ConfigureIrs();
+
+    DriverResult WriteRegistersStep1();
+    DriverResult WriteRegistersStep2();
+
+    DriverResult RequestFrame(u8 frame);
+    DriverResult ResendFrame(u8 frame);
+
+    IrsMode irs_mode{IrsMode::ImageTransfer};
+    IrsResolution resolution{IrsResolution::Size40x30};
+    IrsResolutionCode resolution_code{IrsResolutionCode::Size40x30};
+    IrsFragments fragments{IrsFragments::Size40x30};
+    IrLeds leds{IrLeds::BrightAndDim};
+    IrExLedFilter led_filter{IrExLedFilter::Enabled};
+    IrImageFlip image_flip{IrImageFlip::Normal};
+    u8 digital_gain{0x01};
+    u16 exposure{0x2490};
+    u16 led_intensity{0x0f10};
+    u32 denoise{0x012344};
+
+    u8 packet_fragment{};
+    std::vector<u8> buf_image; // 8bpp greyscale image.
+
+    bool is_enabled{};
+};
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h
index 36c00a8d70..273c8d07d3 100644
--- a/src/input_common/helpers/joycon_protocol/joycon_types.h
+++ b/src/input_common/helpers/joycon_protocol/joycon_types.h
@@ -18,7 +18,7 @@
 
 namespace InputCommon::Joycon {
 constexpr u32 MaxErrorCount = 50;
-constexpr u32 MaxBufferSize = 60;
+constexpr u32 MaxBufferSize = 368;
 constexpr u32 MaxResponseSize = 49;
 constexpr u32 MaxSubCommandResponseSize = 64;
 constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
@@ -273,6 +273,80 @@ enum class NFCTagType : u8 {
     Ntag215 = 0x01,
 };
 
+enum class IrsMode : u8 {
+    None = 0x02,
+    Moment = 0x03,
+    Dpd = 0x04,
+    Clustering = 0x06,
+    ImageTransfer = 0x07,
+    Silhouette = 0x08,
+    TeraImage = 0x09,
+    SilhouetteTeraImage = 0x0A,
+};
+
+enum class IrsResolution {
+    Size320x240,
+    Size160x120,
+    Size80x60,
+    Size40x30,
+    Size20x15,
+    None,
+};
+
+enum class IrsResolutionCode : u8 {
+    Size320x240 = 0x00, // Full pixel array
+    Size160x120 = 0x50, // Sensor Binning [2 X 2]
+    Size80x60 = 0x64,   // Sensor Binning [4 x 2] and Skipping [1 x 2]
+    Size40x30 = 0x69,   // Sensor Binning [4 x 2] and Skipping [2 x 4]
+    Size20x15 = 0x6A,   // Sensor Binning [4 x 2] and Skipping [4 x 4]
+};
+
+// Size of image divided by 300
+enum class IrsFragments : u8 {
+    Size20x15 = 0x00,
+    Size40x30 = 0x03,
+    Size80x60 = 0x0f,
+    Size160x120 = 0x3f,
+    Size320x240 = 0xFF,
+};
+
+enum class IrLeds : u8 {
+    BrightAndDim = 0x00,
+    Bright = 0x20,
+    Dim = 0x10,
+    None = 0x30,
+};
+
+enum class IrExLedFilter : u8 {
+    Disabled = 0x00,
+    Enabled = 0x03,
+};
+
+enum class IrImageFlip : u8 {
+    Normal = 0x00,
+    Inverted = 0x02,
+};
+
+enum class IrRegistersAddress : u16 {
+    UpdateTime = 0x0400,
+    FinalizeConfig = 0x0700,
+    LedFilter = 0x0e00,
+    Leds = 0x1000,
+    LedIntensitiyMSB = 0x1100,
+    LedIntensitiyLSB = 0x1200,
+    ImageFlip = 0x2d00,
+    Resolution = 0x2e00,
+    DigitalGainLSB = 0x2e01,
+    DigitalGainMSB = 0x2f01,
+    ExposureLSB = 0x3001,
+    ExposureMSB = 0x3101,
+    ExposureTime = 0x3201,
+    WhitePixelThreshold = 0x4301,
+    DenoiseSmoothing = 0x6701,
+    DenoiseEdge = 0x6801,
+    DenoiseColor = 0x6901,
+};
+
 enum class DriverResult {
     Success,
     WrongReply,
@@ -456,6 +530,36 @@ struct NFCRequestState {
 };
 static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size");
 
+struct IrsConfigure {
+    MCUCommand command;
+    MCUSubCommand sub_command;
+    IrsMode irs_mode;
+    IrsFragments number_of_fragments;
+    u16 mcu_major_version;
+    u16 mcu_minor_version;
+    INSERT_PADDING_BYTES(0x1D);
+    u8 crc;
+};
+static_assert(sizeof(IrsConfigure) == 0x26, "IrsConfigure is an invalid size");
+
+#pragma pack(push, 1)
+struct IrsRegister {
+    IrRegistersAddress address;
+    u8 value;
+};
+static_assert(sizeof(IrsRegister) == 0x3, "IrsRegister is an invalid size");
+
+struct IrsWriteRegisters {
+    MCUCommand command;
+    MCUSubCommand sub_command;
+    u8 number_of_registers;
+    std::array<IrsRegister, 9> registers;
+    INSERT_PADDING_BYTES(0x7);
+    u8 crc;
+};
+static_assert(sizeof(IrsWriteRegisters) == 0x26, "IrsWriteRegisters is an invalid size");
+#pragma pack(pop)
+
 struct FirmwareVersion {
     u8 major;
     u8 minor;
@@ -490,6 +594,7 @@ struct JoyconCallbacks {
     std::function<void(int, const MotionData&)> on_motion_data;
     std::function<void(f32)> on_ring_data;
     std::function<void(const std::vector<u8>&)> on_amiibo_data;
+    std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data;
 };
 
 } // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
index fd05d98f38..940b20b7f0 100644
--- a/src/input_common/helpers/joycon_protocol/poller.cpp
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -74,10 +74,14 @@ void JoyconPoller::UpdateColor(const Color& color) {
     callbacks.on_color_data(color);
 }
 
-void JoyconPoller::updateAmiibo(const std::vector<u8>& amiibo_data) {
+void JoyconPoller::UpdateAmiibo(const std::vector<u8>& amiibo_data) {
     callbacks.on_amiibo_data(amiibo_data);
 }
 
+void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) {
+    callbacks.on_camera_data(camera_data, format);
+}
+
 void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) {
     float normalized_value = static_cast<float>(value - ring_status.default_value);
     if (normalized_value > 0) {
diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h
index c40fc7bca8..354d41dad3 100644
--- a/src/input_common/helpers/joycon_protocol/poller.h
+++ b/src/input_common/helpers/joycon_protocol/poller.h
@@ -36,7 +36,8 @@ public:
 
     void UpdateColor(const Color& color);
     void UpdateRing(s16 value, const RingStatus& ring_status);
-    void updateAmiibo(const std::vector<u8>& amiibo_data);
+    void UpdateAmiibo(const std::vector<u8>& amiibo_data);
+    void UpdateCamera(const std::vector<u8>& amiibo_data, IrsResolution format);
 
 private:
     void UpdateActiveLeftPadInput(const InputReportActive& input,

From d05ea2f3ebdf62e328d2edbfc5b9bc01e3453569 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Mon, 2 Jan 2023 22:11:03 -0600
Subject: [PATCH 19/24] input_common: Fix issue where ring and irs are enabled
 at the same time

---
 src/core/hle/service/hid/irs.cpp              |  4 +--
 src/input_common/helpers/joycon_driver.cpp    | 30 ++++++++++++-------
 src/input_common/helpers/joycon_driver.h      |  1 +
 .../joycon_protocol/common_protocol.cpp       |  4 +--
 4 files changed, 24 insertions(+), 15 deletions(-)

diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index 3c1fa2274f..1a6fa2a44c 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -74,8 +74,6 @@ void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) {
     LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}",
                 applet_resource_user_id);
 
-    npad_device->SetPollingMode(Common::Input::PollingMode::Active);
-
     IPC::ResponseBuilder rb{ctx, 2};
     rb.Push(ResultSuccess);
 }
@@ -514,7 +512,7 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {
     auto result = IsIrCameraHandleValid(parameters.camera_handle);
     if (result.IsSuccess()) {
         // TODO: Stop image processor async
-        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
+        npad_device->SetPollingMode(Common::Input::PollingMode::Active);
         result = ResultSuccess;
     }
 
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index 040832a4ba..e8aef028af 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -262,6 +262,16 @@ DriverResult JoyconDriver::SetPollingMode() {
         irs_protocol->DisableIrs();
     }
 
+    if (nfc_protocol->IsEnabled()) {
+        amiibo_detected = false;
+        nfc_protocol->DisableNfc();
+    }
+
+    if (ring_protocol->IsEnabled()) {
+        ring_connected = false;
+        ring_protocol->DisableRingCon();
+    }
+
     if (irs_enabled && supported_features.irs) {
         auto result = irs_protocol->EnableIrs();
         if (result == DriverResult::Success) {
@@ -272,11 +282,6 @@ DriverResult JoyconDriver::SetPollingMode() {
         LOG_ERROR(Input, "Error enabling IRS");
     }
 
-    if (nfc_protocol->IsEnabled()) {
-        amiibo_detected = false;
-        nfc_protocol->DisableNfc();
-    }
-
     if (nfc_enabled && supported_features.nfc) {
         auto result = nfc_protocol->EnableNfc();
         if (result == DriverResult::Success) {
@@ -290,11 +295,6 @@ DriverResult JoyconDriver::SetPollingMode() {
         LOG_ERROR(Input, "Error enabling NFC");
     }
 
-    if (ring_protocol->IsEnabled()) {
-        ring_connected = false;
-        ring_protocol->DisableRingCon();
-    }
-
     if (hidbus_enabled && supported_features.hidbus) {
         auto result = ring_protocol->EnableRingCon();
         if (result == DriverResult::Success) {
@@ -418,6 +418,12 @@ DriverResult JoyconDriver::SetPasiveMode() {
 }
 
 DriverResult JoyconDriver::SetActiveMode() {
+    if (is_ring_disabled_by_irs) {
+        is_ring_disabled_by_irs = false;
+        SetActiveMode();
+        return SetRingConMode();
+    }
+
     std::scoped_lock lock{mutex};
     motion_enabled = true;
     hidbus_enabled = false;
@@ -434,6 +440,10 @@ DriverResult JoyconDriver::SetIrMode() {
         return DriverResult::NotSupported;
     }
 
+    if (ring_connected) {
+        is_ring_disabled_by_irs = true;
+    }
+
     motion_enabled = false;
     hidbus_enabled = false;
     nfc_enabled = false;
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index 61ecf4a6c2..e8e65e1331 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -107,6 +107,7 @@ private:
     bool starlink_connected{};
     bool ring_connected{};
     bool amiibo_detected{};
+    bool is_ring_disabled_by_irs{};
 
     // Harware configuration
     u8 leds{};
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
index a329db1070..153a3908c6 100644
--- a/src/input_common/helpers/joycon_protocol/common_protocol.cpp
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -74,8 +74,8 @@ DriverResult JoyconCommonProtocol::SendData(std::span<const u8> buffer) {
 }
 
 DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, std::vector<u8>& output) {
-    constexpr int timeout_mili = 100;
-    constexpr int MaxTries = 10;
+    constexpr int timeout_mili = 66;
+    constexpr int MaxTries = 15;
     int tries = 0;
     output.resize(MaxSubCommandResponseSize);
 

From b40aefb39ea8b4259acdbe0616790c2234d9b9ef Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sun, 8 Jan 2023 21:37:13 -0600
Subject: [PATCH 20/24] input_common: Drop Pro controller support from custom
 driver

---
 src/input_common/drivers/joycon.cpp        | 36 ----------------------
 src/input_common/drivers/joycon.h          |  1 -
 src/input_common/drivers/sdl_driver.cpp    |  6 ++--
 src/input_common/helpers/joycon_driver.cpp |  4 +--
 4 files changed, 4 insertions(+), 43 deletions(-)

diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index 6c03e09537..fff886ca8a 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -44,12 +44,6 @@ void Joycons::Reset() {
         }
         device->Stop();
     }
-    for (const auto& device : pro_joycons) {
-        if (!device) {
-            continue;
-        }
-        device->Stop();
-    }
     SDL_hid_exit();
 }
 
@@ -65,11 +59,6 @@ void Joycons::Setup() {
         PreSetController(GetIdentifier(port, Joycon::ControllerType::Right));
         device = std::make_shared<Joycon::JoyconDriver>(port++);
     }
-    port = 0;
-    for (auto& device : pro_joycons) {
-        PreSetController(GetIdentifier(port, Joycon::ControllerType::Pro));
-        device = std::make_shared<Joycon::JoyconDriver>(port++);
-    }
 
     if (!scan_thread_running) {
         scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
@@ -141,14 +130,6 @@ bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const {
             }
         }
         break;
-    case Joycon::ControllerType::Pro:
-    case Joycon::ControllerType::Grip:
-        for (const auto& device : pro_joycons) {
-            if (is_handle_identical(device)) {
-                return false;
-            }
-        }
-        break;
     default:
         return false;
     }
@@ -219,13 +200,6 @@ std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle(
             }
         }
     }
-    if (type == Joycon::ControllerType::Pro || type == Joycon::ControllerType::Grip) {
-        for (const auto& device : pro_joycons) {
-            if (!device->IsConnected()) {
-                return device;
-            }
-        }
-    }
     return nullptr;
 }
 
@@ -431,13 +405,6 @@ std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifie
             }
         }
     }
-    if (type == Joycon::ControllerType::Pro || type == Joycon::ControllerType::Grip) {
-        for (const auto& device : pro_joycons) {
-            if (is_handle_active(device)) {
-                return device;
-            }
-        }
-    }
     return nullptr;
 }
 
@@ -475,9 +442,6 @@ std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
     for (const auto& controller : right_joycons) {
         add_entry(controller);
     }
-    for (const auto& controller : pro_joycons) {
-        add_entry(controller);
-    }
 
     // List dual joycon pairs
     for (std::size_t i = 0; i < MaxSupportedControllers; i++) {
diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h
index f180b74783..f5cc787db0 100644
--- a/src/input_common/drivers/joycon.h
+++ b/src/input_common/drivers/joycon.h
@@ -104,7 +104,6 @@ private:
     // Joycon types are split by type to ease supporting dualjoycon configurations
     std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{};
     std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> right_joycons{};
-    std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> pro_joycons{};
 };
 
 } // namespace InputCommon
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index 51a9d8962e..e915ec0908 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -319,7 +319,8 @@ void SDLDriver::InitJoystick(int joystick_index) {
     const auto guid = GetGUID(sdl_joystick);
 
     if (Settings::values.enable_joycon_driver) {
-        if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e) {
+        if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e &&
+            (guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) {
             LOG_ERROR(Input, "Device black listed {}", joystick_index);
             SDL_JoystickClose(sdl_joystick);
             return;
@@ -451,11 +452,10 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
     // Disable hidapi drivers for switch controllers when the custom joycon driver is enabled
     if (Settings::values.enable_joycon_driver) {
         SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "0");
-        SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "0");
     } else {
         SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
-        SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");
     }
+    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1");
 
     // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
     // driver on Linux.
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index e8aef028af..5525723435 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -540,11 +540,9 @@ void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) {
 
 DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
                                          ControllerType& controller_type) {
-    static constexpr std::array<std::pair<u32, ControllerType>, 4> supported_devices{
+    static constexpr std::array<std::pair<u32, ControllerType>, 2> supported_devices{
         std::pair<u32, ControllerType>{0x2006, ControllerType::Left},
         {0x2007, ControllerType::Right},
-        {0x2009, ControllerType::Pro},
-        {0x200E, ControllerType::Grip},
     };
     constexpr u16 nintendo_vendor_id = 0x057e;
 

From 4a307a7b3aa3afea7d62674f6cf40b76f3ffb5e3 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Fri, 13 Jan 2023 13:39:33 -0600
Subject: [PATCH 21/24] core: hid: Only set the polling mode to the correct
 side

---
 src/core/hid/emulated_controller.cpp          | 30 +++++++++++++------
 src/core/hid/emulated_controller.h            |  4 ++-
 src/core/hle/service/hid/controllers/npad.cpp | 14 ++++++++-
 src/core/hle/service/hid/hidbus/ringcon.cpp   |  6 ++--
 src/core/hle/service/hid/irs.cpp              | 27 +++++++++++------
 src/core/hle/service/nfc/nfc_device.cpp       |  6 ++--
 src/core/hle/service/nfp/nfp_device.cpp       |  6 ++--
 src/yuzu/configuration/configure_ringcon.cpp  |  6 ++--
 8 files changed, 71 insertions(+), 28 deletions(-)

diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index faf9e7c4e5..f83abad055 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -1208,19 +1208,31 @@ bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
 }
 
 Common::Input::DriverResult EmulatedController::SetPollingMode(
-    Common::Input::PollingMode polling_mode) {
-    LOG_INFO(Service_HID, "Set polling mode {}", polling_mode);
-    auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
+    EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) {
+    LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index);
+
+    auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)];
+    auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
     auto& nfc_output_device = output_devices[3];
 
-    const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
-    const auto mapped_nfc_result = output_device->SetPollingMode(polling_mode);
-
-    if (virtual_nfc_result == Common::Input::DriverResult::Success) {
-        return virtual_nfc_result;
+    if (device_index == EmulatedDeviceIndex::LeftIndex) {
+        return left_output_device->SetPollingMode(polling_mode);
     }
 
-    return mapped_nfc_result;
+    if (device_index == EmulatedDeviceIndex::RightIndex) {
+        const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
+        const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode);
+
+        if (virtual_nfc_result == Common::Input::DriverResult::Success) {
+            return virtual_nfc_result;
+        }
+        return mapped_nfc_result;
+    }
+
+    left_output_device->SetPollingMode(polling_mode);
+    right_output_device->SetPollingMode(polling_mode);
+    nfc_output_device->SetPollingMode(polling_mode);
+    return Common::Input::DriverResult::Success;
 }
 
 bool EmulatedController::SetCameraFormat(
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index edebfc15c2..3ac77b2b5e 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -363,10 +363,12 @@ public:
 
     /**
      * Sets the desired data to be polled from a controller
+     * @param device_index index of the controller to set the polling mode
      * @param polling_mode type of input desired buttons, gyro, nfc, ir, etc.
      * @return driver result from this command
      */
-    Common::Input::DriverResult SetPollingMode(Common::Input::PollingMode polling_mode);
+    Common::Input::DriverResult SetPollingMode(EmulatedDeviceIndex device_index,
+                                               Common::Input::PollingMode polling_mode);
 
     /**
      * Sets the desired camera format to be polled from a controller
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index fe5bf94d27..5713f12888 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -337,7 +337,19 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
     controller.is_connected = true;
     controller.device->Connect();
     controller.device->SetLedPattern();
-    controller.device->SetPollingMode(Common::Input::PollingMode::Active);
+    if (controller_type == Core::HID::NpadStyleIndex::JoyconDual) {
+        if (controller.is_dual_left_connected) {
+            controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::LeftIndex,
+                                              Common::Input::PollingMode::Active);
+        }
+        if (controller.is_dual_right_connected) {
+            controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                              Common::Input::PollingMode::Active);
+        }
+    } else {
+        controller.device->SetPollingMode(Core::HID::EmulatedDeviceIndex::AllDevices,
+                                          Common::Input::PollingMode::Active);
+    }
     SignalStyleSetChangedEvent(npad_id);
     WriteEmptyEntry(controller.shared_memory);
 }
diff --git a/src/core/hle/service/hid/hidbus/ringcon.cpp b/src/core/hle/service/hid/hidbus/ringcon.cpp
index af776d5062..78ed470144 100644
--- a/src/core/hle/service/hid/hidbus/ringcon.cpp
+++ b/src/core/hle/service/hid/hidbus/ringcon.cpp
@@ -18,12 +18,14 @@ RingController::RingController(Core::HID::HIDCore& hid_core_,
 RingController::~RingController() = default;
 
 void RingController::OnInit() {
-    input->SetPollingMode(Common::Input::PollingMode::Ring);
+    input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                          Common::Input::PollingMode::Ring);
     return;
 }
 
 void RingController::OnRelease() {
-    input->SetPollingMode(Common::Input::PollingMode::Active);
+    input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                          Common::Input::PollingMode::Active);
     return;
 };
 
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index 1a6fa2a44c..52f402c568 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -108,7 +108,8 @@ void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) {
     auto result = IsIrCameraHandleValid(parameters.camera_handle);
     if (result.IsSuccess()) {
         // TODO: Stop Image processor
-        npad_device->SetPollingMode(Common::Input::PollingMode::Active);
+        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                    Common::Input::PollingMode::Active);
         result = ResultSuccess;
     }
 
@@ -140,7 +141,8 @@ void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) {
         MakeProcessor<MomentProcessor>(parameters.camera_handle, device);
         auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
-        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
+        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                    Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -172,7 +174,8 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
         auto& image_transfer_processor =
             GetProcessor<ClusteringProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
-        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
+        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                    Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -222,7 +225,8 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {
             GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
         image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
-        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
+        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                    Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -298,7 +302,8 @@ void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) {
         auto& image_transfer_processor =
             GetProcessor<TeraPluginProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
-        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
+        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                    Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -348,7 +353,8 @@ void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) {
         MakeProcessor<PointingProcessor>(camera_handle, device);
         auto& image_transfer_processor = GetProcessor<PointingProcessor>(camera_handle);
         image_transfer_processor.SetConfig(processor_config);
-        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
+        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                    Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -459,7 +465,8 @@ void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) {
             GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
         image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
-        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
+        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                    Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -486,7 +493,8 @@ void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) {
         MakeProcessor<IrLedProcessor>(camera_handle, device);
         auto& image_transfer_processor = GetProcessor<IrLedProcessor>(camera_handle);
         image_transfer_processor.SetConfig(processor_config);
-        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
+        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                    Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -512,7 +520,8 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {
     auto result = IsIrCameraHandleValid(parameters.camera_handle);
     if (result.IsSuccess()) {
         // TODO: Stop image processor async
-        npad_device->SetPollingMode(Common::Input::PollingMode::Active);
+        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                    Common::Input::PollingMode::Active);
         result = ResultSuccess;
     }
 
diff --git a/src/core/hle/service/nfc/nfc_device.cpp b/src/core/hle/service/nfc/nfc_device.cpp
index c9815edbc6..9a3234e8c5 100644
--- a/src/core/hle/service/nfc/nfc_device.cpp
+++ b/src/core/hle/service/nfc/nfc_device.cpp
@@ -130,7 +130,8 @@ Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) {
         return WrongDeviceState;
     }
 
-    if (npad_device->SetPollingMode(Common::Input::PollingMode::NFC) !=
+    if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                    Common::Input::PollingMode::NFC) !=
         Common::Input::DriverResult::Success) {
         LOG_ERROR(Service_NFC, "Nfc not supported");
         return NfcDisabled;
@@ -142,7 +143,8 @@ Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) {
 }
 
 Result NfcDevice::StopDetection() {
-    npad_device->SetPollingMode(Common::Input::PollingMode::Active);
+    npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                Common::Input::PollingMode::Active);
 
     if (device_state == NFP::DeviceState::Initialized) {
         return ResultSuccess;
diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp
index 7b80139616..e67a76f55a 100644
--- a/src/core/hle/service/nfp/nfp_device.cpp
+++ b/src/core/hle/service/nfp/nfp_device.cpp
@@ -152,7 +152,8 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {
         return WrongDeviceState;
     }
 
-    if (npad_device->SetPollingMode(Common::Input::PollingMode::NFC) !=
+    if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                    Common::Input::PollingMode::NFC) !=
         Common::Input::DriverResult::Success) {
         LOG_ERROR(Service_NFP, "Nfc not supported");
         return NfcDisabled;
@@ -164,7 +165,8 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {
 }
 
 Result NfpDevice::StopDetection() {
-    npad_device->SetPollingMode(Common::Input::PollingMode::Active);
+    npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                Common::Input::PollingMode::Active);
 
     if (device_state == DeviceState::Initialized) {
         return ResultSuccess;
diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp
index 697c36fb4a..1275f10c89 100644
--- a/src/yuzu/configuration/configure_ringcon.cpp
+++ b/src/yuzu/configuration/configure_ringcon.cpp
@@ -214,7 +214,8 @@ ConfigureRingController::ConfigureRingController(QWidget* parent,
 }
 
 ConfigureRingController::~ConfigureRingController() {
-    emulated_controller->SetPollingMode(Common::Input::PollingMode::Active);
+    emulated_controller->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+                                        Common::Input::PollingMode::Active);
     emulated_controller->DisableConfiguration();
 
     if (is_controller_set) {
@@ -290,7 +291,8 @@ void ConfigureRingController::EnableRingController() {
     // SetPollingMode is blocking. Allow to update the button status before calling the command
     repaint();
 
-    const auto result = emulated_controller->SetPollingMode(Common::Input::PollingMode::Ring);
+    const auto result = emulated_controller->SetPollingMode(
+        Core::HID::EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::Ring);
     switch (result) {
     case Common::Input::DriverResult::Success:
         is_ring_enabled = true;

From 340f15d1fa79594dbe12a6e19140ba012751b533 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Fri, 13 Jan 2023 23:29:05 -0600
Subject: [PATCH 22/24] input_common: Address byte review

---
 src/input_common/drivers/joycon.cpp           |  50 ++---
 src/input_common/drivers/joycon.h             |   1 -
 src/input_common/drivers/sdl_driver.cpp       |   2 +-
 src/input_common/helpers/joycon_driver.cpp    |   8 +-
 src/input_common/helpers/joycon_driver.h      |   3 +-
 .../helpers/joycon_protocol/calibration.cpp   |   9 +-
 .../joycon_protocol/common_protocol.cpp       |  24 +--
 .../helpers/joycon_protocol/common_protocol.h |  22 +-
 .../joycon_protocol/generic_functions.cpp     |  54 ++---
 .../helpers/joycon_protocol/irs.cpp           |  18 +-
 .../helpers/joycon_protocol/joycon_types.h    |  12 ++
 .../helpers/joycon_protocol/nfc.cpp           | 191 ++++++++----------
 .../helpers/joycon_protocol/nfc.h             |   4 +-
 .../helpers/joycon_protocol/poller.cpp        |  18 +-
 .../helpers/joycon_protocol/ringcon.cpp       |  36 ++--
 .../helpers/joycon_protocol/rumble.cpp        |  19 +-
 16 files changed, 224 insertions(+), 247 deletions(-)

diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index fff886ca8a..1582def13c 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -60,15 +60,12 @@ void Joycons::Setup() {
         device = std::make_shared<Joycon::JoyconDriver>(port++);
     }
 
-    if (!scan_thread_running) {
-        scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
-    }
+    scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
 }
 
 void Joycons::ScanThread(std::stop_token stop_token) {
     constexpr u16 nintendo_vendor_id = 0x057e;
-    Common::SetCurrentThreadName("yuzu:input:JoyconScanThread");
-    scan_thread_running = true;
+    Common::SetCurrentThreadName("JoyconScanThread");
     while (!stop_token.stop_requested()) {
         SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0);
         SDL_hid_device_info* cur_dev = devs;
@@ -82,9 +79,9 @@ void Joycons::ScanThread(std::stop_token stop_token) {
             cur_dev = cur_dev->next;
         }
 
+        SDL_hid_free_enumeration(devs);
         std::this_thread::sleep_for(std::chrono::seconds(5));
     }
-    scan_thread_running = false;
 }
 
 bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const {
@@ -185,19 +182,19 @@ void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
 
 std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle(
     Joycon::ControllerType type) const {
-
     if (type == Joycon::ControllerType::Left) {
-        for (const auto& device : left_joycons) {
-            if (!device->IsConnected()) {
-                return device;
-            }
+        const auto unconnected_device =
+            std::ranges::find_if(left_joycons, [](auto& device) { return !device->IsConnected(); });
+        if (unconnected_device != left_joycons.end()) {
+            return *unconnected_device;
         }
     }
     if (type == Joycon::ControllerType::Right) {
-        for (const auto& device : right_joycons) {
-            if (!device->IsConnected()) {
-                return device;
-            }
+        const auto unconnected_device = std::ranges::find_if(
+            right_joycons, [](auto& device) { return !device->IsConnected(); });
+
+        if (unconnected_device != right_joycons.end()) {
+            return *unconnected_device;
         }
     }
     return nullptr;
@@ -391,20 +388,25 @@ std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifie
         return false;
     };
     const auto type = static_cast<Joycon::ControllerType>(identifier.pad);
+
     if (type == Joycon::ControllerType::Left) {
-        for (const auto& device : left_joycons) {
-            if (is_handle_active(device)) {
-                return device;
-            }
+        const auto matching_device = std::ranges::find_if(
+            left_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });
+
+        if (matching_device != left_joycons.end()) {
+            return *matching_device;
         }
     }
+
     if (type == Joycon::ControllerType::Right) {
-        for (const auto& device : right_joycons) {
-            if (is_handle_active(device)) {
-                return device;
-            }
+        const auto matching_device = std::ranges::find_if(
+            right_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });
+
+        if (matching_device != right_joycons.end()) {
+            return *matching_device;
         }
     }
+
     return nullptr;
 }
 
@@ -676,7 +678,7 @@ std::string Joycons::JoyconName(Joycon::ControllerType type) const {
     case Joycon::ControllerType::Dual:
         return "Dual Joycon";
     default:
-        return "Unknow Joycon";
+        return "Unknown Joycon";
     }
 }
 } // namespace InputCommon
diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h
index f5cc787db0..6d2e2ec786 100644
--- a/src/input_common/drivers/joycon.h
+++ b/src/input_common/drivers/joycon.h
@@ -99,7 +99,6 @@ private:
     std::string JoyconName(Joycon::ControllerType type) const;
 
     std::jthread scan_thread;
-    bool scan_thread_running{};
 
     // Joycon types are split by type to ease supporting dualjoycon configurations
     std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{};
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index e915ec0908..a0103edde2 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -321,7 +321,7 @@ void SDLDriver::InitJoystick(int joystick_index) {
     if (Settings::values.enable_joycon_driver) {
         if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e &&
             (guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) {
-            LOG_ERROR(Input, "Device black listed {}", joystick_index);
+            LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index);
             SDL_JoystickClose(sdl_joystick);
             return;
         }
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index 5525723435..4159e5717a 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -123,7 +123,7 @@ DriverResult JoyconDriver::InitializeDevice() {
 }
 
 void JoyconDriver::InputThread(std::stop_token stop_token) {
-    LOG_INFO(Input, "JC Adapter input thread started");
+    LOG_INFO(Input, "Joycon Adapter input thread started");
     Common::SetCurrentThreadName("JoyconInput");
     input_thread_running = true;
 
@@ -157,7 +157,7 @@ void JoyconDriver::InputThread(std::stop_token stop_token) {
 
     is_connected = false;
     input_thread_running = false;
-    LOG_INFO(Input, "JC Adapter input thread stopped");
+    LOG_INFO(Input, "Joycon Adapter input thread stopped");
 }
 
 void JoyconDriver::OnNewData(std::span<u8> buffer) {
@@ -349,7 +349,7 @@ JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
 }
 
 bool JoyconDriver::IsInputThreadValid() const {
-    if (!is_connected) {
+    if (!is_connected.load()) {
         return false;
     }
     if (hidapi_handle->handle == nullptr) {
@@ -491,7 +491,7 @@ DriverResult JoyconDriver::SetRingConMode() {
 
 bool JoyconDriver::IsConnected() const {
     std::scoped_lock lock{mutex};
-    return is_connected;
+    return is_connected.load();
 }
 
 bool JoyconDriver::IsVibrationEnabled() const {
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index e8e65e1331..c1e189fa52 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -3,6 +3,7 @@
 
 #pragma once
 
+#include <atomic>
 #include <functional>
 #include <mutex>
 #include <span>
@@ -97,7 +98,7 @@ private:
     std::unique_ptr<RumbleProtocol> rumble_protocol;
 
     // Connection status
-    bool is_connected{};
+    std::atomic<bool> is_connected{};
     u64 delta_time;
     std::size_t error_counter{};
     std::shared_ptr<JoyconHandle> hidapi_handle;
diff --git a/src/input_common/helpers/joycon_protocol/calibration.cpp b/src/input_common/helpers/joycon_protocol/calibration.cpp
index cd30ab8698..f6e7e97d59 100644
--- a/src/input_common/helpers/joycon_protocol/calibration.cpp
+++ b/src/input_common/helpers/joycon_protocol/calibration.cpp
@@ -12,10 +12,10 @@ CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle)
     : JoyconCommonProtocol(std::move(handle)) {}
 
 DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) {
+    ScopedSetBlocking sb(this);
     std::vector<u8> buffer;
     DriverResult result{DriverResult::Success};
     calibration = {};
-    SetBlocking();
 
     result = ReadSPI(CalAddr::USER_LEFT_MAGIC, sizeof(u16), buffer);
 
@@ -44,15 +44,14 @@ DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration
     // Set a valid default calibration if data is missing
     ValidateCalibration(calibration);
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibration& calibration) {
+    ScopedSetBlocking sb(this);
     std::vector<u8> buffer;
     DriverResult result{DriverResult::Success};
     calibration = {};
-    SetBlocking();
 
     result = ReadSPI(CalAddr::USER_RIGHT_MAGIC, sizeof(u16), buffer);
 
@@ -81,15 +80,14 @@ DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibratio
     // Set a valid default calibration if data is missing
     ValidateCalibration(calibration);
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) {
+    ScopedSetBlocking sb(this);
     std::vector<u8> buffer;
     DriverResult result{DriverResult::Success};
     calibration = {};
-    SetBlocking();
 
     result = ReadSPI(CalAddr::USER_IMU_MAGIC, sizeof(u16), buffer);
 
@@ -124,7 +122,6 @@ DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibrati
 
     ValidateCalibration(calibration);
 
-    SetNonBlocking();
     return result;
 }
 
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
index 153a3908c6..417d0dcc50 100644
--- a/src/input_common/helpers/joycon_protocol/common_protocol.cpp
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -58,9 +58,8 @@ DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device
 }
 
 DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) {
-    const std::vector<u8> buffer{static_cast<u8>(report_mode)};
-    std::vector<u8> output;
-    return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer, output);
+    const std::array<u8, 1> buffer{static_cast<u8>(report_mode)};
+    return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer);
 }
 
 DriverResult JoyconCommonProtocol::SendData(std::span<const u8> buffer) {
@@ -120,7 +119,12 @@ DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const
     return DriverResult::Success;
 }
 
-DriverResult JoyconCommonProtocol::SendMcuCommand(SubCommand sc, std::span<const u8> buffer) {
+DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) {
+    std::vector<u8> output;
+    return SendSubCommand(sc, buffer, output);
+}
+
+DriverResult JoyconCommonProtocol::SendMCUCommand(SubCommand sc, std::span<const u8> buffer) {
     std::vector<u8> local_buffer(MaxResponseSize);
 
     local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
@@ -147,7 +151,7 @@ DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffe
 DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output) {
     constexpr std::size_t MaxTries = 10;
     std::size_t tries = 0;
-    std::vector<u8> buffer = {0x00, 0x00, 0x00, 0x00, size};
+    std::array<u8, 5> buffer = {0x00, 0x00, 0x00, 0x00, size};
     std::vector<u8> local_buffer(size + 20);
 
     buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF);
@@ -169,10 +173,8 @@ DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8
 }
 
 DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
-    std::vector<u8> output;
-
-    const std::vector<u8> mcu_state{static_cast<u8>(enable ? 1 : 0)};
-    const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state, output);
+    const std::array<u8, 1> mcu_state{static_cast<u8>(enable ? 1 : 0)};
+    const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state);
 
     if (result != DriverResult::Success) {
         LOG_ERROR(Input, "SendMCUData failed with error {}", result);
@@ -183,13 +185,11 @@ DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
 
 DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) {
     LOG_DEBUG(Input, "ConfigureMCU");
-    std::vector<u8> output;
-
     std::array<u8, sizeof(MCUConfig)> config_buffer;
     memcpy(config_buffer.data(), &config, sizeof(MCUConfig));
     config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36);
 
-    const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer, output);
+    const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer);
 
     if (result != DriverResult::Success) {
         LOG_ERROR(Input, "Set MCU config failed with error {}", result);
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h
index 2a3feaf598..903bcf4025 100644
--- a/src/input_common/helpers/joycon_protocol/common_protocol.h
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.h
@@ -74,12 +74,19 @@ public:
      */
     DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, std::vector<u8>& output);
 
+    /**
+     * Sends a sub command to the device and waits for it's reply and ignores the output
+     * @param sc sub command to be send
+     * @param buffer data to be send
+     */
+    DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer);
+
     /**
      * Sends a mcu command to the device
      * @param sc sub command to be send
      * @param buffer data to be send
      */
-    DriverResult SendMcuCommand(SubCommand sc, std::span<const u8> buffer);
+    DriverResult SendMCUCommand(SubCommand sc, std::span<const u8> buffer);
 
     /**
      * Sends vibration data to the joycon
@@ -150,4 +157,17 @@ private:
     std::shared_ptr<JoyconHandle> hidapi_handle;
 };
 
+class ScopedSetBlocking {
+public:
+    explicit ScopedSetBlocking(JoyconCommonProtocol* self) : m_self{self} {
+        m_self->SetBlocking();
+    }
+
+    ~ScopedSetBlocking() {
+        m_self->SetNonBlocking();
+    }
+
+private:
+    JoyconCommonProtocol* m_self{};
+};
 } // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.cpp b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
index cbd9ff4f8b..52bb8b61aa 100644
--- a/src/input_common/helpers/joycon_protocol/generic_functions.cpp
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
@@ -10,22 +10,18 @@ GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle)
     : JoyconCommonProtocol(std::move(handle)) {}
 
 DriverResult GenericProtocol::EnablePassiveMode() {
-    SetBlocking();
-    const auto result = SetReportMode(ReportMode::SIMPLE_HID_MODE);
-    SetNonBlocking();
-    return result;
+    ScopedSetBlocking sb(this);
+    return SetReportMode(ReportMode::SIMPLE_HID_MODE);
 }
 
 DriverResult GenericProtocol::EnableActiveMode() {
-    SetBlocking();
-    const auto result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
-    SetNonBlocking();
-    return result;
+    ScopedSetBlocking sb(this);
+    return SetReportMode(ReportMode::STANDARD_FULL_60HZ);
 }
 
 DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) {
+    ScopedSetBlocking sb(this);
     std::vector<u8> output;
-    SetBlocking();
 
     const auto result = SendSubCommand(SubCommand::REQ_DEV_INFO, {}, output);
 
@@ -34,7 +30,6 @@ DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) {
         memcpy(&device_info, output.data(), sizeof(DeviceInfo));
     }
 
-    SetNonBlocking();
     return result;
 }
 
@@ -43,36 +38,30 @@ DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type)
 }
 
 DriverResult GenericProtocol::EnableImu(bool enable) {
+    ScopedSetBlocking sb(this);
     const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)};
-    std::vector<u8> output;
-    SetBlocking();
-    const auto result = SendSubCommand(SubCommand::ENABLE_IMU, buffer, output);
-    SetNonBlocking();
-    return result;
+    return SendSubCommand(SubCommand::ENABLE_IMU, buffer);
 }
 
 DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
                                            AccelerometerSensitivity asen,
                                            AccelerometerPerformance afrec) {
+    ScopedSetBlocking sb(this);
     const std::array<u8, 4> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
                                    static_cast<u8>(gfrec), static_cast<u8>(afrec)};
-    std::vector<u8> output;
-    SetBlocking();
-    const auto result = SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer, output);
-    SetNonBlocking();
-    return result;
+    return SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer);
 }
 
 DriverResult GenericProtocol::GetBattery(u32& battery_level) {
+    // This function is meant to request the high resolution battery status
     battery_level = 0;
     return DriverResult::NotSupported;
 }
 
 DriverResult GenericProtocol::GetColor(Color& color) {
+    ScopedSetBlocking sb(this);
     std::vector<u8> buffer;
-    SetBlocking();
     const auto result = ReadSPI(CalAddr::COLOR_DATA, 12, buffer);
-    SetNonBlocking();
 
     color = {};
     if (result == DriverResult::Success) {
@@ -86,10 +75,9 @@ DriverResult GenericProtocol::GetColor(Color& color) {
 }
 
 DriverResult GenericProtocol::GetSerialNumber(SerialNumber& serial_number) {
+    ScopedSetBlocking sb(this);
     std::vector<u8> buffer;
-    SetBlocking();
     const auto result = ReadSPI(CalAddr::SERIAL_NUMBER, 16, buffer);
-    SetNonBlocking();
 
     serial_number = {};
     if (result == DriverResult::Success) {
@@ -115,14 +103,9 @@ DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) {
 }
 
 DriverResult GenericProtocol::SetHomeLight() {
+    ScopedSetBlocking sb(this);
     static constexpr std::array<u8, 3> buffer{0x0f, 0xf0, 0x00};
-    std::vector<u8> output;
-    SetBlocking();
-
-    const auto result = SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer, output);
-
-    SetNonBlocking();
-    return result;
+    return SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer);
 }
 
 DriverResult GenericProtocol::SetLedBusy() {
@@ -130,14 +113,9 @@ DriverResult GenericProtocol::SetLedBusy() {
 }
 
 DriverResult GenericProtocol::SetLedPattern(u8 leds) {
+    ScopedSetBlocking sb(this);
     const std::array<u8, 1> buffer{leds};
-    std::vector<u8> output;
-    SetBlocking();
-
-    const auto result = SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer, output);
-
-    SetNonBlocking();
-    return result;
+    return SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer);
 }
 
 DriverResult GenericProtocol::SetLedBlinkPattern(u8 leds) {
diff --git a/src/input_common/helpers/joycon_protocol/irs.cpp b/src/input_common/helpers/joycon_protocol/irs.cpp
index 9dfa503c23..09e17bc5b6 100644
--- a/src/input_common/helpers/joycon_protocol/irs.cpp
+++ b/src/input_common/helpers/joycon_protocol/irs.cpp
@@ -12,8 +12,8 @@ IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle)
 
 DriverResult IrsProtocol::EnableIrs() {
     LOG_INFO(Input, "Enable IRS");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
@@ -49,14 +49,13 @@ DriverResult IrsProtocol::EnableIrs() {
 
     is_enabled = true;
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult IrsProtocol::DisableIrs() {
     LOG_DEBUG(Input, "Disable IRS");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = EnableMCU(false);
@@ -64,7 +63,6 @@ DriverResult IrsProtocol::DisableIrs() {
 
     is_enabled = false;
 
-    SetNonBlocking();
     return result;
 }
 
@@ -148,7 +146,7 @@ DriverResult IrsProtocol::ConfigureIrs() {
     };
     buf_image.resize((static_cast<u8>(fragments) + 1) * 300);
 
-    std::vector<u8> request_data(sizeof(IrsConfigure));
+    std::array<u8, sizeof(IrsConfigure)> request_data{};
     memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure));
     request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
     do {
@@ -191,7 +189,7 @@ DriverResult IrsProtocol::WriteRegistersStep1() {
         .crc = {},
     };
 
-    std::vector<u8> request_data(sizeof(IrsWriteRegisters));
+    std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
     memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
     request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
 
@@ -208,7 +206,7 @@ DriverResult IrsProtocol::WriteRegistersStep1() {
 
         // First time we need to set the report mode
         if (result == DriverResult::Success && tries == 0) {
-            result = SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request);
+            result = SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
         }
         if (result == DriverResult::Success && tries == 0) {
             GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output);
@@ -250,7 +248,7 @@ DriverResult IrsProtocol::WriteRegistersStep2() {
         .crc = {},
     };
 
-    std::vector<u8> request_data(sizeof(IrsWriteRegisters));
+    std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
     memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
     request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
     do {
@@ -272,7 +270,7 @@ DriverResult IrsProtocol::RequestFrame(u8 frame) {
     mcu_request[3] = frame;
     mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
     mcu_request[37] = 0xFF;
-    return SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request);
+    return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
 }
 
 DriverResult IrsProtocol::ResendFrame(u8 frame) {
@@ -282,7 +280,7 @@ DriverResult IrsProtocol::ResendFrame(u8 frame) {
     mcu_request[3] = 0x0;
     mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
     mcu_request[37] = 0xFF;
-    return SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request);
+    return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
 }
 
 std::vector<u8> IrsProtocol::GetImage() const {
diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h
index 273c8d07d3..e2d47349f0 100644
--- a/src/input_common/helpers/joycon_protocol/joycon_types.h
+++ b/src/input_common/helpers/joycon_protocol/joycon_types.h
@@ -273,6 +273,18 @@ enum class NFCTagType : u8 {
     Ntag215 = 0x01,
 };
 
+enum class NFCPages {
+    Block0 = 0,
+    Block45 = 45,
+    Block135 = 135,
+    Block231 = 231,
+};
+
+enum class NFCStatus : u8 {
+    LastPackage = 0x04,
+    TagLost = 0x07,
+};
+
 enum class IrsMode : u8 {
     None = 0x02,
     Moment = 0x03,
diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp
index 8755e310b8..5c0f717228 100644
--- a/src/input_common/helpers/joycon_protocol/nfc.cpp
+++ b/src/input_common/helpers/joycon_protocol/nfc.cpp
@@ -12,8 +12,8 @@ NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle)
 
 DriverResult NfcProtocol::EnableNfc() {
     LOG_INFO(Input, "Enable NFC");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
@@ -35,14 +35,13 @@ DriverResult NfcProtocol::EnableNfc() {
         result = ConfigureMCU(config);
     }
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult NfcProtocol::DisableNfc() {
     LOG_DEBUG(Input, "Disable NFC");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = EnableMCU(false);
@@ -50,15 +49,14 @@ DriverResult NfcProtocol::DisableNfc() {
 
     is_enabled = false;
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult NfcProtocol::StartNFCPollingMode() {
     LOG_DEBUG(Input, "Start NFC pooling Mode");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
     TagFoundData tag_data{};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC);
@@ -70,15 +68,14 @@ DriverResult NfcProtocol::StartNFCPollingMode() {
         is_enabled = true;
     }
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
     LOG_DEBUG(Input, "Start NFC pooling Mode");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
     TagFoundData tag_data{};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = StartPolling(tag_data);
@@ -96,20 +93,18 @@ DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
         result = GetAmiiboData(data);
     }
 
-    SetNonBlocking();
     return result;
 }
 
 bool NfcProtocol::HasAmiibo() {
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
     TagFoundData tag_data{};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = StartPolling(tag_data);
     }
 
-    SetNonBlocking();
     return result == DriverResult::Success;
 }
 
@@ -169,55 +164,53 @@ DriverResult NfcProtocol::ReadTag(const TagFoundData& data) {
     LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string);
 
     tries = 0;
-    std::size_t ntag_pages = 0;
+    NFCPages ntag_pages = NFCPages::Block0;
     // Read Tag data
-loop1:
     while (true) {
         auto result = SendReadAmiiboRequest(output, ntag_pages);
-
-        int attempt = 0;
-        while (1) {
-            if (attempt != 0) {
-                result = GetMCUDataResponse(ReportMode::NFC_IR_MODE_60HZ, output);
-            }
-            if ((output[49] == 0x3a || output[49] == 0x2a) && output[56] == 0x07) {
-                return DriverResult::ErrorReadingData;
-            }
-            if (output[49] == 0x3a && output[51] == 0x07 && output[52] == 0x01) {
-                if (data.type != 2) {
-                    goto loop1;
-                }
-                switch (output[74]) {
-                case 0:
-                    ntag_pages = 135;
-                    break;
-                case 3:
-                    ntag_pages = 45;
-                    break;
-                case 4:
-                    ntag_pages = 231;
-                    break;
-                default:
-                    return DriverResult::ErrorReadingData;
-                }
-                goto loop1;
-            }
-            if (output[49] == 0x2a && output[56] == 0x04) {
-                // finished
-                SendStopPollingRequest(output);
-                return DriverResult::Success;
-            }
-            if (output[49] == 0x2a) {
-                goto loop1;
-            }
-            if (attempt++ > 6) {
-                goto loop1;
-            }
-        }
+        const auto mcu_report = static_cast<MCUReport>(output[49]);
+        const auto nfc_status = static_cast<NFCStatus>(output[56]);
 
         if (result != DriverResult::Success) {
             return result;
         }
+
+        if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) &&
+            nfc_status == NFCStatus::TagLost) {
+            return DriverResult::ErrorReadingData;
+        }
+
+        if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07 && output[52] == 0x01) {
+            if (data.type != 2) {
+                continue;
+            }
+            switch (output[74]) {
+            case 0:
+                ntag_pages = NFCPages::Block135;
+                break;
+            case 3:
+                ntag_pages = NFCPages::Block45;
+                break;
+            case 4:
+                ntag_pages = NFCPages::Block231;
+                break;
+            default:
+                return DriverResult::ErrorReadingData;
+            }
+            continue;
+        }
+
+        if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
+            // finished
+            SendStopPollingRequest(output);
+            return DriverResult::Success;
+        }
+
+        // Ignore other state reports
+        if (mcu_report == MCUReport::NFCState) {
+            continue;
+        }
+
         if (tries++ > timeout_limit) {
             return DriverResult::Timeout;
         }
@@ -231,47 +224,44 @@ DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) {
     std::vector<u8> output;
     std::size_t tries = 0;
 
-    std::size_t ntag_pages = 135;
+    NFCPages ntag_pages = NFCPages::Block135;
     std::size_t ntag_buffer_pos = 0;
     // Read Tag data
-loop1:
     while (true) {
         auto result = SendReadAmiiboRequest(output, ntag_pages);
-
-        int attempt = 0;
-        while (1) {
-            if (attempt != 0) {
-                result = GetMCUDataResponse(ReportMode::NFC_IR_MODE_60HZ, output);
-            }
-            if ((output[49] == 0x3a || output[49] == 0x2a) && output[56] == 0x07) {
-                return DriverResult::ErrorReadingData;
-            }
-            if (output[49] == 0x3a && output[51] == 0x07) {
-                std::size_t payload_size = (output[54] << 8 | output[55]) & 0x7FF;
-                if (output[52] == 0x01) {
-                    memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 116,
-                           payload_size - 60);
-                    ntag_buffer_pos += payload_size - 60;
-                } else {
-                    memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 56, payload_size);
-                }
-                goto loop1;
-            }
-            if (output[49] == 0x2a && output[56] == 0x04) {
-                LOG_INFO(Input, "Finished reading amiibo");
-                return DriverResult::Success;
-            }
-            if (output[49] == 0x2a) {
-                goto loop1;
-            }
-            if (attempt++ > 4) {
-                goto loop1;
-            }
-        }
+        const auto mcu_report = static_cast<MCUReport>(output[49]);
+        const auto nfc_status = static_cast<NFCStatus>(output[56]);
 
         if (result != DriverResult::Success) {
             return result;
         }
+
+        if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) &&
+            nfc_status == NFCStatus::TagLost) {
+            return DriverResult::ErrorReadingData;
+        }
+
+        if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07) {
+            std::size_t payload_size = (output[54] << 8 | output[55]) & 0x7FF;
+            if (output[52] == 0x01) {
+                memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 116, payload_size - 60);
+                ntag_buffer_pos += payload_size - 60;
+            } else {
+                memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 56, payload_size);
+            }
+            continue;
+        }
+
+        if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
+            LOG_INFO(Input, "Finished reading amiibo");
+            return DriverResult::Success;
+        }
+
+        // Ignore other state reports
+        if (mcu_report == MCUReport::NFCState) {
+            continue;
+        }
+
         if (tries++ > timeout_limit) {
             return DriverResult::Timeout;
         }
@@ -298,7 +288,7 @@ DriverResult NfcProtocol::SendStartPollingRequest(std::vector<u8>& output) {
         .crc = {},
     };
 
-    std::vector<u8> request_data(sizeof(NFCRequestState));
+    std::array<u8, sizeof(NFCRequestState)> request_data{};
     memcpy(request_data.data(), &request, sizeof(NFCRequestState));
     request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
     return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
@@ -315,7 +305,7 @@ DriverResult NfcProtocol::SendStopPollingRequest(std::vector<u8>& output) {
         .crc = {},
     };
 
-    std::vector<u8> request_data(sizeof(NFCRequestState));
+    std::array<u8, sizeof(NFCRequestState)> request_data{};
     memcpy(request_data.data(), &request, sizeof(NFCRequestState));
     request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
     return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
@@ -338,7 +328,7 @@ DriverResult NfcProtocol::SendStartWaitingRecieveRequest(std::vector<u8>& output
     return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
 }
 
-DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, std::size_t ntag_pages) {
+DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages) {
     NFCRequestState request{
         .sub_command = MCUSubCommand::ReadDeviceMode,
         .command_argument = NFCReadCommand::Ntag,
@@ -357,20 +347,19 @@ DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, std::si
         .crc = {},
     };
 
-    std::vector<u8> request_data(sizeof(NFCRequestState));
+    std::array<u8, sizeof(NFCRequestState)> request_data{};
     memcpy(request_data.data(), &request, sizeof(NFCRequestState));
     request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
     return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
 }
 
-NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(std::size_t pages) const {
-    if (pages == 0) {
+NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const {
+    switch (pages) {
+    case NFCPages::Block0:
         return {
             .block_count = 1,
         };
-    }
-
-    if (pages == 45) {
+    case NFCPages::Block45:
         return {
             .block_count = 1,
             .blocks =
@@ -378,9 +367,7 @@ NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(std::size_t pages) const {
                     NFCReadBlock{0x00, 0x2C},
                 },
         };
-    }
-
-    if (pages == 135) {
+    case NFCPages::Block135:
         return {
             .block_count = 3,
             .blocks =
@@ -390,9 +377,7 @@ NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(std::size_t pages) const {
                     {0x78, 0x86},
                 },
         };
-    }
-
-    if (pages == 231) {
+    case NFCPages::Block231:
         return {
             .block_count = 4,
             .blocks =
@@ -403,9 +388,9 @@ NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(std::size_t pages) const {
                     {0xb4, 0xe6},
                 },
         };
-    }
-
-    return {};
+    default:
+        return {};
+    };
 }
 
 bool NfcProtocol::IsEnabled() const {
diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h
index 5cb0e5a652..e63665aa97 100644
--- a/src/input_common/helpers/joycon_protocol/nfc.h
+++ b/src/input_common/helpers/joycon_protocol/nfc.h
@@ -51,9 +51,9 @@ private:
 
     DriverResult SendStartWaitingRecieveRequest(std::vector<u8>& output);
 
-    DriverResult SendReadAmiiboRequest(std::vector<u8>& output, std::size_t ntag_pages);
+    DriverResult SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages);
 
-    NFCReadBlockCommand GetReadBlockCommand(std::size_t pages) const;
+    NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const;
 
     bool is_enabled{};
 };
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
index 940b20b7f0..7f8e093faf 100644
--- a/src/input_common/helpers/joycon_protocol/poller.cpp
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -224,9 +224,9 @@ void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) {
         Joycon::PasivePadButton::StickL,
     };
 
-    for (std::size_t i = 0; i < left_buttons.size(); ++i) {
-        const bool button_status = (input.button_input & static_cast<u32>(left_buttons[i])) != 0;
-        const int button = static_cast<int>(left_buttons[i]);
+    for (auto left_button : left_buttons) {
+        const bool button_status = (input.button_input & static_cast<u32>(left_button)) != 0;
+        const int button = static_cast<int>(left_button);
         callbacks.on_button_data(button, button_status);
     }
 }
@@ -241,9 +241,9 @@ void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) {
         Joycon::PasivePadButton::StickR,
     };
 
-    for (std::size_t i = 0; i < right_buttons.size(); ++i) {
-        const bool button_status = (input.button_input & static_cast<u32>(right_buttons[i])) != 0;
-        const int button = static_cast<int>(right_buttons[i]);
+    for (auto right_button : right_buttons) {
+        const bool button_status = (input.button_input & static_cast<u32>(right_button)) != 0;
+        const int button = static_cast<int>(right_button);
         callbacks.on_button_data(button, button_status);
     }
 }
@@ -259,9 +259,9 @@ void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) {
         Joycon::PasivePadButton::StickL,  Joycon::PasivePadButton::StickR,
     };
 
-    for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
-        const bool button_status = (input.button_input & static_cast<u32>(pro_buttons[i])) != 0;
-        const int button = static_cast<int>(pro_buttons[i]);
+    for (auto pro_button : pro_buttons) {
+        const bool button_status = (input.button_input & static_cast<u32>(pro_button)) != 0;
+        const int button = static_cast<int>(pro_button);
         callbacks.on_button_data(button, button_status);
     }
 }
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.cpp b/src/input_common/helpers/joycon_protocol/ringcon.cpp
index 8adad57dd6..12f81309e1 100644
--- a/src/input_common/helpers/joycon_protocol/ringcon.cpp
+++ b/src/input_common/helpers/joycon_protocol/ringcon.cpp
@@ -11,8 +11,8 @@ RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle)
 
 DriverResult RingConProtocol::EnableRingCon() {
     LOG_DEBUG(Input, "Enable Ringcon");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
@@ -30,14 +30,13 @@ DriverResult RingConProtocol::EnableRingCon() {
         result = ConfigureMCU(config);
     }
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult RingConProtocol::DisableRingCon() {
     LOG_DEBUG(Input, "Disable RingCon");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = EnableMCU(false);
@@ -45,15 +44,14 @@ DriverResult RingConProtocol::DisableRingCon() {
 
     is_enabled = false;
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult RingConProtocol::StartRingconPolling() {
     LOG_DEBUG(Input, "Enable Ringcon");
-    bool is_connected = false;
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
-    SetBlocking();
+    bool is_connected = false;
 
     if (result == DriverResult::Success) {
         result = IsRingConnected(is_connected);
@@ -66,13 +64,13 @@ DriverResult RingConProtocol::StartRingconPolling() {
         is_enabled = true;
     }
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
     LOG_DEBUG(Input, "IsRingConnected");
     constexpr std::size_t max_tries = 28;
+    constexpr u8 ring_controller_id = 0x20;
     std::vector<u8> output;
     std::size_t tries = 0;
     is_connected = false;
@@ -88,7 +86,7 @@ DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
         if (tries++ >= max_tries) {
             return DriverResult::NoDeviceDetected;
         }
-    } while (output[14] != 0x59 || output[16] != 0x20);
+    } while (output[16] != ring_controller_id);
 
     is_connected = true;
     return DriverResult::Success;
@@ -96,30 +94,20 @@ DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
 
 DriverResult RingConProtocol::ConfigureRing() {
     LOG_DEBUG(Input, "ConfigureRing");
-    constexpr std::size_t max_tries = 28;
-    DriverResult result{DriverResult::Success};
-    std::vector<u8> output;
-    std::size_t tries = 0;
 
     static constexpr std::array<u8, 37> ring_config{
         0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16, 0xED, 0x34, 0x36,
         0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6, 0xA9, 0x22, 0x00, 0x00, 0x04, 0x00,
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36};
-    do {
-        result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config, output);
 
-        if (result != DriverResult::Success) {
-            return result;
-        }
-        if (tries++ >= max_tries) {
-            return DriverResult::NoDeviceDetected;
-        }
-    } while (output[14] != 0x5C);
+    const DriverResult result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config);
+
+    if (result != DriverResult::Success) {
+        return result;
+    }
 
     static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02};
-    result = SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data, output);
-
-    return result;
+    return SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data);
 }
 
 bool RingConProtocol::IsEnabled() const {
diff --git a/src/input_common/helpers/joycon_protocol/rumble.cpp b/src/input_common/helpers/joycon_protocol/rumble.cpp
index fad67a94ba..63b60c9468 100644
--- a/src/input_common/helpers/joycon_protocol/rumble.cpp
+++ b/src/input_common/helpers/joycon_protocol/rumble.cpp
@@ -14,12 +14,9 @@ RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle)
 
 DriverResult RumbleProtocol::EnableRumble(bool is_enabled) {
     LOG_DEBUG(Input, "Enable Rumble");
+    ScopedSetBlocking sb(this);
     const std::array<u8, 1> buffer{static_cast<u8>(is_enabled ? 1 : 0)};
-    std::vector<u8> output;
-    SetBlocking();
-    const auto result = SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer, output);
-    SetNonBlocking();
-    return result;
+    return SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer);
 }
 
 DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) {
@@ -66,9 +63,9 @@ u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const {
 }
 
 u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
-    /* More information about these values can be found here:
-     * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
-     */
+    // More information about these values can be found here:
+    // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
+
     static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
         std::pair<f32, int>{0.0f, 0x0},
         {0.01f, 0x2},
@@ -183,9 +180,9 @@ u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
 }
 
 u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const {
-    /* More information about these values can be found here:
-     * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
-     */
+    // More information about these values can be found here:
+    // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
+
     static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
         std::pair<f32, int>{0.0f, 0x0040},
         {0.01f, 0x8040},

From fafa92cfb8f78f3a3adaf5bc87f35f495a70ec3f Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sat, 14 Jan 2023 00:36:03 -0600
Subject: [PATCH 23/24] input_common: Fix joycon mappings

---
 src/input_common/drivers/joycon.cpp | 105 +++++++++++++---------------
 src/input_common/drivers/joycon.h   |   5 +-
 2 files changed, 53 insertions(+), 57 deletions(-)

diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index 1582def13c..7122093c62 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -411,13 +411,25 @@ std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifie
 }
 
 PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const {
+    const std::array<u8, 16> guid{0, 0, 0, 0, 0, 0, 0, 0,
+                                  0, 0, 0, 0, 0, 0, 0, static_cast<u8>(type)};
     return {
-        .guid = Common::UUID{Common::InvalidUUID},
+        .guid = Common::UUID{guid},
         .port = port,
         .pad = static_cast<std::size_t>(type),
     };
 }
 
+Common::ParamPackage Joycons::GetParamPackage(std::size_t port, Joycon::ControllerType type) const {
+    const auto identifier = GetIdentifier(port, type);
+    return {
+        {"engine", GetEngineName()},
+        {"guid", identifier.guid.RawString()},
+        {"port", std::to_string(identifier.port)},
+        {"pad", std::to_string(identifier.pad)},
+    };
+}
+
 std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
     std::vector<Common::ParamPackage> devices{};
 
@@ -428,14 +440,11 @@ std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
         if (!device->IsConnected()) {
             return;
         }
+        auto param = GetParamPackage(device->GetDevicePort(), device->GetHandleDeviceType());
         std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()),
                                        device->GetDevicePort() + 1);
-        devices.emplace_back(Common::ParamPackage{
-            {"engine", GetEngineName()},
-            {"display", std::move(name)},
-            {"port", std::to_string(device->GetDevicePort())},
-            {"pad", std::to_string(static_cast<std::size_t>(device->GetHandleDeviceType()))},
-        });
+        param.Set("display", std::move(name));
+        devices.emplace_back(param);
     };
 
     for (const auto& controller : left_joycons) {
@@ -453,14 +462,15 @@ std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
         if (!left_joycons[i]->IsConnected() || !right_joycons[i]->IsConnected()) {
             continue;
         }
-        constexpr auto type = Joycon::ControllerType::Dual;
+        auto main_param = GetParamPackage(i, left_joycons[i]->GetHandleDeviceType());
+        const auto second_param = GetParamPackage(i, right_joycons[i]->GetHandleDeviceType());
+        const auto type = Joycon::ControllerType::Dual;
         std::string name = fmt::format("{} {}", JoyconName(type), i + 1);
-        devices.emplace_back(Common::ParamPackage{
-            {"engine", GetEngineName()},
-            {"display", std::move(name)},
-            {"port", std::to_string(i)},
-            {"pad", std::to_string(static_cast<std::size_t>(type))},
-        });
+
+        main_param.Set("display", std::move(name));
+        main_param.Set("guid2", second_param.Get("guid", ""));
+        main_param.Set("pad", std::to_string(static_cast<size_t>(type)));
+        devices.emplace_back(main_param);
     }
 
     return devices;
@@ -496,26 +506,21 @@ ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& par
 
     ButtonMapping mapping{};
     for (const auto& [switch_button, joycon_button, side] : switch_to_joycon_button) {
-        int pad = params.Get("pad", 0);
-        if (pad == static_cast<int>(Joycon::ControllerType::Dual)) {
-            pad = side ? static_cast<int>(Joycon::ControllerType::Right)
-                       : static_cast<int>(Joycon::ControllerType::Left);
+        const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
+        auto pad = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
+        if (pad == Joycon::ControllerType::Dual) {
+            pad = side ? Joycon::ControllerType::Right : Joycon::ControllerType::Left;
         }
 
-        Common::ParamPackage button_params{};
-        button_params.Set("engine", GetEngineName());
-        button_params.Set("port", params.Get("port", 0));
-        button_params.Set("pad", pad);
+        Common::ParamPackage button_params = GetParamPackage(port, pad);
         button_params.Set("button", static_cast<int>(joycon_button));
         mapping.insert_or_assign(switch_button, std::move(button_params));
     }
 
     // Map SL and SR buttons for left joycons
     if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Left)) {
-        Common::ParamPackage button_params{};
-        button_params.Set("engine", GetEngineName());
-        button_params.Set("port", params.Get("port", 0));
-        button_params.Set("pad", static_cast<int>(Joycon::ControllerType::Left));
+        const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
+        Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Left);
 
         Common::ParamPackage sl_button_params = button_params;
         Common::ParamPackage sr_button_params = button_params;
@@ -527,10 +532,8 @@ ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& par
 
     // Map SL and SR buttons for right joycons
     if (params.Get("pad", 0) == static_cast<int>(Joycon::ControllerType::Right)) {
-        Common::ParamPackage button_params{};
-        button_params.Set("engine", GetEngineName());
-        button_params.Set("port", params.Get("port", 0));
-        button_params.Set("pad", static_cast<int>(Joycon::ControllerType::Right));
+        const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
+        Common::ParamPackage button_params = GetParamPackage(port, Joycon::ControllerType::Right);
 
         Common::ParamPackage sl_button_params = button_params;
         Common::ParamPackage sr_button_params = button_params;
@@ -548,25 +551,20 @@ AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& par
         return {};
     }
 
-    int pad_left = params.Get("pad", 0);
-    int pad_right = pad_left;
-    if (pad_left == static_cast<int>(Joycon::ControllerType::Dual)) {
-        pad_left = static_cast<int>(Joycon::ControllerType::Left);
-        pad_right = static_cast<int>(Joycon::ControllerType::Right);
+    const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
+    auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
+    auto pad_right = pad_left;
+    if (pad_left == Joycon::ControllerType::Dual) {
+        pad_left = Joycon::ControllerType::Left;
+        pad_right = Joycon::ControllerType::Right;
     }
 
     AnalogMapping mapping = {};
-    Common::ParamPackage left_analog_params;
-    left_analog_params.Set("engine", GetEngineName());
-    left_analog_params.Set("port", params.Get("port", 0));
-    left_analog_params.Set("pad", pad_left);
+    Common::ParamPackage left_analog_params = GetParamPackage(port, pad_left);
     left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX));
     left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY));
     mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
-    Common::ParamPackage right_analog_params;
-    right_analog_params.Set("engine", GetEngineName());
-    right_analog_params.Set("port", params.Get("port", 0));
-    right_analog_params.Set("pad", pad_right);
+    Common::ParamPackage right_analog_params = GetParamPackage(port, pad_right);
     right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX));
     right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY));
     mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
@@ -578,24 +576,19 @@ MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& par
         return {};
     }
 
-    int pad_left = params.Get("pad", 0);
-    int pad_right = pad_left;
-    if (pad_left == static_cast<int>(Joycon::ControllerType::Dual)) {
-        pad_left = static_cast<int>(Joycon::ControllerType::Left);
-        pad_right = static_cast<int>(Joycon::ControllerType::Right);
+    const std::size_t port = static_cast<std::size_t>(params.Get("port", 0));
+    auto pad_left = static_cast<Joycon::ControllerType>(params.Get("pad", 0));
+    auto pad_right = pad_left;
+    if (pad_left == Joycon::ControllerType::Dual) {
+        pad_left = Joycon::ControllerType::Left;
+        pad_right = Joycon::ControllerType::Right;
     }
 
     MotionMapping mapping = {};
-    Common::ParamPackage left_motion_params;
-    left_motion_params.Set("engine", GetEngineName());
-    left_motion_params.Set("port", params.Get("port", 0));
-    left_motion_params.Set("pad", pad_left);
+    Common::ParamPackage left_motion_params = GetParamPackage(port, pad_left);
     left_motion_params.Set("motion", 0);
     mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
-    Common::ParamPackage right_Motion_params;
-    right_Motion_params.Set("engine", GetEngineName());
-    right_Motion_params.Set("port", params.Get("port", 0));
-    right_Motion_params.Set("pad", pad_right);
+    Common::ParamPackage right_Motion_params = GetParamPackage(port, pad_right);
     right_Motion_params.Set("motion", 1);
     mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params));
     return mapping;
diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h
index 6d2e2ec786..316d383d88 100644
--- a/src/input_common/drivers/joycon.h
+++ b/src/input_common/drivers/joycon.h
@@ -88,9 +88,12 @@ private:
     /// Returns a JoyconHandle corresponding to a PadIdentifier
     std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const;
 
-    /// Returns a PadIdentifier corresponding to the port number
+    /// Returns a PadIdentifier corresponding to the port number and joycon type
     PadIdentifier GetIdentifier(std::size_t port, Joycon::ControllerType type) const;
 
+    /// Returns a ParamPackage corresponding to the port number and joycon type
+    Common::ParamPackage GetParamPackage(std::size_t port, Joycon::ControllerType type) const;
+
     std::string JoyconName(std::size_t port) const;
 
     Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;

From d9ee7c32975bb8d840cf93a086d6b4be39d7bfd2 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Thu, 19 Jan 2023 20:20:19 -0600
Subject: [PATCH 24/24] core: hid: Make use of SCOPE_EXIT and SCOPE_GUARD where
 applicable

---
 src/core/hid/emulated_controller.cpp | 105 ++++++++++-----------------
 1 file changed, 38 insertions(+), 67 deletions(-)

diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index f83abad055..0e06468da1 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 #include <algorithm>
+#include <common/scope_exit.h>
 
 #include "common/polyfill_ranges.h"
 #include "common/thread.h"
@@ -834,17 +835,21 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
     if (index >= controller.stick_values.size()) {
         return;
     }
-    std::unique_lock lock{mutex};
+    auto trigger_guard =
+        SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Stick, !is_configuring); });
+    std::scoped_lock lock{mutex};
     const auto stick_value = TransformToStick(callback);
 
     // Only read stick values that have the same uuid or are over the threshold to avoid flapping
     if (controller.stick_values[index].uuid != uuid) {
         const bool is_tas = uuid == TAS_UUID;
         if (is_tas && stick_value.x.value == 0 && stick_value.y.value == 0) {
+            trigger_guard.Cancel();
             return;
         }
         if (!is_tas && !stick_value.down && !stick_value.up && !stick_value.left &&
             !stick_value.right) {
+            trigger_guard.Cancel();
             return;
         }
     }
@@ -855,8 +860,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
     if (is_configuring) {
         controller.analog_stick_state.left = {};
         controller.analog_stick_state.right = {};
-        lock.unlock();
-        TriggerOnChange(ControllerTriggerType::Stick, false);
         return;
     }
 
@@ -881,9 +884,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
         controller.npad_button_state.stick_r_down.Assign(controller.stick_values[index].down);
         break;
     }
-
-    lock.unlock();
-    TriggerOnChange(ControllerTriggerType::Stick, true);
 }
 
 void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callback,
@@ -891,7 +891,9 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
     if (index >= controller.trigger_values.size()) {
         return;
     }
-    std::unique_lock lock{mutex};
+    auto trigger_guard =
+        SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring); });
+    std::scoped_lock lock{mutex};
     const auto trigger_value = TransformToTrigger(callback);
 
     // Only read trigger values that have the same uuid or are pressed once
@@ -907,13 +909,12 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
     if (is_configuring) {
         controller.gc_trigger_state.left = 0;
         controller.gc_trigger_state.right = 0;
-        lock.unlock();
-        TriggerOnChange(ControllerTriggerType::Trigger, false);
         return;
     }
 
     // Only GC controllers have analog triggers
     if (npad_type != NpadStyleIndex::GameCube) {
+        trigger_guard.Cancel();
         return;
     }
 
@@ -930,9 +931,6 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
         controller.npad_button_state.zr.Assign(trigger.pressed.value);
         break;
     }
-
-    lock.unlock();
-    TriggerOnChange(ControllerTriggerType::Trigger, true);
 }
 
 void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback,
@@ -940,7 +938,8 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
     if (index >= controller.motion_values.size()) {
         return;
     }
-    std::unique_lock lock{mutex};
+    SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Motion, !is_configuring); });
+    std::scoped_lock lock{mutex};
     auto& raw_status = controller.motion_values[index].raw_status;
     auto& emulated = controller.motion_values[index].emulated;
 
@@ -961,8 +960,6 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
     force_update_motion = raw_status.force_update;
 
     if (is_configuring) {
-        lock.unlock();
-        TriggerOnChange(ControllerTriggerType::Motion, false);
         return;
     }
 
@@ -972,9 +969,6 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
     motion.rotation = emulated.GetRotations();
     motion.orientation = emulated.GetOrientation();
     motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
-
-    lock.unlock();
-    TriggerOnChange(ControllerTriggerType::Motion, true);
 }
 
 void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback,
@@ -982,16 +976,17 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback
     if (index >= controller.color_values.size()) {
         return;
     }
-    std::unique_lock lock{mutex};
+    auto trigger_guard =
+        SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Color, !is_configuring); });
+    std::scoped_lock lock{mutex};
     controller.color_values[index] = TransformToColor(callback);
 
     if (is_configuring) {
-        lock.unlock();
-        TriggerOnChange(ControllerTriggerType::Color, false);
         return;
     }
 
     if (controller.color_values[index].body == 0) {
+        trigger_guard.Cancel();
         return;
     }
 
@@ -1024,9 +1019,6 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback
             break;
         }
     }
-
-    lock.unlock();
-    TriggerOnChange(ControllerTriggerType::Color, true);
 }
 
 void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback,
@@ -1034,12 +1026,11 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
     if (index >= controller.battery_values.size()) {
         return;
     }
-    std::unique_lock lock{mutex};
+    SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Battery, !is_configuring); });
+    std::scoped_lock lock{mutex};
     controller.battery_values[index] = TransformToBattery(callback);
 
     if (is_configuring) {
-        lock.unlock();
-        TriggerOnChange(ControllerTriggerType::Battery, false);
         return;
     }
 
@@ -1095,18 +1086,14 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
         };
         break;
     }
-
-    lock.unlock();
-    TriggerOnChange(ControllerTriggerType::Battery, true);
 }
 
 void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) {
-    std::unique_lock lock{mutex};
+    SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring); });
+    std::scoped_lock lock{mutex};
     controller.camera_values = TransformToCamera(callback);
 
     if (is_configuring) {
-        lock.unlock();
-        TriggerOnChange(ControllerTriggerType::IrSensor, false);
         return;
     }
 
@@ -1114,36 +1101,28 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback
     controller.camera_state.format =
         static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format);
     controller.camera_state.data = controller.camera_values.data;
-
-    lock.unlock();
-    TriggerOnChange(ControllerTriggerType::IrSensor, true);
 }
 
 void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
-    std::unique_lock lock{mutex};
+    SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::RingController, !is_configuring); });
+    std::scoped_lock lock{mutex};
     const auto force_value = TransformToStick(callback);
 
     controller.ring_analog_value = force_value.x;
 
     if (is_configuring) {
-        lock.unlock();
-        TriggerOnChange(ControllerTriggerType::RingController, false);
         return;
     }
 
     controller.ring_analog_state.force = force_value.x.value;
-
-    lock.unlock();
-    TriggerOnChange(ControllerTriggerType::RingController, true);
 }
 
 void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
-    std::unique_lock lock{mutex};
+    SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring); });
+    std::scoped_lock lock{mutex};
     controller.nfc_values = TransformToNfc(callback);
 
     if (is_configuring) {
-        lock.unlock();
-        TriggerOnChange(ControllerTriggerType::Nfc, false);
         return;
     }
 
@@ -1151,9 +1130,6 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
         controller.nfc_values.state,
         controller.nfc_values.data,
     };
-
-    lock.unlock();
-    TriggerOnChange(ControllerTriggerType::Nfc, true);
 }
 
 bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
@@ -1412,39 +1388,35 @@ void EmulatedController::Connect(bool use_temporary_value) {
         return;
     }
 
-    std::unique_lock lock{mutex};
+    auto trigger_guard =
+        SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); });
+    std::scoped_lock lock{mutex};
     if (is_configuring) {
         tmp_is_connected = true;
-        lock.unlock();
-        TriggerOnChange(ControllerTriggerType::Connected, false);
         return;
     }
 
     if (is_connected) {
+        trigger_guard.Cancel();
         return;
     }
     is_connected = true;
-
-    lock.unlock();
-    TriggerOnChange(ControllerTriggerType::Connected, true);
 }
 
 void EmulatedController::Disconnect() {
-    std::unique_lock lock{mutex};
+    auto trigger_guard =
+        SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); });
+    std::scoped_lock lock{mutex};
     if (is_configuring) {
         tmp_is_connected = false;
-        lock.unlock();
-        TriggerOnChange(ControllerTriggerType::Disconnected, false);
         return;
     }
 
     if (!is_connected) {
+        trigger_guard.Cancel();
         return;
     }
     is_connected = false;
-
-    lock.unlock();
-    TriggerOnChange(ControllerTriggerType::Disconnected, true);
 }
 
 bool EmulatedController::IsConnected(bool get_temporary_value) const {
@@ -1469,19 +1441,21 @@ NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) c
 }
 
 void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
-    std::unique_lock lock{mutex};
+    auto trigger_guard =
+        SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Type, !is_configuring); });
+    std::scoped_lock lock{mutex};
 
     if (is_configuring) {
         if (tmp_npad_type == npad_type_) {
+            trigger_guard.Cancel();
             return;
         }
         tmp_npad_type = npad_type_;
-        lock.unlock();
-        TriggerOnChange(ControllerTriggerType::Type, false);
         return;
     }
 
     if (npad_type == npad_type_) {
+        trigger_guard.Cancel();
         return;
     }
     if (is_connected) {
@@ -1489,9 +1463,6 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
                     NpadIdTypeToIndex(npad_id_type));
     }
     npad_type = npad_type_;
-
-    lock.unlock();
-    TriggerOnChange(ControllerTriggerType::Type, true);
 }
 
 LedPattern EmulatedController::GetLedPattern() const {
@@ -1589,7 +1560,7 @@ DebugPadButton EmulatedController::GetDebugPadButtons() const {
 }
 
 AnalogSticks EmulatedController::GetSticks() const {
-    std::unique_lock lock{mutex};
+    std::scoped_lock lock{mutex};
 
     if (is_configuring) {
         return {};