Jelajahi Sumber

Merge pull request #9219 from german77/nfc_impl

service: nfc: Implement NFC IUser service
bunnei 3 tahun lalu
induk
melakukan
f047f376d4

+ 5 - 0
src/core/CMakeLists.txt

@@ -530,6 +530,11 @@ add_library(core STATIC
     hle/service/ncm/ncm.h
     hle/service/nfc/nfc.cpp
     hle/service/nfc/nfc.h
+    hle/service/nfc/nfc_device.cpp
+    hle/service/nfc/nfc_device.h
+    hle/service/nfc/nfc_result.h
+    hle/service/nfc/nfc_user.cpp
+    hle/service/nfc/nfc_user.h
     hle/service/nfp/amiibo_crypto.cpp
     hle/service/nfp/amiibo_crypto.h
     hle/service/nfp/nfp.cpp

+ 1 - 70
src/core/hle/service/nfc/nfc.cpp

@@ -7,6 +7,7 @@
 #include "common/settings.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/nfc/nfc.h"
+#include "core/hle/service/nfc/nfc_user.h"
 #include "core/hle/service/service.h"
 #include "core/hle/service/sm/sm.h"
 
@@ -97,76 +98,6 @@ private:
     }
 };
 
-class IUser final : public ServiceFramework<IUser> {
-public:
-    explicit IUser(Core::System& system_) : ServiceFramework{system_, "NFC::IUser"} {
-        // clang-format off
-        static const FunctionInfo functions[] = {
-            {0, &IUser::InitializeOld, "InitializeOld"},
-            {1, &IUser::FinalizeOld, "FinalizeOld"},
-            {2, &IUser::GetStateOld, "GetStateOld"},
-            {3, &IUser::IsNfcEnabledOld, "IsNfcEnabledOld"},
-            {400, &IUser::InitializeOld, "Initialize"},
-            {401, &IUser::FinalizeOld, "Finalize"},
-            {402, &IUser::GetStateOld, "GetState"},
-            {403, &IUser::IsNfcEnabledOld, "IsNfcEnabled"},
-            {404, nullptr, "ListDevices"},
-            {405, nullptr, "GetDeviceState"},
-            {406, nullptr, "GetNpadId"},
-            {407, nullptr, "AttachAvailabilityChangeEvent"},
-            {408, nullptr, "StartDetection"},
-            {409, nullptr, "StopDetection"},
-            {410, nullptr, "GetTagInfo"},
-            {411, nullptr, "AttachActivateEvent"},
-            {412, nullptr, "AttachDeactivateEvent"},
-            {1000, nullptr, "ReadMifare"},
-            {1001, nullptr, "WriteMifare"},
-            {1300, nullptr, "SendCommandByPassThrough"},
-            {1301, nullptr, "KeepPassThroughSession"},
-            {1302, nullptr, "ReleasePassThroughSession"},
-        };
-        // clang-format on
-
-        RegisterHandlers(functions);
-    }
-
-private:
-    enum class NfcStates : u32 {
-        Finalized = 6,
-    };
-
-    void InitializeOld(Kernel::HLERequestContext& ctx) {
-        LOG_DEBUG(Service_NFC, "called");
-
-        IPC::ResponseBuilder rb{ctx, 2, 0};
-        rb.Push(ResultSuccess);
-        // We don't deal with hardware initialization so we can just stub this.
-    }
-
-    void IsNfcEnabledOld(Kernel::HLERequestContext& ctx) {
-        LOG_DEBUG(Service_NFC, "IsNfcEnabledOld");
-
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(ResultSuccess);
-        rb.PushRaw<u8>(true);
-    }
-
-    void GetStateOld(Kernel::HLERequestContext& ctx) {
-        LOG_WARNING(Service_NFC, "(STUBBED) called");
-
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(ResultSuccess);
-        rb.PushEnum(NfcStates::Finalized); // TODO(ogniK): Figure out if this matches nfp
-    }
-
-    void FinalizeOld(Kernel::HLERequestContext& ctx) {
-        LOG_WARNING(Service_NFC, "(STUBBED) called");
-
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ResultSuccess);
-    }
-};
-
 class NFC_U final : public ServiceFramework<NFC_U> {
 public:
     explicit NFC_U(Core::System& system_) : ServiceFramework{system_, "nfc:user"} {

+ 197 - 0
src/core/hle/service/nfc/nfc_device.cpp

@@ -0,0 +1,197 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/input.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hid/hid_types.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/nfc/nfc_device.h"
+#include "core/hle/service/nfc/nfc_result.h"
+#include "core/hle/service/nfc/nfc_user.h"
+
+namespace Service::NFC {
+NfcDevice::NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
+                     KernelHelpers::ServiceContext& service_context_,
+                     Kernel::KEvent* availability_change_event_)
+    : npad_id{npad_id_}, system{system_}, service_context{service_context_},
+      availability_change_event{availability_change_event_} {
+    activate_event = service_context.CreateEvent("IUser:NFCActivateEvent");
+    deactivate_event = service_context.CreateEvent("IUser:NFCDeactivateEvent");
+    npad_device = system.HIDCore().GetEmulatedController(npad_id);
+
+    Core::HID::ControllerUpdateCallback engine_callback{
+        .on_change = [this](Core::HID::ControllerTriggerType type) { NpadUpdate(type); },
+        .is_npad_service = false,
+    };
+    is_controller_set = true;
+    callback_key = npad_device->SetCallback(engine_callback);
+}
+
+NfcDevice::~NfcDevice() {
+    activate_event->Close();
+    deactivate_event->Close();
+    if (!is_controller_set) {
+        return;
+    }
+    npad_device->DeleteCallback(callback_key);
+    is_controller_set = false;
+};
+
+void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
+    if (type == Core::HID::ControllerTriggerType::Connected ||
+        type == Core::HID::ControllerTriggerType::Disconnected) {
+        availability_change_event->Signal();
+        return;
+    }
+
+    if (type != Core::HID::ControllerTriggerType::Nfc) {
+        return;
+    }
+
+    if (!npad_device->IsConnected()) {
+        return;
+    }
+
+    const auto nfc_status = npad_device->GetNfc();
+    switch (nfc_status.state) {
+    case Common::Input::NfcState::NewAmiibo:
+        LoadNfcTag(nfc_status.data);
+        break;
+    case Common::Input::NfcState::AmiiboRemoved:
+        if (device_state != NFP::DeviceState::SearchingForTag) {
+            CloseNfcTag();
+        }
+        break;
+    default:
+        break;
+    }
+}
+
+bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
+    if (device_state != NFP::DeviceState::SearchingForTag) {
+        LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state);
+        return false;
+    }
+
+    if (data.size() != sizeof(NFP::EncryptedNTAG215File)) {
+        LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size());
+        return false;
+    }
+
+    memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
+
+    device_state = NFP::DeviceState::TagFound;
+    deactivate_event->GetReadableEvent().Clear();
+    activate_event->Signal();
+    return true;
+}
+
+void NfcDevice::CloseNfcTag() {
+    LOG_INFO(Service_NFC, "Remove nfc tag");
+
+    device_state = NFP::DeviceState::TagRemoved;
+    encrypted_tag_data = {};
+    activate_event->GetReadableEvent().Clear();
+    deactivate_event->Signal();
+}
+
+Kernel::KReadableEvent& NfcDevice::GetActivateEvent() const {
+    return activate_event->GetReadableEvent();
+}
+
+Kernel::KReadableEvent& NfcDevice::GetDeactivateEvent() const {
+    return deactivate_event->GetReadableEvent();
+}
+
+void NfcDevice::Initialize() {
+    device_state =
+        npad_device->HasNfc() ? NFP::DeviceState::Initialized : NFP::DeviceState::Unavailable;
+    encrypted_tag_data = {};
+}
+
+void NfcDevice::Finalize() {
+    if (device_state == NFP::DeviceState::SearchingForTag ||
+        device_state == NFP::DeviceState::TagRemoved) {
+        StopDetection();
+    }
+    device_state = NFP::DeviceState::Unavailable;
+}
+
+Result NfcDevice::StartDetection(s32 protocol_) {
+    if (device_state != NFP::DeviceState::Initialized &&
+        device_state != NFP::DeviceState::TagRemoved) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        return WrongDeviceState;
+    }
+
+    if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) {
+        LOG_ERROR(Service_NFC, "Nfc not supported");
+        return NfcDisabled;
+    }
+
+    device_state = NFP::DeviceState::SearchingForTag;
+    protocol = protocol_;
+    return ResultSuccess;
+}
+
+Result NfcDevice::StopDetection() {
+    npad_device->SetPollingMode(Common::Input::PollingMode::Active);
+
+    if (device_state == NFP::DeviceState::Initialized) {
+        return ResultSuccess;
+    }
+
+    if (device_state == NFP::DeviceState::TagFound ||
+        device_state == NFP::DeviceState::TagMounted) {
+        CloseNfcTag();
+        return ResultSuccess;
+    }
+    if (device_state == NFP::DeviceState::SearchingForTag ||
+        device_state == NFP::DeviceState::TagRemoved) {
+        device_state = NFP::DeviceState::Initialized;
+        return ResultSuccess;
+    }
+
+    LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+    return WrongDeviceState;
+}
+
+Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info) const {
+    if (device_state != NFP::DeviceState::TagFound &&
+        device_state != NFP::DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        if (device_state == NFP::DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    // Protocol and tag type may change here
+    tag_info = {
+        .uuid = encrypted_tag_data.uuid.uid,
+        .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
+        .protocol = NFP::TagProtocol::TypeA,
+        .tag_type = NFP::TagType::Type2,
+    };
+
+    return ResultSuccess;
+}
+
+u64 NfcDevice::GetHandle() const {
+    // Generate a handle based of the npad id
+    return static_cast<u64>(npad_id);
+}
+
+NFP::DeviceState NfcDevice::GetCurrentState() const {
+    return device_state;
+}
+
+Core::HID::NpadIdType NfcDevice::GetNpadId() const {
+    return npad_id;
+}
+
+} // namespace Service::NFC

+ 70 - 0
src/core/hle/service/nfc/nfc_device.h

@@ -0,0 +1,70 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nfp/nfp_types.h"
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace Core::HID {
+class EmulatedController;
+enum class ControllerTriggerType;
+enum class NpadIdType : u32;
+} // namespace Core::HID
+
+namespace Service::NFC {
+class NfcDevice {
+public:
+    NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
+              KernelHelpers::ServiceContext& service_context_,
+              Kernel::KEvent* availability_change_event_);
+    ~NfcDevice();
+
+    void Initialize();
+    void Finalize();
+
+    Result StartDetection(s32 protocol_);
+    Result StopDetection();
+
+    Result GetTagInfo(NFP::TagInfo& tag_info) const;
+
+    u64 GetHandle() const;
+    NFP::DeviceState GetCurrentState() const;
+    Core::HID::NpadIdType GetNpadId() const;
+
+    Kernel::KReadableEvent& GetActivateEvent() const;
+    Kernel::KReadableEvent& GetDeactivateEvent() const;
+
+private:
+    void NpadUpdate(Core::HID::ControllerTriggerType type);
+    bool LoadNfcTag(std::span<const u8> data);
+    void CloseNfcTag();
+
+    bool is_controller_set{};
+    int callback_key;
+    const Core::HID::NpadIdType npad_id;
+    Core::System& system;
+    Core::HID::EmulatedController* npad_device = nullptr;
+    KernelHelpers::ServiceContext& service_context;
+    Kernel::KEvent* activate_event = nullptr;
+    Kernel::KEvent* deactivate_event = nullptr;
+    Kernel::KEvent* availability_change_event = nullptr;
+
+    s32 protocol{};
+    NFP::DeviceState device_state{NFP::DeviceState::Unavailable};
+
+    NFP::EncryptedNTAG215File encrypted_tag_data{};
+};
+
+} // namespace Service::NFC

+ 17 - 0
src/core/hle/service/nfc/nfc_result.h

@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::NFC {
+
+constexpr Result DeviceNotFound(ErrorModule::NFC, 64);
+constexpr Result InvalidArgument(ErrorModule::NFC, 65);
+constexpr Result WrongDeviceState(ErrorModule::NFC, 73);
+constexpr Result NfcDisabled(ErrorModule::NFC, 80);
+constexpr Result TagRemoved(ErrorModule::NFC, 97);
+constexpr Result CorruptedData(ErrorModule::NFC, 144);
+
+} // namespace Service::NFC

+ 365 - 0
src/core/hle/service/nfc/nfc_user.cpp

@@ -0,0 +1,365 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hid/hid_types.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/nfc/nfc_device.h"
+#include "core/hle/service/nfc/nfc_result.h"
+#include "core/hle/service/nfc/nfc_user.h"
+#include "core/hle/service/time/clock_types.h"
+
+namespace Service::NFC {
+
+IUser::IUser(Core::System& system_)
+    : ServiceFramework{system_, "NFC::IUser"}, service_context{system_, service_name} {
+    static const FunctionInfo functions[] = {
+        {0, &IUser::Initialize, "InitializeOld"},
+        {1, &IUser::Finalize, "FinalizeOld"},
+        {2, &IUser::GetState, "GetStateOld"},
+        {3, &IUser::IsNfcEnabled, "IsNfcEnabledOld"},
+        {400, &IUser::Initialize, "Initialize"},
+        {401, &IUser::Finalize, "Finalize"},
+        {402, &IUser::GetState, "GetState"},
+        {403, &IUser::IsNfcEnabled, "IsNfcEnabled"},
+        {404, &IUser::ListDevices, "ListDevices"},
+        {405, &IUser::GetDeviceState, "GetDeviceState"},
+        {406, &IUser::GetNpadId, "GetNpadId"},
+        {407, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
+        {408, &IUser::StartDetection, "StartDetection"},
+        {409, &IUser::StopDetection, "StopDetection"},
+        {410, &IUser::GetTagInfo, "GetTagInfo"},
+        {411, &IUser::AttachActivateEvent, "AttachActivateEvent"},
+        {412, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
+        {1000, nullptr, "ReadMifare"},
+        {1001, nullptr, "WriteMifare"},
+        {1300, &IUser::SendCommandByPassThrough, "SendCommandByPassThrough"},
+        {1301, nullptr, "KeepPassThroughSession"},
+        {1302, nullptr, "ReleasePassThroughSession"},
+    };
+    RegisterHandlers(functions);
+
+    availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent");
+
+    for (u32 device_index = 0; device_index < 10; device_index++) {
+        devices[device_index] =
+            std::make_shared<NfcDevice>(Core::HID::IndexToNpadIdType(device_index), system,
+                                        service_context, availability_change_event);
+    }
+}
+
+IUser ::~IUser() {
+    availability_change_event->Close();
+}
+
+void IUser::Initialize(Kernel::HLERequestContext& ctx) {
+    LOG_INFO(Service_NFC, "called");
+
+    state = State::Initialized;
+
+    for (auto& device : devices) {
+        device->Initialize();
+    }
+
+    IPC::ResponseBuilder rb{ctx, 2, 0};
+    rb.Push(ResultSuccess);
+}
+
+void IUser::Finalize(Kernel::HLERequestContext& ctx) {
+    LOG_INFO(Service_NFC, "called");
+
+    state = State::NonInitialized;
+
+    for (auto& device : devices) {
+        device->Finalize();
+    }
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void IUser::GetState(Kernel::HLERequestContext& ctx) {
+    LOG_DEBUG(Service_NFC, "called");
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    rb.PushEnum(state);
+}
+
+void IUser::IsNfcEnabled(Kernel::HLERequestContext& ctx) {
+    LOG_DEBUG(Service_NFC, "called");
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    rb.Push(state != State::NonInitialized);
+}
+
+void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
+    LOG_INFO(Service_NFC, "called");
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    if (!ctx.CanWriteBuffer()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(InvalidArgument);
+        return;
+    }
+
+    if (ctx.GetWriteBufferSize() == 0) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(InvalidArgument);
+        return;
+    }
+
+    std::vector<u64> nfp_devices;
+    const std::size_t max_allowed_devices = ctx.GetWriteBufferSize() / sizeof(u64);
+
+    for (auto& device : devices) {
+        if (nfp_devices.size() >= max_allowed_devices) {
+            continue;
+        }
+        if (device->GetCurrentState() != NFP::DeviceState::Unavailable) {
+            nfp_devices.push_back(device->GetHandle());
+        }
+    }
+
+    if (nfp_devices.empty()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    ctx.WriteBuffer(nfp_devices);
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    rb.Push(static_cast<s32>(nfp_devices.size()));
+}
+
+void IUser::GetDeviceState(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
+
+    auto device = GetNfcDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    rb.PushEnum(device.value()->GetCurrentState());
+}
+
+void IUser::GetNpadId(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfcDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    rb.PushEnum(device.value()->GetNpadId());
+}
+
+void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
+    LOG_INFO(Service_NFC, "called");
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    IPC::ResponseBuilder rb{ctx, 2, 1};
+    rb.Push(ResultSuccess);
+    rb.PushCopyObjects(availability_change_event->GetReadableEvent());
+}
+
+void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    const auto nfp_protocol{rp.Pop<s32>()};
+    LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfcDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    const auto result = device.value()->StartDetection(nfp_protocol);
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
+}
+
+void IUser::StopDetection(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfcDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    const auto result = device.value()->StopDetection();
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
+}
+
+void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfcDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    NFP::TagInfo tag_info{};
+    const auto result = device.value()->GetTagInfo(tag_info);
+    ctx.WriteBuffer(tag_info);
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
+}
+
+void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfcDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    IPC::ResponseBuilder rb{ctx, 2, 1};
+    rb.Push(ResultSuccess);
+    rb.PushCopyObjects(device.value()->GetActivateEvent());
+}
+
+void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfcDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    IPC::ResponseBuilder rb{ctx, 2, 1};
+    rb.Push(ResultSuccess);
+    rb.PushCopyObjects(device.value()->GetDeactivateEvent());
+}
+
+void IUser::SendCommandByPassThrough(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    const auto timeout{rp.PopRaw<Time::Clock::TimeSpanType>()};
+    const auto command_data{ctx.ReadBuffer()};
+
+    LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, timeout={}, data_size={}",
+             device_handle, timeout.ToSeconds(), command_data.size());
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfcDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    std::vector<u8> out_data(1);
+    // TODO: Request data from nfc device
+    ctx.WriteBuffer(out_data);
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    rb.Push(static_cast<u32>(out_data.size()));
+}
+
+std::optional<std::shared_ptr<NfcDevice>> IUser::GetNfcDevice(u64 handle) {
+    for (auto& device : devices) {
+        if (device->GetHandle() == handle) {
+            return device;
+        }
+    }
+    return std::nullopt;
+}
+
+} // namespace Service::NFC

+ 52 - 0
src/core/hle/service/nfc/nfc_user.h

@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <optional>
+
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/service.h"
+
+namespace Service::NFC {
+class NfcDevice;
+
+class IUser final : public ServiceFramework<IUser> {
+public:
+    explicit IUser(Core::System& system_);
+    ~IUser();
+
+private:
+    enum class State : u32 {
+        NonInitialized,
+        Initialized,
+    };
+
+    void Initialize(Kernel::HLERequestContext& ctx);
+    void Finalize(Kernel::HLERequestContext& ctx);
+    void GetState(Kernel::HLERequestContext& ctx);
+    void IsNfcEnabled(Kernel::HLERequestContext& ctx);
+    void ListDevices(Kernel::HLERequestContext& ctx);
+    void GetDeviceState(Kernel::HLERequestContext& ctx);
+    void GetNpadId(Kernel::HLERequestContext& ctx);
+    void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
+    void StartDetection(Kernel::HLERequestContext& ctx);
+    void StopDetection(Kernel::HLERequestContext& ctx);
+    void GetTagInfo(Kernel::HLERequestContext& ctx);
+    void AttachActivateEvent(Kernel::HLERequestContext& ctx);
+    void AttachDeactivateEvent(Kernel::HLERequestContext& ctx);
+    void SendCommandByPassThrough(Kernel::HLERequestContext& ctx);
+
+    std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle);
+
+    KernelHelpers::ServiceContext service_context;
+
+    std::array<std::shared_ptr<NfcDevice>, 10> devices{};
+
+    State state{State::NonInitialized};
+    Kernel::KEvent* availability_change_event;
+};
+
+} // namespace Service::NFC

+ 0 - 1
src/core/hle/service/nfp/amiibo_crypto.cpp

@@ -12,7 +12,6 @@
 #include "common/fs/fs.h"
 #include "common/fs/path_util.h"
 #include "common/logging/log.h"
-#include "core/hle/service/mii/mii_manager.h"
 #include "core/hle/service/nfp/amiibo_crypto.h"
 
 namespace Service::NFP::AmiiboCrypto {

+ 2 - 4
src/core/hle/service/nfp/nfp_device.cpp

@@ -2,10 +2,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 #include <array>
-#include <atomic>
 
-#include "common/fs/file.h"
-#include "common/fs/path_util.h"
 #include "common/input.h"
 #include "common/logging/log.h"
 #include "common/string_util.h"
@@ -19,7 +16,6 @@
 #include "core/hle/service/mii/mii_manager.h"
 #include "core/hle/service/mii/types.h"
 #include "core/hle/service/nfp/amiibo_crypto.h"
-#include "core/hle/service/nfp/nfp.h"
 #include "core/hle/service/nfp/nfp_device.h"
 #include "core/hle/service/nfp/nfp_result.h"
 #include "core/hle/service/nfp/nfp_user.h"
@@ -49,6 +45,8 @@ NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
 }
 
 NfpDevice::~NfpDevice() {
+    activate_event->Close();
+    deactivate_event->Close();
     if (!is_controller_set) {
         return;
     }

+ 1 - 2
src/core/hle/service/nfp/nfp_device.h

@@ -3,11 +3,10 @@
 
 #pragma once
 
-#include <array>
 #include <span>
 #include <vector>
 
-#include "common/common_funcs.h"
+#include "common/common_types.h"
 #include "core/hle/service/kernel_helpers.h"
 #include "core/hle/service/nfp/nfp_types.h"
 #include "core/hle/service/service.h"

+ 8 - 7
src/core/hle/service/nfp/nfp_user.cpp

@@ -1,9 +1,6 @@
 // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
-#include <array>
-#include <atomic>
-
 #include "common/logging/log.h"
 #include "core/core.h"
 #include "core/hid/hid_types.h"
@@ -55,8 +52,12 @@ IUser::IUser(Core::System& system_)
     }
 }
 
+IUser ::~IUser() {
+    availability_change_event->Close();
+}
+
 void IUser::Initialize(Kernel::HLERequestContext& ctx) {
-    LOG_INFO(Service_NFC, "called");
+    LOG_INFO(Service_NFP, "called");
 
     state = State::Initialized;
 
@@ -64,7 +65,7 @@ void IUser::Initialize(Kernel::HLERequestContext& ctx) {
         device->Initialize();
     }
 
-    IPC::ResponseBuilder rb{ctx, 2, 0};
+    IPC::ResponseBuilder rb{ctx, 2};
     rb.Push(ResultSuccess);
 }
 
@@ -551,9 +552,9 @@ void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
 }
 
 void IUser::GetState(Kernel::HLERequestContext& ctx) {
-    LOG_DEBUG(Service_NFC, "called");
+    LOG_DEBUG(Service_NFP, "called");
 
-    IPC::ResponseBuilder rb{ctx, 3, 0};
+    IPC::ResponseBuilder rb{ctx, 3};
     rb.Push(ResultSuccess);
     rb.PushEnum(state);
 }

+ 5 - 0
src/core/hle/service/nfp/nfp_user.h

@@ -3,6 +3,10 @@
 
 #pragma once
 
+#include <array>
+#include <memory>
+#include <optional>
+
 #include "core/hle/service/kernel_helpers.h"
 #include "core/hle/service/service.h"
 
@@ -12,6 +16,7 @@ class NfpDevice;
 class IUser final : public ServiceFramework<IUser> {
 public:
     explicit IUser(Core::System& system_);
+    ~IUser();
 
 private:
     enum class State : u32 {