code:
// 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 JoyconCallbacks& callbacks_) {
    callbacks = std::move(callbacks_);
}

void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
                                  const RingStatus& ring_status) {
    InputReportActive data{};
    memcpy(&data, buffer.data(), sizeof(InputReportActive));

    switch (device_type) {
    case ControllerType::Left:
        UpdateActiveLeftPadInput(data, motion_status);
        break;
    case ControllerType::Right:
        UpdateActiveRightPadInput(data, motion_status);
        break;
    case ControllerType::Pro:
        UpdateActiveProPadInput(data, motion_status);
        break;
    default:
        break;
    }

    if (ring_status.is_enabled) {
        UpdateRing(data.ring_input, ring_status);
    }

    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 ControllerType::Left:
        UpdatePassiveLeftPadInput(data);
        break;
    case ControllerType::Right:
        UpdatePassiveRightPadInput(data);
        break;
    case ControllerType::Pro:
        UpdatePassiveProPadInput(data);
        break;
    default:
        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::UpdateAmiibo(const Joycon::TagInfo& tag_info) {
    callbacks.on_amiibo_data(tag_info);
}

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) {
        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{
        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::UpdatePassiveLeftPadInput(const InputReportPassive& input) {
    static constexpr std::array<PassivePadButton, 11> left_buttons{
        PassivePadButton::Down_A,  PassivePadButton::Right_X, PassivePadButton::Left_B,
        PassivePadButton::Up_Y,    PassivePadButton::SL,      PassivePadButton::SR,
        PassivePadButton::L_R,     PassivePadButton::ZL_ZR,   PassivePadButton::Minus,
        PassivePadButton::Capture, PassivePadButton::StickL,
    };

    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);
    }

    const auto [left_axis_x, left_axis_y] =
        GetPassiveAxisValue(static_cast<PassivePadStick>(input.stick_state));
    callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
    callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
}

void JoyconPoller::UpdatePassiveRightPadInput(const InputReportPassive& input) {
    static constexpr std::array<PassivePadButton, 11> right_buttons{
        PassivePadButton::Down_A, PassivePadButton::Right_X, PassivePadButton::Left_B,
        PassivePadButton::Up_Y,   PassivePadButton::SL,      PassivePadButton::SR,
        PassivePadButton::L_R,    PassivePadButton::ZL_ZR,   PassivePadButton::Plus,
        PassivePadButton::Home,   PassivePadButton::StickR,
    };

    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);
    }

    const auto [right_axis_x, right_axis_y] =
        GetPassiveAxisValue(static_cast<PassivePadStick>(input.stick_state));
    callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
    callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
}

void JoyconPoller::UpdatePassiveProPadInput(const InputReportPassive& input) {
    static constexpr std::array<PassivePadButton, 14> pro_buttons{
        PassivePadButton::Down_A, PassivePadButton::Right_X, PassivePadButton::Left_B,
        PassivePadButton::Up_Y,   PassivePadButton::SL,      PassivePadButton::SR,
        PassivePadButton::L_R,    PassivePadButton::ZL_ZR,   PassivePadButton::Minus,
        PassivePadButton::Plus,   PassivePadButton::Capture, PassivePadButton::Home,
        PassivePadButton::StickL, PassivePadButton::StickR,
    };

    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);
    }

    const auto [left_axis_x, left_axis_y] =
        GetPassiveAxisValue(static_cast<PassivePadStick>(input.stick_state & 0xf));
    const auto [right_axis_x, right_axis_y] =
        GetPassiveAxisValue(static_cast<PassivePadStick>(input.stick_state >> 4));
    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);
}

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;
}

std::pair<f32, f32> JoyconPoller::GetPassiveAxisValue(PassivePadStick raw_value) const {
    switch (raw_value) {
    case PassivePadStick::Right:
        return {1.0f, 0.0f};
    case PassivePadStick::RightDown:
        return {1.0f, -1.0f};
    case PassivePadStick::Down:
        return {0.0f, -1.0f};
    case PassivePadStick::DownLeft:
        return {-1.0f, -1.0f};
    case PassivePadStick::Left:
        return {-1.0f, 0.0f};
    case PassivePadStick::LeftUp:
        return {-1.0f, 1.0f};
    case PassivePadStick::Up:
        return {0.0f, 1.0f};
    case PassivePadStick::UpRight:
        return {1.0f, 1.0f};
    case PassivePadStick::Neutral:
    default:
        return {0.0f, 0.0f};
    }
}

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


code:
// 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 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,
                        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);
    void UpdateAmiibo(const Joycon::TagInfo& tag_info);
    void UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format);

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 UpdatePassiveLeftPadInput(const InputReportPassive& buffer);
    void UpdatePassiveRightPadInput(const InputReportPassive& buffer);
    void UpdatePassiveProPadInput(const InputReportPassive& buffer);

    /// Returns a calibrated joystick axis from raw axis data
    f32 GetAxisValue(u16 raw_value, JoyStickAxisCalibration calibration) const;

    /// Returns a digital joystick axis from passive axis data
    std::pair<f32, f32> GetPassiveAxisValue(PassivePadStick raw_value) 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{};

    JoyconCallbacks callbacks{};
};

} // namespace InputCommon::Joycon

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/input.h"
#include "common/logging/log.h"
#include "input_common/helpers/joycon_protocol/ringcon.h"

namespace InputCommon::Joycon {

RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle)
    : JoyconCommonProtocol(std::move(handle)) {}

Common::Input::DriverResult RingConProtocol::EnableRingCon() {
    LOG_DEBUG(Input, "Enable Ringcon");
    ScopedSetBlocking sb(this);
    Common::Input::DriverResult result{Common::Input::DriverResult::Success};

    if (result == Common::Input::DriverResult::Success) {
        result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
    }
    if (result == Common::Input::DriverResult::Success) {
        result = EnableMCU(true);
    }
    if (result == Common::Input::DriverResult::Success) {
        const MCUConfig config{
            .command = MCUCommand::ConfigureMCU,
            .sub_command = MCUSubCommand::SetDeviceMode,
            .mode = MCUMode::Standby,
            .crc = {},
        };
        result = ConfigureMCU(config);
    }

    return result;
}

Common::Input::DriverResult RingConProtocol::DisableRingCon() {
    LOG_DEBUG(Input, "Disable RingCon");
    ScopedSetBlocking sb(this);
    Common::Input::DriverResult result{Common::Input::DriverResult::Success};

    if (result == Common::Input::DriverResult::Success) {
        result = EnableMCU(false);
    }

    is_enabled = false;

    return result;
}

Common::Input::DriverResult RingConProtocol::StartRingconPolling() {
    LOG_DEBUG(Input, "Enable Ringcon");
    ScopedSetBlocking sb(this);
    Common::Input::DriverResult result{Common::Input::DriverResult::Success};
    bool is_connected = false;

    if (result == Common::Input::DriverResult::Success) {
        result = IsRingConnected(is_connected);
    }
    if (result == Common::Input::DriverResult::Success && is_connected) {
        LOG_INFO(Input, "Ringcon detected");
        result = ConfigureRing();
    }
    if (result == Common::Input::DriverResult::Success) {
        is_enabled = true;
    }

    return result;
}

Common::Input::DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
    LOG_DEBUG(Input, "IsRingConnected");
    constexpr std::size_t max_tries = 42;
    SubCommandResponse output{};
    std::size_t tries = 0;
    is_connected = false;

    do {
        const auto result = SendSubCommand(SubCommand::GET_EXTERNAL_DEVICE_INFO, {}, output);

        if (result != Common::Input::DriverResult::Success &&
            result != Common::Input::DriverResult::Timeout) {
            return result;
        }

        if (tries++ >= max_tries) {
            return Common::Input::DriverResult::NoDeviceDetected;
        }
    } while (output.external_device_id != ExternalDeviceId::RingController);

    is_connected = true;
    return Common::Input::DriverResult::Success;
}

Common::Input::DriverResult RingConProtocol::ConfigureRing() {
    LOG_DEBUG(Input, "ConfigureRing");

    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};

    const Common::Input::DriverResult result =
        SendSubCommand(SubCommand::SET_EXTERNAL_FORMAT_CONFIG, ring_config);

    if (result != Common::Input::DriverResult::Success) {
        return result;
    }

    static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02};
    return SendSubCommand(SubCommand::ENABLE_EXTERNAL_POLLING, ringcon_data);
}

bool RingConProtocol::IsEnabled() const {
    return is_enabled;
}

} // namespace InputCommon::Joycon


code:
// 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 Common::Input {
enum class DriverResult;
}

namespace InputCommon::Joycon {

class RingConProtocol final : private JoyconCommonProtocol {
public:
    explicit RingConProtocol(std::shared_ptr<JoyconHandle> handle);

    Common::Input::DriverResult EnableRingCon();

    Common::Input::DriverResult DisableRingCon();

    Common::Input::DriverResult StartRingconPolling();

    bool IsEnabled() const;

private:
    Common::Input::DriverResult IsRingConnected(bool& is_connected);

    Common::Input::DriverResult ConfigureRing();

    bool is_enabled{};
};

} // namespace InputCommon::Joycon


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <algorithm>
#include <cmath>

#include "common/input.h"
#include "common/logging/log.h"
#include "input_common/helpers/joycon_protocol/rumble.h"

namespace InputCommon::Joycon {

RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle)
    : JoyconCommonProtocol(std::move(handle)) {}

Common::Input::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)};
    return SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer);
}

Common::Input::DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) {
    std::array<u8, sizeof(DefaultVibrationBuffer)> buffer{};

    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

    static constexpr std::array<std::pair<f32, int>, 101> high_frequency_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_frequency_amplitude) {
        if (amplitude <= amplitude_value) {
            return static_cast<u8>(code);
        }
    }

    return static_cast<u8>(high_frequency_amplitude[high_frequency_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

    static constexpr std::array<std::pair<f32, int>, 101> high_frequency_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_frequency_amplitude) {
        if (amplitude <= amplitude_value) {
            return static_cast<u16>(code);
        }
    }

    return static_cast<u16>(high_frequency_amplitude[high_frequency_amplitude.size() - 1].second);
}

} // namespace InputCommon::Joycon


code:
// 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 Common::Input {
enum class DriverResult;
}

namespace InputCommon::Joycon {

class RumbleProtocol final : private JoyconCommonProtocol {
public:
    explicit RumbleProtocol(std::shared_ptr<JoyconHandle> handle);

    Common::Input::DriverResult EnableRumble(bool is_enabled);

    Common::Input::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


code:
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <chrono>
#include <cmath>
#include "common/math_util.h"
#include "common/settings.h"
#include "input_common/helpers/stick_from_buttons.h"

namespace InputCommon {

class Stick final : public Common::Input::InputDevice {
public:
    // Some games such as EARTH DEFENSE FORCE: WORLD BROTHERS
    // do not play nicely with the theoretical maximum range.
    // Using a value one lower from the maximum emulates real stick behavior.
    static constexpr float MAX_RANGE = 32766.0f / 32767.0f;
    static constexpr float TAU = Common::PI * 2.0f;
    // Use wider angle to ease the transition.
    static constexpr float APERTURE = TAU * 0.15f;

    using Button = std::unique_ptr<Common::Input::InputDevice>;

    Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, Button updater_,
          float modifier_scale_, float modifier_angle_)
        : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
          right(std::move(right_)), modifier(std::move(modifier_)), updater(std::move(updater_)),
          modifier_scale(modifier_scale_), modifier_angle(modifier_angle_) {
        up->SetCallback({
            .on_change =
                [this](const Common::Input::CallbackStatus& callback_) {
                    UpdateUpButtonStatus(callback_);
                },
        });
        down->SetCallback({
            .on_change =
                [this](const Common::Input::CallbackStatus& callback_) {
                    UpdateDownButtonStatus(callback_);
                },
        });
        left->SetCallback({
            .on_change =
                [this](const Common::Input::CallbackStatus& callback_) {
                    UpdateLeftButtonStatus(callback_);
                },
        });
        right->SetCallback({
            .on_change =
                [this](const Common::Input::CallbackStatus& callback_) {
                    UpdateRightButtonStatus(callback_);
                },
        });
        modifier->SetCallback({
            .on_change =
                [this](const Common::Input::CallbackStatus& callback_) {
                    UpdateModButtonStatus(callback_);
                },
        });
        updater->SetCallback({
            .on_change = [this](const Common::Input::CallbackStatus& callback_) { SoftUpdate(); },
        });
        last_x_axis_value = 0.0f;
        last_y_axis_value = 0.0f;
    }

    bool IsAngleGreater(float old_angle, float new_angle) const {
        const float top_limit = new_angle + APERTURE;
        return (old_angle > new_angle && old_angle <= top_limit) ||
               (old_angle + TAU > new_angle && old_angle + TAU <= top_limit);
    }

    bool IsAngleSmaller(float old_angle, float new_angle) const {
        const float bottom_limit = new_angle - APERTURE;
        return (old_angle >= bottom_limit && old_angle < new_angle) ||
               (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle);
    }

    float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const {
        float new_angle = angle;

        auto time_difference = static_cast<float>(
            std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count());
        time_difference /= 1000.0f;
        if (time_difference > 0.5f) {
            time_difference = 0.5f;
        }

        if (IsAngleGreater(new_angle, goal_angle)) {
            new_angle -= modifier_angle * time_difference;
            if (new_angle < 0) {
                new_angle += TAU;
            }
            if (!IsAngleGreater(new_angle, goal_angle)) {
                return goal_angle;
            }
        } else if (IsAngleSmaller(new_angle, goal_angle)) {
            new_angle += modifier_angle * time_difference;
            if (new_angle >= TAU) {
                new_angle -= TAU;
            }
            if (!IsAngleSmaller(new_angle, goal_angle)) {
                return goal_angle;
            }
        } else {
            return goal_angle;
        }
        return new_angle;
    }

    void SetGoalAngle(bool r, bool l, bool u, bool d) {
        // Move to the right
        if (r && !u && !d) {
            goal_angle = 0.0f;
        }

        // Move to the upper right
        if (r && u && !d) {
            goal_angle = Common::PI * 0.25f;
        }

        // Move up
        if (u && !l && !r) {
            goal_angle = Common::PI * 0.5f;
        }

        // Move to the upper left
        if (l && u && !d) {
            goal_angle = Common::PI * 0.75f;
        }

        // Move to the left
        if (l && !u && !d) {
            goal_angle = Common::PI;
        }

        // Move to the bottom left
        if (l && !u && d) {
            goal_angle = Common::PI * 1.25f;
        }

        // Move down
        if (d && !l && !r) {
            goal_angle = Common::PI * 1.5f;
        }

        // Move to the bottom right
        if (r && !u && d) {
            goal_angle = Common::PI * 1.75f;
        }
    }

    void UpdateUpButtonStatus(const Common::Input::CallbackStatus& button_callback) {
        up_status = button_callback.button_status.value;
        UpdateStatus();
    }

    void UpdateDownButtonStatus(const Common::Input::CallbackStatus& button_callback) {
        down_status = button_callback.button_status.value;
        UpdateStatus();
    }

    void UpdateLeftButtonStatus(const Common::Input::CallbackStatus& button_callback) {
        left_status = button_callback.button_status.value;
        UpdateStatus();
    }

    void UpdateRightButtonStatus(const Common::Input::CallbackStatus& button_callback) {
        right_status = button_callback.button_status.value;
        UpdateStatus();
    }

    void UpdateModButtonStatus(const Common::Input::CallbackStatus& button_callback) {
        const auto& new_status = button_callback.button_status;
        const bool new_button_value = new_status.inverted ? !new_status.value : new_status.value;
        modifier_status.toggle = new_status.toggle;

        // Update button status with current
        if (!modifier_status.toggle) {
            modifier_status.locked = false;
            if (modifier_status.value != new_button_value) {
                modifier_status.value = new_button_value;
            }
        } else {
            // Toggle button and lock status
            if (new_button_value && !modifier_status.locked) {
                modifier_status.locked = true;
                modifier_status.value = !modifier_status.value;
            }

            // Unlock button ready for next press
            if (!new_button_value && modifier_status.locked) {
                modifier_status.locked = false;
            }
        }

        UpdateStatus();
    }

    void UpdateStatus() {
        bool r = right_status;
        bool l = left_status;
        bool u = up_status;
        bool d = down_status;

        // Eliminate contradictory movements
        if (r && l) {
            r = false;
            l = false;
        }
        if (u && d) {
            u = false;
            d = false;
        }

        // Move if a key is pressed
        if (r || l || u || d) {
            amplitude = modifier_status.value ? modifier_scale : MAX_RANGE;
        } else {
            amplitude = 0;
        }

        const auto now = std::chrono::steady_clock::now();
        const auto time_difference = static_cast<u64>(
            std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count());

        if (time_difference < 10) {
            // Disable analog mode if inputs are too fast
            SetGoalAngle(r, l, u, d);
            angle = goal_angle;
        } else {
            angle = GetAngle(now);
            SetGoalAngle(r, l, u, d);
        }

        last_update = now;
        Common::Input::CallbackStatus status{
            .type = Common::Input::InputType::Stick,
            .stick_status = GetStatus(),
        };
        last_x_axis_value = status.stick_status.x.raw_value;
        last_y_axis_value = status.stick_status.y.raw_value;
        TriggerOnChange(status);
    }

    void ForceUpdate() override {
        up->ForceUpdate();
        down->ForceUpdate();
        left->ForceUpdate();
        right->ForceUpdate();
        modifier->ForceUpdate();
    }

    void SoftUpdate() {
        Common::Input::CallbackStatus status{
            .type = Common::Input::InputType::Stick,
            .stick_status = GetStatus(),
        };
        if (last_x_axis_value == status.stick_status.x.raw_value &&
            last_y_axis_value == status.stick_status.y.raw_value) {
            return;
        }
        last_x_axis_value = status.stick_status.x.raw_value;
        last_y_axis_value = status.stick_status.y.raw_value;
        TriggerOnChange(status);
    }

    Common::Input::StickStatus GetStatus() const {
        Common::Input::StickStatus status{};
        status.x.properties = properties;
        status.y.properties = properties;

        if (Settings::values.emulate_analog_keyboard) {
            const auto now = std::chrono::steady_clock::now();
            const float angle_ = GetAngle(now);
            status.x.raw_value = std::cos(angle_) * amplitude;
            status.y.raw_value = std::sin(angle_) * amplitude;
            return status;
        }

        status.x.raw_value = std::cos(goal_angle) * amplitude;
        status.y.raw_value = std::sin(goal_angle) * amplitude;
        return status;
    }

private:
    static constexpr Common::Input::AnalogProperties properties{
        .deadzone = 0.0f,
        .range = 1.0f,
        .threshold = 0.5f,
        .offset = 0.0f,
        .inverted = false,
        .toggle = false,
    };

    Button up;
    Button down;
    Button left;
    Button right;
    Button modifier;
    Button updater;
    float modifier_scale{};
    float modifier_angle{};
    float angle{};
    float goal_angle{};
    float amplitude{};
    bool up_status{};
    bool down_status{};
    bool left_status{};
    bool right_status{};
    float last_x_axis_value{};
    float last_y_axis_value{};
    Common::Input::ButtonStatus modifier_status{};
    std::chrono::time_point<std::chrono::steady_clock> last_update;
};

std::unique_ptr<Common::Input::InputDevice> StickFromButton::Create(
    const Common::ParamPackage& params) {
    const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
    auto up = Common::Input::CreateInputDeviceFromString(params.Get("up", null_engine));
    auto down = Common::Input::CreateInputDeviceFromString(params.Get("down", null_engine));
    auto left = Common::Input::CreateInputDeviceFromString(params.Get("left", null_engine));
    auto right = Common::Input::CreateInputDeviceFromString(params.Get("right", null_engine));
    auto modifier = Common::Input::CreateInputDeviceFromString(params.Get("modifier", null_engine));
    auto updater = Common::Input::CreateInputDeviceFromString("engine:updater,button:0");
    auto modifier_scale = params.Get("modifier_scale", 0.5f);
    auto modifier_angle = params.Get("modifier_angle", 5.5f);
    return std::make_unique<Stick>(std::move(up), std::move(down), std::move(left),
                                   std::move(right), std::move(modifier), std::move(updater),
                                   modifier_scale, modifier_angle);
}

} // namespace InputCommon


code:
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "common/input.h"

namespace InputCommon {

/**
 * An analog device factory that takes direction button devices and combines them into a analog
 * device.
 */
class StickFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
public:
    /**
     * Creates an analog device from direction button devices
     * @param params contains parameters for creating the device:
     *     - "up": a serialized ParamPackage for creating a button device for up direction
     *     - "down": a serialized ParamPackage for creating a button device for down direction
     *     - "left": a serialized ParamPackage for creating a button device for left direction
     *     - "right": a serialized ParamPackage  for creating a button device for right direction
     *     - "modifier": a serialized ParamPackage for creating a button device as the modifier
     *     - "modifier_scale": a float for the multiplier the modifier gives to the position
     */
    std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
};

} // namespace InputCommon


code:
// SPDX-FileCopyrightText: 2020 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <algorithm>
#include "common/settings.h"
#include "input_common/helpers/touch_from_buttons.h"

namespace InputCommon {

class TouchFromButtonDevice final : public Common::Input::InputDevice {
public:
    using Button = std::unique_ptr<Common::Input::InputDevice>;
    TouchFromButtonDevice(Button button_, float x_, float y_)
        : button(std::move(button_)), x(x_), y(y_) {
        last_button_value = false;
        button->SetCallback({
            .on_change =
                [this](const Common::Input::CallbackStatus& callback_) {
                    UpdateButtonStatus(callback_);
                },
        });
        button->ForceUpdate();
    }

    void ForceUpdate() override {
        button->ForceUpdate();
    }

    Common::Input::TouchStatus GetStatus(bool pressed) const {
        const Common::Input::ButtonStatus button_status{
            .value = pressed,
        };
        Common::Input::TouchStatus status{
            .pressed = button_status,
            .x = {},
            .y = {},
        };
        status.x.properties = properties;
        status.y.properties = properties;

        if (!pressed) {
            return status;
        }

        status.x.raw_value = x;
        status.y.raw_value = y;
        return status;
    }

    void UpdateButtonStatus(const Common::Input::CallbackStatus& button_callback) {
        const Common::Input::CallbackStatus status{
            .type = Common::Input::InputType::Touch,
            .touch_status = GetStatus(button_callback.button_status.value),
        };
        if (last_button_value != button_callback.button_status.value) {
            last_button_value = button_callback.button_status.value;
            TriggerOnChange(status);
        }
    }

private:
    static constexpr Common::Input::AnalogProperties properties{
        .deadzone = 0.0f,
        .range = 1.0f,
        .threshold = 0.5f,
        .offset = 0.0f,
        .inverted = false,
        .toggle = false,
    };

    Button button;
    bool last_button_value;
    const float x;
    const float y;
};

std::unique_ptr<Common::Input::InputDevice> TouchFromButton::Create(
    const Common::ParamPackage& params) {
    const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
    auto button = Common::Input::CreateInputDeviceFromString(params.Get("button", null_engine));
    const float x = params.Get("x", 0.0f) / 1280.0f;
    const float y = params.Get("y", 0.0f) / 720.0f;
    return std::make_unique<TouchFromButtonDevice>(std::move(button), x, y);
}

} // namespace InputCommon


code:
// SPDX-FileCopyrightText: 2020 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "common/input.h"

namespace InputCommon {

/**
 * A touch device factory that takes a list of button devices and combines them into a touch device.
 */
class TouchFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
public:
    /**
     * Creates a touch device from a list of button devices
     */
    std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
};

} // namespace InputCommon


code:
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <cstddef>
#include <cstring>
#include "common/logging/log.h"
#include "input_common/helpers/udp_protocol.h"

namespace InputCommon::CemuhookUDP {

static constexpr std::size_t GetSizeOfResponseType(Type t) {
    switch (t) {
    case Type::Version:
        return sizeof(Response::Version);
    case Type::PortInfo:
        return sizeof(Response::PortInfo);
    case Type::PadData:
        return sizeof(Response::PadData);
    }
    return 0;
}

namespace Response {

/**
 * Returns Type if the packet is valid, else none
 *
 * Note: Modifies the buffer to zero out the crc (since that's the easiest way to check without
 * copying the buffer)
 */
std::optional<Type> Validate(u8* data, std::size_t size) {
    if (size < sizeof(Header)) {
        return std::nullopt;
    }
    Header header{};
    std::memcpy(&header, data, sizeof(Header));
    if (header.magic != SERVER_MAGIC) {
        LOG_ERROR(Input, "UDP Packet has an unexpected magic value");
        return std::nullopt;
    }
    if (header.protocol_version != PROTOCOL_VERSION) {
        LOG_ERROR(Input, "UDP Packet protocol mismatch");
        return std::nullopt;
    }
    if (header.type < Type::Version || header.type > Type::PadData) {
        LOG_ERROR(Input, "UDP Packet is an unknown type");
        return std::nullopt;
    }

    // Packet size must equal sizeof(Header) + sizeof(Data)
    // and also verify that the packet info mentions the correct size. Since the spec includes the
    // type of the packet as part of the data, we need to include it in size calculations here
    // ie: payload_length == sizeof(T) + sizeof(Type)
    const std::size_t data_len = GetSizeOfResponseType(header.type);
    if (header.payload_length != data_len + sizeof(Type) || size < data_len + sizeof(Header)) {
        LOG_ERROR(
            Input,
            "UDP Packet payload length doesn't match. Received: {} PayloadLength: {} Expected: {}",
            size, header.payload_length, data_len + sizeof(Type));
        return std::nullopt;
    }

    const u32 crc32 = header.crc;
    boost::crc_32_type result;
    // zero out the crc in the buffer and then run the crc against it
    std::memset(&data[offsetof(Header, crc)], 0, sizeof(u32_le));

    result.process_bytes(data, data_len + sizeof(Header));
    if (crc32 != result.checksum()) {
        LOG_ERROR(Input, "UDP Packet CRC check failed. Offset: {}", offsetof(Header, crc));
        return std::nullopt;
    }
    return header.type;
}
} // namespace Response

} // namespace InputCommon::CemuhookUDP


code:
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <optional>
#include <type_traits>

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4701) // Potentially uninitialized local variable 'result' used
#endif

#include <boost/crc.hpp>

#ifdef _MSC_VER
#pragma warning(pop)
#endif

#include "common/swap.h"

namespace InputCommon::CemuhookUDP {

constexpr std::size_t MAX_PACKET_SIZE = 100;
constexpr u16 PROTOCOL_VERSION = 1001;
constexpr u32 CLIENT_MAGIC = 0x43555344; // DSUC (but flipped for LE)
constexpr u32 SERVER_MAGIC = 0x53555344; // DSUS (but flipped for LE)

enum class Type : u32 {
    Version = 0x00100000,
    PortInfo = 0x00100001,
    PadData = 0x00100002,
};

struct Header {
    u32_le magic{};
    u16_le protocol_version{};
    u16_le payload_length{};
    u32_le crc{};
    u32_le id{};
    ///> In the protocol, the type of the packet is not part of the header, but its convenient to
    ///> include in the header so the callee doesn't have to duplicate the type twice when building
    ///> the data
    Type type{};
};
static_assert(sizeof(Header) == 20, "UDP Message Header struct has wrong size");
static_assert(std::is_trivially_copyable_v<Header>, "UDP Message Header is not trivially copyable");

using MacAddress = std::array<u8, 6>;
constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0};

#pragma pack(push, 1)
template <typename T>
struct Message {
    Header header{};
    T data;
};
#pragma pack(pop)

template <typename T>
constexpr Type GetMessageType();

template <typename T>
Message<T> CreateMessage(const u32 magic, const T data, const u32 sender_id) {
    boost::crc_32_type crc;
    Header header{
        magic, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, sender_id, GetMessageType<T>(),
    };
    Message<T> message{header, data};
    crc.process_bytes(&message, sizeof(Message<T>));
    message.header.crc = crc.checksum();
    return message;
}

namespace Request {

enum RegisterFlags : u8 {
    AllPads,
    PadID,
    PadMACAddress,
};

struct Version {};
/**
 * Requests the server to send information about what controllers are plugged into the ports
 * In yuzu's case, we only have one controller, so for simplicity's sake, we can just send a
 * request explicitly for the first controller port and leave it at that. In the future it would be
 * nice to make this configurable
 */
constexpr u32 MAX_PORTS = 4;
struct PortInfo {
    u32_le pad_count{}; ///> Number of ports to request data for
    std::array<u8, MAX_PORTS> port;
};
static_assert(std::is_trivially_copyable_v<PortInfo>,
              "UDP Request PortInfo is not trivially copyable");

/**
 * Request the latest pad information from the server. If the server hasn't received this message
 * from the client in a reasonable time frame, the server will stop sending updates. The default
 * timeout seems to be 5 seconds.
 */
struct PadData {
    /// Determines which method will be used as a look up for the controller
    RegisterFlags flags{};
    /// Index of the port of the controller to retrieve data about
    u8 port_id{};
    /// Mac address of the controller to retrieve data about
    MacAddress mac;
};
static_assert(sizeof(PadData) == 8, "UDP Request PadData struct has wrong size");
static_assert(std::is_trivially_copyable_v<PadData>,
              "UDP Request PadData is not trivially copyable");

/**
 * Creates a message with the proper header data that can be sent to the server.
 * @param data Request body to send
 * @param client_id ID of the udp client (usually not checked on the server)
 */
template <typename T>
Message<T> Create(const T data, const u32 client_id = 0) {
    return CreateMessage(CLIENT_MAGIC, data, client_id);
}
} // namespace Request

namespace Response {

enum class ConnectionType : u8 {
    None,
    Usb,
    Bluetooth,
};

enum class State : u8 {
    Disconnected,
    Reserved,
    Connected,
};

enum class Model : u8 {
    None,
    PartialGyro,
    FullGyro,
    Generic,
};

enum class Battery : u8 {
    None = 0x00,
    Dying = 0x01,
    Low = 0x02,
    Medium = 0x03,
    High = 0x04,
    Full = 0x05,
    Charging = 0xEE,
    Charged = 0xEF,
};

struct Version {
    u16_le version{};
};
static_assert(sizeof(Version) == 2, "UDP Response Version struct has wrong size");
static_assert(std::is_trivially_copyable_v<Version>,
              "UDP Response Version is not trivially copyable");

struct PortInfo {
    u8 id{};
    State state{};
    Model model{};
    ConnectionType connection_type{};
    MacAddress mac;
    Battery battery{};
    u8 is_pad_active{};
};
static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size");
static_assert(std::is_trivially_copyable_v<PortInfo>,
              "UDP Response PortInfo is not trivially copyable");

struct TouchPad {
    u8 is_active{};
    u8 id{};
    u16_le x{};
    u16_le y{};
};
static_assert(sizeof(TouchPad) == 6, "UDP Response TouchPad struct has wrong size ");

#pragma pack(push, 1)
struct PadData {
    PortInfo info{};
    u32_le packet_counter{};

    u16_le digital_button{};
    // The following union isn't trivially copyable but we don't use this input anyway.
    // union DigitalButton {
    //     u16_le button;
    //     BitField<0, 1, u16> button_1;   // Share
    //     BitField<1, 1, u16> button_2;   // L3
    //     BitField<2, 1, u16> button_3;   // R3
    //     BitField<3, 1, u16> button_4;   // Options
    //     BitField<4, 1, u16> button_5;   // Up
    //     BitField<5, 1, u16> button_6;   // Right
    //     BitField<6, 1, u16> button_7;   // Down
    //     BitField<7, 1, u16> button_8;   // Left
    //     BitField<8, 1, u16> button_9;   // L2
    //     BitField<9, 1, u16> button_10;  // R2
    //     BitField<10, 1, u16> button_11; // L1
    //     BitField<11, 1, u16> button_12; // R1
    //     BitField<12, 1, u16> button_13; // Triangle
    //     BitField<13, 1, u16> button_14; // Circle
    //     BitField<14, 1, u16> button_15; // Cross
    //     BitField<15, 1, u16> button_16; // Square
    // } digital_button;

    u8 home;
    /// If the device supports a "click" on the touchpad, this will change to 1 when a click happens
    u8 touch_hard_press{};
    u8 left_stick_x{};
    u8 left_stick_y{};
    u8 right_stick_x{};
    u8 right_stick_y{};

    struct AnalogButton {
        u8 button_dpad_left_analog{};
        u8 button_dpad_down_analog{};
        u8 button_dpad_right_analog{};
        u8 button_dpad_up_analog{};
        u8 button_square_analog{};
        u8 button_cross_analog{};
        u8 button_circle_analog{};
        u8 button_triangle_analog{};
        u8 button_r1_analog{};
        u8 button_l1_analog{};
        u8 trigger_r2{};
        u8 trigger_l2{};
    } analog_button;

    std::array<TouchPad, 2> touch;

    u64_le motion_timestamp;

    struct Accelerometer {
        float x{};
        float y{};
        float z{};
    } accel;

    struct Gyroscope {
        float pitch{};
        float yaw{};
        float roll{};
    } gyro;
};
#pragma pack(pop)

static_assert(sizeof(PadData) == 80, "UDP Response PadData struct has wrong size ");
static_assert(std::is_trivially_copyable_v<PadData>,
              "UDP Response PadData is not trivially copyable");

static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE,
              "UDP MAX_PACKET_SIZE is no longer larger than Message<PadData>");

static_assert(sizeof(PadData::AnalogButton) == 12,
              "UDP Response AnalogButton struct has wrong size ");
static_assert(sizeof(PadData::Accelerometer) == 12,
              "UDP Response Accelerometer struct has wrong size ");
static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size ");

/**
 * Create a Response Message from the data
 * @param data array of bytes sent from the server
 * @return boost::none if it failed to parse or Type if it succeeded. The client can then safely
 * copy the data into the appropriate struct for that Type
 */
std::optional<Type> Validate(u8* data, std::size_t size);

} // namespace Response

template <>
constexpr Type GetMessageType<Request::Version>() {
    return Type::Version;
}
template <>
constexpr Type GetMessageType<Request::PortInfo>() {
    return Type::PortInfo;
}
template <>
constexpr Type GetMessageType<Request::PadData>() {
    return Type::PadData;
}
template <>
constexpr Type GetMessageType<Response::Version>() {
    return Type::Version;
}
template <>
constexpr Type GetMessageType<Response::PortInfo>() {
    return Type::PortInfo;
}
template <>
constexpr Type GetMessageType<Response::PadData>() {
    return Type::PadData;
}
} // namespace InputCommon::CemuhookUDP


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/logging/log.h"
#include "input_common/input_engine.h"

namespace InputCommon {

void InputEngine::PreSetController(const PadIdentifier& identifier) {
    std::scoped_lock lock{mutex};
    controller_list.try_emplace(identifier);
}

void InputEngine::PreSetButton(const PadIdentifier& identifier, int button) {
    std::scoped_lock lock{mutex};
    ControllerData& controller = controller_list.at(identifier);
    controller.buttons.try_emplace(button, false);
}

void InputEngine::PreSetHatButton(const PadIdentifier& identifier, int button) {
    std::scoped_lock lock{mutex};
    ControllerData& controller = controller_list.at(identifier);
    controller.hat_buttons.try_emplace(button, u8{0});
}

void InputEngine::PreSetAxis(const PadIdentifier& identifier, int axis) {
    std::scoped_lock lock{mutex};
    ControllerData& controller = controller_list.at(identifier);
    controller.axes.try_emplace(axis, 0.0f);
}

void InputEngine::PreSetMotion(const PadIdentifier& identifier, int motion) {
    std::scoped_lock lock{mutex};
    ControllerData& controller = controller_list.at(identifier);
    controller.motions.try_emplace(motion);
}

void InputEngine::SetButton(const PadIdentifier& identifier, int button, bool value) {
    {
        std::scoped_lock lock{mutex};
        ControllerData& controller = controller_list.at(identifier);
        if (!configuring) {
            controller.buttons.insert_or_assign(button, value);
        }
    }
    TriggerOnButtonChange(identifier, button, value);
}

void InputEngine::SetHatButton(const PadIdentifier& identifier, int button, u8 value) {
    {
        std::scoped_lock lock{mutex};
        ControllerData& controller = controller_list.at(identifier);
        if (!configuring) {
            controller.hat_buttons.insert_or_assign(button, value);
        }
    }
    TriggerOnHatButtonChange(identifier, button, value);
}

void InputEngine::SetAxis(const PadIdentifier& identifier, int axis, f32 value) {
    {
        std::scoped_lock lock{mutex};
        ControllerData& controller = controller_list.at(identifier);
        if (!configuring) {
            controller.axes.insert_or_assign(axis, value);
        }
    }
    TriggerOnAxisChange(identifier, axis, value);
}

void InputEngine::SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value) {
    {
        std::scoped_lock lock{mutex};
        ControllerData& controller = controller_list.at(identifier);
        if (!configuring) {
            controller.battery = value;
        }
    }
    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};
        ControllerData& controller = controller_list.at(identifier);
        if (!configuring) {
            controller.motions.insert_or_assign(motion, value);
        }
    }
    TriggerOnMotionChange(identifier, motion, value);
}

void InputEngine::SetCamera(const PadIdentifier& identifier,
                            const Common::Input::CameraStatus& value) {
    {
        std::scoped_lock lock{mutex};
        ControllerData& controller = controller_list.at(identifier);
        if (!configuring) {
            controller.camera = value;
        }
    }
    TriggerOnCameraChange(identifier, value);
}

void InputEngine::SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value) {
    {
        std::scoped_lock lock{mutex};
        ControllerData& controller = controller_list.at(identifier);
        if (!configuring) {
            controller.nfc = value;
        }
    }
    TriggerOnNfcChange(identifier, value);
}

bool InputEngine::GetButton(const PadIdentifier& identifier, int button) 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 false;
    }
    const ControllerData& controller = controller_iter->second;
    const auto button_iter = controller.buttons.find(button);
    if (button_iter == controller.buttons.cend()) {
        LOG_ERROR(Input, "Invalid button {}", button);
        return false;
    }
    return button_iter->second;
}

bool InputEngine::GetHatButton(const PadIdentifier& identifier, int button, u8 direction) 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 false;
    }
    const ControllerData& controller = controller_iter->second;
    const auto hat_iter = controller.hat_buttons.find(button);
    if (hat_iter == controller.hat_buttons.cend()) {
        LOG_ERROR(Input, "Invalid hat button {}", button);
        return false;
    }
    return (hat_iter->second & direction) != 0;
}

f32 InputEngine::GetAxis(const PadIdentifier& identifier, int axis) 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 0.0f;
    }
    const ControllerData& controller = controller_iter->second;
    const auto axis_iter = controller.axes.find(axis);
    if (axis_iter == controller.axes.cend()) {
        LOG_ERROR(Input, "Invalid axis {}", axis);
        return 0.0f;
    }
    return axis_iter->second;
}

Common::Input::BatteryLevel InputEngine::GetBattery(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 Common::Input::BatteryLevel::Charging;
    }
    const ControllerData& controller = controller_iter->second;
    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);
    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.motions.at(motion);
}

Common::Input::CameraStatus InputEngine::GetCamera(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.camera;
}

Common::Input::NfcStatus InputEngine::GetNfc(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.nfc;
}

void InputEngine::ResetButtonState() {
    for (const auto& controller : controller_list) {
        for (const auto& button : controller.second.buttons) {
            SetButton(controller.first, button.first, false);
        }
        for (const auto& button : controller.second.hat_buttons) {
            SetHatButton(controller.first, button.first, 0);
        }
    }
}

void InputEngine::ResetAnalogState() {
    for (const auto& controller : controller_list) {
        for (const auto& axis : controller.second.axes) {
            SetAxis(controller.first, axis.first, 0.0);
        }
    }
}

void InputEngine::TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool 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::Button, button)) {
            continue;
        }
        if (poller.callback.on_change) {
            poller.callback.on_change();
        }
    }
    if (!configuring || !mapping_callback.on_data) {
        return;
    }

    PreSetButton(identifier, button);
    if (value == GetButton(identifier, button)) {
        return;
    }
    mapping_callback.on_data(MappingData{
        .engine = GetEngineName(),
        .pad = identifier,
        .type = EngineInputType::Button,
        .index = button,
        .button_value = value,
    });
}

void InputEngine::TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 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::HatButton, button)) {
            continue;
        }
        if (poller.callback.on_change) {
            poller.callback.on_change();
        }
    }
    if (!configuring || !mapping_callback.on_data) {
        return;
    }
    for (std::size_t index = 1; index < 0xff; index <<= 1) {
        bool button_value = (value & index) != 0;
        if (button_value == GetHatButton(identifier, button, static_cast<u8>(index))) {
            continue;
        }
        mapping_callback.on_data(MappingData{
            .engine = GetEngineName(),
            .pad = identifier,
            .type = EngineInputType::HatButton,
            .index = button,
            .hat_name = GetHatButtonName(static_cast<u8>(index)),
        });
    }
}

void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 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::Analog, axis)) {
            continue;
        }
        if (poller.callback.on_change) {
            poller.callback.on_change();
        }
    }
    if (!configuring || !mapping_callback.on_data) {
        return;
    }
    if (std::abs(value - GetAxis(identifier, axis)) < 0.5f) {
        return;
    }
    mapping_callback.on_data(MappingData{
        .engine = GetEngineName(),
        .pad = identifier,
        .type = EngineInputType::Analog,
        .index = axis,
        .axis_value = value,
    });
}

void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
                                         [[maybe_unused]] Common::Input::BatteryLevel 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::Battery, 0)) {
            continue;
        }
        if (poller.callback.on_change) {
            poller.callback.on_change();
        }
    }
}

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};
    for (const auto& poller_pair : callback_list) {
        const InputIdentifier& poller = poller_pair.second;
        if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Motion, motion)) {
            continue;
        }
        if (poller.callback.on_change) {
            poller.callback.on_change();
        }
    }
    if (!configuring || !mapping_callback.on_data) {
        return;
    }
    const auto old_value = GetMotion(identifier, motion);
    bool is_active = false;
    if (std::abs(value.accel_x - old_value.accel_x) > 1.5f ||
        std::abs(value.accel_y - old_value.accel_y) > 1.5f ||
        std::abs(value.accel_z - old_value.accel_z) > 1.5f) {
        is_active = true;
    }
    if (std::abs(value.gyro_x - old_value.gyro_x) > 0.6f ||
        std::abs(value.gyro_y - old_value.gyro_y) > 0.6f ||
        std::abs(value.gyro_z - old_value.gyro_z) > 0.6f) {
        is_active = true;
    }
    if (!is_active) {
        return;
    }
    mapping_callback.on_data(MappingData{
        .engine = GetEngineName(),
        .pad = identifier,
        .type = EngineInputType::Motion,
        .index = motion,
        .motion_value = value,
    });
}

void InputEngine::TriggerOnCameraChange(const PadIdentifier& identifier,
                                        [[maybe_unused]] const Common::Input::CameraStatus& 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::Camera, 0)) {
            continue;
        }
        if (poller.callback.on_change) {
            poller.callback.on_change();
        }
    }
}

void InputEngine::TriggerOnNfcChange(const PadIdentifier& identifier,
                                     [[maybe_unused]] const Common::Input::NfcStatus& 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::Nfc, 0)) {
            continue;
        }
        if (poller.callback.on_change) {
            poller.callback.on_change();
        }
    }
}

bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier,
                                         const PadIdentifier& identifier, EngineInputType type,
                                         int index) const {
    if (input_identifier.type != type) {
        return false;
    }
    if (input_identifier.index != index) {
        return false;
    }
    if (input_identifier.identifier != identifier) {
        return false;
    }
    return true;
}

void InputEngine::BeginConfiguration() {
    configuring = true;
}

void InputEngine::EndConfiguration() {
    configuring = false;
}

const std::string& InputEngine::GetEngineName() const {
    return input_engine;
}

int InputEngine::SetCallback(InputIdentifier input_identifier) {
    std::scoped_lock lock{mutex_callback};
    callback_list.insert_or_assign(last_callback_key, std::move(input_identifier));
    return last_callback_key++;
}

void InputEngine::SetMappingCallback(MappingCallback callback) {
    std::scoped_lock lock{mutex_callback};
    mapping_callback = std::move(callback);
}

void InputEngine::DeleteCallback(int key) {
    std::scoped_lock lock{mutex_callback};
    const auto& iterator = callback_list.find(key);
    if (iterator == callback_list.end()) {
        LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
        return;
    }
    callback_list.erase(iterator);
}

} // namespace InputCommon


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <functional>
#include <mutex>
#include <unordered_map>

#include "common/common_types.h"
#include "common/input.h"
#include "common/param_package.h"
#include "common/uuid.h"
#include "input_common/main.h"

// Pad Identifier of data source
struct PadIdentifier {
    Common::UUID guid{};
    std::size_t port{};
    std::size_t pad{};

    friend constexpr bool operator==(const PadIdentifier&, const PadIdentifier&) = default;
};

// Basic motion data containing data from the sensors and a timestamp in microseconds
struct BasicMotion {
    float gyro_x{};
    float gyro_y{};
    float gyro_z{};
    float accel_x{};
    float accel_y{};
    float accel_z{};
    u64 delta_timestamp{};
};

// Types of input that are stored in the engine
enum class EngineInputType {
    None,
    Analog,
    Battery,
    Button,
    Camera,
    Color,
    HatButton,
    Motion,
    Nfc,
};

struct VibrationRequest {
    PadIdentifier identifier;
    Common::Input::VibrationStatus vibration;
};

namespace std {
// Hash used to create lists from PadIdentifier data
template <>
struct hash<PadIdentifier> {
    size_t operator()(const PadIdentifier& pad_id) const noexcept {
        u64 hash_value = pad_id.guid.Hash();
        hash_value ^= (static_cast<u64>(pad_id.port) << 32);
        hash_value ^= static_cast<u64>(pad_id.pad);
        return static_cast<size_t>(hash_value);
    }
};

} // namespace std

namespace InputCommon {

// Data from the engine and device needed for creating a ParamPackage
struct MappingData {
    std::string engine{};
    PadIdentifier pad{};
    EngineInputType type{};
    int index{};
    bool button_value{};
    std::string hat_name{};
    f32 axis_value{};
    BasicMotion motion_value{};
};

// Triggered if data changed on the controller
struct UpdateCallback {
    std::function<void()> on_change;
};

// Triggered if data changed on the controller and the engine is on configuring mode
struct MappingCallback {
    std::function<void(const MappingData&)> on_data;
};

// Input Identifier of data source
struct InputIdentifier {
    PadIdentifier identifier;
    EngineInputType type;
    int index;
    UpdateCallback callback;
};

class InputEngine {
public:
    explicit InputEngine(std::string input_engine_) : input_engine{std::move(input_engine_)} {}

    virtual ~InputEngine() = default;

    // Enable configuring mode for mapping
    void BeginConfiguration();

    // Disable configuring mode for mapping
    void EndConfiguration();

    // Sets a led pattern for a controller
    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::DriverResult SetVibration(
        [[maybe_unused]] const PadIdentifier& identifier,
        [[maybe_unused]] const Common::Input::VibrationStatus& vibration) {
        return Common::Input::DriverResult::NotSupported;
    }

    // Returns true if device supports vibrations
    virtual bool IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) {
        return false;
    }

    // Sets polling mode to a controller
    virtual Common::Input::DriverResult SetPollingMode(
        [[maybe_unused]] const PadIdentifier& identifier,
        [[maybe_unused]] const Common::Input::PollingMode polling_mode) {
        return Common::Input::DriverResult::NotSupported;
    }

    // Sets camera format to a controller
    virtual Common::Input::DriverResult SetCameraFormat(
        [[maybe_unused]] const PadIdentifier& identifier,
        [[maybe_unused]] Common::Input::CameraFormat camera_format) {
        return Common::Input::DriverResult::NotSupported;
    }

    // Returns success if nfc is supported
    virtual Common::Input::NfcState SupportsNfc(
        [[maybe_unused]] const PadIdentifier& identifier) const {
        return Common::Input::NfcState::NotSupported;
    }

    // Start scanning for nfc tags
    virtual Common::Input::NfcState StartNfcPolling(
        [[maybe_unused]] const PadIdentifier& identifier_) {
        return Common::Input::NfcState::NotSupported;
    }

    // Start scanning for nfc tags
    virtual Common::Input::NfcState StopNfcPolling(
        [[maybe_unused]] const PadIdentifier& identifier_) {
        return Common::Input::NfcState::NotSupported;
    }

    // Reads data from amiibo tag
    virtual Common::Input::NfcState ReadAmiiboData(
        [[maybe_unused]] const PadIdentifier& identifier_,
        [[maybe_unused]] std::vector<u8>& out_data) {
        return Common::Input::NfcState::NotSupported;
    }

    // Writes data to an nfc tag
    virtual Common::Input::NfcState WriteNfcData([[maybe_unused]] const PadIdentifier& identifier,
                                                 [[maybe_unused]] const std::vector<u8>& data) {
        return Common::Input::NfcState::NotSupported;
    }

    // Reads data from mifare tag
    virtual Common::Input::NfcState ReadMifareData(
        [[maybe_unused]] const PadIdentifier& identifier_,
        [[maybe_unused]] const Common::Input::MifareRequest& request,
        [[maybe_unused]] Common::Input::MifareRequest& out_data) {
        return Common::Input::NfcState::NotSupported;
    }

    // Write data to mifare tag
    virtual Common::Input::NfcState WriteMifareData(
        [[maybe_unused]] const PadIdentifier& identifier_,
        [[maybe_unused]] const Common::Input::MifareRequest& request) {
        return Common::Input::NfcState::NotSupported;
    }

    // Returns the engine name
    [[nodiscard]] const std::string& GetEngineName() const;

    /// Used for automapping features
    virtual std::vector<Common::ParamPackage> GetInputDevices() const {
        return {};
    }

    /// Retrieves the button mappings for the given device
    virtual ButtonMapping GetButtonMappingForDevice(
        [[maybe_unused]] const Common::ParamPackage& params) {
        return {};
    }

    /// Retrieves the analog mappings for the given device
    virtual AnalogMapping GetAnalogMappingForDevice(
        [[maybe_unused]] const Common::ParamPackage& params) {
        return {};
    }

    /// Retrieves the motion mappings for the given device
    virtual MotionMapping GetMotionMappingForDevice(
        [[maybe_unused]] const Common::ParamPackage& params) {
        return {};
    }

    /// Retrieves the name of the given input.
    virtual Common::Input::ButtonNames GetUIName(
        [[maybe_unused]] const Common::ParamPackage& params) const {
        return Common::Input::ButtonNames::Engine;
    }

    /// Retrieves the index number of the given hat button direction
    virtual u8 GetHatButtonId([[maybe_unused]] const std::string& direction_name) const {
        return 0;
    }

    /// Returns true if axis of a stick aren't mapped in the correct direction
    virtual bool IsStickInverted([[maybe_unused]] const Common::ParamPackage& params) {
        return false;
    }

    void PreSetController(const PadIdentifier& identifier);
    void PreSetButton(const PadIdentifier& identifier, int button);
    void PreSetHatButton(const PadIdentifier& identifier, int button);
    void PreSetAxis(const PadIdentifier& identifier, int axis);
    void PreSetMotion(const PadIdentifier& identifier, int motion);
    void ResetButtonState();
    void ResetAnalogState();

    bool GetButton(const PadIdentifier& identifier, int button) const;
    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;

    int SetCallback(InputIdentifier input_identifier);
    void SetMappingCallback(MappingCallback callback);
    void DeleteCallback(int key);

protected:
    void SetButton(const PadIdentifier& identifier, int button, bool value);
    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);

    virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const {
        return "Unknown";
    }

private:
    struct ControllerData {
        std::unordered_map<int, bool> buttons;
        std::unordered_map<int, u8> hat_buttons;
        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{};
    };

    void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value);
    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,
                               const Common::Input::CameraStatus& value);
    void TriggerOnNfcChange(const PadIdentifier& identifier, const Common::Input::NfcStatus& value);

    bool IsInputIdentifierEqual(const InputIdentifier& input_identifier,
                                const PadIdentifier& identifier, EngineInputType type,
                                int index) const;

    mutable std::mutex mutex;
    mutable std::mutex mutex_callback;
    bool configuring{false};
    const std::string input_engine;
    int last_callback_key = 0;
    std::unordered_map<PadIdentifier, ControllerData> controller_list;
    std::unordered_map<int, InputIdentifier> callback_list;
    MappingCallback mapping_callback;
};

} // namespace InputCommon


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/settings.h"
#include "input_common/input_engine.h"
#include "input_common/input_mapping.h"

namespace InputCommon {

MappingFactory::MappingFactory() = default;

void MappingFactory::BeginMapping(Polling::InputType type) {
    is_enabled = true;
    input_type = type;
    input_queue.Clear();
    first_axis = -1;
    second_axis = -1;
}

Common::ParamPackage MappingFactory::GetNextInput() {
    Common::ParamPackage input;
    input_queue.Pop(input);
    return input;
}

void MappingFactory::RegisterInput(const MappingData& data) {
    if (!is_enabled) {
        return;
    }
    if (!IsDriverValid(data)) {
        return;
    }

    switch (input_type) {
    case Polling::InputType::Button:
        RegisterButton(data);
        return;
    case Polling::InputType::Stick:
        RegisterStick(data);
        return;
    case Polling::InputType::Motion:
        RegisterMotion(data);
        return;
    default:
        return;
    }
}

void MappingFactory::StopMapping() {
    is_enabled = false;
    input_type = Polling::InputType::None;
    input_queue.Clear();
}

void MappingFactory::RegisterButton(const MappingData& data) {
    Common::ParamPackage new_input;
    new_input.Set("engine", data.engine);
    if (data.pad.guid.IsValid()) {
        new_input.Set("guid", data.pad.guid.RawString());
    }
    new_input.Set("port", static_cast<int>(data.pad.port));
    new_input.Set("pad", static_cast<int>(data.pad.pad));

    switch (data.type) {
    case EngineInputType::Button:
        // Workaround for old compatibility
        if (data.engine == "keyboard") {
            new_input.Set("code", data.index);
            break;
        }
        new_input.Set("button", data.index);
        break;
    case EngineInputType::HatButton:
        new_input.Set("hat", data.index);
        new_input.Set("direction", data.hat_name);
        break;
    case EngineInputType::Analog:
        // Ignore mouse axis when mapping buttons
        if (data.engine == "mouse" && data.index != 4) {
            return;
        }
        new_input.Set("axis", data.index);
        new_input.Set("threshold", 0.5f);
        break;
    case EngineInputType::Motion:
        new_input.Set("motion", data.index);
        break;
    default:
        return;
    }
    input_queue.Push(new_input);
}

void MappingFactory::RegisterStick(const MappingData& data) {
    Common::ParamPackage new_input;
    new_input.Set("engine", data.engine);
    if (data.pad.guid.IsValid()) {
        new_input.Set("guid", data.pad.guid.RawString());
    }
    new_input.Set("port", static_cast<int>(data.pad.port));
    new_input.Set("pad", static_cast<int>(data.pad.pad));

    // If engine is mouse map the mouse position as a joystick
    if (data.engine == "mouse") {
        new_input.Set("axis_x", 0);
        new_input.Set("axis_y", 1);
        new_input.Set("threshold", 0.5f);
        new_input.Set("range", 1.0f);
        new_input.Set("deadzone", 0.0f);
        input_queue.Push(new_input);
        return;
    }

    switch (data.type) {
    case EngineInputType::Button:
    case EngineInputType::HatButton:
        RegisterButton(data);
        return;
    case EngineInputType::Analog:
        if (first_axis == data.index) {
            return;
        }
        if (first_axis == -1) {
            first_axis = data.index;
            return;
        }
        new_input.Set("axis_x", first_axis);
        new_input.Set("axis_y", data.index);
        new_input.Set("threshold", 0.5f);
        new_input.Set("range", 0.95f);
        new_input.Set("deadzone", 0.15f);
        break;
    default:
        return;
    }
    input_queue.Push(new_input);
}

void MappingFactory::RegisterMotion(const MappingData& data) {
    Common::ParamPackage new_input;
    new_input.Set("engine", data.engine);
    if (data.pad.guid.IsValid()) {
        new_input.Set("guid", data.pad.guid.RawString());
    }
    new_input.Set("port", static_cast<int>(data.pad.port));
    new_input.Set("pad", static_cast<int>(data.pad.pad));

    // If engine is mouse map it automatically to mouse motion
    if (data.engine == "mouse") {
        new_input.Set("motion", 0);
        new_input.Set("pad", 1);
        new_input.Set("threshold", 0.001f);
        input_queue.Push(new_input);
        return;
    }

    switch (data.type) {
    case EngineInputType::Button:
    case EngineInputType::HatButton:
        RegisterButton(data);
        return;
    case EngineInputType::Analog:
        if (first_axis == data.index) {
            return;
        }
        if (second_axis == data.index) {
            return;
        }
        if (first_axis == -1) {
            first_axis = data.index;
            return;
        }
        if (second_axis == -1) {
            second_axis = data.index;
            return;
        }
        new_input.Set("axis_x", first_axis);
        new_input.Set("axis_y", second_axis);
        new_input.Set("axis_z", data.index);
        new_input.Set("range", 1.0f);
        new_input.Set("deadzone", 0.20f);
        break;
    case EngineInputType::Motion:
        new_input.Set("motion", data.index);
        break;
    default:
        return;
    }
    input_queue.Push(new_input);
}

bool MappingFactory::IsDriverValid(const MappingData& data) const {
    // Only port 0 can be mapped on the keyboard
    if (data.engine == "keyboard" && data.pad.port != 0) {
        return false;
    }
    // Only port 0 can be mapped on the mouse
    if (data.engine == "mouse" && data.pad.port != 0) {
        return false;
    }
    // To prevent mapping with two devices we disable any UDP except motion
    if (!Settings::values.enable_udp_controller && data.engine == "cemuhookudp" &&
        data.type != EngineInputType::Motion) {
        return false;
    }
    // The following drivers don't need to be mapped
    if (data.engine == "touch_from_button") {
        return false;
    }
    if (data.engine == "analog_from_button") {
        return false;
    }
    if (data.engine == "virtual_gamepad") {
        return false;
    }
    return true;
}

} // namespace InputCommon