From f19e7be6e84357234c9fdae3395f988a9bb1ac5b Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sat, 18 Jun 2022 23:32:07 -0500
Subject: [PATCH 1/6] input_common: Add camera driver

---
 src/common/input.h                  | 29 +++++++++-
 src/common/settings.h               |  3 ++
 src/input_common/CMakeLists.txt     |  2 +
 src/input_common/drivers/camera.cpp | 82 +++++++++++++++++++++++++++++
 src/input_common/drivers/camera.h   | 29 ++++++++++
 src/input_common/input_engine.cpp   | 38 +++++++++++++
 src/input_common/input_engine.h     | 21 ++++++--
 src/input_common/input_poller.cpp   | 60 +++++++++++++++++++++
 src/input_common/input_poller.h     | 11 ++++
 src/input_common/main.cpp           | 21 ++++++++
 src/input_common/main.h             |  9 +++-
 11 files changed, 299 insertions(+), 6 deletions(-)
 create mode 100644 src/input_common/drivers/camera.cpp
 create mode 100644 src/input_common/drivers/camera.h

diff --git a/src/common/input.h b/src/common/input.h
index bb42aaacc..995c35d9d 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -28,7 +28,7 @@ enum class InputType {
     Color,
     Vibration,
     Nfc,
-    Ir,
+    IrSensor,
 };
 
 // Internal battery charge level
@@ -53,6 +53,15 @@ enum class PollingMode {
     IR,
 };
 
+enum class CameraFormat {
+    Size320x240,
+    Size160x120,
+    Size80x60,
+    Size40x30,
+    Size20x15,
+    None,
+};
+
 // Vibration reply from the controller
 enum class VibrationError {
     None,
@@ -68,6 +77,13 @@ enum class PollingError {
     Unknown,
 };
 
+// Ir camera reply from the controller
+enum class CameraError {
+    None,
+    NotSupported,
+    Unknown,
+};
+
 // Hint for amplification curve to be used
 enum class VibrationAmplificationType {
     Linear,
@@ -176,6 +192,12 @@ struct LedStatus {
     bool led_4{};
 };
 
+// Raw data fom camera
+struct CameraStatus {
+    CameraFormat format{CameraFormat::None};
+    std::vector<u8> data{};
+};
+
 // List of buttons to be passed to Qt that can be translated
 enum class ButtonNames {
     Undefined,
@@ -233,6 +255,7 @@ struct CallbackStatus {
     BodyColorStatus color_status{};
     BatteryStatus battery_status{};
     VibrationStatus vibration_status{};
+    CameraStatus camera_status{};
 };
 
 // Triggered once every input change
@@ -281,6 +304,10 @@ public:
     virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
         return PollingError::NotSupported;
     }
+
+    virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
+        return CameraError::NotSupported;
+    }
 };
 
 /// An abstract class template for a factory that can create input devices.
diff --git a/src/common/settings.h b/src/common/settings.h
index 06d72c8bf..20959ec89 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -503,6 +503,9 @@ struct Values {
     Setting<bool> enable_ring_controller{true, "enable_ring_controller"};
     RingconRaw ringcon_analogs;
 
+    BasicSetting<bool> enable_ir_sensor{false, "enable_ir_sensor"};
+    BasicSetting<std::string> ir_sensor_device{"auto", "ir_sensor_device"};
+
     // Data Storage
     Setting<bool> use_virtual_sd{true, "use_virtual_sd"};
     Setting<bool> gamecard_inserted{false, "gamecard_inserted"};
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 48e799cf5..90dd629c6 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -1,4 +1,6 @@
 add_library(input_common STATIC
+    drivers/camera.cpp
+    drivers/camera.h
     drivers/gc_adapter.cpp
     drivers/gc_adapter.h
     drivers/keyboard.cpp
diff --git a/src/input_common/drivers/camera.cpp b/src/input_common/drivers/camera.cpp
new file mode 100644
index 000000000..dceea67e0
--- /dev/null
+++ b/src/input_common/drivers/camera.cpp
@@ -0,0 +1,82 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <fmt/format.h>
+
+#include "common/param_package.h"
+#include "input_common/drivers/camera.h"
+
+namespace InputCommon {
+constexpr PadIdentifier identifier = {
+    .guid = Common::UUID{},
+    .port = 0,
+    .pad = 0,
+};
+
+Camera::Camera(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
+    PreSetController(identifier);
+}
+
+void Camera::SetCameraData(std::size_t width, std::size_t height, std::vector<u32> data) {
+    const std::size_t desired_width = getImageWidth();
+    const std::size_t desired_height = getImageHeight();
+    status.data.resize(desired_width * desired_height);
+
+    // Resize image to desired format
+    for (std::size_t y = 0; y < desired_height; y++) {
+        for (std::size_t x = 0; x < desired_width; x++) {
+            const std::size_t pixel_index = y * desired_width + x;
+            const std::size_t old_x = width * x / desired_width;
+            const std::size_t old_y = height * y / desired_height;
+            const std::size_t data_pixel_index = old_y * width + old_x;
+            status.data[pixel_index] = static_cast<u8>(data[data_pixel_index] & 0xFF);
+        }
+    }
+
+    SetCamera(identifier, status);
+}
+
+std::size_t Camera::getImageWidth() const {
+    switch (status.format) {
+    case Common::Input::CameraFormat::Size320x240:
+        return 320;
+    case Common::Input::CameraFormat::Size160x120:
+        return 160;
+    case Common::Input::CameraFormat::Size80x60:
+        return 80;
+    case Common::Input::CameraFormat::Size40x30:
+        return 40;
+    case Common::Input::CameraFormat::Size20x15:
+        return 20;
+    case Common::Input::CameraFormat::None:
+    default:
+        return 0;
+    }
+}
+
+std::size_t Camera::getImageHeight() const {
+    switch (status.format) {
+    case Common::Input::CameraFormat::Size320x240:
+        return 240;
+    case Common::Input::CameraFormat::Size160x120:
+        return 120;
+    case Common::Input::CameraFormat::Size80x60:
+        return 60;
+    case Common::Input::CameraFormat::Size40x30:
+        return 30;
+    case Common::Input::CameraFormat::Size20x15:
+        return 15;
+    case Common::Input::CameraFormat::None:
+    default:
+        return 0;
+    }
+}
+
+Common::Input::CameraError Camera::SetCameraFormat(
+    [[maybe_unused]] const PadIdentifier& identifier_,
+    const Common::Input::CameraFormat camera_format) {
+    status.format = camera_format;
+    return Common::Input::CameraError::None;
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/drivers/camera.h b/src/input_common/drivers/camera.h
new file mode 100644
index 000000000..b8a7c75e5
--- /dev/null
+++ b/src/input_common/drivers/camera.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "input_common/input_engine.h"
+
+namespace InputCommon {
+
+/**
+ * A button device factory representing a keyboard. It receives keyboard events and forward them
+ * to all button devices it created.
+ */
+class Camera final : public InputEngine {
+public:
+    explicit Camera(std::string input_engine_);
+
+    void SetCameraData(std::size_t width, std::size_t height, std::vector<u32> data);
+
+    std::size_t getImageWidth() const;
+    std::size_t getImageHeight() const;
+
+    Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_,
+                                               Common::Input::CameraFormat camera_format) override;
+
+    Common::Input::CameraStatus status{};
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp
index 12214d146..6ede0e4b0 100644
--- a/src/input_common/input_engine.cpp
+++ b/src/input_common/input_engine.cpp
@@ -90,6 +90,18 @@ void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const B
     TriggerOnMotionChange(identifier, motion, value);
 }
 
+void InputEngine::SetCamera(const PadIdentifier& identifier,
+                            const Common::Input::CameraStatus& value) {
+    {
+        std::scoped_lock lock{mutex};
+        ControllerData& controller = controller_list.at(identifier);
+        if (!configuring) {
+            controller.camera = value;
+        }
+    }
+    TriggerOnCameraChange(identifier, value);
+}
+
 bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const {
     std::scoped_lock lock{mutex};
     const auto controller_iter = controller_list.find(identifier);
@@ -165,6 +177,18 @@ BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion)
     return controller.motions.at(motion);
 }
 
+Common::Input::CameraStatus InputEngine::GetCamera(const PadIdentifier& identifier) const {
+    std::scoped_lock lock{mutex};
+    const auto controller_iter = controller_list.find(identifier);
+    if (controller_iter == controller_list.cend()) {
+        LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
+                  identifier.pad, identifier.port);
+        return {};
+    }
+    const ControllerData& controller = controller_iter->second;
+    return controller.camera;
+}
+
 void InputEngine::ResetButtonState() {
     for (const auto& controller : controller_list) {
         for (const auto& button : controller.second.buttons) {
@@ -317,6 +341,20 @@ void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int mot
     });
 }
 
+void InputEngine::TriggerOnCameraChange(const PadIdentifier& identifier,
+                                        [[maybe_unused]] const Common::Input::CameraStatus& value) {
+    std::scoped_lock lock{mutex_callback};
+    for (const auto& poller_pair : callback_list) {
+        const InputIdentifier& poller = poller_pair.second;
+        if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Camera, 0)) {
+            continue;
+        }
+        if (poller.callback.on_change) {
+            poller.callback.on_change();
+        }
+    }
+}
+
 bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier,
                                          const PadIdentifier& identifier, EngineInputType type,
                                          int index) const {
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h
index 13295bd49..f6b3c4610 100644
--- a/src/input_common/input_engine.h
+++ b/src/input_common/input_engine.h
@@ -36,11 +36,12 @@ struct BasicMotion {
 // Types of input that are stored in the engine
 enum class EngineInputType {
     None,
-    Button,
-    HatButton,
     Analog,
-    Motion,
     Battery,
+    Button,
+    Camera,
+    HatButton,
+    Motion,
 };
 
 namespace std {
@@ -115,10 +116,17 @@ public:
     // Sets polling mode to a controller
     virtual Common::Input::PollingError SetPollingMode(
         [[maybe_unused]] const PadIdentifier& identifier,
-        [[maybe_unused]] const Common::Input::PollingMode vibration) {
+        [[maybe_unused]] const Common::Input::PollingMode polling_mode) {
         return Common::Input::PollingError::NotSupported;
     }
 
+    // Sets camera format to a controller
+    virtual Common::Input::CameraError SetCameraFormat(
+        [[maybe_unused]] const PadIdentifier& identifier,
+        [[maybe_unused]] Common::Input::CameraFormat camera_format) {
+        return Common::Input::CameraError::NotSupported;
+    }
+
     // Returns the engine name
     [[nodiscard]] const std::string& GetEngineName() const;
 
@@ -174,6 +182,7 @@ public:
     f32 GetAxis(const PadIdentifier& identifier, int axis) const;
     Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const;
     BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
+    Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const;
 
     int SetCallback(InputIdentifier input_identifier);
     void SetMappingCallback(MappingCallback callback);
@@ -185,6 +194,7 @@ protected:
     void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
     void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
     void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value);
+    void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value);
 
     virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const {
         return "Unknown";
@@ -197,6 +207,7 @@ private:
         std::unordered_map<int, float> axes;
         std::unordered_map<int, BasicMotion> motions;
         Common::Input::BatteryLevel battery{};
+        Common::Input::CameraStatus camera{};
     };
 
     void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value);
@@ -205,6 +216,8 @@ private:
     void TriggerOnBatteryChange(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
     void TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
                                const BasicMotion& value);
+    void TriggerOnCameraChange(const PadIdentifier& identifier,
+                               const Common::Input::CameraStatus& value);
 
     bool IsInputIdentifierEqual(const InputIdentifier& input_identifier,
                                 const PadIdentifier& identifier, EngineInputType type,
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index 49ccb4422..133422d5c 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -664,6 +664,47 @@ private:
     InputEngine* input_engine;
 };
 
+class InputFromCamera final : public Common::Input::InputDevice {
+public:
+    explicit InputFromCamera(PadIdentifier identifier_, InputEngine* input_engine_)
+        : identifier(identifier_), input_engine(input_engine_) {
+        UpdateCallback engine_callback{[this]() { OnChange(); }};
+        const InputIdentifier input_identifier{
+            .identifier = identifier,
+            .type = EngineInputType::Camera,
+            .index = 0,
+            .callback = engine_callback,
+        };
+        callback_key = input_engine->SetCallback(input_identifier);
+    }
+
+    ~InputFromCamera() override {
+        input_engine->DeleteCallback(callback_key);
+    }
+
+    Common::Input::CameraStatus GetStatus() const {
+        return input_engine->GetCamera(identifier);
+    }
+
+    void ForceUpdate() override {
+        OnChange();
+    }
+
+    void OnChange() {
+        const Common::Input::CallbackStatus status{
+            .type = Common::Input::InputType::IrSensor,
+            .camera_status = GetStatus(),
+        };
+
+        TriggerOnChange(status);
+    }
+
+private:
+    const PadIdentifier identifier;
+    int callback_key;
+    InputEngine* input_engine;
+};
+
 class OutputFromIdentifier final : public Common::Input::OutputDevice {
 public:
     explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
@@ -682,6 +723,10 @@ public:
         return input_engine->SetPollingMode(identifier, polling_mode);
     }
 
+    Common::Input::CameraError SetCameraFormat(Common::Input::CameraFormat camera_format) override {
+        return input_engine->SetCameraFormat(identifier, camera_format);
+    }
+
 private:
     const PadIdentifier identifier;
     InputEngine* input_engine;
@@ -920,6 +965,18 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
                                                  properties_y, properties_z, input_engine.get());
 }
 
+std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateCameraDevice(
+    const Common::ParamPackage& params) {
+    const PadIdentifier identifier = {
+        .guid = Common::UUID{params.Get("guid", "")},
+        .port = static_cast<std::size_t>(params.Get("port", 0)),
+        .pad = static_cast<std::size_t>(params.Get("pad", 0)),
+    };
+
+    input_engine->PreSetController(identifier);
+    return std::make_unique<InputFromCamera>(identifier, input_engine.get());
+}
+
 InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_)
     : input_engine(std::move(input_engine_)) {}
 
@@ -928,6 +985,9 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
     if (params.Has("battery")) {
         return CreateBatteryDevice(params);
     }
+    if (params.Has("camera")) {
+        return CreateCameraDevice(params);
+    }
     if (params.Has("button") && params.Has("axis")) {
         return CreateTriggerDevice(params);
     }
diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h
index 6ebe0dbf5..4410a8415 100644
--- a/src/input_common/input_poller.h
+++ b/src/input_common/input_poller.h
@@ -211,6 +211,17 @@ private:
      */
     std::unique_ptr<Common::Input::InputDevice> CreateMotionDevice(Common::ParamPackage params);
 
+    /**
+     * Creates a camera device from the parameters given.
+     * @param params contains parameters for creating the device:
+     *               - "guid": text string for identifying controllers
+     *               - "port": port of the connected device
+     *               - "pad": slot of the connected controller
+     * @returns a unique input device with the parameters specified
+     */
+    std::unique_ptr<Common::Input::InputDevice> CreateCameraDevice(
+        const Common::ParamPackage& params);
+
     std::shared_ptr<InputEngine> input_engine;
 };
 } // namespace InputCommon
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 21834fb6b..ca1cb9542 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -5,6 +5,7 @@
 #include <memory>
 #include "common/input.h"
 #include "common/param_package.h"
+#include "input_common/drivers/camera.h"
 #include "input_common/drivers/gc_adapter.h"
 #include "input_common/drivers/keyboard.h"
 #include "input_common/drivers/mouse.h"
@@ -78,6 +79,15 @@ struct InputSubsystem::Impl {
         Common::Input::RegisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName(),
                                                                     tas_output_factory);
 
+        camera = std::make_shared<Camera>("camera");
+        camera->SetMappingCallback(mapping_callback);
+        camera_input_factory = std::make_shared<InputFactory>(camera);
+        camera_output_factory = std::make_shared<OutputFactory>(camera);
+        Common::Input::RegisterFactory<Common::Input::InputDevice>(camera->GetEngineName(),
+                                                                   camera_input_factory);
+        Common::Input::RegisterFactory<Common::Input::OutputDevice>(camera->GetEngineName(),
+                                                                    camera_output_factory);
+
 #ifdef HAVE_SDL2
         sdl = std::make_shared<SDLDriver>("sdl");
         sdl->SetMappingCallback(mapping_callback);
@@ -317,6 +327,7 @@ struct InputSubsystem::Impl {
     std::shared_ptr<TouchScreen> touch_screen;
     std::shared_ptr<TasInput::Tas> tas_input;
     std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
+    std::shared_ptr<Camera> camera;
 
     std::shared_ptr<InputFactory> keyboard_factory;
     std::shared_ptr<InputFactory> mouse_factory;
@@ -324,12 +335,14 @@ struct InputSubsystem::Impl {
     std::shared_ptr<InputFactory> touch_screen_factory;
     std::shared_ptr<InputFactory> udp_client_input_factory;
     std::shared_ptr<InputFactory> tas_input_factory;
+    std::shared_ptr<InputFactory> camera_input_factory;
 
     std::shared_ptr<OutputFactory> keyboard_output_factory;
     std::shared_ptr<OutputFactory> mouse_output_factory;
     std::shared_ptr<OutputFactory> gcadapter_output_factory;
     std::shared_ptr<OutputFactory> udp_client_output_factory;
     std::shared_ptr<OutputFactory> tas_output_factory;
+    std::shared_ptr<OutputFactory> camera_output_factory;
 
 #ifdef HAVE_SDL2
     std::shared_ptr<SDLDriver> sdl;
@@ -382,6 +395,14 @@ const TasInput::Tas* InputSubsystem::GetTas() const {
     return impl->tas_input.get();
 }
 
+Camera* InputSubsystem::GetCamera() {
+    return impl->camera.get();
+}
+
+const Camera* InputSubsystem::GetCamera() const {
+    return impl->camera.get();
+}
+
 std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
     return impl->GetInputDevices();
 }
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 147c310c4..b756bb5c6 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -30,6 +30,7 @@ enum Values : int;
 }
 
 namespace InputCommon {
+class Camera;
 class Keyboard;
 class Mouse;
 class TouchScreen;
@@ -92,9 +93,15 @@ public:
     /// Retrieves the underlying tas input device.
     [[nodiscard]] TasInput::Tas* GetTas();
 
-    /// Retrieves the underlying  tas input  device.
+    /// Retrieves the underlying tas input device.
     [[nodiscard]] const TasInput::Tas* GetTas() const;
 
+    /// Retrieves the underlying camera input device.
+    [[nodiscard]] Camera* GetCamera();
+
+    /// Retrieves the underlying camera input device.
+    [[nodiscard]] const Camera* GetCamera() const;
+
     /**
      * Returns all available input devices that this Factory can create a new device with.
      * Each returned ParamPackage should have a `display` field used for display, a `engine` field

From cc83e0a6006667d126a7a83dde23a7f8ae3af994 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sat, 18 Jun 2022 23:34:28 -0500
Subject: [PATCH 2/6] yuzu: Hook qt camera to camera driver

---
 CMakeLists.txt                                |   6 +-
 CMakeModules/CopyYuzuQt5Deps.cmake            |   7 +
 src/yuzu/CMakeLists.txt                       |   5 +-
 src/yuzu/bootmanager.cpp                      |  71 ++++++++
 src/yuzu/bootmanager.h                        |  12 ++
 src/yuzu/configuration/config.cpp             |  12 ++
 src/yuzu/configuration/config.h               |   2 +
 src/yuzu/configuration/configure_camera.cpp   | 126 +++++++++++++
 src/yuzu/configuration/configure_camera.h     |  52 ++++++
 src/yuzu/configuration/configure_camera.ui    | 170 ++++++++++++++++++
 src/yuzu/configuration/configure_input.cpp    |   5 +
 .../configure_input_advanced.cpp              |   3 +
 .../configuration/configure_input_advanced.h  |   1 +
 .../configuration/configure_input_advanced.ui |  14 ++
 src/yuzu/main.cpp                             |   9 +
 15 files changed, 491 insertions(+), 4 deletions(-)
 create mode 100644 src/yuzu/configuration/configure_camera.cpp
 create mode 100644 src/yuzu/configuration/configure_camera.h
 create mode 100644 src/yuzu/configuration/configure_camera.ui

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3f7dcc924..40ca8b149 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -196,7 +196,7 @@ if(ENABLE_QT)
     # Check for system Qt on Linux, fallback to bundled Qt
     if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
         if (NOT YUZU_USE_BUNDLED_QT)
-            find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets DBus)
+            find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets DBus Multimedia)
         endif()
         if (NOT Qt5_FOUND OR YUZU_USE_BUNDLED_QT)
             # Check for dependencies, then enable bundled Qt download
@@ -300,9 +300,9 @@ if(ENABLE_QT)
         set(YUZU_QT_NO_CMAKE_SYSTEM_PATH "NO_CMAKE_SYSTEM_PATH")
     endif()
     if ((${CMAKE_SYSTEM_NAME} STREQUAL "Linux") AND YUZU_USE_BUNDLED_QT)
-        find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets Concurrent DBus ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH})
+        find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets Concurrent Multimedia DBus ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH})
     else()
-        find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets Concurrent ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH})
+        find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets Concurrent Multimedia ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH})
     endif()
     if (YUZU_USE_QT_WEB_ENGINE)
         find_package(Qt5 REQUIRED COMPONENTS WebEngineCore WebEngineWidgets)
diff --git a/CMakeModules/CopyYuzuQt5Deps.cmake b/CMakeModules/CopyYuzuQt5Deps.cmake
index 0c27d51a6..4702a504c 100644
--- a/CMakeModules/CopyYuzuQt5Deps.cmake
+++ b/CMakeModules/CopyYuzuQt5Deps.cmake
@@ -10,11 +10,13 @@ function(copy_yuzu_Qt5_deps target_dir)
     set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/")
     set(Qt5_PLATFORMTHEMES_DIR "${Qt5_DIR}/../../../plugins/platformthemes/")
     set(Qt5_PLATFORMINPUTCONTEXTS_DIR "${Qt5_DIR}/../../../plugins/platforminputcontexts/")
+    set(Qt5_MEDIASERVICE_DIR "${Qt5_DIR}/../../../plugins/mediaservice/")
     set(Qt5_XCBGLINTEGRATIONS_DIR "${Qt5_DIR}/../../../plugins/xcbglintegrations/")
     set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/")
     set(Qt5_IMAGEFORMATS_DIR "${Qt5_DIR}/../../../plugins/imageformats/")
     set(Qt5_RESOURCES_DIR "${Qt5_DIR}/../../../resources/")
     set(PLATFORMS ${DLL_DEST}plugins/platforms/)
+    set(MEDIASERVICE ${DLL_DEST}mediaservice/)
     set(STYLES ${DLL_DEST}plugins/styles/)
     set(IMAGEFORMATS ${DLL_DEST}plugins/imageformats/)
     if (MSVC)
@@ -22,6 +24,7 @@ function(copy_yuzu_Qt5_deps target_dir)
             Qt5Core$<$<CONFIG:Debug>:d>.*
             Qt5Gui$<$<CONFIG:Debug>:d>.*
             Qt5Widgets$<$<CONFIG:Debug>:d>.*
+            Qt5Multimedia$<$<CONFIG:Debug>:d>.*
         )
 
         if (YUZU_USE_QT_WEB_ENGINE)
@@ -53,6 +56,10 @@ function(copy_yuzu_Qt5_deps target_dir)
             qjpeg$<$<CONFIG:Debug>:d>.*
             qgif$<$<CONFIG:Debug>:d>.*
         )
+        windows_copy_files(yuzu ${Qt5_MEDIASERVICE_DIR} ${MEDIASERVICE}
+            dsengine$<$<CONFIG:Debug>:d>.*
+            wmfengine$<$<CONFIG:Debug>:d>.*
+        )
     else()
         set(Qt5_DLLS
             "${Qt5_DLL_DIR}libQt5Core.so.5"
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 242867a4f..a11a3b908 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -43,6 +43,9 @@ add_executable(yuzu
     configuration/configure_audio.cpp
     configuration/configure_audio.h
     configuration/configure_audio.ui
+    configuration/configure_camera.cpp
+    configuration/configure_camera.h
+    configuration/configure_camera.ui
     configuration/configure_cpu.cpp
     configuration/configure_cpu.h
     configuration/configure_cpu.ui
@@ -254,7 +257,7 @@ endif()
 create_target_directory_groups(yuzu)
 
 target_link_libraries(yuzu PRIVATE common core input_common video_core)
-target_link_libraries(yuzu PRIVATE Boost::boost glad Qt::Widgets)
+target_link_libraries(yuzu PRIVATE Boost::boost glad Qt::Widgets Qt::Multimedia)
 target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
 
 target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include)
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 01acda22b..774085809 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -5,6 +5,8 @@
 #include <glad/glad.h>
 
 #include <QApplication>
+#include <QCameraImageCapture>
+#include <QCameraInfo>
 #include <QHBoxLayout>
 #include <QMessageBox>
 #include <QPainter>
@@ -31,6 +33,7 @@
 #include "core/core.h"
 #include "core/cpu_manager.h"
 #include "core/frontend/framebuffer_layout.h"
+#include "input_common/drivers/camera.h"
 #include "input_common/drivers/keyboard.h"
 #include "input_common/drivers/mouse.h"
 #include "input_common/drivers/tas_input.h"
@@ -801,6 +804,74 @@ void GRenderWindow::TouchEndEvent() {
     input_subsystem->GetTouchScreen()->ReleaseAllTouch();
 }
 
+void GRenderWindow::InitializeCamera() {
+    if (!Settings::values.enable_ir_sensor) {
+        return;
+    }
+
+    bool camera_found = false;
+    const QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
+    for (const QCameraInfo& cameraInfo : cameras) {
+        if (Settings::values.ir_sensor_device.GetValue() == cameraInfo.deviceName().toStdString() ||
+            Settings::values.ir_sensor_device.GetValue() == "Auto") {
+            camera = std::make_unique<QCamera>(cameraInfo);
+            camera_found = true;
+            break;
+        }
+    }
+
+    if (!camera_found) {
+        return;
+    }
+
+    camera_capture = std::make_unique<QCameraImageCapture>(camera.get());
+    connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this,
+            &GRenderWindow::OnCameraCapture);
+    camera->unload();
+    camera->setCaptureMode(QCamera::CaptureViewfinder);
+    camera->load();
+
+    camera_timer = std::make_unique<QTimer>();
+    connect(camera_timer.get(), &QTimer::timeout, [this] { RequestCameraCapture(); });
+    // This timer should be dependent of camera resolution 5ms for every 100 pixels
+    camera_timer->start(100);
+}
+
+void GRenderWindow::FinalizeCamera() {
+    if (camera_timer) {
+        camera_timer->stop();
+    }
+    if (camera) {
+        camera->unload();
+    }
+}
+
+void GRenderWindow::RequestCameraCapture() {
+    if (!Settings::values.enable_ir_sensor) {
+        return;
+    }
+
+    // Idealy one should only call capture but Qt refuses to take a second capture without
+    // stopping the camera
+    camera->stop();
+    camera->start();
+
+    camera_capture->capture();
+}
+
+void GRenderWindow::OnCameraCapture(int requestId, const QImage& img) {
+    constexpr std::size_t camera_width = 320;
+    constexpr std::size_t camera_height = 240;
+    const auto converted =
+        img.scaled(camera_width, camera_height, Qt::AspectRatioMode::IgnoreAspectRatio,
+                   Qt::TransformationMode::SmoothTransformation)
+            .mirrored(false, true);
+    std::vector<u32> camera_data{};
+    camera_data.resize(camera_width * camera_height);
+    std::memcpy(camera_data.data(), converted.bits(), camera_width * camera_height * sizeof(u32));
+    input_subsystem->GetCamera()->SetCameraData(camera_width, camera_height, camera_data);
+}
+
 bool GRenderWindow::event(QEvent* event) {
     if (event->type() == QEvent::TouchBegin) {
         TouchBeginEvent(static_cast<QTouchEvent*>(event));
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 81fe52c0e..346201768 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -20,6 +20,8 @@
 
 class GRenderWindow;
 class GMainWindow;
+class QCamera;
+class QCameraImageCapture;
 class QKeyEvent;
 
 namespace Core {
@@ -164,6 +166,9 @@ public:
     void mouseReleaseEvent(QMouseEvent* event) override;
     void wheelEvent(QWheelEvent* event) override;
 
+    void InitializeCamera();
+    void FinalizeCamera();
+
     bool event(QEvent* event) override;
 
     void focusOutEvent(QFocusEvent* event) override;
@@ -207,6 +212,9 @@ private:
     void TouchUpdateEvent(const QTouchEvent* event);
     void TouchEndEvent();
 
+    void RequestCameraCapture();
+    void OnCameraCapture(int requestId, const QImage& img);
+
     void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
 
     bool InitializeOpenGL();
@@ -232,6 +240,10 @@ private:
     bool first_frame = false;
     InputCommon::TasInput::TasState last_tas_state;
 
+    std::unique_ptr<QCamera> camera;
+    std::unique_ptr<QCameraImageCapture> camera_capture;
+    std::unique_ptr<QTimer> camera_timer;
+
     Core::System& system;
 
 protected:
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 2840bc5eb..1f76e86b9 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -368,6 +368,11 @@ void Config::ReadHidbusValues() {
     }
 }
 
+void Config::ReadIrCameraValues() {
+    ReadBasicSetting(Settings::values.enable_ir_sensor);
+    ReadBasicSetting(Settings::values.ir_sensor_device);
+}
+
 void Config::ReadAudioValues() {
     qt_config->beginGroup(QStringLiteral("Audio"));
 
@@ -393,6 +398,7 @@ void Config::ReadControlValues() {
     ReadTouchscreenValues();
     ReadMotionTouchValues();
     ReadHidbusValues();
+    ReadIrCameraValues();
 
 #ifdef _WIN32
     ReadBasicSetting(Settings::values.enable_raw_input);
@@ -1005,6 +1011,11 @@ void Config::SaveHidbusValues() {
                  QString::fromStdString(default_param));
 }
 
+void Config::SaveIrCameraValues() {
+    WriteBasicSetting(Settings::values.enable_ir_sensor);
+    WriteBasicSetting(Settings::values.ir_sensor_device);
+}
+
 void Config::SaveValues() {
     if (global) {
         SaveControlValues();
@@ -1047,6 +1058,7 @@ void Config::SaveControlValues() {
     SaveTouchscreenValues();
     SaveMotionTouchValues();
     SaveHidbusValues();
+    SaveIrCameraValues();
 
     WriteGlobalSetting(Settings::values.use_docked_mode);
     WriteGlobalSetting(Settings::values.vibration_enabled);
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index d511b3dbd..a71eabe8e 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -68,6 +68,7 @@ private:
     void ReadTouchscreenValues();
     void ReadMotionTouchValues();
     void ReadHidbusValues();
+    void ReadIrCameraValues();
 
     // Read functions bases off the respective config section names.
     void ReadAudioValues();
@@ -96,6 +97,7 @@ private:
     void SaveTouchscreenValues();
     void SaveMotionTouchValues();
     void SaveHidbusValues();
+    void SaveIrCameraValues();
 
     // Save functions based off the respective config section names.
     void SaveAudioValues();
diff --git a/src/yuzu/configuration/configure_camera.cpp b/src/yuzu/configuration/configure_camera.cpp
new file mode 100644
index 000000000..97febb33c
--- /dev/null
+++ b/src/yuzu/configuration/configure_camera.cpp
@@ -0,0 +1,126 @@
+// Text : Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <memory>
+#include <QCameraImageCapture>
+#include <QCameraInfo>
+#include <QStandardItemModel>
+#include <QTimer>
+
+#include "input_common/drivers/camera.h"
+#include "input_common/main.h"
+#include "ui_configure_camera.h"
+#include "yuzu/configuration/config.h"
+#include "yuzu/configuration/configure_camera.h"
+
+ConfigureCamera::ConfigureCamera(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_)
+    : QDialog(parent), input_subsystem{input_subsystem_},
+      ui(std::make_unique<Ui::ConfigureCamera>()) {
+    ui->setupUi(this);
+
+    connect(ui->restore_defaults_button, &QPushButton::clicked, this,
+            &ConfigureCamera::RestoreDefaults);
+    connect(ui->preview_button, &QPushButton::clicked, this, &ConfigureCamera::PreviewCamera);
+
+    auto blank_image = QImage(320, 240, QImage::Format::Format_RGB32);
+    blank_image.fill(Qt::black);
+    DisplayCapturedFrame(0, blank_image);
+
+    LoadConfiguration();
+    resize(0, 0);
+}
+
+ConfigureCamera::~ConfigureCamera() = default;
+
+void ConfigureCamera::PreviewCamera() {
+    const auto index = ui->ir_sensor_combo_box->currentIndex();
+    bool camera_found = false;
+    const QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
+    for (const QCameraInfo& cameraInfo : cameras) {
+        if (input_devices[index] == cameraInfo.deviceName().toStdString() ||
+            input_devices[index] == "Auto") {
+            LOG_ERROR(Frontend, "Selected Camera {} {}", cameraInfo.description().toStdString(),
+                      cameraInfo.deviceName().toStdString());
+            camera = std::make_unique<QCamera>(cameraInfo);
+            camera_found = true;
+            break;
+        }
+    }
+
+    // Clear previous frame
+    auto blank_image = QImage(320, 240, QImage::Format::Format_RGB32);
+    blank_image.fill(Qt::black);
+    DisplayCapturedFrame(0, blank_image);
+
+    if (!camera_found) {
+        return;
+    }
+
+    camera_capture = std::make_unique<QCameraImageCapture>(camera.get());
+    connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this,
+            &ConfigureCamera::DisplayCapturedFrame);
+    camera->unload();
+    camera->setCaptureMode(QCamera::CaptureViewfinder);
+    camera->load();
+
+    camera_timer = std::make_unique<QTimer>();
+    connect(camera_timer.get(), &QTimer::timeout, [this] {
+        camera->stop();
+        camera->start();
+
+        camera_capture->capture();
+    });
+
+    camera_timer->start(250);
+}
+
+void ConfigureCamera::DisplayCapturedFrame(int requestId, const QImage& img) {
+    LOG_ERROR(Frontend, "ImageCaptured {} {}", img.width(), img.height());
+    const auto converted = img.scaled(320, 240, Qt::AspectRatioMode::IgnoreAspectRatio,
+                                      Qt::TransformationMode::SmoothTransformation);
+    ui->preview_box->setPixmap(QPixmap::fromImage(converted));
+}
+
+void ConfigureCamera::changeEvent(QEvent* event) {
+    if (event->type() == QEvent::LanguageChange) {
+        RetranslateUI();
+    }
+
+    QDialog::changeEvent(event);
+}
+
+void ConfigureCamera::RetranslateUI() {
+    ui->retranslateUi(this);
+}
+
+void ConfigureCamera::ApplyConfiguration() {
+    const auto index = ui->ir_sensor_combo_box->currentIndex();
+    Settings::values.ir_sensor_device.SetValue(input_devices[index]);
+}
+
+void ConfigureCamera::LoadConfiguration() {
+    input_devices.clear();
+    ui->ir_sensor_combo_box->clear();
+    input_devices.push_back("Auto");
+    ui->ir_sensor_combo_box->addItem(tr("Auto"));
+    const auto cameras = QCameraInfo::availableCameras();
+    for (const QCameraInfo& cameraInfo : cameras) {
+        input_devices.push_back(cameraInfo.deviceName().toStdString());
+        ui->ir_sensor_combo_box->addItem(cameraInfo.description());
+    }
+
+    const auto current_device = Settings::values.ir_sensor_device.GetValue();
+
+    const auto devices_it = std::find_if(
+        input_devices.begin(), input_devices.end(),
+        [current_device](const std::string& device) { return device == current_device; });
+    const int device_index =
+        devices_it != input_devices.end()
+            ? static_cast<int>(std::distance(input_devices.begin(), devices_it))
+            : 0;
+    ui->ir_sensor_combo_box->setCurrentIndex(device_index);
+}
+
+void ConfigureCamera::RestoreDefaults() {
+    ui->ir_sensor_combo_box->setCurrentIndex(0);
+}
diff --git a/src/yuzu/configuration/configure_camera.h b/src/yuzu/configuration/configure_camera.h
new file mode 100644
index 000000000..af7551c03
--- /dev/null
+++ b/src/yuzu/configuration/configure_camera.h
@@ -0,0 +1,52 @@
+// Text : Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <QDialog>
+
+class QTimer;
+class QCamera;
+class QCameraImageCapture;
+
+namespace InputCommon {
+class InputSubsystem;
+} // namespace InputCommon
+
+namespace Ui {
+class ConfigureCamera;
+}
+
+class ConfigureCamera : public QDialog {
+    Q_OBJECT
+
+public:
+    explicit ConfigureCamera(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_);
+    ~ConfigureCamera() override;
+
+    void ApplyConfiguration();
+
+private:
+    void changeEvent(QEvent* event) override;
+    void RetranslateUI();
+
+    /// Load configuration settings.
+    void LoadConfiguration();
+
+    /// Restore all buttons to their default values.
+    void RestoreDefaults();
+
+    void DisplayCapturedFrame(int requestId, const QImage& img);
+
+    /// Loads and signals the current selected camera to display a frame
+    void PreviewCamera();
+
+    InputCommon::InputSubsystem* input_subsystem;
+
+    std::unique_ptr<QCamera> camera;
+    std::unique_ptr<QCameraImageCapture> camera_capture;
+    std::unique_ptr<QTimer> camera_timer;
+    std::vector<std::string> input_devices;
+    std::unique_ptr<Ui::ConfigureCamera> ui;
+};
diff --git a/src/yuzu/configuration/configure_camera.ui b/src/yuzu/configuration/configure_camera.ui
new file mode 100644
index 000000000..976a9b1ec
--- /dev/null
+++ b/src/yuzu/configuration/configure_camera.ui
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureCamera</class>
+ <widget class="QDialog" name="ConfigureCamera">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>298</width>
+    <height>339</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Configure Infrared Camera</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="label_2">
+     <property name="minimumSize">
+      <size>
+       <width>280</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="text">
+      <string>Select where the image of the emulated camera comes from. It may be a virtual camera or a real camera.</string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <spacer name="verticalSpacer_2">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeType">
+      <enum>QSizePolicy::Fixed</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="gridGroupBox">
+     <property name="title">
+      <string>Camera Image Source:</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout">
+      <item row="0" column="0">
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>40</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+       <item row="0" column="1">
+         <widget class="QLabel" name="label_3">
+           <property name="text">
+             <string>Input device:</string>
+           </property>
+         </widget>
+       </item>
+       <item row="0" column="2">
+         <widget class="QComboBox" name="ir_sensor_combo_box"/>
+       </item>
+      <item row="0" column="3">
+       <spacer name="horizontalSpacer_2">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>40</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+     </layout>
+    </widget>
+   </item><item>
+    <widget class="QGroupBox" name="previewBox">
+     <property name="title">
+      <string>Preview</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_3">
+      <item>
+       <widget class="QLabel" name="preview_box">
+         <property name="minimumSize">
+           <size>
+             <width>320</width>
+             <height>240</height>
+           </size>
+         </property>
+        <property name="toolTip">
+         <string>Resolution: 320*240</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="preview_button">
+        <property name="text">
+         <string>Click to preview</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QPushButton" name="restore_defaults_button">
+       <property name="text">
+        <string>Restore Defaults</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QDialogButtonBox" name="buttonBox">
+       <property name="standardButtons">
+        <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ConfigureCamera</receiver>
+   <slot>accept()</slot>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>ConfigureCamera</receiver>
+   <slot>reject()</slot>
+   </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 73d7ba24b..f1b061b13 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -15,6 +15,7 @@
 #include "ui_configure_input.h"
 #include "ui_configure_input_advanced.h"
 #include "ui_configure_input_player.h"
+#include "yuzu/configuration/configure_camera.h"
 #include "yuzu/configuration/configure_debug_controller.h"
 #include "yuzu/configuration/configure_input.h"
 #include "yuzu/configuration/configure_input_advanced.h"
@@ -163,6 +164,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
             [this, input_subsystem, &hid_core] {
                 CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core);
             });
+    connect(advanced, &ConfigureInputAdvanced::CallCameraDialog,
+            [this, input_subsystem, &hid_core] {
+                CallConfigureDialog<ConfigureCamera>(*this, input_subsystem);
+            });
 
     connect(ui->vibrationButton, &QPushButton::clicked,
             [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
index f14bdc831..10f841b98 100644
--- a/src/yuzu/configuration/configure_input_advanced.cpp
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -89,6 +89,7 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
             [this] { CallMotionTouchConfigDialog(); });
     connect(ui->ring_controller_configure, &QPushButton::clicked, this,
             [this] { CallRingControllerDialog(); });
+    connect(ui->camera_configure, &QPushButton::clicked, this, [this] { CallCameraDialog(); });
 
 #ifndef _WIN32
     ui->enable_raw_input->setVisible(false);
@@ -136,6 +137,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
     Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
     Settings::values.controller_navigation = ui->controller_navigation->isChecked();
     Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked();
+    Settings::values.enable_ir_sensor = ui->enable_ir_sensor->isChecked();
 }
 
 void ConfigureInputAdvanced::LoadConfiguration() {
@@ -169,6 +171,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
     ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
     ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
     ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());
+    ui->enable_ir_sensor->setChecked(Settings::values.enable_ir_sensor.GetValue());
 
     UpdateUIEnabled();
 }
diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h
index 644e56dd8..fc1230284 100644
--- a/src/yuzu/configuration/configure_input_advanced.h
+++ b/src/yuzu/configuration/configure_input_advanced.h
@@ -29,6 +29,7 @@ signals:
     void CallTouchscreenConfigDialog();
     void CallMotionTouchConfigDialog();
     void CallRingControllerDialog();
+    void CallCameraDialog();
 
 private:
     void changeEvent(QEvent* event) override;
diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui
index 14403cb10..fac8cf827 100644
--- a/src/yuzu/configuration/configure_input_advanced.ui
+++ b/src/yuzu/configuration/configure_input_advanced.ui
@@ -2617,6 +2617,20 @@
                  </property>
                 </widget>
                </item>
+               <item row="5" column="0">
+                <widget class="QCheckBox" name="enable_ir_sensor">
+                 <property name="text">
+                  <string>Infrared Camera</string>
+                 </property>
+                </widget>
+               </item>
+               <item row="5" column="2">
+                <widget class="QPushButton" name="camera_configure">
+                 <property name="text">
+                  <string>Configure</string>
+                 </property>
+                </widget>
+               </item>
               </layout>
              </widget>
             </item>
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index a120f2662..08ccc1555 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1542,6 +1542,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
         mouse_hide_timer.start();
     }
 
+    render_window->InitializeCamera();
+
     std::string title_name;
     std::string title_version;
     const auto res = system->GetGameName(title_name);
@@ -1623,6 +1625,7 @@ void GMainWindow::ShutdownGame() {
     tas_label->clear();
     input_subsystem->GetTas()->Stop();
     OnTasStateChanged();
+    render_window->FinalizeCamera();
 
     // Enable all controllers
     system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
@@ -2862,6 +2865,12 @@ void GMainWindow::OnConfigure() {
         mouse_hide_timer.start();
     }
 
+    // Restart camera config
+    if (emulation_running) {
+        render_window->FinalizeCamera();
+        render_window->InitializeCamera();
+    }
+
     if (!UISettings::values.has_broken_vulkan) {
         renderer_status_button->setEnabled(!emulation_running);
     }

From 57311b2c8b1b9ee1d2c5775866e9591605815246 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sat, 18 Jun 2022 23:36:29 -0500
Subject: [PATCH 3/6] core: hid: Add cammera support

---
 src/core/CMakeLists.txt              |   1 +
 src/core/hid/emulated_controller.cpp |  59 ++++++
 src/core/hid/emulated_controller.h   |  40 +++-
 src/core/hid/input_converter.cpp     |  14 ++
 src/core/hid/input_converter.h       |   8 +
 src/core/hid/irs_types.h             | 304 +++++++++++++++++++++++++++
 6 files changed, 423 insertions(+), 3 deletions(-)
 create mode 100644 src/core/hid/irs_types.h

diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 11d554bad..1f8439f91 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -158,6 +158,7 @@ add_library(core STATIC
     hid/input_converter.h
     hid/input_interpreter.cpp
     hid/input_interpreter.h
+    hid/irs_types.h
     hid/motion_input.cpp
     hid/motion_input.h
     hle/api_version.h
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index bd2384515..8c3895937 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -126,10 +126,14 @@ void EmulatedController::LoadDevices() {
     battery_params[LeftIndex].Set("battery", true);
     battery_params[RightIndex].Set("battery", true);
 
+    camera_params = Common::ParamPackage{"engine:camera,camera:1"};
+
     output_params[LeftIndex] = left_joycon;
     output_params[RightIndex] = right_joycon;
+    output_params[2] = camera_params;
     output_params[LeftIndex].Set("output", true);
     output_params[RightIndex].Set("output", true);
+    output_params[2].Set("output", true);
 
     LoadTASParams();
 
@@ -146,6 +150,7 @@ void EmulatedController::LoadDevices() {
                    Common::Input::CreateDevice<Common::Input::InputDevice>);
     std::transform(battery_params.begin(), battery_params.end(), battery_devices.begin(),
                    Common::Input::CreateDevice<Common::Input::InputDevice>);
+    camera_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(camera_params);
     std::transform(output_params.begin(), output_params.end(), output_devices.begin(),
                    Common::Input::CreateDevice<Common::Input::OutputDevice>);
 
@@ -267,6 +272,14 @@ void EmulatedController::ReloadInput() {
         motion_devices[index]->ForceUpdate();
     }
 
+    if (camera_devices) {
+        camera_devices->SetCallback({
+            .on_change =
+                [this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); },
+        });
+        camera_devices->ForceUpdate();
+    }
+
     // Use a common UUID for TAS
     static constexpr Common::UUID TAS_UUID = Common::UUID{
         {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
@@ -851,6 +864,25 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
     TriggerOnChange(ControllerTriggerType::Battery, true);
 }
 
+void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) {
+    std::unique_lock lock{mutex};
+    controller.camera_values = TransformToCamera(callback);
+
+    if (is_configuring) {
+        lock.unlock();
+        TriggerOnChange(ControllerTriggerType::IrSensor, false);
+        return;
+    }
+
+    controller.camera_state.sample++;
+    controller.camera_state.format =
+        static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format);
+    controller.camera_state.data = controller.camera_values.data;
+
+    lock.unlock();
+    TriggerOnChange(ControllerTriggerType::IrSensor, true);
+}
+
 bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
     if (device_index >= output_devices.size()) {
         return false;
@@ -928,6 +960,23 @@ bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode)
     return output_device->SetPollingMode(polling_mode) == Common::Input::PollingError::None;
 }
 
+bool EmulatedController::SetCameraFormat(
+    Core::IrSensor::ImageTransferProcessorFormat camera_format) {
+    LOG_INFO(Service_HID, "Set camera format {}", camera_format);
+
+    auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
+    auto& camera_output_device = output_devices[2];
+
+    if (right_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
+            camera_format)) == Common::Input::CameraError::None) {
+        return true;
+    }
+
+    // Fallback to Qt camera if native device doesn't have support
+    return camera_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
+               camera_format)) == Common::Input::CameraError::None;
+}
+
 void EmulatedController::SetLedPattern() {
     for (auto& device : output_devices) {
         if (!device) {
@@ -1163,6 +1212,11 @@ BatteryValues EmulatedController::GetBatteryValues() const {
     return controller.battery_values;
 }
 
+CameraValues EmulatedController::GetCameraValues() const {
+    std::scoped_lock lock{mutex};
+    return controller.camera_values;
+}
+
 HomeButtonState EmulatedController::GetHomeButtons() const {
     std::scoped_lock lock{mutex};
     if (is_configuring) {
@@ -1251,6 +1305,11 @@ BatteryLevelState EmulatedController::GetBattery() const {
     return controller.battery_state;
 }
 
+const CameraState& EmulatedController::GetCamera() const {
+    std::scoped_lock lock{mutex};
+    return controller.camera_state;
+}
+
 void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npad_service_update) {
     std::scoped_lock lock{callback_mutex};
     for (const auto& poller_pair : callback_list) {
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index 3f02ed3c0..823c1700c 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -15,10 +15,12 @@
 #include "common/settings.h"
 #include "common/vector_math.h"
 #include "core/hid/hid_types.h"
+#include "core/hid/irs_types.h"
 #include "core/hid/motion_input.h"
 
 namespace Core::HID {
 const std::size_t max_emulated_controllers = 2;
+const std::size_t output_devices = 3;
 struct ControllerMotionInfo {
     Common::Input::MotionStatus raw_status{};
     MotionInput emulated{};
@@ -34,15 +36,16 @@ using TriggerDevices =
     std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
 using BatteryDevices =
     std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
-using OutputDevices =
-    std::array<std::unique_ptr<Common::Input::OutputDevice>, max_emulated_controllers>;
+using CameraDevices = std::unique_ptr<Common::Input::InputDevice>;
+using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices>;
 
 using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
 using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
 using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
 using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
 using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
-using OutputParams = std::array<Common::ParamPackage, max_emulated_controllers>;
+using CameraParams = Common::ParamPackage;
+using OutputParams = std::array<Common::ParamPackage, output_devices>;
 
 using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
 using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
@@ -51,6 +54,7 @@ using TriggerValues =
 using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::NativeMotion::NumMotions>;
 using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
 using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
+using CameraValues = Common::Input::CameraStatus;
 using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
 
 struct AnalogSticks {
@@ -70,6 +74,12 @@ struct BatteryLevelState {
     NpadPowerInfo right{};
 };
 
+struct CameraState {
+    Core::IrSensor::ImageTransferProcessorFormat format{};
+    std::vector<u8> data{};
+    std::size_t sample{};
+};
+
 struct ControllerMotion {
     Common::Vec3f accel{};
     Common::Vec3f gyro{};
@@ -96,6 +106,7 @@ struct ControllerStatus {
     ColorValues color_values{};
     BatteryValues battery_values{};
     VibrationValues vibration_values{};
+    CameraValues camera_values{};
 
     // Data for HID serices
     HomeButtonState home_button_state{};
@@ -107,6 +118,7 @@ struct ControllerStatus {
     NpadGcTriggerState gc_trigger_state{};
     ControllerColors colors_state{};
     BatteryLevelState battery_state{};
+    CameraState camera_state{};
 };
 
 enum class ControllerTriggerType {
@@ -117,6 +129,7 @@ enum class ControllerTriggerType {
     Color,
     Battery,
     Vibration,
+    IrSensor,
     Connected,
     Disconnected,
     Type,
@@ -269,6 +282,9 @@ public:
     /// Returns the latest battery status from the controller with parameters
     BatteryValues GetBatteryValues() const;
 
+    /// Returns the latest camera status from the controller with parameters
+    CameraValues GetCameraValues() const;
+
     /// Returns the latest status of button input for the hid::HomeButton service
     HomeButtonState GetHomeButtons() const;
 
@@ -296,6 +312,9 @@ public:
     /// Returns the latest battery status from the controller
     BatteryLevelState GetBattery() const;
 
+    /// Returns the latest camera status from the controller
+    const CameraState& GetCamera() const;
+
     /**
      * Sends a specific vibration to the output device
      * @return true if vibration had no errors
@@ -315,6 +334,13 @@ public:
      */
     bool SetPollingMode(Common::Input::PollingMode polling_mode);
 
+    /**
+     * Sets the desired camera format to be polled from a controller
+     * @param camera_format size of each frame
+     * @return true if SetCameraFormat was successfull
+     */
+    bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format);
+
     /// Returns the led pattern corresponding to this emulated controller
     LedPattern GetLedPattern() const;
 
@@ -392,6 +418,12 @@ private:
      */
     void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index);
 
+    /**
+     * Updates the camera status of the controller
+     * @param callback A CallbackStatus containing the camera status
+     */
+    void SetCamera(const Common::Input::CallbackStatus& callback);
+
     /**
      * Triggers a callback that something has changed on the controller status
      * @param type Input type of the event to trigger
@@ -417,6 +449,7 @@ private:
     ControllerMotionParams motion_params;
     TriggerParams trigger_params;
     BatteryParams battery_params;
+    CameraParams camera_params;
     OutputParams output_params;
 
     ButtonDevices button_devices;
@@ -424,6 +457,7 @@ private:
     ControllerMotionDevices motion_devices;
     TriggerDevices trigger_devices;
     BatteryDevices battery_devices;
+    CameraDevices camera_devices;
     OutputDevices output_devices;
 
     // TAS related variables
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
index 18d9f042d..68d143a01 100644
--- a/src/core/hid/input_converter.cpp
+++ b/src/core/hid/input_converter.cpp
@@ -270,6 +270,20 @@ Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatu
     return status;
 }
 
+Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback) {
+    Common::Input::CameraStatus camera{};
+    switch (callback.type) {
+    case Common::Input::InputType::IrSensor:
+        camera = callback.camera_status;
+        break;
+    default:
+        LOG_ERROR(Input, "Conversion from type {} to camera not implemented", callback.type);
+        break;
+    }
+
+    return camera;
+}
+
 void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
     const auto& properties = analog.properties;
     float& raw_value = analog.raw_value;
diff --git a/src/core/hid/input_converter.h b/src/core/hid/input_converter.h
index 2be36889f..143c50cc0 100644
--- a/src/core/hid/input_converter.h
+++ b/src/core/hid/input_converter.h
@@ -76,6 +76,14 @@ Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackSta
  */
 Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatus& callback);
 
+/**
+ * Converts raw input data into a valid camera status.
+ *
+ * @param callback Supported callbacks: Camera.
+ * @return A valid CameraObject object.
+ */
+Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback);
+
 /**
  * Converts raw analog data into a valid analog value
  * @param analog An analog object containing raw data and properties
diff --git a/src/core/hid/irs_types.h b/src/core/hid/irs_types.h
new file mode 100644
index 000000000..c73d008a0
--- /dev/null
+++ b/src/core/hid/irs_types.h
@@ -0,0 +1,304 @@
+// 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 "core/hid/hid_types.h"
+
+namespace Core::IrSensor {
+
+// This is nn::irsensor::CameraAmbientNoiseLevel
+enum class CameraAmbientNoiseLevel : u32 {
+    Low,
+    Medium,
+    High,
+    Unkown3, // 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,
+    Unkown2,
+    Unkown3,
+    Unkown4,
+    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,
+    SilhoueteAndImage,
+    SilhuetteOnly,
+};
+
+// This is nn::irsensor::IrSensorFunctionLevel
+enum class IrSensorFunctionLevel : u8 {
+    unknown0,
+    unknown1,
+    unknown2,
+    unknown3,
+    unknown4,
+};
+
+// This is nn::irsensor::MomentProcessorPreprocess
+enum class MomentProcessorPreprocess : u32 {
+    Unkown0,
+    Unkown1,
+};
+
+// This is nn::irsensor::PackedMomentProcessorPreprocess
+enum class PackedMomentProcessorPreprocess : u8 {
+    Unkown0,
+    Unkown1,
+};
+
+// This is nn::irsensor::PointingStatus
+enum class PointingStatus : u32 {
+    Unkown0,
+    Unkown1,
+};
+
+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;
+    u32 object_intensity_min;
+    bool is_external_light_filter_enabled;
+    INSERT_PADDING_BYTES(2);
+};
+static_assert(sizeof(PackedClusteringProcessorConfig) == 0x30,
+              "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
+struct ProcessorState {
+    u64 start{};
+    u32 count{};
+    INSERT_PADDING_BYTES(4);
+    std::array<u8, 0xE10> 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

From 453970059528d564d9ef88f7f9096c05946fbefe Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sat, 18 Jun 2022 23:45:06 -0500
Subject: [PATCH 4/6] service: irs: Split processors and implement
 ImageTransferProcessor

---
 src/core/CMakeLists.txt                       |  14 +
 src/core/hle/service/hid/errors.h             |   3 +
 src/core/hle/service/hid/irs.cpp              | 288 +++++++++++++----
 src/core/hle/service/hid/irs.h                | 291 ++++--------------
 .../hid/irsensor/clustering_processor.cpp     |  36 +++
 .../hid/irsensor/clustering_processor.h       |  74 +++++
 .../hid/irsensor/image_transfer_processor.cpp | 154 +++++++++
 .../hid/irsensor/image_transfer_processor.h   |  73 +++++
 .../service/hid/irsensor/ir_led_processor.cpp |  29 ++
 .../service/hid/irsensor/ir_led_processor.h   |  47 +++
 .../service/hid/irsensor/moment_processor.cpp |  36 +++
 .../service/hid/irsensor/moment_processor.h   |  61 ++++
 .../hid/irsensor/pointing_processor.cpp       |  28 ++
 .../service/hid/irsensor/pointing_processor.h |  62 ++++
 .../service/hid/irsensor/processor_base.cpp   |  67 ++++
 .../hle/service/hid/irsensor/processor_base.h |  33 ++
 .../hid/irsensor/tera_plugin_processor.cpp    |  31 ++
 .../hid/irsensor/tera_plugin_processor.h      |  53 ++++
 18 files changed, 1090 insertions(+), 290 deletions(-)
 create mode 100644 src/core/hle/service/hid/irsensor/clustering_processor.cpp
 create mode 100644 src/core/hle/service/hid/irsensor/clustering_processor.h
 create mode 100644 src/core/hle/service/hid/irsensor/image_transfer_processor.cpp
 create mode 100644 src/core/hle/service/hid/irsensor/image_transfer_processor.h
 create mode 100644 src/core/hle/service/hid/irsensor/ir_led_processor.cpp
 create mode 100644 src/core/hle/service/hid/irsensor/ir_led_processor.h
 create mode 100644 src/core/hle/service/hid/irsensor/moment_processor.cpp
 create mode 100644 src/core/hle/service/hid/irsensor/moment_processor.h
 create mode 100644 src/core/hle/service/hid/irsensor/pointing_processor.cpp
 create mode 100644 src/core/hle/service/hid/irsensor/pointing_processor.h
 create mode 100644 src/core/hle/service/hid/irsensor/processor_base.cpp
 create mode 100644 src/core/hle/service/hid/irsensor/processor_base.h
 create mode 100644 src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp
 create mode 100644 src/core/hle/service/hid/irsensor/tera_plugin_processor.h

diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 1f8439f91..32cc2f392 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -478,6 +478,20 @@ add_library(core STATIC
     hle/service/hid/hidbus/starlink.h
     hle/service/hid/hidbus/stubbed.cpp
     hle/service/hid/hidbus/stubbed.h
+    hle/service/hid/irsensor/clustering_processor.cpp
+    hle/service/hid/irsensor/clustering_processor.h
+    hle/service/hid/irsensor/image_transfer_processor.cpp
+    hle/service/hid/irsensor/image_transfer_processor.h
+    hle/service/hid/irsensor/ir_led_processor.cpp
+    hle/service/hid/irsensor/ir_led_processor.h
+    hle/service/hid/irsensor/moment_processor.cpp
+    hle/service/hid/irsensor/moment_processor.h
+    hle/service/hid/irsensor/pointing_processor.cpp
+    hle/service/hid/irsensor/pointing_processor.h
+    hle/service/hid/irsensor/processor_base.cpp
+    hle/service/hid/irsensor/processor_base.h
+    hle/service/hid/irsensor/tera_plugin_processor.cpp
+    hle/service/hid/irsensor/tera_plugin_processor.h
     hle/service/jit/jit_context.cpp
     hle/service/jit/jit_context.h
     hle/service/jit/jit.cpp
diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h
index 46282f42e..1bd62461c 100644
--- a/src/core/hle/service/hid/errors.h
+++ b/src/core/hle/service/hid/errors.h
@@ -18,4 +18,7 @@ constexpr Result NpadIsSameType{ErrorModule::HID, 602};
 constexpr Result InvalidNpadId{ErrorModule::HID, 709};
 constexpr Result NpadNotConnected{ErrorModule::HID, 710};
 
+constexpr ResultCode InvalidProcessorState{ErrorModule::Irsensor, 78};
+constexpr ResultCode InvalidIrCameraHandle{ErrorModule::Irsensor, 204};
+
 } // namespace Service::HID
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index d2a91d913..53a31df79 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -1,14 +1,26 @@
 // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include <algorithm>
+#include <random>
+
 #include "core/core.h"
 #include "core/core_timing.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/kernel/k_shared_memory.h"
 #include "core/hle/kernel/k_transfer_memory.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/service/hid/errors.h"
 #include "core/hle/service/hid/irs.h"
+#include "core/hle/service/hid/irsensor/clustering_processor.h"
+#include "core/hle/service/hid/irsensor/image_transfer_processor.h"
+#include "core/hle/service/hid/irsensor/ir_led_processor.h"
+#include "core/hle/service/hid/irsensor/moment_processor.h"
+#include "core/hle/service/hid/irsensor/pointing_processor.h"
+#include "core/hle/service/hid/irsensor/tera_plugin_processor.h"
+#include "core/memory.h"
 
 namespace Service::HID {
 
@@ -36,8 +48,13 @@ IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} {
     };
     // clang-format on
 
+    u8* raw_shared_memory = system.Kernel().GetIrsSharedMem().GetPointer();
     RegisterHandlers(functions);
+    shared_memory = std::construct_at(reinterpret_cast<StatusManager*>(raw_shared_memory));
+
+    npad_device = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
 }
+IRS::~IRS() = default;
 
 void IRS::ActivateIrsensor(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
@@ -75,7 +92,7 @@ void IRS::GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
 void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     struct Parameters {
-        IrCameraHandle camera_handle;
+        Core::IrSensor::IrCameraHandle camera_handle;
         INSERT_PADDING_WORDS_NOINIT(1);
         u64 applet_resource_user_id;
     };
@@ -88,17 +105,23 @@ void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) {
                 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
                 parameters.applet_resource_user_id);
 
+    auto result = IsIrCameraHandleValid(parameters.camera_handle);
+    if (result.IsSuccess()) {
+        // TODO: Stop Image processor
+        result = ResultSuccess;
+    }
+
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(result);
 }
 
 void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     struct Parameters {
-        IrCameraHandle camera_handle;
+        Core::IrSensor::IrCameraHandle camera_handle;
         INSERT_PADDING_WORDS_NOINIT(1);
         u64 applet_resource_user_id;
-        PackedMomentProcessorConfig processor_config;
+        Core::IrSensor::PackedMomentProcessorConfig processor_config;
     };
     static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size.");
 
@@ -109,17 +132,26 @@ void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) {
                 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
                 parameters.applet_resource_user_id);
 
+    const auto result = IsIrCameraHandleValid(parameters.camera_handle);
+
+    if (result.IsSuccess()) {
+        auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
+        MakeProcessor<MomentProcessor>(parameters.camera_handle, device);
+        auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle);
+        image_transfer_processor.SetConfig(parameters.processor_config);
+    }
+
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(result);
 }
 
 void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     struct Parameters {
-        IrCameraHandle camera_handle;
+        Core::IrSensor::IrCameraHandle camera_handle;
         INSERT_PADDING_WORDS_NOINIT(1);
         u64 applet_resource_user_id;
-        PackedClusteringProcessorConfig processor_config;
+        Core::IrSensor::PackedClusteringProcessorConfig processor_config;
     };
     static_assert(sizeof(Parameters) == 0x40, "Parameters has incorrect size.");
 
@@ -130,17 +162,27 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
                 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
                 parameters.applet_resource_user_id);
 
+    auto result = IsIrCameraHandleValid(parameters.camera_handle);
+
+    if (result.IsSuccess()) {
+        auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
+        MakeProcessor<ClusteringProcessor>(parameters.camera_handle, device);
+        auto& image_transfer_processor =
+            GetProcessor<ClusteringProcessor>(parameters.camera_handle);
+        image_transfer_processor.SetConfig(parameters.processor_config);
+    }
+
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(result);
 }
 
 void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     struct Parameters {
-        IrCameraHandle camera_handle;
+        Core::IrSensor::IrCameraHandle camera_handle;
         INSERT_PADDING_WORDS_NOINIT(1);
         u64 applet_resource_user_id;
-        PackedImageTransferProcessorConfig processor_config;
+        Core::IrSensor::PackedImageTransferProcessorConfig processor_config;
         u32 transfer_memory_size;
     };
     static_assert(sizeof(Parameters) == 0x30, "Parameters has incorrect size.");
@@ -151,20 +193,42 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {
     auto t_mem =
         system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
 
-    LOG_WARNING(Service_IRS,
-                "(STUBBED) called, npad_type={}, npad_id={}, transfer_memory_size={}, "
-                "applet_resource_user_id={}",
-                parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
-                parameters.transfer_memory_size, parameters.applet_resource_user_id);
+    if (t_mem.IsNull()) {
+        LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(ResultUnknown);
+        return;
+    }
+
+    ASSERT_MSG(t_mem->GetSize() == parameters.transfer_memory_size, "t_mem has incorrect size");
+
+    u8* transfer_memory = system.Memory().GetPointer(t_mem->GetSourceAddress());
+
+    LOG_INFO(Service_IRS,
+             "called, npad_type={}, npad_id={}, transfer_memory_size={}, transfer_memory_size={}, "
+             "applet_resource_user_id={}",
+             parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
+             parameters.transfer_memory_size, t_mem->GetSize(), parameters.applet_resource_user_id);
+
+    const auto result = IsIrCameraHandleValid(parameters.camera_handle);
+
+    if (result.IsSuccess()) {
+        auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
+        MakeProcessorWithCoreContext<ImageTransferProcessor>(parameters.camera_handle, device);
+        auto& image_transfer_processor =
+            GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
+        image_transfer_processor.SetConfig(parameters.processor_config);
+        image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
+    }
 
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(result);
 }
 
 void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     struct Parameters {
-        IrCameraHandle camera_handle;
+        Core::IrSensor::IrCameraHandle camera_handle;
         INSERT_PADDING_WORDS_NOINIT(1);
         u64 applet_resource_user_id;
     };
@@ -172,21 +236,40 @@ void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) {
 
     const auto parameters{rp.PopRaw<Parameters>()};
 
-    LOG_WARNING(Service_IRS,
-                "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}",
-                parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
-                parameters.applet_resource_user_id);
+    LOG_DEBUG(Service_IRS, "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}",
+              parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
+              parameters.applet_resource_user_id);
 
-    IPC::ResponseBuilder rb{ctx, 5};
+    const auto result = IsIrCameraHandleValid(parameters.camera_handle);
+    if (result.IsError()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(result);
+        return;
+    }
+
+    const auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
+
+    if (device.mode != Core::IrSensor::IrSensorMode::ImageTransferProcessor) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(InvalidProcessorState);
+        return;
+    }
+
+    std::vector<u8> data{};
+    const auto& image_transfer_processor =
+        GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
+    const auto& state = image_transfer_processor.GetState(data);
+
+    ctx.WriteBuffer(data);
+    IPC::ResponseBuilder rb{ctx, 6};
     rb.Push(ResultSuccess);
-    rb.PushRaw<u64>(system.CoreTiming().GetCPUTicks());
-    rb.PushRaw<u32>(0);
+    rb.PushRaw(state);
 }
 
 void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
-    const auto camera_handle{rp.PopRaw<IrCameraHandle>()};
-    const auto processor_config{rp.PopRaw<PackedTeraPluginProcessorConfig>()};
+    const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()};
+    const auto processor_config{rp.PopRaw<Core::IrSensor::PackedTeraPluginProcessorConfig>()};
     const auto applet_resource_user_id{rp.Pop<u64>()};
 
     LOG_WARNING(Service_IRS,
@@ -196,8 +279,17 @@ void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) {
                 processor_config.required_mcu_version.major,
                 processor_config.required_mcu_version.minor, applet_resource_user_id);
 
+    const auto result = IsIrCameraHandleValid(camera_handle);
+
+    if (result.IsSuccess()) {
+        auto& device = GetIrCameraSharedMemoryDeviceEntry(camera_handle);
+        MakeProcessor<TeraPluginProcessor>(camera_handle, device);
+        auto& image_transfer_processor = GetProcessor<TeraPluginProcessor>(camera_handle);
+        image_transfer_processor.SetConfig(processor_config);
+    }
+
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(result);
 }
 
 void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) {
@@ -211,13 +303,13 @@ void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) {
         return;
     }
 
-    IrCameraHandle camera_handle{
+    Core::IrSensor::IrCameraHandle camera_handle{
         .npad_id = static_cast<u8>(NpadIdTypeToIndex(npad_id)),
         .npad_type = Core::HID::NpadStyleIndex::None,
     };
 
-    LOG_WARNING(Service_IRS, "(STUBBED) called, npad_id={}, camera_npad_id={}, camera_npad_type={}",
-                npad_id, camera_handle.npad_id, camera_handle.npad_type);
+    LOG_INFO(Service_IRS, "called, npad_id={}, camera_npad_id={}, camera_npad_type={}", npad_id,
+             camera_handle.npad_id, camera_handle.npad_type);
 
     IPC::ResponseBuilder rb{ctx, 3};
     rb.Push(ResultSuccess);
@@ -226,8 +318,8 @@ void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) {
 
 void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
-    const auto camera_handle{rp.PopRaw<IrCameraHandle>()};
-    const auto processor_config{rp.PopRaw<PackedPointingProcessorConfig>()};
+    const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()};
+    const auto processor_config{rp.PopRaw<Core::IrSensor::PackedPointingProcessorConfig>()};
     const auto applet_resource_user_id{rp.Pop<u64>()};
 
     LOG_WARNING(
@@ -236,14 +328,23 @@ void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) {
         camera_handle.npad_type, camera_handle.npad_id, processor_config.required_mcu_version.major,
         processor_config.required_mcu_version.minor, applet_resource_user_id);
 
+    auto result = IsIrCameraHandleValid(camera_handle);
+
+    if (result.IsSuccess()) {
+        auto& device = GetIrCameraSharedMemoryDeviceEntry(camera_handle);
+        MakeProcessor<PointingProcessor>(camera_handle, device);
+        auto& image_transfer_processor = GetProcessor<PointingProcessor>(camera_handle);
+        image_transfer_processor.SetConfig(processor_config);
+    }
+
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(result);
 }
 
 void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     struct Parameters {
-        IrCameraHandle camera_handle;
+        Core::IrSensor::IrCameraHandle camera_handle;
         INSERT_PADDING_WORDS_NOINIT(1);
         u64 applet_resource_user_id;
     };
@@ -256,14 +357,20 @@ void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) {
                 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
                 parameters.applet_resource_user_id);
 
+    auto result = IsIrCameraHandleValid(parameters.camera_handle);
+    if (result.IsSuccess()) {
+        // TODO: Suspend image processor
+        result = ResultSuccess;
+    }
+
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(result);
 }
 
 void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
-    const auto camera_handle{rp.PopRaw<IrCameraHandle>()};
-    const auto mcu_version{rp.PopRaw<PackedMcuVersion>()};
+    const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()};
+    const auto mcu_version{rp.PopRaw<Core::IrSensor::PackedMcuVersion>()};
     const auto applet_resource_user_id{rp.Pop<u64>()};
 
     LOG_WARNING(
@@ -272,37 +379,45 @@ void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) {
         camera_handle.npad_type, camera_handle.npad_id, applet_resource_user_id, mcu_version.major,
         mcu_version.minor);
 
+    auto result = IsIrCameraHandleValid(camera_handle);
+    if (result.IsSuccess()) {
+        // TODO: Check firmware version
+        result = ResultSuccess;
+    }
+
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(result);
 }
 
 void IRS::SetFunctionLevel(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
-    struct Parameters {
-        IrCameraHandle camera_handle;
-        PackedFunctionLevel function_level;
-        u64 applet_resource_user_id;
-    };
-    static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+    const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()};
+    const auto function_level{rp.PopRaw<Core::IrSensor::PackedFunctionLevel>()};
+    const auto applet_resource_user_id{rp.Pop<u64>()};
 
-    const auto parameters{rp.PopRaw<Parameters>()};
+    LOG_WARNING(
+        Service_IRS,
+        "(STUBBED) called, npad_type={}, npad_id={}, function_level={}, applet_resource_user_id={}",
+        camera_handle.npad_type, camera_handle.npad_id, function_level.function_level,
+        applet_resource_user_id);
 
-    LOG_WARNING(Service_IRS,
-                "(STUBBED) called, npad_type={}, npad_id={}, applet_resource_user_id={}",
-                parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
-                parameters.applet_resource_user_id);
+    auto result = IsIrCameraHandleValid(camera_handle);
+    if (result.IsSuccess()) {
+        // TODO: Set Function level
+        result = ResultSuccess;
+    }
 
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(result);
 }
 
 void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     struct Parameters {
-        IrCameraHandle camera_handle;
+        Core::IrSensor::IrCameraHandle camera_handle;
         INSERT_PADDING_WORDS_NOINIT(1);
         u64 applet_resource_user_id;
-        PackedImageTransferProcessorExConfig processor_config;
+        Core::IrSensor::PackedImageTransferProcessorExConfig processor_config;
         u64 transfer_memory_size;
     };
     static_assert(sizeof(Parameters) == 0x38, "Parameters has incorrect size.");
@@ -313,20 +428,33 @@ void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) {
     auto t_mem =
         system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
 
-    LOG_WARNING(Service_IRS,
-                "(STUBBED) called, npad_type={}, npad_id={}, transfer_memory_size={}, "
-                "applet_resource_user_id={}",
-                parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
-                parameters.transfer_memory_size, parameters.applet_resource_user_id);
+    u8* transfer_memory = system.Memory().GetPointer(t_mem->GetSourceAddress());
+
+    LOG_INFO(Service_IRS,
+             "called, npad_type={}, npad_id={}, transfer_memory_size={}, "
+             "applet_resource_user_id={}",
+             parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
+             parameters.transfer_memory_size, parameters.applet_resource_user_id);
+
+    auto result = IsIrCameraHandleValid(parameters.camera_handle);
+
+    if (result.IsSuccess()) {
+        auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
+        MakeProcessorWithCoreContext<ImageTransferProcessor>(parameters.camera_handle, device);
+        auto& image_transfer_processor =
+            GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
+        image_transfer_processor.SetConfig(parameters.processor_config);
+        image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
+    }
 
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(result);
 }
 
 void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
-    const auto camera_handle{rp.PopRaw<IrCameraHandle>()};
-    const auto processor_config{rp.PopRaw<PackedIrLedProcessorConfig>()};
+    const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()};
+    const auto processor_config{rp.PopRaw<Core::IrSensor::PackedIrLedProcessorConfig>()};
     const auto applet_resource_user_id{rp.Pop<u64>()};
 
     LOG_WARNING(Service_IRS,
@@ -336,14 +464,23 @@ void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) {
                 processor_config.required_mcu_version.major,
                 processor_config.required_mcu_version.minor, applet_resource_user_id);
 
+    auto result = IsIrCameraHandleValid(camera_handle);
+
+    if (result.IsSuccess()) {
+        auto& device = GetIrCameraSharedMemoryDeviceEntry(camera_handle);
+        MakeProcessor<IrLedProcessor>(camera_handle, device);
+        auto& image_transfer_processor = GetProcessor<IrLedProcessor>(camera_handle);
+        image_transfer_processor.SetConfig(processor_config);
+    }
+
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(result);
 }
 
 void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     struct Parameters {
-        IrCameraHandle camera_handle;
+        Core::IrSensor::IrCameraHandle camera_handle;
         INSERT_PADDING_WORDS_NOINIT(1);
         u64 applet_resource_user_id;
     };
@@ -356,14 +493,20 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {
                 parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
                 parameters.applet_resource_user_id);
 
+    auto result = IsIrCameraHandleValid(parameters.camera_handle);
+    if (result.IsSuccess()) {
+        // TODO: Stop image processor async
+        result = ResultSuccess;
+    }
+
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(result);
 }
 
 void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     struct Parameters {
-        PackedFunctionLevel function_level;
+        Core::IrSensor::PackedFunctionLevel function_level;
         INSERT_PADDING_WORDS_NOINIT(1);
         u64 applet_resource_user_id;
     };
@@ -378,7 +521,22 @@ void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) {
     rb.Push(ResultSuccess);
 }
 
-IRS::~IRS() = default;
+ResultCode IRS::IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const {
+    if (camera_handle.npad_id >
+        static_cast<u8>(NpadIdTypeToIndex(Core::HID::NpadIdType::Handheld))) {
+        return InvalidIrCameraHandle;
+    }
+    if (camera_handle.npad_type != Core::HID::NpadStyleIndex::None) {
+        return InvalidIrCameraHandle;
+    }
+    return ResultSuccess;
+}
+
+Core::IrSensor::DeviceFormat& IRS::GetIrCameraSharedMemoryDeviceEntry(
+    const Core::IrSensor::IrCameraHandle& camera_handle) {
+    ASSERT_MSG(sizeof(StatusManager::device) > camera_handle.npad_id, "invalid npad_id");
+    return shared_memory->device[camera_handle.npad_id];
+}
 
 IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} {
     // clang-format off
diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h
index 361dc2213..ae7f7719b 100644
--- a/src/core/hle/service/hid/irs.h
+++ b/src/core/hle/service/hid/irs.h
@@ -4,12 +4,18 @@
 #pragma once
 
 #include "core/hid/hid_types.h"
+#include "core/hid/irs_types.h"
+#include "core/hle/service/hid/irsensor/processor_base.h"
 #include "core/hle/service/service.h"
 
 namespace Core {
 class System;
 }
 
+namespace Core::HID {
+class EmulatedController;
+} // namespace Core::HID
+
 namespace Service::HID {
 
 class IRS final : public ServiceFramework<IRS> {
@@ -18,234 +24,19 @@ public:
     ~IRS() override;
 
 private:
-    // This is nn::irsensor::IrCameraStatus
-    enum IrCameraStatus : u32 {
-        Available,
-        Unsupported,
-        Unconnected,
+    // This is nn::irsensor::detail::AruidFormat
+    struct AruidFormat {
+        u64 sensor_aruid;
+        u64 sensor_aruid_status;
     };
+    static_assert(sizeof(AruidFormat) == 0x10, "AruidFormat is an invalid size");
 
-    // This is nn::irsensor::IrCameraInternalStatus
-    enum IrCameraInternalStatus : u32 {
-        Stopped,
-        FirmwareUpdateNeeded,
-        Unkown2,
-        Unkown3,
-        Unkown4,
-        FirmwareVersionRequested,
-        FirmwareVersionIsInvalid,
-        Ready,
-        Setting,
+    // This is nn::irsensor::detail::StatusManager
+    struct StatusManager {
+        std::array<Core::IrSensor::DeviceFormat, 9> device;
+        std::array<AruidFormat, 5> aruid;
     };
-
-    // This is nn::irsensor::detail::StatusManager::IrSensorMode
-    enum IrSensorMode : u64 {
-        None,
-        MomentProcessor,
-        ClusteringProcessor,
-        ImageTransferProcessor,
-        PointingProcessorMarker,
-        TeraPluginProcessor,
-        IrLedProcessor,
-    };
-
-    // This is nn::irsensor::ImageProcessorStatus
-    enum ImageProcessorStatus : u8 {
-        stopped,
-        running,
-    };
-
-    // This is nn::irsensor::ImageTransferProcessorFormat
-    enum ImageTransferProcessorFormat : u8 {
-        Size320x240,
-        Size160x120,
-        Size80x60,
-        Size40x30,
-        Size20x15,
-    };
-
-    // This is nn::irsensor::AdaptiveClusteringMode
-    enum AdaptiveClusteringMode : u8 {
-        StaticFov,
-        DynamicFov,
-    };
-
-    // This is nn::irsensor::AdaptiveClusteringTargetDistance
-    enum AdaptiveClusteringTargetDistance : u8 {
-        Near,
-        Middle,
-        Far,
-    };
-
-    // This is nn::irsensor::IrsHandAnalysisMode
-    enum IrsHandAnalysisMode : u8 {
-        Silhouette,
-        Image,
-        SilhoueteAndImage,
-        SilhuetteOnly,
-    };
-
-    // This is nn::irsensor::IrSensorFunctionLevel
-    enum IrSensorFunctionLevel : u8 {
-        unknown0,
-        unknown1,
-        unknown2,
-        unknown3,
-        unknown4,
-    };
-
-    // 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");
-
-    struct IrsRect {
-        s16 x;
-        s16 y;
-        s16 width;
-        s16 height;
-    };
-
-    // 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::MomentProcessorConfig
-    struct MomentProcessorConfig {
-        u64 exposire_time;
-        u8 light_target;
-        u8 gain;
-        u8 is_negative_used;
-        INSERT_PADDING_BYTES(7);
-        IrsRect window_of_interest;
-        u8 preprocess;
-        u8 preprocess_intensity_threshold;
-        INSERT_PADDING_BYTES(5);
-    };
-    static_assert(sizeof(MomentProcessorConfig) == 0x28,
-                  "MomentProcessorConfig is an invalid size");
-
-    // This is nn::irsensor::PackedMomentProcessorConfig
-    struct PackedMomentProcessorConfig {
-        u64 exposire_time;
-        u8 light_target;
-        u8 gain;
-        u8 is_negative_used;
-        INSERT_PADDING_BYTES(5);
-        IrsRect window_of_interest;
-        PackedMcuVersion required_mcu_version;
-        u8 preprocess;
-        u8 preprocess_intensity_threshold;
-        INSERT_PADDING_BYTES(2);
-    };
-    static_assert(sizeof(PackedMomentProcessorConfig) == 0x20,
-                  "PackedMomentProcessorConfig is an invalid size");
-
-    // This is nn::irsensor::ClusteringProcessorConfig
-    struct ClusteringProcessorConfig {
-        u64 exposire_time;
-        u32 light_target;
-        u32 gain;
-        u8 is_negative_used;
-        INSERT_PADDING_BYTES(7);
-        IrsRect window_of_interest;
-        u32 pixel_count_min;
-        u32 pixel_count_max;
-        u32 object_intensity_min;
-        u8 is_external_light_filter_enabled;
-        INSERT_PADDING_BYTES(3);
-    };
-    static_assert(sizeof(ClusteringProcessorConfig) == 0x30,
-                  "ClusteringProcessorConfig is an invalid size");
-
-    // This is nn::irsensor::PackedClusteringProcessorConfig
-    struct PackedClusteringProcessorConfig {
-        u64 exposire_time;
-        u8 light_target;
-        u8 gain;
-        u8 is_negative_used;
-        INSERT_PADDING_BYTES(5);
-        IrsRect window_of_interest;
-        PackedMcuVersion required_mcu_version;
-        u32 pixel_count_min;
-        u32 pixel_count_max;
-        u32 object_intensity_min;
-        u8 is_external_light_filter_enabled;
-        INSERT_PADDING_BYTES(2);
-    };
-    static_assert(sizeof(PackedClusteringProcessorConfig) == 0x30,
-                  "PackedClusteringProcessorConfig is an invalid size");
-
-    // This is nn::irsensor::PackedImageTransferProcessorConfig
-    struct PackedImageTransferProcessorConfig {
-        u64 exposire_time;
-        u8 light_target;
-        u8 gain;
-        u8 is_negative_used;
-        INSERT_PADDING_BYTES(5);
-        PackedMcuVersion required_mcu_version;
-        u8 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;
-        INSERT_PADDING_BYTES(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 {
-        u64 exposire_time;
-        u8 light_target;
-        u8 gain;
-        u8 is_negative_used;
-        INSERT_PADDING_BYTES(5);
-        PackedMcuVersion required_mcu_version;
-        ImageTransferProcessorFormat origin_format;
-        ImageTransferProcessorFormat trimming_format;
-        u16 trimming_start_x;
-        u16 trimming_start_y;
-        u8 is_external_light_filter_enabled;
-        INSERT_PADDING_BYTES(3);
-    };
-    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");
+    static_assert(sizeof(StatusManager) == 0x8000, "StatusManager is an invalid size");
 
     void ActivateIrsensor(Kernel::HLERequestContext& ctx);
     void DeactivateIrsensor(Kernel::HLERequestContext& ctx);
@@ -265,6 +56,56 @@ private:
     void RunIrLedProcessor(Kernel::HLERequestContext& ctx);
     void StopImageProcessorAsync(Kernel::HLERequestContext& ctx);
     void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx);
+
+    ResultCode IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const;
+    Core::IrSensor::DeviceFormat& GetIrCameraSharedMemoryDeviceEntry(
+        const Core::IrSensor::IrCameraHandle& camera_handle);
+
+    template <typename T>
+    void MakeProcessor(const Core::IrSensor::IrCameraHandle& handle,
+                       Core::IrSensor::DeviceFormat& device_state) {
+        const auto index = static_cast<std::size_t>(handle.npad_id);
+        if (index > sizeof(processors)) {
+            LOG_CRITICAL(Service_IRS, "Invalid index {}", index);
+            return;
+        }
+        processors[index] = std::make_unique<T>(device_state);
+    }
+
+    template <typename T>
+    void MakeProcessorWithCoreContext(const Core::IrSensor::IrCameraHandle& handle,
+                                      Core::IrSensor::DeviceFormat& device_state) {
+        const auto index = static_cast<std::size_t>(handle.npad_id);
+        if (index > sizeof(processors)) {
+            LOG_CRITICAL(Service_IRS, "Invalid index {}", index);
+            return;
+        }
+        processors[index] = std::make_unique<T>(system.HIDCore(), device_state, index);
+    }
+
+    template <typename T>
+    T& GetProcessor(const Core::IrSensor::IrCameraHandle& handle) {
+        const auto index = static_cast<std::size_t>(handle.npad_id);
+        if (index > sizeof(processors)) {
+            LOG_CRITICAL(Service_IRS, "Invalid index {}", index);
+            return static_cast<T&>(*processors[0]);
+        }
+        return static_cast<T&>(*processors[index]);
+    }
+
+    template <typename T>
+    const T& GetProcessor(const Core::IrSensor::IrCameraHandle& handle) const {
+        const auto index = static_cast<std::size_t>(handle.npad_id);
+        if (index > sizeof(processors)) {
+            LOG_CRITICAL(Service_IRS, "Invalid index {}", index);
+            return static_cast<T&>(*processors[0]);
+        }
+        return static_cast<T&>(*processors[index]);
+    }
+
+    Core::HID::EmulatedController* npad_device = nullptr;
+    StatusManager* shared_memory = nullptr;
+    std::array<std::unique_ptr<ProcessorBase>, 9> processors{};
 };
 
 class IRS_SYS final : public ServiceFramework<IRS_SYS> {
diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.cpp b/src/core/hle/service/hid/irsensor/clustering_processor.cpp
new file mode 100644
index 000000000..aac3e4fec
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/clustering_processor.cpp
@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/hid/irsensor/clustering_processor.h"
+
+namespace Service::HID {
+ClusteringProcessor::ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format)
+    : device(device_format) {
+    device.mode = Core::IrSensor::IrSensorMode::ClusteringProcessor;
+    device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
+    device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
+    device.state.start = 0;
+    device.state.count = 0;
+}
+
+ClusteringProcessor::~ClusteringProcessor() = default;
+
+void ClusteringProcessor::StartProcessor() {}
+
+void ClusteringProcessor::SuspendProcessor() {}
+
+void ClusteringProcessor::StopProcessor() {}
+
+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.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;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.h b/src/core/hle/service/hid/irsensor/clustering_processor.h
new file mode 100644
index 000000000..58b2acf1c
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/clustering_processor.h
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hid/irs_types.h"
+#include "core/hle/service/hid/irsensor/processor_base.h"
+
+namespace Service::HID {
+class ClusteringProcessor final : public ProcessorBase {
+public:
+    explicit ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format);
+    ~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:
+    // 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");
+
+    ClusteringProcessorConfig current_config{};
+    Core::IrSensor::DeviceFormat& device;
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp
new file mode 100644
index 000000000..703e825e6
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp
@@ -0,0 +1,154 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hle/service/hid/irsensor/image_transfer_processor.h"
+
+namespace Service::HID {
+ImageTransferProcessor::ImageTransferProcessor(Core::HID::HIDCore& hid_core_,
+                                               Core::IrSensor::DeviceFormat& device_format,
+                                               std::size_t npad_index)
+    : device{device_format} {
+    npad_device = hid_core_.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;
+    device.state.start = 0;
+    device.state.count = 0;
+}
+
+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;
+    device.state.start = 0;
+    device.state.count = 1;
+    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 (!is_transfer_memory_set) {
+        return;
+    }
+
+    const auto camera_data = npad_device->GetCamera();
+
+    // This indicates how much ambient light is precent
+    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);
+        memset(transfer_memory, 0, 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);
+        memset(transfer_memory, 0, 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);
+        memset(transfer_memory, 0, 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];
+        }
+    }
+
+    memcpy(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::SetTransferMemoryPointer(u8* t_mem) {
+    is_transfer_memory_set = true;
+    transfer_memory = t_mem;
+}
+
+Core::IrSensor::ImageTransferProcessorState ImageTransferProcessor::GetState(
+    std::vector<u8>& data) const {
+    const auto size = GetDataSize(current_config.trimming_format);
+    data.resize(size);
+    memcpy(data.data(), transfer_memory, size);
+    return processor_state;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.h b/src/core/hle/service/hid/irsensor/image_transfer_processor.h
new file mode 100644
index 000000000..b557eaf20
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.h
@@ -0,0 +1,73 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hid/irs_types.h"
+#include "core/hle/service/hid/irsensor/processor_base.h"
+
+namespace Core::HID {
+class EmulatedController;
+} // namespace Core::HID
+
+namespace Service::HID {
+class ImageTransferProcessor final : public ProcessorBase {
+public:
+    explicit ImageTransferProcessor(Core::HID::HIDCore& hid_core_,
+                                    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 SetTransferMemoryPointer(u8* t_mem);
+
+    Core::IrSensor::ImageTransferProcessorState GetState(std::vector<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{};
+
+    u8* transfer_memory = nullptr;
+    bool is_transfer_memory_set = false;
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/irsensor/ir_led_processor.cpp b/src/core/hle/service/hid/irsensor/ir_led_processor.cpp
new file mode 100644
index 000000000..656e17c95
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/ir_led_processor.cpp
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/hid/irsensor/ir_led_processor.h"
+
+namespace Service::HID {
+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;
+    device.state.start = 0;
+    device.state.count = 0;
+}
+
+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::HID
diff --git a/src/core/hle/service/hid/irsensor/ir_led_processor.h b/src/core/hle/service/hid/irsensor/ir_led_processor.h
new file mode 100644
index 000000000..cb04d0b94
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/ir_led_processor.h
@@ -0,0 +1,47 @@
+// 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 "core/hid/irs_types.h"
+#include "core/hle/service/hid/irsensor/processor_base.h"
+
+namespace Service::HID {
+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::HID
diff --git a/src/core/hle/service/hid/irsensor/moment_processor.cpp b/src/core/hle/service/hid/irsensor/moment_processor.cpp
new file mode 100644
index 000000000..fb36be577
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/moment_processor.cpp
@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/hid/irsensor/moment_processor.h"
+
+namespace Service::HID {
+MomentProcessor::MomentProcessor(Core::IrSensor::DeviceFormat& device_format)
+    : device(device_format) {
+    device.mode = Core::IrSensor::IrSensorMode::MomentProcessor;
+    device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
+    device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
+    device.state.start = 0;
+    device.state.count = 0;
+}
+
+MomentProcessor::~MomentProcessor() = default;
+
+void MomentProcessor::StartProcessor() {}
+
+void MomentProcessor::SuspendProcessor() {}
+
+void MomentProcessor::StopProcessor() {}
+
+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;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/irsensor/moment_processor.h b/src/core/hle/service/hid/irsensor/moment_processor.h
new file mode 100644
index 000000000..63cdadfda
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/moment_processor.h
@@ -0,0 +1,61 @@
+// 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 "core/hid/irs_types.h"
+#include "core/hle/service/hid/irsensor/processor_base.h"
+
+namespace Service::HID {
+class MomentProcessor final : public ProcessorBase {
+public:
+    explicit MomentProcessor(Core::IrSensor::DeviceFormat& device_format);
+    ~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:
+    // 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, 0x30> stadistic;
+    };
+    static_assert(sizeof(MomentProcessorState) == 0x258, "MomentProcessorState is an invalid size");
+
+    MomentProcessorConfig current_config{};
+    Core::IrSensor::DeviceFormat& device;
+};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/irsensor/pointing_processor.cpp b/src/core/hle/service/hid/irsensor/pointing_processor.cpp
new file mode 100644
index 000000000..7eb123636
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/pointing_processor.cpp
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/hid/irsensor/pointing_processor.h"
+
+namespace Service::HID {
+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;
+    device.state.start = 0;
+    device.state.count = 0;
+}
+
+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::HID
diff --git a/src/core/hle/service/hid/irsensor/pointing_processor.h b/src/core/hle/service/hid/irsensor/pointing_processor.h
new file mode 100644
index 000000000..2f67eff91
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/pointing_processor.h
@@ -0,0 +1,62 @@
+// 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 "core/hid/irs_types.h"
+#include "core/hle/service/hid/irsensor/processor_base.h"
+
+namespace Service::HID {
+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 unkown_float1;
+        float position_x;
+        float position_y;
+        float unkown_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::HID
diff --git a/src/core/hle/service/hid/irsensor/processor_base.cpp b/src/core/hle/service/hid/irsensor/processor_base.cpp
new file mode 100644
index 000000000..bc8025375
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/processor_base.cpp
@@ -0,0 +1,67 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/hid/irsensor/processor_base.h"
+
+namespace Service::HID {
+
+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::HID
diff --git a/src/core/hle/service/hid/irsensor/processor_base.h b/src/core/hle/service/hid/irsensor/processor_base.h
new file mode 100644
index 000000000..49831aab6
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/processor_base.h
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hid/irs_types.h"
+
+namespace Service::HID {
+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::HID
diff --git a/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp b/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp
new file mode 100644
index 000000000..deec22072
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "core/hle/service/hid/irsensor/tera_plugin_processor.h"
+
+namespace Service::HID {
+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;
+    device.state.start = 0;
+    device.state.count = 0;
+}
+
+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::HID
diff --git a/src/core/hle/service/hid/irsensor/tera_plugin_processor.h b/src/core/hle/service/hid/irsensor/tera_plugin_processor.h
new file mode 100644
index 000000000..60f8057a5
--- /dev/null
+++ b/src/core/hle/service/hid/irsensor/tera_plugin_processor.h
@@ -0,0 +1,53 @@
+// 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 "core/hid/irs_types.h"
+#include "core/hle/service/hid/irsensor/processor_base.h"
+
+namespace Service::HID {
+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::HID

From 097785e19e3865ac1060b9fae564a3a1e36c695d Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sun, 19 Jun 2022 16:27:48 -0500
Subject: [PATCH 5/6] service: irs: Move to IRS namespace and minor fixes

---
 CMakeModules/CopyYuzuQt5Deps.cmake            |  1 +
 src/core/hid/irs_types.h                      | 11 ++--
 src/core/hle/service/hid/errors.h             |  6 ++-
 src/core/hle/service/hid/hid.cpp              |  4 +-
 src/core/hle/service/hid/irs.cpp              | 50 +++++++++++--------
 src/core/hle/service/hid/irs.h                |  4 +-
 .../hid/irsensor/clustering_processor.cpp     |  6 +--
 .../hid/irsensor/clustering_processor.h       |  4 +-
 .../hid/irsensor/image_transfer_processor.cpp |  8 +--
 .../hid/irsensor/image_transfer_processor.h   |  4 +-
 .../service/hid/irsensor/ir_led_processor.cpp |  6 +--
 .../service/hid/irsensor/ir_led_processor.h   |  4 +-
 .../service/hid/irsensor/moment_processor.cpp |  6 +--
 .../service/hid/irsensor/moment_processor.h   |  4 +-
 .../hid/irsensor/pointing_processor.cpp       |  6 +--
 .../service/hid/irsensor/pointing_processor.h |  5 +-
 .../service/hid/irsensor/processor_base.cpp   |  4 +-
 .../hle/service/hid/irsensor/processor_base.h |  4 +-
 .../hid/irsensor/tera_plugin_processor.cpp    |  6 +--
 .../hid/irsensor/tera_plugin_processor.h      |  4 +-
 20 files changed, 71 insertions(+), 76 deletions(-)

diff --git a/CMakeModules/CopyYuzuQt5Deps.cmake b/CMakeModules/CopyYuzuQt5Deps.cmake
index 4702a504c..6c5044caa 100644
--- a/CMakeModules/CopyYuzuQt5Deps.cmake
+++ b/CMakeModules/CopyYuzuQt5Deps.cmake
@@ -25,6 +25,7 @@ function(copy_yuzu_Qt5_deps target_dir)
             Qt5Gui$<$<CONFIG:Debug>:d>.*
             Qt5Widgets$<$<CONFIG:Debug>:d>.*
             Qt5Multimedia$<$<CONFIG:Debug>:d>.*
+            Qt5Network$<$<CONFIG:Debug>:d>.*
         )
 
         if (YUZU_USE_QT_WEB_ENGINE)
diff --git a/src/core/hid/irs_types.h b/src/core/hid/irs_types.h
index c73d008a0..88c5b016d 100644
--- a/src/core/hid/irs_types.h
+++ b/src/core/hid/irs_types.h
@@ -201,11 +201,11 @@ struct PackedClusteringProcessorConfig {
     PackedMcuVersion required_mcu_version;
     u32 pixel_count_min;
     u32 pixel_count_max;
-    u32 object_intensity_min;
+    u8 object_intensity_min;
     bool is_external_light_filter_enabled;
     INSERT_PADDING_BYTES(2);
 };
-static_assert(sizeof(PackedClusteringProcessorConfig) == 0x30,
+static_assert(sizeof(PackedClusteringProcessorConfig) == 0x28,
               "PackedClusteringProcessorConfig is an invalid size");
 
 // This is nn::irsensor::PackedImageTransferProcessorConfig
@@ -273,12 +273,9 @@ struct HandAnalysisConfig {
 };
 static_assert(sizeof(HandAnalysisConfig) == 0x4, "HandAnalysisConfig is an invalid size");
 
-// This is nn::irsensor::detail::ProcessorState
+// This is nn::irsensor::detail::ProcessorState contents are different for each processor
 struct ProcessorState {
-    u64 start{};
-    u32 count{};
-    INSERT_PADDING_BYTES(4);
-    std::array<u8, 0xE10> processor_raw_data{};
+    std::array<u8, 0xE20> processor_raw_data{};
 };
 static_assert(sizeof(ProcessorState) == 0xE20, "ProcessorState is an invalid size");
 
diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h
index 1bd62461c..accc2b646 100644
--- a/src/core/hle/service/hid/errors.h
+++ b/src/core/hle/service/hid/errors.h
@@ -18,7 +18,11 @@ constexpr Result NpadIsSameType{ErrorModule::HID, 602};
 constexpr Result InvalidNpadId{ErrorModule::HID, 709};
 constexpr Result NpadNotConnected{ErrorModule::HID, 710};
 
+} // namespace Service::HID
+
+namespace Service::IRS {
+
 constexpr ResultCode InvalidProcessorState{ErrorModule::Irsensor, 78};
 constexpr ResultCode InvalidIrCameraHandle{ErrorModule::Irsensor, 204};
 
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 89bb12442..5ecbddf94 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -2345,8 +2345,8 @@ void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system
     std::make_shared<HidSys>(system)->InstallAsService(service_manager);
     std::make_shared<HidTmp>(system)->InstallAsService(service_manager);
 
-    std::make_shared<IRS>(system)->InstallAsService(service_manager);
-    std::make_shared<IRS_SYS>(system)->InstallAsService(service_manager);
+    std::make_shared<Service::IRS::IRS>(system)->InstallAsService(service_manager);
+    std::make_shared<Service::IRS::IRS_SYS>(system)->InstallAsService(service_manager);
 
     std::make_shared<XCD_SYS>(system)->InstallAsService(service_manager);
 }
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index 53a31df79..b6e7c0792 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -22,7 +22,7 @@
 #include "core/hle/service/hid/irsensor/tera_plugin_processor.h"
 #include "core/memory.h"
 
-namespace Service::HID {
+namespace Service::IRS {
 
 IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} {
     // clang-format off
@@ -60,7 +60,7 @@ void IRS::ActivateIrsensor(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     const auto applet_resource_user_id{rp.Pop<u64>()};
 
-    LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
+    LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}",
                 applet_resource_user_id);
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -71,7 +71,7 @@ void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     const auto applet_resource_user_id{rp.Pop<u64>()};
 
-    LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
+    LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}",
                 applet_resource_user_id);
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -153,7 +153,7 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
         u64 applet_resource_user_id;
         Core::IrSensor::PackedClusteringProcessorConfig processor_config;
     };
-    static_assert(sizeof(Parameters) == 0x40, "Parameters has incorrect size.");
+    static_assert(sizeof(Parameters) == 0x38, "Parameters has incorrect size.");
 
     const auto parameters{rp.PopRaw<Parameters>()};
 
@@ -194,7 +194,7 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {
         system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
 
     if (t_mem.IsNull()) {
-        LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
+        LOG_ERROR(Service_IRS, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
         IPC::ResponseBuilder rb{ctx, 2};
         rb.Push(ResultUnknown);
         return;
@@ -268,24 +268,32 @@ void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) {
 
 void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
-    const auto camera_handle{rp.PopRaw<Core::IrSensor::IrCameraHandle>()};
-    const auto processor_config{rp.PopRaw<Core::IrSensor::PackedTeraPluginProcessorConfig>()};
-    const auto applet_resource_user_id{rp.Pop<u64>()};
+    struct Parameters {
+        Core::IrSensor::IrCameraHandle camera_handle;
+        Core::IrSensor::PackedTeraPluginProcessorConfig processor_config;
+        INSERT_PADDING_WORDS_NOINIT(1);
+        u64 applet_resource_user_id;
+    };
+    static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
 
-    LOG_WARNING(Service_IRS,
-                "(STUBBED) called, npad_type={}, npad_id={}, mode={}, mcu_version={}.{}, "
-                "applet_resource_user_id={}",
-                camera_handle.npad_type, camera_handle.npad_id, processor_config.mode,
-                processor_config.required_mcu_version.major,
-                processor_config.required_mcu_version.minor, applet_resource_user_id);
+    const auto parameters{rp.PopRaw<Parameters>()};
 
-    const auto result = IsIrCameraHandleValid(camera_handle);
+    LOG_WARNING(
+        Service_IRS,
+        "(STUBBED) called, npad_type={}, npad_id={}, mode={}, mcu_version={}.{}, "
+        "applet_resource_user_id={}",
+        parameters.camera_handle.npad_type, parameters.camera_handle.npad_id,
+        parameters.processor_config.mode, parameters.processor_config.required_mcu_version.major,
+        parameters.processor_config.required_mcu_version.minor, parameters.applet_resource_user_id);
+
+    const auto result = IsIrCameraHandleValid(parameters.camera_handle);
 
     if (result.IsSuccess()) {
-        auto& device = GetIrCameraSharedMemoryDeviceEntry(camera_handle);
-        MakeProcessor<TeraPluginProcessor>(camera_handle, device);
-        auto& image_transfer_processor = GetProcessor<TeraPluginProcessor>(camera_handle);
-        image_transfer_processor.SetConfig(processor_config);
+        auto& device = GetIrCameraSharedMemoryDeviceEntry(parameters.camera_handle);
+        MakeProcessor<TeraPluginProcessor>(parameters.camera_handle, device);
+        auto& image_transfer_processor =
+            GetProcessor<TeraPluginProcessor>(parameters.camera_handle);
+        image_transfer_processor.SetConfig(parameters.processor_config);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -299,7 +307,7 @@ void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) {
     if (npad_id > Core::HID::NpadIdType::Player8 && npad_id != Core::HID::NpadIdType::Invalid &&
         npad_id != Core::HID::NpadIdType::Handheld) {
         IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(InvalidNpadId);
+        rb.Push(Service::HID::InvalidNpadId);
         return;
     }
 
@@ -553,4 +561,4 @@ IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} {
 
 IRS_SYS::~IRS_SYS() = default;
 
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h
index ae7f7719b..516620b4d 100644
--- a/src/core/hle/service/hid/irs.h
+++ b/src/core/hle/service/hid/irs.h
@@ -16,7 +16,7 @@ namespace Core::HID {
 class EmulatedController;
 } // namespace Core::HID
 
-namespace Service::HID {
+namespace Service::IRS {
 
 class IRS final : public ServiceFramework<IRS> {
 public:
@@ -114,4 +114,4 @@ public:
     ~IRS_SYS() override;
 };
 
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.cpp b/src/core/hle/service/hid/irsensor/clustering_processor.cpp
index aac3e4fec..6479af212 100644
--- a/src/core/hle/service/hid/irsensor/clustering_processor.cpp
+++ b/src/core/hle/service/hid/irsensor/clustering_processor.cpp
@@ -3,14 +3,12 @@
 
 #include "core/hle/service/hid/irsensor/clustering_processor.h"
 
-namespace Service::HID {
+namespace Service::IRS {
 ClusteringProcessor::ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format)
     : device(device_format) {
     device.mode = Core::IrSensor::IrSensorMode::ClusteringProcessor;
     device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
     device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
-    device.state.start = 0;
-    device.state.count = 0;
 }
 
 ClusteringProcessor::~ClusteringProcessor() = default;
@@ -33,4 +31,4 @@ void ClusteringProcessor::SetConfig(Core::IrSensor::PackedClusteringProcessorCon
     current_config.object_intensity_min = config.object_intensity_min;
 }
 
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/clustering_processor.h b/src/core/hle/service/hid/irsensor/clustering_processor.h
index 58b2acf1c..6e2ba8846 100644
--- a/src/core/hle/service/hid/irsensor/clustering_processor.h
+++ b/src/core/hle/service/hid/irsensor/clustering_processor.h
@@ -7,7 +7,7 @@
 #include "core/hid/irs_types.h"
 #include "core/hle/service/hid/irsensor/processor_base.h"
 
-namespace Service::HID {
+namespace Service::IRS {
 class ClusteringProcessor final : public ProcessorBase {
 public:
     explicit ClusteringProcessor(Core::IrSensor::DeviceFormat& device_format);
@@ -71,4 +71,4 @@ private:
     ClusteringProcessorConfig current_config{};
     Core::IrSensor::DeviceFormat& device;
 };
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp
index 703e825e6..98f0c579d 100644
--- a/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp
+++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.cpp
@@ -5,7 +5,7 @@
 #include "core/hid/hid_core.h"
 #include "core/hle/service/hid/irsensor/image_transfer_processor.h"
 
-namespace Service::HID {
+namespace Service::IRS {
 ImageTransferProcessor::ImageTransferProcessor(Core::HID::HIDCore& hid_core_,
                                                Core::IrSensor::DeviceFormat& device_format,
                                                std::size_t npad_index)
@@ -21,8 +21,6 @@ ImageTransferProcessor::ImageTransferProcessor(Core::HID::HIDCore& hid_core_,
     device.mode = Core::IrSensor::IrSensorMode::ImageTransferProcessor;
     device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
     device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
-    device.state.start = 0;
-    device.state.count = 0;
 }
 
 ImageTransferProcessor::~ImageTransferProcessor() {
@@ -33,8 +31,6 @@ void ImageTransferProcessor::StartProcessor() {
     is_active = true;
     device.camera_status = Core::IrSensor::IrCameraStatus::Available;
     device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Ready;
-    device.state.start = 0;
-    device.state.count = 1;
     processor_state.sampling_number = 0;
     processor_state.ambient_noise_level = Core::IrSensor::CameraAmbientNoiseLevel::Low;
 }
@@ -151,4 +147,4 @@ Core::IrSensor::ImageTransferProcessorState ImageTransferProcessor::GetState(
     return processor_state;
 }
 
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/image_transfer_processor.h b/src/core/hle/service/hid/irsensor/image_transfer_processor.h
index b557eaf20..393df492d 100644
--- a/src/core/hle/service/hid/irsensor/image_transfer_processor.h
+++ b/src/core/hle/service/hid/irsensor/image_transfer_processor.h
@@ -11,7 +11,7 @@ namespace Core::HID {
 class EmulatedController;
 } // namespace Core::HID
 
-namespace Service::HID {
+namespace Service::IRS {
 class ImageTransferProcessor final : public ProcessorBase {
 public:
     explicit ImageTransferProcessor(Core::HID::HIDCore& hid_core_,
@@ -70,4 +70,4 @@ private:
     u8* transfer_memory = nullptr;
     bool is_transfer_memory_set = false;
 };
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/ir_led_processor.cpp b/src/core/hle/service/hid/irsensor/ir_led_processor.cpp
index 656e17c95..8e6dd99e4 100644
--- a/src/core/hle/service/hid/irsensor/ir_led_processor.cpp
+++ b/src/core/hle/service/hid/irsensor/ir_led_processor.cpp
@@ -3,14 +3,12 @@
 
 #include "core/hle/service/hid/irsensor/ir_led_processor.h"
 
-namespace Service::HID {
+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;
-    device.state.start = 0;
-    device.state.count = 0;
 }
 
 IrLedProcessor::~IrLedProcessor() = default;
@@ -26,4 +24,4 @@ void IrLedProcessor::SetConfig(Core::IrSensor::PackedIrLedProcessorConfig config
         static_cast<Core::IrSensor::CameraLightTarget>(config.light_target);
 }
 
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/ir_led_processor.h b/src/core/hle/service/hid/irsensor/ir_led_processor.h
index cb04d0b94..c3d8693c9 100644
--- a/src/core/hle/service/hid/irsensor/ir_led_processor.h
+++ b/src/core/hle/service/hid/irsensor/ir_led_processor.h
@@ -8,7 +8,7 @@
 #include "core/hid/irs_types.h"
 #include "core/hle/service/hid/irsensor/processor_base.h"
 
-namespace Service::HID {
+namespace Service::IRS {
 class IrLedProcessor final : public ProcessorBase {
 public:
     explicit IrLedProcessor(Core::IrSensor::DeviceFormat& device_format);
@@ -44,4 +44,4 @@ private:
     Core::IrSensor::DeviceFormat& device;
 };
 
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/moment_processor.cpp b/src/core/hle/service/hid/irsensor/moment_processor.cpp
index fb36be577..dbaca420a 100644
--- a/src/core/hle/service/hid/irsensor/moment_processor.cpp
+++ b/src/core/hle/service/hid/irsensor/moment_processor.cpp
@@ -3,14 +3,12 @@
 
 #include "core/hle/service/hid/irsensor/moment_processor.h"
 
-namespace Service::HID {
+namespace Service::IRS {
 MomentProcessor::MomentProcessor(Core::IrSensor::DeviceFormat& device_format)
     : device(device_format) {
     device.mode = Core::IrSensor::IrSensorMode::MomentProcessor;
     device.camera_status = Core::IrSensor::IrCameraStatus::Unconnected;
     device.camera_internal_status = Core::IrSensor::IrCameraInternalStatus::Stopped;
-    device.state.start = 0;
-    device.state.count = 0;
 }
 
 MomentProcessor::~MomentProcessor() = default;
@@ -33,4 +31,4 @@ void MomentProcessor::SetConfig(Core::IrSensor::PackedMomentProcessorConfig conf
     current_config.preprocess_intensity_threshold = config.preprocess_intensity_threshold;
 }
 
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/moment_processor.h b/src/core/hle/service/hid/irsensor/moment_processor.h
index 63cdadfda..d4bd22e0f 100644
--- a/src/core/hle/service/hid/irsensor/moment_processor.h
+++ b/src/core/hle/service/hid/irsensor/moment_processor.h
@@ -8,7 +8,7 @@
 #include "core/hid/irs_types.h"
 #include "core/hle/service/hid/irsensor/processor_base.h"
 
-namespace Service::HID {
+namespace Service::IRS {
 class MomentProcessor final : public ProcessorBase {
 public:
     explicit MomentProcessor(Core::IrSensor::DeviceFormat& device_format);
@@ -58,4 +58,4 @@ private:
     Core::IrSensor::DeviceFormat& device;
 };
 
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/pointing_processor.cpp b/src/core/hle/service/hid/irsensor/pointing_processor.cpp
index 7eb123636..929f177fc 100644
--- a/src/core/hle/service/hid/irsensor/pointing_processor.cpp
+++ b/src/core/hle/service/hid/irsensor/pointing_processor.cpp
@@ -3,14 +3,12 @@
 
 #include "core/hle/service/hid/irsensor/pointing_processor.h"
 
-namespace Service::HID {
+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;
-    device.state.start = 0;
-    device.state.count = 0;
 }
 
 PointingProcessor::~PointingProcessor() = default;
@@ -25,4 +23,4 @@ void PointingProcessor::SetConfig(Core::IrSensor::PackedPointingProcessorConfig
     current_config.window_of_interest = config.window_of_interest;
 }
 
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/pointing_processor.h b/src/core/hle/service/hid/irsensor/pointing_processor.h
index 2f67eff91..cf4930794 100644
--- a/src/core/hle/service/hid/irsensor/pointing_processor.h
+++ b/src/core/hle/service/hid/irsensor/pointing_processor.h
@@ -3,12 +3,11 @@
 
 #pragma once
 
-#include "common/bit_field.h"
 #include "common/common_types.h"
 #include "core/hid/irs_types.h"
 #include "core/hle/service/hid/irsensor/processor_base.h"
 
-namespace Service::HID {
+namespace Service::IRS {
 class PointingProcessor final : public ProcessorBase {
 public:
     explicit PointingProcessor(Core::IrSensor::DeviceFormat& device_format);
@@ -59,4 +58,4 @@ private:
     Core::IrSensor::DeviceFormat& device;
 };
 
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/processor_base.cpp b/src/core/hle/service/hid/irsensor/processor_base.cpp
index bc8025375..4d43ca17a 100644
--- a/src/core/hle/service/hid/irsensor/processor_base.cpp
+++ b/src/core/hle/service/hid/irsensor/processor_base.cpp
@@ -3,7 +3,7 @@
 
 #include "core/hle/service/hid/irsensor/processor_base.h"
 
-namespace Service::HID {
+namespace Service::IRS {
 
 ProcessorBase::ProcessorBase() {}
 ProcessorBase::~ProcessorBase() = default;
@@ -64,4 +64,4 @@ std::size_t ProcessorBase::GetDataHeight(
     }
 }
 
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/processor_base.h b/src/core/hle/service/hid/irsensor/processor_base.h
index 49831aab6..bc0d2977b 100644
--- a/src/core/hle/service/hid/irsensor/processor_base.h
+++ b/src/core/hle/service/hid/irsensor/processor_base.h
@@ -6,7 +6,7 @@
 #include "common/common_types.h"
 #include "core/hid/irs_types.h"
 
-namespace Service::HID {
+namespace Service::IRS {
 class ProcessorBase {
 public:
     explicit ProcessorBase();
@@ -30,4 +30,4 @@ protected:
 
     bool is_active{false};
 };
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp b/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp
index deec22072..e691c840a 100644
--- a/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp
+++ b/src/core/hle/service/hid/irsensor/tera_plugin_processor.cpp
@@ -3,14 +3,12 @@
 
 #include "core/hle/service/hid/irsensor/tera_plugin_processor.h"
 
-namespace Service::HID {
+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;
-    device.state.start = 0;
-    device.state.count = 0;
 }
 
 TeraPluginProcessor::~TeraPluginProcessor() = default;
@@ -28,4 +26,4 @@ void TeraPluginProcessor::SetConfig(Core::IrSensor::PackedTeraPluginProcessorCon
     current_config.unknown_3 = config.unknown_3;
 }
 
-} // namespace Service::HID
+} // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irsensor/tera_plugin_processor.h b/src/core/hle/service/hid/irsensor/tera_plugin_processor.h
index 60f8057a5..bbea7ed0b 100644
--- a/src/core/hle/service/hid/irsensor/tera_plugin_processor.h
+++ b/src/core/hle/service/hid/irsensor/tera_plugin_processor.h
@@ -8,7 +8,7 @@
 #include "core/hid/irs_types.h"
 #include "core/hle/service/hid/irsensor/processor_base.h"
 
-namespace Service::HID {
+namespace Service::IRS {
 class TeraPluginProcessor final : public ProcessorBase {
 public:
     explicit TeraPluginProcessor(Core::IrSensor::DeviceFormat& device_format);
@@ -50,4 +50,4 @@ private:
     Core::IrSensor::DeviceFormat& device;
 };
 
-} // namespace Service::HID
+} // namespace Service::IRS

From 403bdc4dafe89a463f3d93b9a389a1010ca5ff16 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 28 Jun 2022 19:35:51 -0500
Subject: [PATCH 6/6] yuzu: Add webcam support and rebase to latest master

---
 src/common/settings.h                       |  4 ++--
 src/core/hle/service/hid/errors.h           |  4 ++--
 src/core/hle/service/hid/irs.cpp            |  2 +-
 src/core/hle/service/hid/irs.h              |  2 +-
 src/yuzu/bootmanager.cpp                    | 19 ++++++++++++----
 src/yuzu/bootmanager.h                      |  2 ++
 src/yuzu/configuration/configure_camera.cpp | 24 +++++++++++++++------
 src/yuzu/configuration/configure_camera.h   |  2 ++
 8 files changed, 43 insertions(+), 16 deletions(-)

diff --git a/src/common/settings.h b/src/common/settings.h
index 20959ec89..1079cf8cb 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -503,8 +503,8 @@ struct Values {
     Setting<bool> enable_ring_controller{true, "enable_ring_controller"};
     RingconRaw ringcon_analogs;
 
-    BasicSetting<bool> enable_ir_sensor{false, "enable_ir_sensor"};
-    BasicSetting<std::string> ir_sensor_device{"auto", "ir_sensor_device"};
+    Setting<bool> enable_ir_sensor{false, "enable_ir_sensor"};
+    Setting<std::string> ir_sensor_device{"auto", "ir_sensor_device"};
 
     // Data Storage
     Setting<bool> use_virtual_sd{true, "use_virtual_sd"};
diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h
index accc2b646..4613a4e60 100644
--- a/src/core/hle/service/hid/errors.h
+++ b/src/core/hle/service/hid/errors.h
@@ -22,7 +22,7 @@ constexpr Result NpadNotConnected{ErrorModule::HID, 710};
 
 namespace Service::IRS {
 
-constexpr ResultCode InvalidProcessorState{ErrorModule::Irsensor, 78};
-constexpr ResultCode InvalidIrCameraHandle{ErrorModule::Irsensor, 204};
+constexpr Result InvalidProcessorState{ErrorModule::Irsensor, 78};
+constexpr Result InvalidIrCameraHandle{ErrorModule::Irsensor, 204};
 
 } // namespace Service::IRS
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index b6e7c0792..d5107e41f 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -529,7 +529,7 @@ void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) {
     rb.Push(ResultSuccess);
 }
 
-ResultCode IRS::IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const {
+Result IRS::IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const {
     if (camera_handle.npad_id >
         static_cast<u8>(NpadIdTypeToIndex(Core::HID::NpadIdType::Handheld))) {
         return InvalidIrCameraHandle;
diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h
index 516620b4d..2e6115c73 100644
--- a/src/core/hle/service/hid/irs.h
+++ b/src/core/hle/service/hid/irs.h
@@ -57,7 +57,7 @@ private:
     void StopImageProcessorAsync(Kernel::HLERequestContext& ctx);
     void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx);
 
-    ResultCode IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const;
+    Result IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_handle) const;
     Core::IrSensor::DeviceFormat& GetIrCameraSharedMemoryDeviceEntry(
         const Core::IrSensor::IrCameraHandle& camera_handle);
 
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 774085809..0ee3820a2 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -830,6 +830,10 @@ void GRenderWindow::InitializeCamera() {
     camera->unload();
     camera->setCaptureMode(QCamera::CaptureViewfinder);
     camera->load();
+    camera->start();
+
+    pending_camera_snapshots = 0;
+    is_virtual_camera = false;
 
     camera_timer = std::make_unique<QTimer>();
     connect(camera_timer.get(), &QTimer::timeout, [this] { RequestCameraCapture(); });
@@ -851,11 +855,17 @@ void GRenderWindow::RequestCameraCapture() {
         return;
     }
 
-    // Idealy one should only call capture but Qt refuses to take a second capture without
-    // stopping the camera
-    camera->stop();
-    camera->start();
+    // If the camera doesn't capture, test for virtual cameras
+    if (pending_camera_snapshots > 5) {
+        is_virtual_camera = true;
+    }
+    // Virtual cameras like obs need to reset the camera every capture
+    if (is_virtual_camera) {
+        camera->stop();
+        camera->start();
+    }
 
+    pending_camera_snapshots++;
     camera_capture->capture();
 }
 
@@ -870,6 +880,7 @@ void GRenderWindow::OnCameraCapture(int requestId, const QImage& img) {
     camera_data.resize(camera_width * camera_height);
     std::memcpy(camera_data.data(), converted.bits(), camera_width * camera_height * sizeof(u32));
     input_subsystem->GetCamera()->SetCameraData(camera_width, camera_height, camera_data);
+    pending_camera_snapshots = 0;
 }
 
 bool GRenderWindow::event(QEvent* event) {
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 346201768..b4781e697 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -240,6 +240,8 @@ private:
     bool first_frame = false;
     InputCommon::TasInput::TasState last_tas_state;
 
+    bool is_virtual_camera;
+    int pending_camera_snapshots;
     std::unique_ptr<QCamera> camera;
     std::unique_ptr<QCameraImageCapture> camera_capture;
     std::unique_ptr<QTimer> camera_timer;
diff --git a/src/yuzu/configuration/configure_camera.cpp b/src/yuzu/configuration/configure_camera.cpp
index 97febb33c..73cdcf3f2 100644
--- a/src/yuzu/configuration/configure_camera.cpp
+++ b/src/yuzu/configuration/configure_camera.cpp
@@ -39,8 +39,8 @@ void ConfigureCamera::PreviewCamera() {
     for (const QCameraInfo& cameraInfo : cameras) {
         if (input_devices[index] == cameraInfo.deviceName().toStdString() ||
             input_devices[index] == "Auto") {
-            LOG_ERROR(Frontend, "Selected Camera {} {}", cameraInfo.description().toStdString(),
-                      cameraInfo.deviceName().toStdString());
+            LOG_INFO(Frontend, "Selected Camera {} {}", cameraInfo.description().toStdString(),
+                     cameraInfo.deviceName().toStdString());
             camera = std::make_unique<QCamera>(cameraInfo);
             camera_found = true;
             break;
@@ -62,12 +62,23 @@ void ConfigureCamera::PreviewCamera() {
     camera->unload();
     camera->setCaptureMode(QCamera::CaptureViewfinder);
     camera->load();
+    camera->start();
+
+    pending_snapshots = 0;
+    is_virtual_camera = false;
 
     camera_timer = std::make_unique<QTimer>();
     connect(camera_timer.get(), &QTimer::timeout, [this] {
-        camera->stop();
-        camera->start();
-
+        // If the camera doesn't capture, test for virtual cameras
+        if (pending_snapshots > 5) {
+            is_virtual_camera = true;
+        }
+        // Virtual cameras like obs need to reset the camera every capture
+        if (is_virtual_camera) {
+            camera->stop();
+            camera->start();
+        }
+        pending_snapshots++;
         camera_capture->capture();
     });
 
@@ -75,10 +86,11 @@ void ConfigureCamera::PreviewCamera() {
 }
 
 void ConfigureCamera::DisplayCapturedFrame(int requestId, const QImage& img) {
-    LOG_ERROR(Frontend, "ImageCaptured {} {}", img.width(), img.height());
+    LOG_INFO(Frontend, "ImageCaptured {} {}", img.width(), img.height());
     const auto converted = img.scaled(320, 240, Qt::AspectRatioMode::IgnoreAspectRatio,
                                       Qt::TransformationMode::SmoothTransformation);
     ui->preview_box->setPixmap(QPixmap::fromImage(converted));
+    pending_snapshots = 0;
 }
 
 void ConfigureCamera::changeEvent(QEvent* event) {
diff --git a/src/yuzu/configuration/configure_camera.h b/src/yuzu/configuration/configure_camera.h
index af7551c03..db9833b5c 100644
--- a/src/yuzu/configuration/configure_camera.h
+++ b/src/yuzu/configuration/configure_camera.h
@@ -44,6 +44,8 @@ private:
 
     InputCommon::InputSubsystem* input_subsystem;
 
+    bool is_virtual_camera;
+    int pending_snapshots;
     std::unique_ptr<QCamera> camera;
     std::unique_ptr<QCameraImageCapture> camera_capture;
     std::unique_ptr<QTimer> camera_timer;