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

#pragma once

#include <array>
#include <span>
#include "common/typed_address.h"
#include "core/hle/result.h"

namespace Core {
class System;
}

namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel

namespace Service::KernelHelpers {
class ServiceContext;
}

namespace Service::HID {

// This is nn::hidbus::JoyPollingMode
enum class JoyPollingMode : u32 {
    SixAxisSensorDisable,
    SixAxisSensorEnable,
    ButtonOnly,
};

struct DataAccessorHeader {
    Result result{ResultUnknown};
    INSERT_PADDING_WORDS(0x1);
    std::array<u8, 0x18> unused{};
    u64 latest_entry{};
    u64 total_entries{};
};
static_assert(sizeof(DataAccessorHeader) == 0x30, "DataAccessorHeader is an invalid size");

struct JoyDisableSixAxisPollingData {
    std::array<u8, 0x26> data;
    u8 out_size;
    INSERT_PADDING_BYTES(0x1);
    u64 sampling_number;
};
static_assert(sizeof(JoyDisableSixAxisPollingData) == 0x30,
              "JoyDisableSixAxisPollingData is an invalid size");

struct JoyEnableSixAxisPollingData {
    std::array<u8, 0x8> data;
    u8 out_size;
    INSERT_PADDING_BYTES(0x7);
    u64 sampling_number;
};
static_assert(sizeof(JoyEnableSixAxisPollingData) == 0x18,
              "JoyEnableSixAxisPollingData is an invalid size");

struct JoyButtonOnlyPollingData {
    std::array<u8, 0x2c> data;
    u8 out_size;
    INSERT_PADDING_BYTES(0x3);
    u64 sampling_number;
};
static_assert(sizeof(JoyButtonOnlyPollingData) == 0x38,
              "JoyButtonOnlyPollingData is an invalid size");

struct JoyDisableSixAxisPollingEntry {
    u64 sampling_number;
    JoyDisableSixAxisPollingData polling_data;
};
static_assert(sizeof(JoyDisableSixAxisPollingEntry) == 0x38,
              "JoyDisableSixAxisPollingEntry is an invalid size");

struct JoyEnableSixAxisPollingEntry {
    u64 sampling_number;
    JoyEnableSixAxisPollingData polling_data;
};
static_assert(sizeof(JoyEnableSixAxisPollingEntry) == 0x20,
              "JoyEnableSixAxisPollingEntry is an invalid size");

struct JoyButtonOnlyPollingEntry {
    u64 sampling_number;
    JoyButtonOnlyPollingData polling_data;
};
static_assert(sizeof(JoyButtonOnlyPollingEntry) == 0x40,
              "JoyButtonOnlyPollingEntry is an invalid size");

struct JoyDisableSixAxisDataAccessor {
    DataAccessorHeader header{};
    std::array<JoyDisableSixAxisPollingEntry, 0xb> entries{};
};
static_assert(sizeof(JoyDisableSixAxisDataAccessor) == 0x298,
              "JoyDisableSixAxisDataAccessor is an invalid size");

struct JoyEnableSixAxisDataAccessor {
    DataAccessorHeader header{};
    std::array<JoyEnableSixAxisPollingEntry, 0xb> entries{};
};
static_assert(sizeof(JoyEnableSixAxisDataAccessor) == 0x190,
              "JoyEnableSixAxisDataAccessor is an invalid size");

struct ButtonOnlyPollingDataAccessor {
    DataAccessorHeader header;
    std::array<JoyButtonOnlyPollingEntry, 0xb> entries;
};
static_assert(sizeof(ButtonOnlyPollingDataAccessor) == 0x2F0,
              "ButtonOnlyPollingDataAccessor is an invalid size");

class HidbusBase {
public:
    explicit HidbusBase(Core::System& system_, KernelHelpers::ServiceContext& service_context_);
    virtual ~HidbusBase();

    void ActivateDevice();

    void DeactivateDevice();

    bool IsDeviceActivated() const;

    // Enables/disables the device
    void Enable(bool enable);

    // returns true if device is enabled
    bool IsEnabled() const;

    // returns true if polling mode is enabled
    bool IsPollingMode() const;

    // returns polling mode
    JoyPollingMode GetPollingMode() const;

    // Sets and enables JoyPollingMode
    void SetPollingMode(JoyPollingMode mode);

    // Disables JoyPollingMode
    void DisablePollingMode();

    // Called on EnableJoyPollingReceiveMode
    void SetTransferMemoryAddress(Common::ProcessAddress t_mem);

    Kernel::KReadableEvent& GetSendCommandAsycEvent() const;

    virtual void OnInit() {}

    virtual void OnRelease() {}

    // Updates device transfer memory
    virtual void OnUpdate() {}

    // Returns the device ID of the joycon
    virtual u8 GetDeviceId() const {
        return {};
    }

    // Assigns a command from data
    virtual bool SetCommand(std::span<const u8> data) {
        return {};
    }

    // Returns a reply from a command
    virtual u64 GetReply(std::span<u8> out_data) const {
        return {};
    }

protected:
    bool is_activated{};
    bool device_enabled{};
    bool polling_mode_enabled{};
    JoyPollingMode polling_mode = {};
    // TODO(German77): All data accessors need to be replaced with a ring lifo object
    JoyDisableSixAxisDataAccessor disable_sixaxis_data{};
    JoyEnableSixAxisDataAccessor enable_sixaxis_data{};
    ButtonOnlyPollingDataAccessor button_only_data{};

    Common::ProcessAddress transfer_memory{};

    Core::System& system;
    Kernel::KEvent* send_command_async_event;
    KernelHelpers::ServiceContext& service_context;
};
} // namespace Service::HID


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

#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/memory.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hidbus/ringcon.h"

namespace Service::HID {

RingController::RingController(Core::System& system_,
                               KernelHelpers::ServiceContext& service_context_)
    : HidbusBase(system_, service_context_) {
    input = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
}

RingController::~RingController() = default;

void RingController::OnInit() {
    input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
                          Common::Input::PollingMode::Ring);
    return;
}

void RingController::OnRelease() {
    input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
                          Common::Input::PollingMode::Active);
    return;
};

void RingController::OnUpdate() {
    if (!is_activated) {
        return;
    }

    if (!device_enabled) {
        return;
    }

    if (!polling_mode_enabled || transfer_memory == 0) {
        return;
    }

    // TODO: Increment multitasking counters from motion and sensor data

    switch (polling_mode) {
    case JoyPollingMode::SixAxisSensorEnable: {
        enable_sixaxis_data.header.total_entries = 10;
        enable_sixaxis_data.header.result = ResultSuccess;
        const auto& last_entry =
            enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry];

        enable_sixaxis_data.header.latest_entry =
            (enable_sixaxis_data.header.latest_entry + 1) % 10;
        auto& curr_entry = enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry];

        curr_entry.sampling_number = last_entry.sampling_number + 1;
        curr_entry.polling_data.sampling_number = curr_entry.sampling_number;

        const RingConData ringcon_value = GetSensorValue();
        curr_entry.polling_data.out_size = sizeof(ringcon_value);
        std::memcpy(curr_entry.polling_data.data.data(), &ringcon_value, sizeof(ringcon_value));

        system.ApplicationMemory().WriteBlock(transfer_memory, &enable_sixaxis_data,
                                              sizeof(enable_sixaxis_data));
        break;
    }
    default:
        LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
        break;
    }
}

RingController::RingConData RingController::GetSensorValue() const {
    RingConData ringcon_sensor_value{
        .status = DataValid::Valid,
        .data = 0,
    };

    const f32 force_value = input->GetRingSensorForce().force * range;
    ringcon_sensor_value.data = static_cast<s16>(force_value) + idle_value;

    return ringcon_sensor_value;
}

u8 RingController::GetDeviceId() const {
    return device_id;
}

u64 RingController::GetReply(std::span<u8> out_data) const {
    const RingConCommands current_command = command;

    switch (current_command) {
    case RingConCommands::GetFirmwareVersion:
        return GetFirmwareVersionReply(out_data);
    case RingConCommands::ReadId:
        return GetReadIdReply(out_data);
    case RingConCommands::c20105:
        return GetC020105Reply(out_data);
    case RingConCommands::ReadUnkCal:
        return GetReadUnkCalReply(out_data);
    case RingConCommands::ReadFactoryCal:
        return GetReadFactoryCalReply(out_data);
    case RingConCommands::ReadUserCal:
        return GetReadUserCalReply(out_data);
    case RingConCommands::ReadRepCount:
        return GetReadRepCountReply(out_data);
    case RingConCommands::ReadTotalPushCount:
        return GetReadTotalPushCountReply(out_data);
    case RingConCommands::ResetRepCount:
        return GetResetRepCountReply(out_data);
    case RingConCommands::SaveCalData:
        return GetSaveDataReply(out_data);
    default:
        return GetErrorReply(out_data);
    }
}

bool RingController::SetCommand(std::span<const u8> data) {
    if (data.size() < 4) {
        LOG_ERROR(Service_HID, "Command size not supported {}", data.size());
        command = RingConCommands::Error;
        return false;
    }

    std::memcpy(&command, data.data(), sizeof(RingConCommands));

    switch (command) {
    case RingConCommands::GetFirmwareVersion:
    case RingConCommands::ReadId:
    case RingConCommands::c20105:
    case RingConCommands::ReadUnkCal:
    case RingConCommands::ReadFactoryCal:
    case RingConCommands::ReadUserCal:
    case RingConCommands::ReadRepCount:
    case RingConCommands::ReadTotalPushCount:
        ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
        send_command_async_event->Signal();
        return true;
    case RingConCommands::ResetRepCount:
        ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
        total_rep_count = 0;
        send_command_async_event->Signal();
        return true;
    case RingConCommands::SaveCalData: {
        ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes");

        SaveCalData save_info{};
        std::memcpy(&save_info, data.data(), sizeof(SaveCalData));
        user_calibration = save_info.calibration;
        send_command_async_event->Signal();
        return true;
    }
    default:
        LOG_ERROR(Service_HID, "Command not implemented {}", command);
        command = RingConCommands::Error;
        // Signal a reply to avoid softlocking the game
        send_command_async_event->Signal();
        return false;
    }
}

u64 RingController::GetFirmwareVersionReply(std::span<u8> out_data) const {
    const FirmwareVersionReply reply{
        .status = DataValid::Valid,
        .firmware = version,
    };

    return GetData(reply, out_data);
}

u64 RingController::GetReadIdReply(std::span<u8> out_data) const {
    // The values are hardcoded from a real joycon
    const ReadIdReply reply{
        .status = DataValid::Valid,
        .id_l_x0 = 8,
        .id_l_x0_2 = 41,
        .id_l_x4 = 22294,
        .id_h_x0 = 19777,
        .id_h_x0_2 = 13621,
        .id_h_x4 = 8245,
    };

    return GetData(reply, out_data);
}

u64 RingController::GetC020105Reply(std::span<u8> out_data) const {
    const Cmd020105Reply reply{
        .status = DataValid::Valid,
        .data = 1,
    };

    return GetData(reply, out_data);
}

u64 RingController::GetReadUnkCalReply(std::span<u8> out_data) const {
    const ReadUnkCalReply reply{
        .status = DataValid::Valid,
        .data = 0,
    };

    return GetData(reply, out_data);
}

u64 RingController::GetReadFactoryCalReply(std::span<u8> out_data) const {
    const ReadFactoryCalReply reply{
        .status = DataValid::Valid,
        .calibration = factory_calibration,
    };

    return GetData(reply, out_data);
}

u64 RingController::GetReadUserCalReply(std::span<u8> out_data) const {
    const ReadUserCalReply reply{
        .status = DataValid::Valid,
        .calibration = user_calibration,
    };

    return GetData(reply, out_data);
}

u64 RingController::GetReadRepCountReply(std::span<u8> out_data) const {
    const GetThreeByteReply reply{
        .status = DataValid::Valid,
        .data = {total_rep_count, 0, 0},
        .crc = GetCrcValue({total_rep_count, 0, 0, 0}),
    };

    return GetData(reply, out_data);
}

u64 RingController::GetReadTotalPushCountReply(std::span<u8> out_data) const {
    const GetThreeByteReply reply{
        .status = DataValid::Valid,
        .data = {total_push_count, 0, 0},
        .crc = GetCrcValue({total_push_count, 0, 0, 0}),
    };

    return GetData(reply, out_data);
}

u64 RingController::GetResetRepCountReply(std::span<u8> out_data) const {
    return GetReadRepCountReply(out_data);
}

u64 RingController::GetSaveDataReply(std::span<u8> out_data) const {
    const StatusReply reply{
        .status = DataValid::Valid,
    };

    return GetData(reply, out_data);
}

u64 RingController::GetErrorReply(std::span<u8> out_data) const {
    const ErrorReply reply{
        .status = DataValid::BadCRC,
    };

    return GetData(reply, out_data);
}

u8 RingController::GetCrcValue(const std::vector<u8>& data) const {
    u8 crc = 0;
    for (std::size_t index = 0; index < data.size(); index++) {
        for (u8 i = 0x80; i > 0; i >>= 1) {
            bool bit = (crc & 0x80) != 0;
            if ((data[index] & i) != 0) {
                bit = !bit;
            }
            crc <<= 1;
            if (bit) {
                crc ^= 0x8d;
            }
        }
    }
    return crc;
}

template <typename T>
u64 RingController::GetData(const T& reply, std::span<u8> out_data) const {
    static_assert(std::is_trivially_copyable_v<T>);
    const auto data_size = static_cast<u64>(std::min(sizeof(reply), out_data.size()));
    std::memcpy(out_data.data(), &reply, data_size);
    return data_size;
}

} // namespace Service::HID

repo_name: litucks/torzu

license: mit


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

#pragma once

#include <array>
#include <span>

#include "common/common_types.h"
#include "hid_core/hidbus/hidbus_base.h"

namespace Core::HID {
class EmulatedController;
} // namespace Core::HID

namespace Service::HID {

class RingController final : public HidbusBase {
public:
    explicit RingController(Core::System& system_, KernelHelpers::ServiceContext& service_context_);
    ~RingController() override;

    void OnInit() override;

    void OnRelease() override;

    // Updates ringcon transfer memory
    void OnUpdate() override;

    // Returns the device ID of the joycon
    u8 GetDeviceId() const override;

    // Assigns a command from data
    bool SetCommand(std::span<const u8> data) override;

    // Returns a reply from a command
    u64 GetReply(std::span<u8> data) const override;

private:
    // These values are obtained from a real ring controller
    static constexpr s16 idle_value = 2280;
    static constexpr s16 idle_deadzone = 120;
    static constexpr s16 range = 2500;

    // Most missing command names are leftovers from other firmware versions
    enum class RingConCommands : u32 {
        GetFirmwareVersion = 0x00020000,
        ReadId = 0x00020100,
        JoyPolling = 0x00020101,
        Unknown1 = 0x00020104,
        c20105 = 0x00020105,
        Unknown2 = 0x00020204,
        Unknown3 = 0x00020304,
        Unknown4 = 0x00020404,
        ReadUnkCal = 0x00020504,
        ReadFactoryCal = 0x00020A04,
        Unknown5 = 0x00021104,
        Unknown6 = 0x00021204,
        Unknown7 = 0x00021304,
        ReadUserCal = 0x00021A04,
        ReadRepCount = 0x00023104,
        ReadTotalPushCount = 0x00023204,
        ResetRepCount = 0x04013104,
        Unknown8 = 0x04011104,
        Unknown9 = 0x04011204,
        Unknown10 = 0x04011304,
        SaveCalData = 0x10011A04,
        Error = 0xFFFFFFFF,
    };

    enum class DataValid : u32 {
        Valid,
        BadCRC,
        Cal,
    };

    struct FirmwareVersion {
        u8 sub;
        u8 main;
    };
    static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");

    struct FactoryCalibration {
        s32_le os_max;
        s32_le hk_max;
        s32_le zero_min;
        s32_le zero_max;
    };
    static_assert(sizeof(FactoryCalibration) == 0x10, "FactoryCalibration is an invalid size");

    struct CalibrationValue {
        s16 value;
        u16 crc;
    };
    static_assert(sizeof(CalibrationValue) == 0x4, "CalibrationValue is an invalid size");

    struct UserCalibration {
        CalibrationValue os_max;
        CalibrationValue hk_max;
        CalibrationValue zero;
    };
    static_assert(sizeof(UserCalibration) == 0xC, "UserCalibration is an invalid size");

    struct SaveCalData {
        RingConCommands command;
        UserCalibration calibration;
        INSERT_PADDING_BYTES_NOINIT(4);
    };
    static_assert(sizeof(SaveCalData) == 0x14, "SaveCalData is an invalid size");
    static_assert(std::is_trivially_copyable_v<SaveCalData>,
                  "SaveCalData must be trivially copyable");

    struct FirmwareVersionReply {
        DataValid status;
        FirmwareVersion firmware;
        INSERT_PADDING_BYTES(0x2);
    };
    static_assert(sizeof(FirmwareVersionReply) == 0x8, "FirmwareVersionReply is an invalid size");

    struct Cmd020105Reply {
        DataValid status;
        u8 data;
        INSERT_PADDING_BYTES(0x3);
    };
    static_assert(sizeof(Cmd020105Reply) == 0x8, "Cmd020105Reply is an invalid size");

    struct StatusReply {
        DataValid status;
    };
    static_assert(sizeof(StatusReply) == 0x4, "StatusReply is an invalid size");

    struct GetThreeByteReply {
        DataValid status;
        std::array<u8, 3> data;
        u8 crc;
    };
    static_assert(sizeof(GetThreeByteReply) == 0x8, "GetThreeByteReply is an invalid size");

    struct ReadUnkCalReply {
        DataValid status;
        u16 data;
        INSERT_PADDING_BYTES(0x2);
    };
    static_assert(sizeof(ReadUnkCalReply) == 0x8, "ReadUnkCalReply is an invalid size");

    struct ReadFactoryCalReply {
        DataValid status;
        FactoryCalibration calibration;
    };
    static_assert(sizeof(ReadFactoryCalReply) == 0x14, "ReadFactoryCalReply is an invalid size");

    struct ReadUserCalReply {
        DataValid status;
        UserCalibration calibration;
        INSERT_PADDING_BYTES(0x4);
    };
    static_assert(sizeof(ReadUserCalReply) == 0x14, "ReadUserCalReply is an invalid size");

    struct ReadIdReply {
        DataValid status;
        u16 id_l_x0;
        u16 id_l_x0_2;
        u16 id_l_x4;
        u16 id_h_x0;
        u16 id_h_x0_2;
        u16 id_h_x4;
    };
    static_assert(sizeof(ReadIdReply) == 0x10, "ReadIdReply is an invalid size");

    struct ErrorReply {
        DataValid status;
        INSERT_PADDING_BYTES(0x3);
    };
    static_assert(sizeof(ErrorReply) == 0x8, "ErrorReply is an invalid size");

    struct RingConData {
        DataValid status;
        s16_le data;
        INSERT_PADDING_BYTES(0x2);
    };
    static_assert(sizeof(RingConData) == 0x8, "RingConData is an invalid size");

    // Returns RingConData struct with pressure sensor values
    RingConData GetSensorValue() const;

    // Returns 8 byte reply with firmware version
    u64 GetFirmwareVersionReply(std::span<u8> out_data) const;

    // Returns 16 byte reply with ID values
    u64 GetReadIdReply(std::span<u8> out_data) const;

    // (STUBBED) Returns 8 byte reply
    u64 GetC020105Reply(std::span<u8> out_data) const;

    // (STUBBED) Returns 8 byte empty reply
    u64 GetReadUnkCalReply(std::span<u8> out_data) const;

    // Returns 20 byte reply with factory calibration values
    u64 GetReadFactoryCalReply(std::span<u8> out_data) const;

    // Returns 20 byte reply with user calibration values
    u64 GetReadUserCalReply(std::span<u8> out_data) const;

    // Returns 8 byte reply
    u64 GetReadRepCountReply(std::span<u8> out_data) const;

    // Returns 8 byte reply
    u64 GetReadTotalPushCountReply(std::span<u8> out_data) const;

    // Returns 8 byte reply
    u64 GetResetRepCountReply(std::span<u8> out_data) const;

    // Returns 4 byte save data reply
    u64 GetSaveDataReply(std::span<u8> out_data) const;

    // Returns 8 byte error reply
    u64 GetErrorReply(std::span<u8> out_data) const;

    // Returns 8 bit redundancy check from provided data
    u8 GetCrcValue(const std::vector<u8>& data) const;

    // Converts structs to an u8 vector equivalent
    template <typename T>
    u64 GetData(const T& reply, std::span<u8> out_data) const;

    RingConCommands command{RingConCommands::Error};

    // These counters are used in multitasking mode while the switch is sleeping
    // Total steps taken
    u8 total_rep_count = 0;
    // Total times the ring was pushed
    u8 total_push_count = 0;

    const u8 device_id = 0x20;
    const FirmwareVersion version = {
        .sub = 0x0,
        .main = 0x2c,
    };
    const FactoryCalibration factory_calibration = {
        .os_max = idle_value + range + idle_deadzone,
        .hk_max = idle_value - range - idle_deadzone,
        .zero_min = idle_value - idle_deadzone,
        .zero_max = idle_value + idle_deadzone,
    };
    UserCalibration user_calibration = {
        .os_max = {.value = range, .crc = 228},
        .hk_max = {.value = -range, .crc = 239},
        .zero = {.value = idle_value, .crc = 225},
    };

    Core::HID::EmulatedController* input;
};
} // namespace Service::HID


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

#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hidbus/starlink.h"

namespace Service::HID {
constexpr u8 DEVICE_ID = 0x28;

Starlink::Starlink(Core::System& system_, KernelHelpers::ServiceContext& service_context_)
    : HidbusBase(system_, service_context_) {}
Starlink::~Starlink() = default;

void Starlink::OnInit() {
    return;
}

void Starlink::OnRelease() {
    return;
};

void Starlink::OnUpdate() {
    if (!is_activated) {
        return;
    }
    if (!device_enabled) {
        return;
    }
    if (!polling_mode_enabled || transfer_memory == 0) {
        return;
    }

    LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
}

u8 Starlink::GetDeviceId() const {
    return DEVICE_ID;
}

u64 Starlink::GetReply(std::span<u8> out_data) const {
    return {};
}

bool Starlink::SetCommand(std::span<const u8> data) {
    LOG_ERROR(Service_HID, "Command not implemented");
    return false;
}

} // namespace Service::HID


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

#pragma once

#include "common/common_types.h"
#include "hid_core/hidbus/hidbus_base.h"

namespace Core::HID {
class EmulatedController;
} // namespace Core::HID

namespace Service::HID {

class Starlink final : public HidbusBase {
public:
    explicit Starlink(Core::System& system_, KernelHelpers::ServiceContext& service_context_);
    ~Starlink() override;

    void OnInit() override;

    void OnRelease() override;

    // Updates ringcon transfer memory
    void OnUpdate() override;

    // Returns the device ID of the joycon
    u8 GetDeviceId() const override;

    // Assigns a command from data
    bool SetCommand(std::span<const u8> data) override;

    // Returns a reply from a command
    u64 GetReply(std::span<u8> out_data) const override;
};

} // namespace Service::HID


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

#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hidbus/stubbed.h"

namespace Service::HID {
constexpr u8 DEVICE_ID = 0xFF;

HidbusStubbed::HidbusStubbed(Core::System& system_, KernelHelpers::ServiceContext& service_context_)
    : HidbusBase(system_, service_context_) {}
HidbusStubbed::~HidbusStubbed() = default;

void HidbusStubbed::OnInit() {
    return;
}

void HidbusStubbed::OnRelease() {
    return;
};

void HidbusStubbed::OnUpdate() {
    if (!is_activated) {
        return;
    }
    if (!device_enabled) {
        return;
    }
    if (!polling_mode_enabled || transfer_memory == 0) {
        return;
    }

    LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
}

u8 HidbusStubbed::GetDeviceId() const {
    return DEVICE_ID;
}

u64 HidbusStubbed::GetReply(std::span<u8> out_data) const {
    return {};
}

bool HidbusStubbed::SetCommand(std::span<const u8> data) {
    LOG_ERROR(Service_HID, "Command not implemented");
    return false;
}

} // namespace Service::HID


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

#pragma once

#include "common/common_types.h"
#include "hid_core/hidbus/hidbus_base.h"

namespace Core::HID {
class EmulatedController;
} // namespace Core::HID

namespace Service::HID {

class HidbusStubbed final : public HidbusBase {
public:
    explicit HidbusStubbed(Core::System& system_, KernelHelpers::ServiceContext& service_context_);
    ~HidbusStubbed() override;

    void OnInit() override;

    void OnRelease() override;

    // Updates ringcon transfer memory
    void OnUpdate() override;

    // Returns the device ID of the joycon
    u8 GetDeviceId() const override;

    // Assigns a command from data
    bool SetCommand(std::span<const u8> data) override;

    // Returns a reply from a command
    u64 GetReply(std::span<u8> out_data) const override;
};

} // namespace Service::HID


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

#include <queue>

#include "core/core.h"
#include "core/core_timing.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/irsensor/clustering_processor.h"

namespace Service::IRS {
ClusteringProcessor::ClusteringProcessor(Core::System& system_,
                                         Core::IrSensor::DeviceFormat& device_format,
                                         std::size_t npad_index)
    : device{device_format}, system{system_} {
    npad_device = system.HIDCore().GetEmulatedControllerByIndex(npad_index);

    device.mode = Core::IrSensor::IrSensorMode::ClusteringProcessor;
    device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
    device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
    SetDefaultConfig();

    shared_memory = std::construct_at(
        reinterpret_cast<ClusteringSharedMemory*>(&device_format.state.processor_raw_data));

    Core::HID::ControllerUpdateCallback engine_callback{
        .on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); },
        .is_npad_service = true,
    };
    callback_key = npad_device->SetCallback(engine_callback);
}

ClusteringProcessor::~ClusteringProcessor() {
    npad_device->DeleteCallback(callback_key);
};

void ClusteringProcessor::StartProcessor() {
    device.camera_status = Core::IrSensor::IrCameraStatus::Available;
    device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready;
}

void ClusteringProcessor::SuspendProcessor() {}

void ClusteringProcessor::StopProcessor() {}

void ClusteringProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) {
    if (type != Core::HID::ControllerTriggerType::IrSensor) {
        return;
    }

    next_state = {};
    const auto& camera_data = npad_device->GetCamera();
    auto filtered_image = camera_data.data;

    RemoveLowIntensityData(filtered_image);

    const auto window_start_x = static_cast<std::size_t>(current_config.window_of_interest.x);
    const auto window_start_y = static_cast<std::size_t>(current_config.window_of_interest.y);
    const auto window_end_x =
        window_start_x + static_cast<std::size_t>(current_config.window_of_interest.width);
    const auto window_end_y =
        window_start_y + static_cast<std::size_t>(current_config.window_of_interest.height);

    for (std::size_t y = window_start_y; y < window_end_y; y++) {
        for (std::size_t x = window_start_x; x < window_end_x; x++) {
            u8 pixel = GetPixel(filtered_image, x, y);
            if (pixel == 0) {
                continue;
            }
            const auto cluster = GetClusterProperties(filtered_image, x, y);
            if (cluster.pixel_count > current_config.pixel_count_max) {
                continue;
            }
            if (cluster.pixel_count < current_config.pixel_count_min) {
                continue;
            }
            // Cluster object limit reached
            if (next_state.object_count >= next_state.data.size()) {
                continue;
            }
            next_state.data[next_state.object_count] = cluster;
            next_state.object_count++;
        }
    }

    next_state.sampling_number = camera_data.sample;
    next_state.timestamp = system.CoreTiming().GetGlobalTimeNs().count();
    next_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
    shared_memory->clustering_lifo.WriteNextEntry(next_state);

    if (!IsProcessorActive()) {
        StartProcessor();
    }
}

void ClusteringProcessor::RemoveLowIntensityData(std::vector<u8>& data) {
    for (u8& pixel : data) {
        if (pixel < current_config.pixel_count_min) {
            pixel = 0;
        }
    }
}

ClusteringProcessor::ClusteringData ClusteringProcessor::GetClusterProperties(std::vector<u8>& data,
                                                                              std::size_t x,
                                                                              std::size_t y) {
    using DataPoint = Common::Point<std::size_t>;
    std::queue<DataPoint> search_points{};
    ClusteringData current_cluster = GetPixelProperties(data, x, y);
    SetPixel(data, x, y, 0);
    search_points.emplace<DataPoint>({x, y});

    while (!search_points.empty()) {
        const auto point = search_points.front();
        search_points.pop();

        // Avoid negative numbers
        if (point.x == 0 || point.y == 0) {
            continue;
        }

        std::array<DataPoint, 4> new_points{
            DataPoint{point.x - 1, point.y},
            {point.x, point.y - 1},
            {point.x + 1, point.y},
            {point.x, point.y + 1},
        };

        for (const auto new_point : new_points) {
            if (new_point.x >= width) {
                continue;
            }
            if (new_point.y >= height) {
                continue;
            }
            if (GetPixel(data, new_point.x, new_point.y) < current_config.object_intensity_min) {
                continue;
            }
            const ClusteringData cluster = GetPixelProperties(data, new_point.x, new_point.y);
            current_cluster = MergeCluster(current_cluster, cluster);
            SetPixel(data, new_point.x, new_point.y, 0);
            search_points.emplace<DataPoint>({new_point.x, new_point.y});
        }
    }

    return current_cluster;
}

ClusteringProcessor::ClusteringData ClusteringProcessor::GetPixelProperties(
    const std::vector<u8>& data, std::size_t x, std::size_t y) const {
    return {
        .average_intensity = GetPixel(data, x, y) / 255.0f,
        .centroid =
            {
                .x = static_cast<f32>(x),
                .y = static_cast<f32>(y),

            },
        .pixel_count = 1,
        .bound =
            {
                .x = static_cast<s16>(x),
                .y = static_cast<s16>(y),
                .width = 1,
                .height = 1,
            },
    };
}

ClusteringProcessor::ClusteringData ClusteringProcessor::MergeCluster(
    const ClusteringData a, const ClusteringData b) const {
    const f32 a_pixel_count = static_cast<f32>(a.pixel_count);
    const f32 b_pixel_count = static_cast<f32>(b.pixel_count);
    const f32 pixel_count = a_pixel_count + b_pixel_count;
    const f32 average_intensity =
        (a.average_intensity * a_pixel_count + b.average_intensity * b_pixel_count) / pixel_count;
    const Core::IrSensor::IrsCentroid centroid = {
        .x = (a.centroid.x * a_pixel_count + b.centroid.x * b_pixel_count) / pixel_count,
        .y = (a.centroid.y * a_pixel_count + b.centroid.y * b_pixel_count) / pixel_count,
    };
    s16 bound_start_x = a.bound.x < b.bound.x ? a.bound.x : b.bound.x;
    s16 bound_start_y = a.bound.y < b.bound.y ? a.bound.y : b.bound.y;
    s16 a_bound_end_x = a.bound.x + a.bound.width;
    s16 a_bound_end_y = a.bound.y + a.bound.height;
    s16 b_bound_end_x = b.bound.x + b.bound.width;
    s16 b_bound_end_y = b.bound.y + b.bound.height;

    const Core::IrSensor::IrsRect bound = {
        .x = bound_start_x,
        .y = bound_start_y,
        .width = a_bound_end_x > b_bound_end_x ? static_cast<s16>(a_bound_end_x - bound_start_x)
                                               : static_cast<s16>(b_bound_end_x - bound_start_x),
        .height = a_bound_end_y > b_bound_end_y ? static_cast<s16>(a_bound_end_y - bound_start_y)
                                                : static_cast<s16>(b_bound_end_y - bound_start_y),
    };

    return {
        .average_intensity = average_intensity,
        .centroid = centroid,
        .pixel_count = static_cast<u32>(pixel_count),
        .bound = bound,
    };
}

u8 ClusteringProcessor::GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const {
    if ((y * width) + x >= data.size()) {
        return 0;
    }
    return data[(y * width) + x];
}

void ClusteringProcessor::SetPixel(std::vector<u8>& data, std::size_t x, std::size_t y, u8 value) {
    if ((y * width) + x >= data.size()) {
        return;
    }
    data[(y * width) + x] = value;
}

void ClusteringProcessor::SetDefaultConfig() {
    using namespace std::literals::chrono_literals;
    current_config.camera_config.exposure_time = std::chrono::microseconds(200ms).count();
    current_config.camera_config.gain = 2;
    current_config.camera_config.is_negative_used = false;
    current_config.camera_config.light_target = Core::IrSensor::CameraLightTarget::BrightLeds;
    current_config.window_of_interest = {
        .x = 0,
        .y = 0,
        .width = width,
        .height = height,
    };
    current_config.pixel_count_min = 3;
    current_config.pixel_count_max = static_cast<u32>(GetDataSize(format));
    current_config.is_external_light_filter_enabled = true;
    current_config.object_intensity_min = 150;

    npad_device->SetCameraFormat(format);
}

void ClusteringProcessor::SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config) {
    current_config.camera_config.exposure_time = config.camera_config.exposure_time;
    current_config.camera_config.gain = config.camera_config.gain;
    current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
    current_config.camera_config.light_target =
        static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
    current_config.window_of_interest = config.window_of_interest;
    current_config.pixel_count_min = config.pixel_count_min;
    current_config.pixel_count_max = config.pixel_count_max;
    current_config.is_external_light_filter_enabled = config.is_external_light_filter_enabled;
    current_config.object_intensity_min = config.object_intensity_min;

    LOG_INFO(Service_IRS,
             "Processor config, exposure_time={}, gain={}, is_negative_used={}, "
             "light_target={}, window_of_interest=({}, {}, {}, {}), pixel_count_min={}, "
             "pixel_count_max={}, is_external_light_filter_enabled={}, object_intensity_min={}",
             current_config.camera_config.exposure_time, current_config.camera_config.gain,
             current_config.camera_config.is_negative_used,
             current_config.camera_config.light_target, current_config.window_of_interest.x,
             current_config.window_of_interest.y, current_config.window_of_interest.width,
             current_config.window_of_interest.height, current_config.pixel_count_min,
             current_config.pixel_count_max, current_config.is_external_light_filter_enabled,
             current_config.object_intensity_min);

    npad_device->SetCameraFormat(format);
}

} // namespace Service::IRS


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

#pragma once

#include "common/common_types.h"
#include "hid_core/irsensor/irs_types.h"
#include "hid_core/irsensor/processor_base.h"
#include "hid_core/resources/irs_ring_lifo.h"

namespace Core {
class System;
}

namespace Core::HID {
class EmulatedController;
} // namespace Core::HID

namespace Service::IRS {
class ClusteringProcessor final : public ProcessorBase {
public:
    explicit ClusteringProcessor(Core::System& system_, Core::IrSensor::DeviceFormat& device_format,
                                 std::size_t npad_index);
    ~ClusteringProcessor() override;

    // Called when the processor is initialized
    void StartProcessor() override;

    // Called when the processor is suspended
    void SuspendProcessor() override;

    // Called when the processor is stopped
    void StopProcessor() override;

    // Sets config parameters of the camera
    void SetConfig(Core::IrSensor::PackedClusteringProcessorConfig config);

private:
    static constexpr auto format = Core::IrSensor::ImageTransferProcessorFormat::Size320x240;
    static constexpr std::size_t width = 320;
    static constexpr std::size_t height = 240;

    // This is nn::irsensor::ClusteringProcessorConfig
    struct ClusteringProcessorConfig {
        Core::IrSensor::CameraConfig camera_config;
        Core::IrSensor::IrsRect window_of_interest;
        u32 pixel_count_min;
        u32 pixel_count_max;
        u32 object_intensity_min;
        bool is_external_light_filter_enabled;
        INSERT_PADDING_BYTES(3);
    };
    static_assert(sizeof(ClusteringProcessorConfig) == 0x30,
                  "ClusteringProcessorConfig is an invalid size");

    // This is nn::irsensor::AdaptiveClusteringProcessorConfig
    struct AdaptiveClusteringProcessorConfig {
        Core::IrSensor::AdaptiveClusteringMode mode;
        Core::IrSensor::AdaptiveClusteringTargetDistance target_distance;
    };
    static_assert(sizeof(AdaptiveClusteringProcessorConfig) == 0x8,
                  "AdaptiveClusteringProcessorConfig is an invalid size");

    // This is nn::irsensor::ClusteringData
    struct ClusteringData {
        f32 average_intensity;
        Core::IrSensor::IrsCentroid centroid;
        u32 pixel_count;
        Core::IrSensor::IrsRect bound;
    };
    static_assert(sizeof(ClusteringData) == 0x18, "ClusteringData is an invalid size");

    // This is nn::irsensor::ClusteringProcessorState
    struct ClusteringProcessorState {
        s64 sampling_number;
        u64 timestamp;
        u8 object_count;
        INSERT_PADDING_BYTES(3);
        Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
        std::array<ClusteringData, 0x10> data;
    };
    static_assert(sizeof(ClusteringProcessorState) == 0x198,
                  "ClusteringProcessorState is an invalid size");

    struct ClusteringSharedMemory {
        Service::IRS::Lifo<ClusteringProcessorState, 6> clustering_lifo;
        static_assert(sizeof(clustering_lifo) == 0x9A0, "clustering_lifo is an invalid size");
        INSERT_PADDING_WORDS(0x11F);
    };
    static_assert(sizeof(ClusteringSharedMemory) == 0xE20,
                  "ClusteringSharedMemory is an invalid size");

    void OnControllerUpdate(Core::HID::ControllerTriggerType type);
    void RemoveLowIntensityData(std::vector<u8>& data);
    ClusteringData GetClusterProperties(std::vector<u8>& data, std::size_t x, std::size_t y);
    ClusteringData GetPixelProperties(const std::vector<u8>& data, std::size_t x,
                                      std::size_t y) const;
    ClusteringData MergeCluster(const ClusteringData a, const ClusteringData b) const;
    u8 GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const;
    void SetPixel(std::vector<u8>& data, std::size_t x, std::size_t y, u8 value);

    // Sets config parameters of the camera
    void SetDefaultConfig();

    ClusteringSharedMemory* shared_memory = nullptr;
    ClusteringProcessorState next_state{};

    ClusteringProcessorConfig current_config{};
    Core::IrSensor::DeviceFormat& device;
    Core::HID::EmulatedController* npad_device;
    int callback_key{};

    Core::System& system;
};
} // namespace Service::IRS


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

#include "core/core.h"
#include "core/memory.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/irsensor/image_transfer_processor.h"

namespace Service::IRS {
ImageTransferProcessor::ImageTransferProcessor(Core::System& system_,
                                               Core::IrSensor::DeviceFormat& device_format,
                                               std::size_t npad_index)
    : device{device_format}, system{system_} {
    npad_device = system.HIDCore().GetEmulatedControllerByIndex(npad_index);

    Core::HID::ControllerUpdateCallback engine_callback{
        .on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); },
        .is_npad_service = true,
    };
    callback_key = npad_device->SetCallback(engine_callback);

    device.mode = Core::IrSensor::IrSensorMode::ImageTransferProcessor;
    device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
    device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
}

ImageTransferProcessor::~ImageTransferProcessor() {
    npad_device->DeleteCallback(callback_key);
};

void ImageTransferProcessor::StartProcessor() {
    is_active = true;
    device.camera_status = Core::IrSensor::IrCameraStatus::Available;
    device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready;
    processor_state.sampling_number = 0;
    processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
}

void ImageTransferProcessor::SuspendProcessor() {}

void ImageTransferProcessor::StopProcessor() {}

void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) {
    if (type != Core::HID::ControllerTriggerType::IrSensor) {
        return;
    }
    if (transfer_memory == 0) {
        return;
    }

    const auto& camera_data = npad_device->GetCamera();

    // This indicates how much ambient light is present
    processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
    processor_state.sampling_number = camera_data.sample;

    if (camera_data.format != current_config.origin_format) {
        LOG_WARNING(Service_IRS, "Wrong Input format {} expected {}", camera_data.format,
                    current_config.origin_format);
        system.ApplicationMemory().ZeroBlock(transfer_memory,
                                             GetDataSize(current_config.trimming_format));
        return;
    }

    if (current_config.origin_format > current_config.trimming_format) {
        LOG_WARNING(Service_IRS, "Origin format {} is smaller than trimming format {}",
                    current_config.origin_format, current_config.trimming_format);
        system.ApplicationMemory().ZeroBlock(transfer_memory,
                                             GetDataSize(current_config.trimming_format));
        return;
    }

    std::vector<u8> window_data{};
    const auto origin_width = GetDataWidth(current_config.origin_format);
    const auto origin_height = GetDataHeight(current_config.origin_format);
    const auto trimming_width = GetDataWidth(current_config.trimming_format);
    const auto trimming_height = GetDataHeight(current_config.trimming_format);
    window_data.resize(GetDataSize(current_config.trimming_format));

    if (trimming_width + current_config.trimming_start_x > origin_width ||
        trimming_height + current_config.trimming_start_y > origin_height) {
        LOG_WARNING(Service_IRS,
                    "Trimming area ({}, {}, {}, {}) is outside of origin area ({}, {})",
                    current_config.trimming_start_x, current_config.trimming_start_y,
                    trimming_width, trimming_height, origin_width, origin_height);
        system.ApplicationMemory().ZeroBlock(transfer_memory,
                                             GetDataSize(current_config.trimming_format));
        return;
    }

    for (std::size_t y = 0; y < trimming_height; y++) {
        for (std::size_t x = 0; x < trimming_width; x++) {
            const std::size_t window_index = (y * trimming_width) + x;
            const std::size_t origin_index =
                ((y + current_config.trimming_start_y) * origin_width) + x +
                current_config.trimming_start_x;
            window_data[window_index] = camera_data.data[origin_index];
        }
    }

    system.ApplicationMemory().WriteBlock(transfer_memory, window_data.data(),
                                          GetDataSize(current_config.trimming_format));

    if (!IsProcessorActive()) {
        StartProcessor();
    }
}

void ImageTransferProcessor::SetConfig(Core::IrSensor::PackedImageTransferProcessorConfig config) {
    current_config.camera_config.exposure_time = config.camera_config.exposure_time;
    current_config.camera_config.gain = config.camera_config.gain;
    current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
    current_config.camera_config.light_target =
        static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
    current_config.origin_format =
        static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.format);
    current_config.trimming_format =
        static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.format);
    current_config.trimming_start_x = 0;
    current_config.trimming_start_y = 0;

    npad_device->SetCameraFormat(current_config.origin_format);
}

void ImageTransferProcessor::SetConfig(
    Core::IrSensor::PackedImageTransferProcessorExConfig config) {
    current_config.camera_config.exposure_time = config.camera_config.exposure_time;
    current_config.camera_config.gain = config.camera_config.gain;
    current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
    current_config.camera_config.light_target =
        static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
    current_config.origin_format =
        static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.origin_format);
    current_config.trimming_format =
        static_cast<Core::IrSensor::ImageTransferProcessorFormat>(config.trimming_format);
    current_config.trimming_start_x = config.trimming_start_x;
    current_config.trimming_start_y = config.trimming_start_y;

    npad_device->SetCameraFormat(current_config.origin_format);
}

void ImageTransferProcessor::SetTransferMemoryAddress(Common::ProcessAddress t_mem) {
    transfer_memory = t_mem;
}

Core::IrSensor::ImageTransferProcessorState ImageTransferProcessor::GetState(
    std::span<u8> data) const {
    const auto size = std::min(GetDataSize(current_config.trimming_format), data.size());
    system.ApplicationMemory().ReadBlock(transfer_memory, data.data(), size);
    return processor_state;
}

} // namespace Service::IRS


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

#pragma once

#include <span>

#include "common/typed_address.h"
#include "hid_core/irsensor/irs_types.h"
#include "hid_core/irsensor/processor_base.h"

namespace Core {
class System;
}

namespace Core::HID {
class EmulatedController;
} // namespace Core::HID

namespace Service::IRS {
class ImageTransferProcessor final : public ProcessorBase {
public:
    explicit ImageTransferProcessor(Core::System& system_,
                                    Core::IrSensor::DeviceFormat& device_format,
                                    std::size_t npad_index);
    ~ImageTransferProcessor() override;

    // Called when the processor is initialized
    void StartProcessor() override;

    // Called when the processor is suspended
    void SuspendProcessor() override;

    // Called when the processor is stopped
    void StopProcessor() override;

    // Sets config parameters of the camera
    void SetConfig(Core::IrSensor::PackedImageTransferProcessorConfig config);
    void SetConfig(Core::IrSensor::PackedImageTransferProcessorExConfig config);

    // Transfer memory where the image data will be stored
    void SetTransferMemoryAddress(Common::ProcessAddress t_mem);

    Core::IrSensor::ImageTransferProcessorState GetState(std::span<u8> data) const;

private:
    // This is nn::irsensor::ImageTransferProcessorConfig
    struct ImageTransferProcessorConfig {
        Core::IrSensor::CameraConfig camera_config;
        Core::IrSensor::ImageTransferProcessorFormat format;
    };
    static_assert(sizeof(ImageTransferProcessorConfig) == 0x20,
                  "ImageTransferProcessorConfig is an invalid size");

    // This is nn::irsensor::ImageTransferProcessorExConfig
    struct ImageTransferProcessorExConfig {
        Core::IrSensor::CameraConfig camera_config;
        Core::IrSensor::ImageTransferProcessorFormat origin_format;
        Core::IrSensor::ImageTransferProcessorFormat trimming_format;
        u16 trimming_start_x;
        u16 trimming_start_y;
        bool is_external_light_filter_enabled;
        INSERT_PADDING_BYTES(3);
    };
    static_assert(sizeof(ImageTransferProcessorExConfig) == 0x28,
                  "ImageTransferProcessorExConfig is an invalid size");

    void OnControllerUpdate(Core::HID::ControllerTriggerType type);

    ImageTransferProcessorExConfig current_config{};
    Core::IrSensor::ImageTransferProcessorState processor_state{};
    Core::IrSensor::DeviceFormat& device;
    Core::HID::EmulatedController* npad_device;
    int callback_key{};

    Core::System& system;
    Common::ProcessAddress transfer_memory{};
};
} // namespace Service::IRS


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

#include "hid_core/irsensor/ir_led_processor.h"

namespace Service::IRS {
IrLedProcessor::IrLedProcessor(Core::IrSensor::DeviceFormat& device_format)
    : device(device_format) {
    device.mode = Core::IrSensor::IrSensorMode::IrLedProcessor;
    device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
    device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
}

IrLedProcessor::~IrLedProcessor() = default;

void IrLedProcessor::StartProcessor() {}

void IrLedProcessor::SuspendProcessor() {}

void IrLedProcessor::StopProcessor() {}

void IrLedProcessor::SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config) {
    current_config.light_target =
        static_cast<Core::IrSensor::CameraLightTarget>(config.light_target);
}

} // namespace Service::IRS


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

#pragma once

#include "common/bit_field.h"
#include "common/common_types.h"
#include "hid_core/irsensor/irs_types.h"
#include "hid_core/irsensor/processor_base.h"

namespace Service::IRS {
class IrLedProcessor final : public ProcessorBase {
public:
    explicit IrLedProcessor(Core::IrSensor::DeviceFormat& device_format);
    ~IrLedProcessor() override;

    // Called when the processor is initialized
    void StartProcessor() override;

    // Called when the processor is suspended
    void SuspendProcessor() override;

    // Called when the processor is stopped
    void StopProcessor() override;

    // Sets config parameters of the camera
    void SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config);

private:
    // This is nn::irsensor::IrLedProcessorConfig
    struct IrLedProcessorConfig {
        Core::IrSensor::CameraLightTarget light_target;
    };
    static_assert(sizeof(IrLedProcessorConfig) == 0x4, "IrLedProcessorConfig is an invalid size");

    struct IrLedProcessorState {
        s64 sampling_number;
        u64 timestamp;
        std::array<u8, 0x8> data;
    };
    static_assert(sizeof(IrLedProcessorState) == 0x18, "IrLedProcessorState is an invalid size");

    IrLedProcessorConfig current_config{};
    Core::IrSensor::DeviceFormat& device;
};

} // namespace Service::IRS


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

#pragma once

#include "common/common_funcs.h"
#include "common/common_types.h"
#include "hid_core/hid_types.h"

namespace Core::IrSensor {

// This is nn::irsensor::CameraAmbientNoiseLevel
enum class CameraAmbientNoiseLevel : u32 {
    Low,
    Medium,
    High,
    Unknown3, // This level can't be reached
};

// This is nn::irsensor::CameraLightTarget
enum class CameraLightTarget : u32 {
    AllLeds,
    BrightLeds,
    DimLeds,
    None,
};

// This is nn::irsensor::PackedCameraLightTarget
enum class PackedCameraLightTarget : u8 {
    AllLeds,
    BrightLeds,
    DimLeds,
    None,
};

// This is nn::irsensor::AdaptiveClusteringMode
enum class AdaptiveClusteringMode : u32 {
    StaticFov,
    DynamicFov,
};

// This is nn::irsensor::AdaptiveClusteringTargetDistance
enum class AdaptiveClusteringTargetDistance : u32 {
    Near,
    Middle,
    Far,
};

// This is nn::irsensor::ImageTransferProcessorFormat
enum class ImageTransferProcessorFormat : u32 {
    Size320x240,
    Size160x120,
    Size80x60,
    Size40x30,
    Size20x15,
};

// This is nn::irsensor::PackedImageTransferProcessorFormat
enum class PackedImageTransferProcessorFormat : u8 {
    Size320x240,
    Size160x120,
    Size80x60,
    Size40x30,
    Size20x15,
};

// This is nn::irsensor::IrCameraStatus
enum class IrCameraStatus : u32 {
    Available,
    Unsupported,
    Unconnected,
};

// This is nn::irsensor::IrCameraInternalStatus
enum class IrCameraInternalStatus : u32 {
    Stopped,
    FirmwareUpdateNeeded,
    Unknown2,
    Unknown3,
    Unknown4,
    FirmwareVersionRequested,
    FirmwareVersionIsInvalid,
    Ready,
    Setting,
};

// This is nn::irsensor::detail::StatusManager::IrSensorMode
enum class IrSensorMode : u64 {
    None,
    MomentProcessor,
    ClusteringProcessor,
    ImageTransferProcessor,
    PointingProcessorMarker,
    TeraPluginProcessor,
    IrLedProcessor,
};

// This is nn::irsensor::ImageProcessorStatus
enum ImageProcessorStatus : u32 {
    Stopped,
    Running,
};

// This is nn::irsensor::HandAnalysisMode
enum class HandAnalysisMode : u32 {
    None,
    Silhouette,
    Image,
    SilhouetteAndImage,
    SilhouetteOnly,
};

// This is nn::irsensor::IrSensorFunctionLevel
enum class IrSensorFunctionLevel : u8 {
    unknown0,
    unknown1,
    unknown2,
    unknown3,
    unknown4,
};

// This is nn::irsensor::MomentProcessorPreprocess
enum class MomentProcessorPreprocess : u32 {
    Unknown0,
    Unknown1,
};

// This is nn::irsensor::PackedMomentProcessorPreprocess
enum class PackedMomentProcessorPreprocess : u8 {
    Unknown0,
    Unknown1,
};

// This is nn::irsensor::PointingStatus
enum class PointingStatus : u32 {
    Unknown0,
    Unknown1,
};

struct IrsRect {
    s16 x;
    s16 y;
    s16 width;
    s16 height;
};

struct IrsCentroid {
    f32 x;
    f32 y;
};

struct CameraConfig {
    u64 exposure_time;
    CameraLightTarget light_target;
    u32 gain;
    bool is_negative_used;
    INSERT_PADDING_BYTES(7);
};
static_assert(sizeof(CameraConfig) == 0x18, "CameraConfig is an invalid size");

struct PackedCameraConfig {
    u64 exposure_time;
    PackedCameraLightTarget light_target;
    u8 gain;
    bool is_negative_used;
    INSERT_PADDING_BYTES(5);
};
static_assert(sizeof(PackedCameraConfig) == 0x10, "PackedCameraConfig is an invalid size");

// This is nn::irsensor::IrCameraHandle
struct IrCameraHandle {
    u8 npad_id{};
    Core::HID::NpadStyleIndex npad_type{Core::HID::NpadStyleIndex::None};
    INSERT_PADDING_BYTES(2);
};
static_assert(sizeof(IrCameraHandle) == 4, "IrCameraHandle is an invalid size");

// This is nn::irsensor::PackedMcuVersion
struct PackedMcuVersion {
    u16 major;
    u16 minor;
};
static_assert(sizeof(PackedMcuVersion) == 4, "PackedMcuVersion is an invalid size");

// This is nn::irsensor::PackedMomentProcessorConfig
struct PackedMomentProcessorConfig {
    PackedCameraConfig camera_config;
    IrsRect window_of_interest;
    PackedMcuVersion required_mcu_version;
    PackedMomentProcessorPreprocess preprocess;
    u8 preprocess_intensity_threshold;
    INSERT_PADDING_BYTES(2);
};
static_assert(sizeof(PackedMomentProcessorConfig) == 0x20,
              "PackedMomentProcessorConfig is an invalid size");

// This is nn::irsensor::PackedClusteringProcessorConfig
struct PackedClusteringProcessorConfig {
    PackedCameraConfig camera_config;
    IrsRect window_of_interest;
    PackedMcuVersion required_mcu_version;
    u32 pixel_count_min;
    u32 pixel_count_max;
    u8 object_intensity_min;
    bool is_external_light_filter_enabled;
    INSERT_PADDING_BYTES(2);
};
static_assert(sizeof(PackedClusteringProcessorConfig) == 0x28,
              "PackedClusteringProcessorConfig is an invalid size");

// This is nn::irsensor::PackedImageTransferProcessorConfig
struct PackedImageTransferProcessorConfig {
    PackedCameraConfig camera_config;
    PackedMcuVersion required_mcu_version;
    PackedImageTransferProcessorFormat format;
    INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(PackedImageTransferProcessorConfig) == 0x18,
              "PackedImageTransferProcessorConfig is an invalid size");

// This is nn::irsensor::PackedTeraPluginProcessorConfig
struct PackedTeraPluginProcessorConfig {
    PackedMcuVersion required_mcu_version;
    u8 mode;
    u8 unknown_1;
    u8 unknown_2;
    u8 unknown_3;
};
static_assert(sizeof(PackedTeraPluginProcessorConfig) == 0x8,
              "PackedTeraPluginProcessorConfig is an invalid size");

// This is nn::irsensor::PackedPointingProcessorConfig
struct PackedPointingProcessorConfig {
    IrsRect window_of_interest;
    PackedMcuVersion required_mcu_version;
};
static_assert(sizeof(PackedPointingProcessorConfig) == 0xC,
              "PackedPointingProcessorConfig is an invalid size");

// This is nn::irsensor::PackedFunctionLevel
struct PackedFunctionLevel {
    IrSensorFunctionLevel function_level;
    INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(PackedFunctionLevel) == 0x4, "PackedFunctionLevel is an invalid size");

// This is nn::irsensor::PackedImageTransferProcessorExConfig
struct PackedImageTransferProcessorExConfig {
    PackedCameraConfig camera_config;
    PackedMcuVersion required_mcu_version;
    PackedImageTransferProcessorFormat origin_format;
    PackedImageTransferProcessorFormat trimming_format;
    u16 trimming_start_x;
    u16 trimming_start_y;
    bool is_external_light_filter_enabled;
    INSERT_PADDING_BYTES(5);
};
static_assert(sizeof(PackedImageTransferProcessorExConfig) == 0x20,
              "PackedImageTransferProcessorExConfig is an invalid size");

// This is nn::irsensor::PackedIrLedProcessorConfig
struct PackedIrLedProcessorConfig {
    PackedMcuVersion required_mcu_version;
    u8 light_target;
    INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(PackedIrLedProcessorConfig) == 0x8,
              "PackedIrLedProcessorConfig is an invalid size");

// This is nn::irsensor::HandAnalysisConfig
struct HandAnalysisConfig {
    HandAnalysisMode mode;
};
static_assert(sizeof(HandAnalysisConfig) == 0x4, "HandAnalysisConfig is an invalid size");

// This is nn::irsensor::detail::ProcessorState contents are different for each processor
struct ProcessorState {
    std::array<u8, 0xE20> processor_raw_data{};
};
static_assert(sizeof(ProcessorState) == 0xE20, "ProcessorState is an invalid size");

// This is nn::irsensor::detail::DeviceFormat
struct DeviceFormat {
    Core::IrSensor::IrCameraStatus camera_status{Core::IrSensor::IrCameraStatus::Unconnected};
    Core::IrSensor::IrCameraInternalStatus camera_internal_status{
        Core::IrSensor::IrCameraInternalStatus::Ready};
    Core::IrSensor::IrSensorMode mode{Core::IrSensor::IrSensorMode::None};
    ProcessorState state{};
};
static_assert(sizeof(DeviceFormat) == 0xE30, "DeviceFormat is an invalid size");

// This is nn::irsensor::ImageTransferProcessorState
struct ImageTransferProcessorState {
    u64 sampling_number;
    Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
    INSERT_PADDING_BYTES(4);
};
static_assert(sizeof(ImageTransferProcessorState) == 0x10,
              "ImageTransferProcessorState is an invalid size");

} // namespace Core::IrSensor


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

#include "core/core.h"
#include "core/core_timing.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/irsensor/moment_processor.h"

namespace Service::IRS {
static constexpr auto format = Core::IrSensor::ImageTransferProcessorFormat::Size40x30;
static constexpr std::size_t ImageWidth = 40;
static constexpr std::size_t ImageHeight = 30;

MomentProcessor::MomentProcessor(Core::System& system_, Core::IrSensor::DeviceFormat& device_format,
                                 std::size_t npad_index)
    : device(device_format), system{system_} {
    npad_device = system.HIDCore().GetEmulatedControllerByIndex(npad_index);

    device.mode = Core::IrSensor::IrSensorMode::MomentProcessor;
    device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
    device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;

    shared_memory = std::construct_at(
        reinterpret_cast<MomentSharedMemory*>(&device_format.state.processor_raw_data));

    Core::HID::ControllerUpdateCallback engine_callback{
        .on_change = [this](Core::HID::ControllerTriggerType type) { OnControllerUpdate(type); },
        .is_npad_service = true,
    };
    callback_key = npad_device->SetCallback(engine_callback);
}

MomentProcessor::~MomentProcessor() {
    npad_device->DeleteCallback(callback_key);
};

void MomentProcessor::StartProcessor() {
    device.camera_status = Core::IrSensor::IrCameraStatus::Available;
    device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready;
}

void MomentProcessor::SuspendProcessor() {}

void MomentProcessor::StopProcessor() {}

void MomentProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType type) {
    if (type != Core::HID::ControllerTriggerType::IrSensor) {
        return;
    }

    next_state = {};
    const auto& camera_data = npad_device->GetCamera();

    const auto window_width = static_cast<std::size_t>(current_config.window_of_interest.width);
    const auto window_height = static_cast<std::size_t>(current_config.window_of_interest.height);
    const auto window_start_x = static_cast<std::size_t>(current_config.window_of_interest.x);
    const auto window_start_y = static_cast<std::size_t>(current_config.window_of_interest.y);

    const std::size_t block_width = window_width / Columns;
    const std::size_t block_height = window_height / Rows;

    for (std::size_t row = 0; row < Rows; row++) {
        for (std::size_t column = 0; column < Columns; column++) {
            const size_t x_pos = (column * block_width) + window_start_x;
            const size_t y_pos = (row * block_height) + window_start_y;
            auto& statistic = next_state.statistic[column + (row * Columns)];
            statistic = GetStatistic(camera_data.data, x_pos, y_pos, block_width, block_height);
        }
    }

    next_state.sampling_number = camera_data.sample;
    next_state.timestamp = system.CoreTiming().GetGlobalTimeNs().count();
    next_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
    shared_memory->moment_lifo.WriteNextEntry(next_state);

    if (!IsProcessorActive()) {
        StartProcessor();
    }
}

u8 MomentProcessor::GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const {
    if ((y * ImageWidth) + x >= data.size()) {
        return 0;
    }
    return data[(y * ImageWidth) + x];
}

MomentProcessor::MomentStatistic MomentProcessor::GetStatistic(const std::vector<u8>& data,
                                                               std::size_t start_x,
                                                               std::size_t start_y,
                                                               std::size_t width,
                                                               std::size_t height) const {
    // The actual implementation is always 320x240
    static constexpr std::size_t RealWidth = 320;
    static constexpr std::size_t RealHeight = 240;
    static constexpr std::size_t Threshold = 30;
    MomentStatistic statistic{};
    std::size_t active_points{};

    // Sum all data points on the block that meet with the threshold
    for (std::size_t y = 0; y < width; y++) {
        for (std::size_t x = 0; x < height; x++) {
            const size_t x_pos = x + start_x;
            const size_t y_pos = y + start_y;
            const auto pixel =
                GetPixel(data, x_pos * ImageWidth / RealWidth, y_pos * ImageHeight / RealHeight);

            if (pixel < Threshold) {
                continue;
            }

            statistic.average_intensity += pixel;

            statistic.centroid.x += static_cast<float>(x_pos);
            statistic.centroid.y += static_cast<float>(y_pos);

            active_points++;
        }
    }

    // Return an empty field if no points were available
    if (active_points == 0) {
        return {};
    }

    // Finally calculate the actual centroid and average intensity
    statistic.centroid.x /= static_cast<float>(active_points);
    statistic.centroid.y /= static_cast<float>(active_points);
    statistic.average_intensity /= static_cast<f32>(width * height);

    return statistic;
}

void MomentProcessor::SetConfig(Core::IrSensor::PackedMomentProcessorConfig config) {
    current_config.camera_config.exposure_time = config.camera_config.exposure_time;
    current_config.camera_config.gain = config.camera_config.gain;
    current_config.camera_config.is_negative_used = config.camera_config.is_negative_used;
    current_config.camera_config.light_target =
        static_cast<Core::IrSensor::CameraLightTarget>(config.camera_config.light_target);
    current_config.window_of_interest = config.window_of_interest;
    current_config.preprocess =
        static_cast<Core::IrSensor::MomentProcessorPreprocess>(config.preprocess);
    current_config.preprocess_intensity_threshold = config.preprocess_intensity_threshold;

    npad_device->SetCameraFormat(format);
}

} // namespace Service::IRS


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

#pragma once

#include "common/bit_field.h"
#include "common/common_types.h"
#include "hid_core/irsensor/irs_types.h"
#include "hid_core/irsensor/processor_base.h"
#include "hid_core/resources/irs_ring_lifo.h"

namespace Core {
class System;
}

namespace Core::HID {
class EmulatedController;
} // namespace Core::HID

namespace Service::IRS {
class MomentProcessor final : public ProcessorBase {
public:
    explicit MomentProcessor(Core::System& system_, Core::IrSensor::DeviceFormat& device_format,
                             std::size_t npad_index);
    ~MomentProcessor() override;

    // Called when the processor is initialized
    void StartProcessor() override;

    // Called when the processor is suspended
    void SuspendProcessor() override;

    // Called when the processor is stopped
    void StopProcessor() override;

    // Sets config parameters of the camera
    void SetConfig(Core::IrSensor::PackedMomentProcessorConfig config);

private:
    static constexpr std::size_t Columns = 8;
    static constexpr std::size_t Rows = 6;

    // This is nn::irsensor::MomentProcessorConfig
    struct MomentProcessorConfig {
        Core::IrSensor::CameraConfig camera_config;
        Core::IrSensor::IrsRect window_of_interest;
        Core::IrSensor::MomentProcessorPreprocess preprocess;
        u32 preprocess_intensity_threshold;
    };
    static_assert(sizeof(MomentProcessorConfig) == 0x28,
                  "MomentProcessorConfig is an invalid size");

    // This is nn::irsensor::MomentStatistic
    struct MomentStatistic {
        f32 average_intensity;
        Core::IrSensor::IrsCentroid centroid;
    };
    static_assert(sizeof(MomentStatistic) == 0xC, "MomentStatistic is an invalid size");

    // This is nn::irsensor::MomentProcessorState
    struct MomentProcessorState {
        s64 sampling_number;
        u64 timestamp;
        Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
        INSERT_PADDING_BYTES(4);
        std::array<MomentStatistic, Columns * Rows> statistic;
    };
    static_assert(sizeof(MomentProcessorState) == 0x258, "MomentProcessorState is an invalid size");

    struct MomentSharedMemory {
        Service::IRS::Lifo<MomentProcessorState, 6> moment_lifo;
    };
    static_assert(sizeof(MomentSharedMemory) == 0xE20, "MomentSharedMemory is an invalid size");

    void OnControllerUpdate(Core::HID::ControllerTriggerType type);
    u8 GetPixel(const std::vector<u8>& data, std::size_t x, std::size_t y) const;
    MomentStatistic GetStatistic(const std::vector<u8>& data, std::size_t start_x,
                                 std::size_t start_y, std::size_t width, std::size_t height) const;

    MomentSharedMemory* shared_memory = nullptr;
    MomentProcessorState next_state{};

    MomentProcessorConfig current_config{};
    Core::IrSensor::DeviceFormat& device;
    Core::HID::EmulatedController* npad_device;
    int callback_key{};

    Core::System& system;
};

} // namespace Service::IRS


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

#include "hid_core/irsensor/pointing_processor.h"

namespace Service::IRS {
PointingProcessor::PointingProcessor(Core::IrSensor::DeviceFormat& device_format)
    : device(device_format) {
    device.mode = Core::IrSensor::IrSensorMode::PointingProcessorMarker;
    device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
    device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
}

PointingProcessor::~PointingProcessor() = default;

void PointingProcessor::StartProcessor() {}

void PointingProcessor::SuspendProcessor() {}

void PointingProcessor::StopProcessor() {}

void PointingProcessor::SetConfig(Core::IrSensor::PackedPointingProcessorConfig config) {
    current_config.window_of_interest = config.window_of_interest;
}

} // namespace Service::IRS


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

#pragma once

#include "common/common_types.h"
#include "hid_core/irsensor/irs_types.h"
#include "hid_core/irsensor/processor_base.h"

namespace Service::IRS {
class PointingProcessor final : public ProcessorBase {
public:
    explicit PointingProcessor(Core::IrSensor::DeviceFormat& device_format);
    ~PointingProcessor() override;

    // Called when the processor is initialized
    void StartProcessor() override;

    // Called when the processor is suspended
    void SuspendProcessor() override;

    // Called when the processor is stopped
    void StopProcessor() override;

    // Sets config parameters of the camera
    void SetConfig(Core::IrSensor::PackedPointingProcessorConfig config);

private:
    // This is nn::irsensor::PointingProcessorConfig
    struct PointingProcessorConfig {
        Core::IrSensor::IrsRect window_of_interest;
    };
    static_assert(sizeof(PointingProcessorConfig) == 0x8,
                  "PointingProcessorConfig is an invalid size");

    struct PointingProcessorMarkerData {
        u8 pointing_status;
        INSERT_PADDING_BYTES(3);
        u32 unknown;
        float unknown_float1;
        float position_x;
        float position_y;
        float unknown_float2;
        Core::IrSensor::IrsRect window_of_interest;
    };
    static_assert(sizeof(PointingProcessorMarkerData) == 0x20,
                  "PointingProcessorMarkerData is an invalid size");

    struct PointingProcessorMarkerState {
        s64 sampling_number;
        u64 timestamp;
        std::array<PointingProcessorMarkerData, 0x3> data;
    };
    static_assert(sizeof(PointingProcessorMarkerState) == 0x70,
                  "PointingProcessorMarkerState is an invalid size");

    PointingProcessorConfig current_config{};
    Core::IrSensor::DeviceFormat& device;
};

} // namespace Service::IRS


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

#include "hid_core/irsensor/processor_base.h"

namespace Service::IRS {

ProcessorBase::ProcessorBase() {}
ProcessorBase::~ProcessorBase() = default;

bool ProcessorBase::IsProcessorActive() const {
    return is_active;
}

std::size_t ProcessorBase::GetDataSize(Core::IrSensor::ImageTransferProcessorFormat format) const {
    switch (format) {
    case Core::IrSensor::ImageTransferProcessorFormat::Size320x240:
        return 320 * 240;
    case Core::IrSensor::ImageTransferProcessorFormat::Size160x120:
        return 160 * 120;
    case Core::IrSensor::ImageTransferProcessorFormat::Size80x60:
        return 80 * 60;
    case Core::IrSensor::ImageTransferProcessorFormat::Size40x30:
        return 40 * 30;
    case Core::IrSensor::ImageTransferProcessorFormat::Size20x15:
        return 20 * 15;
    default:
        return 0;
    }
}

std::size_t ProcessorBase::GetDataWidth(Core::IrSensor::ImageTransferProcessorFormat format) const {
    switch (format) {
    case Core::IrSensor::ImageTransferProcessorFormat::Size320x240:
        return 320;
    case Core::IrSensor::ImageTransferProcessorFormat::Size160x120:
        return 160;
    case Core::IrSensor::ImageTransferProcessorFormat::Size80x60:
        return 80;
    case Core::IrSensor::ImageTransferProcessorFormat::Size40x30:
        return 40;
    case Core::IrSensor::ImageTransferProcessorFormat::Size20x15:
        return 20;
    default:
        return 0;
    }
}

std::size_t ProcessorBase::GetDataHeight(
    Core::IrSensor::ImageTransferProcessorFormat format) const {
    switch (format) {
    case Core::IrSensor::ImageTransferProcessorFormat::Size320x240:
        return 240;
    case Core::IrSensor::ImageTransferProcessorFormat::Size160x120:
        return 120;
    case Core::IrSensor::ImageTransferProcessorFormat::Size80x60:
        return 60;
    case Core::IrSensor::ImageTransferProcessorFormat::Size40x30:
        return 30;
    case Core::IrSensor::ImageTransferProcessorFormat::Size20x15:
        return 15;
    default:
        return 0;
    }
}

} // namespace Service::IRS


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

#pragma once

#include "common/common_types.h"
#include "hid_core/irsensor/irs_types.h"

namespace Service::IRS {
class ProcessorBase {
public:
    explicit ProcessorBase();
    virtual ~ProcessorBase();

    virtual void StartProcessor() = 0;
    virtual void SuspendProcessor() = 0;
    virtual void StopProcessor() = 0;

    bool IsProcessorActive() const;

protected:
    /// Returns the number of bytes the image uses
    std::size_t GetDataSize(Core::IrSensor::ImageTransferProcessorFormat format) const;

    /// Returns the width of the image
    std::size_t GetDataWidth(Core::IrSensor::ImageTransferProcessorFormat format) const;

    /// Returns the height of the image
    std::size_t GetDataHeight(Core::IrSensor::ImageTransferProcessorFormat format) const;

    bool is_active{false};
};
} // namespace Service::IRS


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

#include "hid_core/irsensor/tera_plugin_processor.h"

namespace Service::IRS {
TeraPluginProcessor::TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format)
    : device(device_format) {
    device.mode = Core::IrSensor::IrSensorMode::TeraPluginProcessor;
    device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
    device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
}

TeraPluginProcessor::~TeraPluginProcessor() = default;

void TeraPluginProcessor::StartProcessor() {}

void TeraPluginProcessor::SuspendProcessor() {}

void TeraPluginProcessor::StopProcessor() {}

void TeraPluginProcessor::SetConfig(Core::IrSensor::PackedTeraPluginProcessorConfig config) {
    current_config.mode = config.mode;
    current_config.unknown_1 = config.unknown_1;
    current_config.unknown_2 = config.unknown_2;
    current_config.unknown_3 = config.unknown_3;
}

} // namespace Service::IRS


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

#pragma once

#include "common/bit_field.h"
#include "common/common_types.h"
#include "hid_core/irsensor/irs_types.h"
#include "hid_core/irsensor/processor_base.h"

namespace Service::IRS {
class TeraPluginProcessor final : public ProcessorBase {
public:
    explicit TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format);
    ~TeraPluginProcessor() override;

    // Called when the processor is initialized
    void StartProcessor() override;

    // Called when the processor is suspended
    void SuspendProcessor() override;

    // Called when the processor is stopped
    void StopProcessor() override;

    // Sets config parameters of the camera
    void SetConfig(Core::IrSensor::PackedTeraPluginProcessorConfig config);

private:
    // This is nn::irsensor::TeraPluginProcessorConfig
    struct TeraPluginProcessorConfig {
        u8 mode;
        u8 unknown_1;
        u8 unknown_2;
        u8 unknown_3;
    };
    static_assert(sizeof(TeraPluginProcessorConfig) == 0x4,
                  "TeraPluginProcessorConfig is an invalid size");

    struct TeraPluginProcessorState {
        s64 sampling_number;
        u64 timestamp;
        Core::IrSensor::CameraAmbientNoiseLevel ambient_noise_level;
        std::array<u8, 0x12c> data;
    };
    static_assert(sizeof(TeraPluginProcessorState) == 0x140,
                  "TeraPluginProcessorState is an invalid size");

    TeraPluginProcessorConfig current_config{};
    Core::IrSensor::DeviceFormat& device;
};

} // namespace Service::IRS


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

#pragma once

#include "common/common_precompiled_headers.h"


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/set/system_settings_server.h"
#include "core/hle/service/sm/sm.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_util.h"
#include "hid_core/resource_manager.h"

#include "hid_core/resources/applet_resource.h"
#include "hid_core/resources/debug_pad/debug_pad.h"
#include "hid_core/resources/digitizer/digitizer.h"
#include "hid_core/resources/hid_firmware_settings.h"
#include "hid_core/resources/keyboard/keyboard.h"
#include "hid_core/resources/mouse/debug_mouse.h"
#include "hid_core/resources/mouse/mouse.h"
#include "hid_core/resources/npad/npad.h"
#include "hid_core/resources/palma/palma.h"
#include "hid_core/resources/shared_memory_format.h"
#include "hid_core/resources/six_axis/console_six_axis.h"
#include "hid_core/resources/six_axis/seven_six_axis.h"
#include "hid_core/resources/six_axis/six_axis.h"
#include "hid_core/resources/system_buttons/capture_button.h"
#include "hid_core/resources/system_buttons/home_button.h"
#include "hid_core/resources/system_buttons/sleep_button.h"
#include "hid_core/resources/touch_screen/gesture.h"
#include "hid_core/resources/touch_screen/touch_screen.h"
#include "hid_core/resources/touch_screen/touch_screen_driver.h"
#include "hid_core/resources/touch_screen/touch_screen_resource.h"
#include "hid_core/resources/unique_pad/unique_pad.h"
#include "hid_core/resources/vibration/gc_vibration_device.h"
#include "hid_core/resources/vibration/n64_vibration_device.h"
#include "hid_core/resources/vibration/vibration_base.h"
#include "hid_core/resources/vibration/vibration_device.h"

namespace Service::HID {

// Updating period for each HID device.
// Period time is obtained by measuring the number of samples in a second on HW using a homebrew
// Correct npad_update_ns is 4ms this is overclocked to lower input lag
constexpr auto npad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000};    // (1ms, 1000Hz)
constexpr auto default_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 1000Hz)
constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz)
constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000};         // (5ms, 200Hz)

ResourceManager::ResourceManager(Core::System& system_,
                                 std::shared_ptr<HidFirmwareSettings> settings)
    : firmware_settings{settings}, system{system_}, service_context{system_, "hid"} {
    applet_resource = std::make_shared<AppletResource>(system);

    // Register update callbacks
    npad_update_event = Core::Timing::CreateEvent("HID::UpdatePadCallback",
                                                  [this](s64 time, std::chrono::nanoseconds ns_late)
                                                      -> std::optional<std::chrono::nanoseconds> {
                                                      UpdateNpad(ns_late);
                                                      return std::nullopt;
                                                  });
    default_update_event = Core::Timing::CreateEvent(
        "HID::UpdateDefaultCallback",
        [this](s64 time,
               std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
            UpdateControllers(ns_late);
            return std::nullopt;
        });
    mouse_keyboard_update_event = Core::Timing::CreateEvent(
        "HID::UpdateMouseKeyboardCallback",
        [this](s64 time,
               std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
            UpdateMouseKeyboard(ns_late);
            return std::nullopt;
        });
    motion_update_event = Core::Timing::CreateEvent(
        "HID::UpdateMotionCallback",
        [this](s64 time,
               std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
            UpdateMotion(ns_late);
            return std::nullopt;
        });
}

ResourceManager::~ResourceManager() {
    system.CoreTiming().UnscheduleEvent(npad_update_event);
    system.CoreTiming().UnscheduleEvent(default_update_event);
    system.CoreTiming().UnscheduleEvent(mouse_keyboard_update_event);
    system.CoreTiming().UnscheduleEvent(motion_update_event);
    system.CoreTiming().UnscheduleEvent(touch_update_event);
    input_event->Finalize();
};

void ResourceManager::Initialize() {
    if (is_initialized) {
        return;
    }

    system.HIDCore().ReloadInputDevices();

    input_event = service_context.CreateEvent("ResourceManager:InputEvent");

    InitializeHandheldConfig();
    InitializeHidCommonSampler();
    InitializeTouchScreenSampler();
    InitializeConsoleSixAxisSampler();
    InitializeAHidSampler();

    is_initialized = true;
}

std::shared_ptr<AppletResource> ResourceManager::GetAppletResource() const {
    return applet_resource;
}

std::shared_ptr<CaptureButton> ResourceManager::GetCaptureButton() const {
    return capture_button;
}

std::shared_ptr<ConsoleSixAxis> ResourceManager::GetConsoleSixAxis() const {
    return console_six_axis;
}

std::shared_ptr<DebugMouse> ResourceManager::GetDebugMouse() const {
    return debug_mouse;
}

std::shared_ptr<DebugPad> ResourceManager::GetDebugPad() const {
    return debug_pad;
}

std::shared_ptr<Digitizer> ResourceManager::GetDigitizer() const {
    return digitizer;
}

std::shared_ptr<Gesture> ResourceManager::GetGesture() const {
    return gesture;
}

std::shared_ptr<HomeButton> ResourceManager::GetHomeButton() const {
    return home_button;
}

std::shared_ptr<Keyboard> ResourceManager::GetKeyboard() const {
    return keyboard;
}

std::shared_ptr<Mouse> ResourceManager::GetMouse() const {
    return mouse;
}

std::shared_ptr<NPad> ResourceManager::GetNpad() const {
    return npad;
}

std::shared_ptr<Palma> ResourceManager::GetPalma() const {
    return palma;
}

std::shared_ptr<SevenSixAxis> ResourceManager::GetSevenSixAxis() const {
    return seven_six_axis;
}

std::shared_ptr<SixAxis> ResourceManager::GetSixAxis() const {
    return six_axis;
}

std::shared_ptr<SleepButton> ResourceManager::GetSleepButton() const {
    return sleep_button;
}

std::shared_ptr<TouchScreen> ResourceManager::GetTouchScreen() const {
    return touch_screen;
}

std::shared_ptr<UniquePad> ResourceManager::GetUniquePad() const {
    return unique_pad;
}

Result ResourceManager::CreateAppletResource(u64 aruid) {
    if (aruid == SystemAruid) {
        const auto result = RegisterCoreAppletResource();
        if (result.IsError()) {
            return result;
        }
        return GetNpad()->ActivateNpadResource();
    }

    const auto result = CreateAppletResourceImpl(aruid);
    if (result.IsError()) {
        return result;
    }

    // Homebrew doesn't try to activate some controllers, so we activate them by default
    npad->Activate();
    six_axis->Activate();
    touch_screen->Activate();
    gesture->Activate();

    return GetNpad()->ActivateNpadResource(aruid);
}

Result ResourceManager::CreateAppletResourceImpl(u64 aruid) {
    std::scoped_lock lock{shared_mutex};
    return applet_resource->CreateAppletResource(aruid);
}

void ResourceManager::InitializeHandheldConfig() {
    handheld_config = std::make_shared<HandheldConfig>();
    handheld_config->is_handheld_hid_enabled = true;
    handheld_config->is_joycon_rail_enabled = true;
    handheld_config->is_force_handheld_style_vibration = false;
    handheld_config->is_force_handheld = false;
    if (firmware_settings->IsHandheldForced()) {
        handheld_config->is_joycon_rail_enabled = false;
    }
}

void ResourceManager::InitializeHidCommonSampler() {
    debug_pad = std::make_shared<DebugPad>(system.HIDCore());
    mouse = std::make_shared<Mouse>(system.HIDCore());
    debug_mouse = std::make_shared<DebugMouse>(system.HIDCore());
    keyboard = std::make_shared<Keyboard>(system.HIDCore());
    unique_pad = std::make_shared<UniquePad>(system.HIDCore());
    npad = std::make_shared<NPad>(system.HIDCore(), service_context);
    home_button = std::make_shared<HomeButton>(system.HIDCore());
    sleep_button = std::make_shared<SleepButton>(system.HIDCore());
    capture_button = std::make_shared<CaptureButton>(system.HIDCore());
    digitizer = std::make_shared<Digitizer>(system.HIDCore());

    palma = std::make_shared<Palma>(system.HIDCore(), service_context);
    six_axis = std::make_shared<SixAxis>(system.HIDCore(), npad);

    debug_pad->SetAppletResource(applet_resource, &shared_mutex);
    digitizer->SetAppletResource(applet_resource, &shared_mutex);
    unique_pad->SetAppletResource(applet_resource, &shared_mutex);
    keyboard->SetAppletResource(applet_resource, &shared_mutex);

    const auto settings =
        system.ServiceManager().GetService<Service::Set::ISystemSettingsServer>("set:sys", true);
    npad->SetNpadExternals(applet_resource, &shared_mutex, handheld_config, input_event,
                           &input_mutex, settings);

    six_axis->SetAppletResource(applet_resource, &shared_mutex);
    mouse->SetAppletResource(applet_resource, &shared_mutex);
    debug_mouse->SetAppletResource(applet_resource, &shared_mutex);
    home_button->SetAppletResource(applet_resource, &shared_mutex);
    sleep_button->SetAppletResource(applet_resource, &shared_mutex);
    capture_button->SetAppletResource(applet_resource, &shared_mutex);

    system.CoreTiming().ScheduleLoopingEvent(npad_update_ns, npad_update_ns, npad_update_event);
    system.CoreTiming().ScheduleLoopingEvent(default_update_ns, default_update_ns,
                                             default_update_event);
    system.CoreTiming().ScheduleLoopingEvent(mouse_keyboard_update_ns, mouse_keyboard_update_ns,
                                             mouse_keyboard_update_event);
    system.CoreTiming().ScheduleLoopingEvent(motion_update_ns, motion_update_ns,
                                             motion_update_event);
}

void ResourceManager::InitializeTouchScreenSampler() {
    // This is nn.hid.TouchScreenSampler
    touch_resource = std::make_shared<TouchResource>(system);
    touch_driver = std::make_shared<TouchDriver>(system.HIDCore());
    touch_screen = std::make_shared<TouchScreen>(touch_resource);
    gesture = std::make_shared<Gesture>(touch_resource);

    touch_update_event = Core::Timing::CreateEvent(
        "HID::TouchUpdateCallback",
        [this](s64 time,
               std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
            touch_resource->OnTouchUpdate(time);
            return std::nullopt;
        });

    touch_resource->SetTouchDriver(touch_driver);
    touch_resource->SetAppletResource(applet_resource, &shared_mutex);
    touch_resource->SetInputEvent(input_event, &input_mutex);
    touch_resource->SetHandheldConfig(handheld_config);
    touch_resource->SetTimerEvent(touch_update_event);
}

void ResourceManager::InitializeConsoleSixAxisSampler() {
    console_six_axis = std::make_shared<ConsoleSixAxis>(system.HIDCore());
    seven_six_axis = std::make_shared<SevenSixAxis>(system);

    console_six_axis->SetAppletResource(applet_resource, &shared_mutex);
}

void ResourceManager::InitializeAHidSampler() {
    // TODO
}

Result ResourceManager::RegisterCoreAppletResource() {
    std::scoped_lock lock{shared_mutex};
    return applet_resource->RegisterCoreAppletResource();
}

Result ResourceManager::UnregisterCoreAppletResource() {
    std::scoped_lock lock{shared_mutex};
    return applet_resource->UnregisterCoreAppletResource();
}

Result ResourceManager::RegisterAppletResourceUserId(u64 aruid, bool bool_value) {
    std::scoped_lock lock{shared_mutex};
    auto result = applet_resource->RegisterAppletResourceUserId(aruid, bool_value);
    if (result.IsSuccess()) {
        result = npad->RegisterAppletResourceUserId(aruid);
    }
    return result;
}

void ResourceManager::UnregisterAppletResourceUserId(u64 aruid) {
    std::scoped_lock lock{shared_mutex};
    applet_resource->UnregisterAppletResourceUserId(aruid);
    npad->UnregisterAppletResourceUserId(aruid);
    // palma->UnregisterAppletResourceUserId(aruid);
}

Result ResourceManager::GetSharedMemoryHandle(Kernel::KSharedMemory** out_handle, u64 aruid) {
    std::scoped_lock lock{shared_mutex};
    return applet_resource->GetSharedMemoryHandle(out_handle, aruid);
}

void ResourceManager::FreeAppletResourceId(u64 aruid) {
    std::scoped_lock lock{shared_mutex};
    applet_resource->FreeAppletResourceId(aruid);
    npad->FreeAppletResourceId(aruid);
}

void ResourceManager::EnableInput(u64 aruid, bool is_enabled) {
    std::scoped_lock lock{shared_mutex};
    applet_resource->EnableInput(aruid, is_enabled);
}

void ResourceManager::EnableSixAxisSensor(u64 aruid, bool is_enabled) {
    std::scoped_lock lock{shared_mutex};
    applet_resource->EnableSixAxisSensor(aruid, is_enabled);
}

void ResourceManager::EnablePadInput(u64 aruid, bool is_enabled) {
    std::scoped_lock lock{shared_mutex};
    applet_resource->EnablePadInput(aruid, is_enabled);
}

void ResourceManager::EnableTouchScreen(u64 aruid, bool is_enabled) {
    std::scoped_lock lock{shared_mutex};
    applet_resource->EnableTouchScreen(aruid, is_enabled);
}

NpadVibrationBase* ResourceManager::GetVibrationDevice(
    const Core::HID::VibrationDeviceHandle& handle) {
    return npad->GetVibrationDevice(handle);
}

NpadN64VibrationDevice* ResourceManager::GetN64VibrationDevice(
    const Core::HID::VibrationDeviceHandle& handle) {
    return npad->GetN64VibrationDevice(handle);
}

NpadVibrationDevice* ResourceManager::GetNSVibrationDevice(
    const Core::HID::VibrationDeviceHandle& handle) {
    return npad->GetNSVibrationDevice(handle);
}

NpadGcVibrationDevice* ResourceManager::GetGcVibrationDevice(
    const Core::HID::VibrationDeviceHandle& handle) {
    return npad->GetGcVibrationDevice(handle);
}

Result ResourceManager::SetAruidValidForVibration(u64 aruid, bool is_enabled) {
    std::scoped_lock lock{shared_mutex};
    const bool has_changed = applet_resource->SetAruidValidForVibration(aruid, is_enabled);

    if (has_changed) {
        auto devices = npad->GetAllVibrationDevices();
        for ([[maybe_unused]] auto* device : devices) {
            // TODO
        }
    }

    auto* vibration_handler = npad->GetVibrationHandler();
    if (aruid != vibration_handler->GetSessionAruid()) {
        vibration_handler->EndPermitVibrationSession();
    }

    return ResultSuccess;
}

void ResourceManager::SetForceHandheldStyleVibration(bool is_forced) {
    handheld_config->is_force_handheld_style_vibration = is_forced;
}

Result ResourceManager::IsVibrationAruidActive(u64 aruid, bool& is_active) const {
    std::scoped_lock lock{shared_mutex};
    is_active = applet_resource->IsVibrationAruidActive(aruid);
    return ResultSuccess;
}

Result ResourceManager::GetVibrationDeviceInfo(Core::HID::VibrationDeviceInfo& device_info,
                                               const Core::HID::VibrationDeviceHandle& handle) {
    bool check_device_index = false;

    const Result is_valid = IsVibrationHandleValid(handle);
    if (is_valid.IsError()) {
        return is_valid;
    }

    switch (handle.npad_type) {
    case Core::HID::NpadStyleIndex::Fullkey:
    case Core::HID::NpadStyleIndex::Handheld:
    case Core::HID::NpadStyleIndex::JoyconDual:
    case Core::HID::NpadStyleIndex::JoyconLeft:
    case Core::HID::NpadStyleIndex::JoyconRight:
        device_info.type = Core::HID::VibrationDeviceType::LinearResonantActuator;
        check_device_index = true;
        break;
    case Core::HID::NpadStyleIndex::GameCube:
        device_info.type = Core::HID::VibrationDeviceType::GcErm;
        break;
    case Core::HID::NpadStyleIndex::N64:
        device_info.type = Core::HID::VibrationDeviceType::N64;
        break;
    default:
        device_info.type = Core::HID::VibrationDeviceType::Unknown;
        break;
    }

    device_info.position = Core::HID::VibrationDevicePosition::None;
    if (check_device_index) {
        switch (handle.device_index) {
        case Core::HID::DeviceIndex::Left:
            device_info.position = Core::HID::VibrationDevicePosition::Left;
            break;
        case Core::HID::DeviceIndex::Right:
            device_info.position = Core::HID::VibrationDevicePosition::Right;
            break;
        case Core::HID::DeviceIndex::None:
        default:
            ASSERT_MSG(false, "DeviceIndex should never be None!");
            break;
        }
    }
    return ResultSuccess;
}

Result ResourceManager::SendVibrationValue(u64 aruid,
                                           const Core::HID::VibrationDeviceHandle& handle,
                                           const Core::HID::VibrationValue& value) {
    bool has_active_aruid{};
    NpadVibrationDevice* device{nullptr};
    Result result = IsVibrationAruidActive(aruid, has_active_aruid);

    if (result.IsSuccess() && has_active_aruid) {
        result = IsVibrationHandleValid(handle);
    }
    if (result.IsSuccess() && has_active_aruid) {
        device = GetNSVibrationDevice(handle);
    }
    if (device != nullptr) {
        // Prevent sending vibrations to an inactive vibration handle
        if (!device->IsActive()) {
            return ResultSuccess;
        }
        result = device->SendVibrationValue(value);
    }
    return result;
}

Result ResourceManager::GetTouchScreenFirmwareVersion(Core::HID::FirmwareVersion& firmware) const {
    return ResultSuccess;
}

void ResourceManager::UpdateControllers(std::chrono::nanoseconds ns_late) {
    auto& core_timing = system.CoreTiming();
    debug_pad->OnUpdate(core_timing);
    digitizer->OnUpdate(core_timing);
    unique_pad->OnUpdate(core_timing);
    palma->OnUpdate(core_timing);
    home_button->OnUpdate(core_timing);
    sleep_button->OnUpdate(core_timing);
    capture_button->OnUpdate(core_timing);
}

void ResourceManager::UpdateNpad(std::chrono::nanoseconds ns_late) {
    auto& core_timing = system.CoreTiming();
    npad->OnUpdate(core_timing);
}

void ResourceManager::UpdateMouseKeyboard(std::chrono::nanoseconds ns_late) {
    auto& core_timing = system.CoreTiming();
    mouse->OnUpdate(core_timing);
    debug_mouse->OnUpdate(core_timing);
    keyboard->OnUpdate(core_timing);
}

void ResourceManager::UpdateMotion(std::chrono::nanoseconds ns_late) {
    auto& core_timing = system.CoreTiming();
    six_axis->OnUpdate(core_timing);
    seven_six_axis->OnUpdate(core_timing);
    console_six_axis->OnUpdate(core_timing);
}

} // namespace Service::HID