From 8a3d22c4bd54c18edb51fe6513761ec2189e3369 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sat, 24 Sep 2022 20:36:40 -0500
Subject: [PATCH] core: hid: Add nfc support to emulated controller

---
 src/core/hid/emulated_controller.cpp | 70 ++++++++++++++++++++++++++++
 src/core/hid/emulated_controller.h   | 34 ++++++++++++--
 src/core/hid/input_converter.cpp     | 14 ++++++
 src/core/hid/input_converter.h       |  8 ++++
 4 files changed, 123 insertions(+), 3 deletions(-)

diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 01c43be934..142c39003c 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -131,13 +131,16 @@ void EmulatedController::LoadDevices() {
     battery_params[RightIndex].Set("battery", true);
 
     camera_params = Common::ParamPackage{"engine:camera,camera:1"};
+    nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
 
     output_params[LeftIndex] = left_joycon;
     output_params[RightIndex] = right_joycon;
     output_params[2] = camera_params;
+    output_params[3] = nfc_params;
     output_params[LeftIndex].Set("output", true);
     output_params[RightIndex].Set("output", true);
     output_params[2].Set("output", true);
+    output_params[3].Set("output", true);
 
     LoadTASParams();
 
@@ -155,6 +158,7 @@ void EmulatedController::LoadDevices() {
     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);
+    nfc_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(nfc_params);
     std::transform(output_params.begin(), output_params.end(), output_devices.begin(),
                    Common::Input::CreateDevice<Common::Input::OutputDevice>);
 
@@ -284,6 +288,16 @@ void EmulatedController::ReloadInput() {
         camera_devices->ForceUpdate();
     }
 
+    if (nfc_devices) {
+        if (npad_id_type == NpadIdType::Handheld || npad_id_type == NpadIdType::Player1) {
+            nfc_devices->SetCallback({
+                .on_change =
+                    [this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); },
+            });
+            nfc_devices->ForceUpdate();
+        }
+    }
+
     // 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}};
@@ -339,6 +353,8 @@ void EmulatedController::UnloadInput() {
     for (auto& stick : tas_stick_devices) {
         stick.reset();
     }
+    camera_devices.reset();
+    nfc_devices.reset();
 }
 
 void EmulatedController::EnableConfiguration() {
@@ -903,6 +919,25 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback
     TriggerOnChange(ControllerTriggerType::IrSensor, true);
 }
 
+void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
+    std::unique_lock lock{mutex};
+    controller.nfc_values = TransformToNfc(callback);
+
+    if (is_configuring) {
+        lock.unlock();
+        TriggerOnChange(ControllerTriggerType::Nfc, false);
+        return;
+    }
+
+    controller.nfc_state = {
+        controller.nfc_values.state,
+        controller.nfc_values.data,
+    };
+
+    lock.unlock();
+    TriggerOnChange(ControllerTriggerType::Nfc, true);
+}
+
 bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
     if (device_index >= output_devices.size()) {
         return false;
@@ -980,6 +1015,10 @@ bool EmulatedController::TestVibration(std::size_t device_index) {
 bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) {
     LOG_INFO(Service_HID, "Set polling mode {}", polling_mode);
     auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
+    auto& nfc_output_device = output_devices[3];
+
+    nfc_output_device->SetPollingMode(polling_mode);
+
     return output_device->SetPollingMode(polling_mode) == Common::Input::PollingError::None;
 }
 
@@ -1000,6 +1039,32 @@ bool EmulatedController::SetCameraFormat(
                camera_format)) == Common::Input::CameraError::None;
 }
 
+bool EmulatedController::HasNfc() const {
+    const auto& nfc_output_device = output_devices[3];
+
+    switch (npad_type) {
+    case NpadStyleIndex::JoyconRight:
+    case NpadStyleIndex::JoyconDual:
+    case NpadStyleIndex::ProController:
+        break;
+    default:
+        return false;
+    }
+
+    const bool has_virtual_nfc =
+        npad_id_type == NpadIdType::Player1 || npad_id_type == NpadIdType::Handheld;
+    const bool is_virtual_nfc_supported =
+        nfc_output_device->SupportsNfc() != Common::Input::NfcState::NotSupported;
+
+    return is_connected && (has_virtual_nfc && is_virtual_nfc_supported);
+}
+
+bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
+    auto& nfc_output_device = output_devices[3];
+
+    return nfc_output_device->WriteNfcData(data) == Common::Input::NfcState::Success;
+}
+
 void EmulatedController::SetLedPattern() {
     for (auto& device : output_devices) {
         if (!device) {
@@ -1363,6 +1428,11 @@ const CameraState& EmulatedController::GetCamera() const {
     return controller.camera_state;
 }
 
+const NfcState& EmulatedController::GetNfc() const {
+    std::scoped_lock lock{mutex};
+    return controller.nfc_state;
+}
+
 NpadColor EmulatedController::GetNpadColor(u32 color) {
     return {
         .r = static_cast<u8>((color >> 16) & 0xFF),
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index c3aa8f9d37..319226bf82 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -20,7 +20,7 @@
 
 namespace Core::HID {
 const std::size_t max_emulated_controllers = 2;
-const std::size_t output_devices = 3;
+const std::size_t output_devices_size = 4;
 struct ControllerMotionInfo {
     Common::Input::MotionStatus raw_status{};
     MotionInput emulated{};
@@ -37,7 +37,8 @@ using TriggerDevices =
 using BatteryDevices =
     std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
 using CameraDevices = std::unique_ptr<Common::Input::InputDevice>;
-using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices>;
+using NfcDevices = std::unique_ptr<Common::Input::InputDevice>;
+using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>;
 
 using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
 using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
@@ -45,7 +46,8 @@ using ControllerMotionParams = std::array<Common::ParamPackage, Settings::Native
 using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
 using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using CameraParams = Common::ParamPackage;
-using OutputParams = std::array<Common::ParamPackage, output_devices>;
+using NfcParams = Common::ParamPackage;
+using OutputParams = std::array<Common::ParamPackage, output_devices_size>;
 
 using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
 using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
@@ -55,6 +57,7 @@ using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::Native
 using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
 using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
 using CameraValues = Common::Input::CameraStatus;
+using NfcValues = Common::Input::NfcStatus;
 using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
 
 struct AnalogSticks {
@@ -80,6 +83,11 @@ struct CameraState {
     std::size_t sample{};
 };
 
+struct NfcState {
+    Common::Input::NfcState state{};
+    std::vector<u8> data{};
+};
+
 struct ControllerMotion {
     Common::Vec3f accel{};
     Common::Vec3f gyro{};
@@ -107,6 +115,7 @@ struct ControllerStatus {
     BatteryValues battery_values{};
     VibrationValues vibration_values{};
     CameraValues camera_values{};
+    NfcValues nfc_values{};
 
     // Data for HID serices
     HomeButtonState home_button_state{};
@@ -119,6 +128,7 @@ struct ControllerStatus {
     ControllerColors colors_state{};
     BatteryLevelState battery_state{};
     CameraState camera_state{};
+    NfcState nfc_state{};
 };
 
 enum class ControllerTriggerType {
@@ -130,6 +140,7 @@ enum class ControllerTriggerType {
     Battery,
     Vibration,
     IrSensor,
+    Nfc,
     Connected,
     Disconnected,
     Type,
@@ -315,6 +326,9 @@ public:
     /// Returns the latest camera status from the controller
     const CameraState& GetCamera() const;
 
+    /// Returns the latest ntag status from the controller
+    const NfcState& GetNfc() const;
+
     /**
      * Sends a specific vibration to the output device
      * @return true if vibration had no errors
@@ -341,6 +355,12 @@ public:
      */
     bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format);
 
+    /// Returns true if the device has nfc support
+    bool HasNfc() const;
+
+    /// Returns true if the nfc tag was written
+    bool WriteNfc(const std::vector<u8>& data);
+
     /// Returns the led pattern corresponding to this emulated controller
     LedPattern GetLedPattern() const;
 
@@ -424,6 +444,12 @@ private:
      */
     void SetCamera(const Common::Input::CallbackStatus& callback);
 
+    /**
+     * Updates the nfc status of the controller
+     * @param callback A CallbackStatus containing the nfc status
+     */
+    void SetNfc(const Common::Input::CallbackStatus& callback);
+
     /**
      * Converts a color format from bgra to rgba
      * @param color in bgra format
@@ -458,6 +484,7 @@ private:
     TriggerParams trigger_params;
     BatteryParams battery_params;
     CameraParams camera_params;
+    NfcParams nfc_params;
     OutputParams output_params;
 
     ButtonDevices button_devices;
@@ -466,6 +493,7 @@ private:
     TriggerDevices trigger_devices;
     BatteryDevices battery_devices;
     CameraDevices camera_devices;
+    NfcDevices nfc_devices;
     OutputDevices output_devices;
 
     // TAS related variables
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
index 52fb69e9c8..e7b871accb 100644
--- a/src/core/hid/input_converter.cpp
+++ b/src/core/hid/input_converter.cpp
@@ -287,6 +287,20 @@ Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatu
     return camera;
 }
 
+Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback) {
+    Common::Input::NfcStatus nfc{};
+    switch (callback.type) {
+    case Common::Input::InputType::Nfc:
+        nfc = callback.nfc_status;
+        break;
+    default:
+        LOG_ERROR(Input, "Conversion from type {} to NFC not implemented", callback.type);
+        break;
+    }
+
+    return nfc;
+}
+
 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 143c50cc06..b7eb6e660c 100644
--- a/src/core/hid/input_converter.h
+++ b/src/core/hid/input_converter.h
@@ -84,6 +84,14 @@ Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatu
  */
 Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback);
 
+/**
+ * Converts raw input data into a valid nfc status.
+ *
+ * @param callback Supported callbacks: Nfc.
+ * @return A valid CameraObject object.
+ */
+Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback);
+
 /**
  * Converts raw analog data into a valid analog value
  * @param analog An analog object containing raw data and properties