Просмотр исходного кода

Added Amiibo support (#1390)

* Fixed conflict with nfp

* Few fixups for nfc

* Conflict 2

* Fixed AttachAvailabilityChangeEvent

* Conflict 3

* Fixed byte padding

* Refactored amiibo to not reside in "System"

* Removed remaining references of nfc from system

* used enum for Nfc GetStateOld

* Added missing newline

* Moved file operations to front end

* Conflict 4

* Amiibos now use structs and added mutexes

* Removed amiibo_path
David 7 лет назад
Родитель
Сommit
50e4e81fd3

+ 45 - 8
src/core/hle/service/nfc/nfc.cpp

@@ -10,12 +10,13 @@
 #include "core/hle/service/nfc/nfc.h"
 #include "core/hle/service/nfc/nfc.h"
 #include "core/hle/service/service.h"
 #include "core/hle/service/service.h"
 #include "core/hle/service/sm/sm.h"
 #include "core/hle/service/sm/sm.h"
+#include "core/settings.h"
 
 
 namespace Service::NFC {
 namespace Service::NFC {
 
 
 class IAm final : public ServiceFramework<IAm> {
 class IAm final : public ServiceFramework<IAm> {
 public:
 public:
-    explicit IAm() : ServiceFramework{"IAm"} {
+    explicit IAm() : ServiceFramework{"NFC::IAm"} {
         // clang-format off
         // clang-format off
         static const FunctionInfo functions[] = {
         static const FunctionInfo functions[] = {
             {0, nullptr, "Initialize"},
             {0, nullptr, "Initialize"},
@@ -52,7 +53,7 @@ private:
 
 
 class MFIUser final : public ServiceFramework<MFIUser> {
 class MFIUser final : public ServiceFramework<MFIUser> {
 public:
 public:
-    explicit MFIUser() : ServiceFramework{"IUser"} {
+    explicit MFIUser() : ServiceFramework{"NFC::MFIUser"} {
         // clang-format off
         // clang-format off
         static const FunctionInfo functions[] = {
         static const FunctionInfo functions[] = {
             {0, nullptr, "Initialize"},
             {0, nullptr, "Initialize"},
@@ -100,13 +101,13 @@ private:
 
 
 class IUser final : public ServiceFramework<IUser> {
 class IUser final : public ServiceFramework<IUser> {
 public:
 public:
-    explicit IUser() : ServiceFramework{"IUser"} {
+    explicit IUser() : ServiceFramework{"NFC::IUser"} {
         // clang-format off
         // clang-format off
         static const FunctionInfo functions[] = {
         static const FunctionInfo functions[] = {
-            {0, nullptr, "Initialize"},
-            {1, nullptr, "Finalize"},
-            {2, nullptr, "GetState"},
-            {3, nullptr, "IsNfcEnabled"},
+            {0, &IUser::InitializeOld, "InitializeOld"},
+            {1, &IUser::FinalizeOld, "FinalizeOld"},
+            {2, &IUser::GetStateOld, "GetStateOld"},
+            {3, &IUser::IsNfcEnabledOld, "IsNfcEnabledOld"},
             {400, nullptr, "Initialize"},
             {400, nullptr, "Initialize"},
             {401, nullptr, "Finalize"},
             {401, nullptr, "Finalize"},
             {402, nullptr, "GetState"},
             {402, nullptr, "GetState"},
@@ -130,11 +131,47 @@ public:
 
 
         RegisterHandlers(functions);
         RegisterHandlers(functions);
     }
     }
+
+private:
+    enum class NfcStates : u32 {
+        Finalized = 6,
+    };
+
+    void InitializeOld(Kernel::HLERequestContext& ctx) {
+        IPC::ResponseBuilder rb{ctx, 2, 0};
+        rb.Push(RESULT_SUCCESS);
+
+        // We don't deal with hardware initialization so we can just stub this.
+        LOG_DEBUG(Service_NFC, "called");
+    }
+
+    void IsNfcEnabledOld(Kernel::HLERequestContext& ctx) {
+        IPC::ResponseBuilder rb{ctx, 3};
+        rb.Push(RESULT_SUCCESS);
+        rb.PushRaw<u8>(Settings::values.enable_nfc);
+
+        LOG_DEBUG(Service_NFC, "IsNfcEnabledOld");
+    }
+
+    void GetStateOld(Kernel::HLERequestContext& ctx) {
+        LOG_WARNING(Service_NFC, "(STUBBED) called");
+
+        IPC::ResponseBuilder rb{ctx, 3};
+        rb.Push(RESULT_SUCCESS);
+        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(RESULT_SUCCESS);
+    }
 };
 };
 
 
 class NFC_U final : public ServiceFramework<NFC_U> {
 class NFC_U final : public ServiceFramework<NFC_U> {
 public:
 public:
-    explicit NFC_U() : ServiceFramework{"nfc:u"} {
+    explicit NFC_U() : ServiceFramework{"nfc:user"} {
         // clang-format off
         // clang-format off
         static const FunctionInfo functions[] = {
         static const FunctionInfo functions[] = {
             {0, &NFC_U::CreateUserInterface, "CreateUserInterface"},
             {0, &NFC_U::CreateUserInterface, "CreateUserInterface"},

+ 226 - 42
src/core/hle/service/nfp/nfp.cpp

@@ -2,56 +2,67 @@
 // Licensed under GPLv2 or any later version
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 // Refer to the license.txt file included.
 
 
+#include <atomic>
+
 #include "common/logging/log.h"
 #include "common/logging/log.h"
 #include "core/core.h"
 #include "core/core.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/kernel/event.h"
 #include "core/hle/kernel/event.h"
+#include "core/hle/lock.h"
 #include "core/hle/service/hid/hid.h"
 #include "core/hle/service/hid/hid.h"
 #include "core/hle/service/nfp/nfp.h"
 #include "core/hle/service/nfp/nfp.h"
 #include "core/hle/service/nfp/nfp_user.h"
 #include "core/hle/service/nfp/nfp_user.h"
 
 
 namespace Service::NFP {
 namespace Service::NFP {
 
 
+namespace ErrCodes {
+constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP,
+                                    -1); // TODO(ogniK): Find the actual error code
+}
+
 Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
 Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
-    : ServiceFramework(name), module(std::move(module)) {}
+    : ServiceFramework(name), module(std::move(module)) {
+    auto& kernel = Core::System::GetInstance().Kernel();
+    nfc_tag_load =
+        Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IUser:NFCTagDetected");
+}
 
 
 Module::Interface::~Interface() = default;
 Module::Interface::~Interface() = default;
 
 
 class IUser final : public ServiceFramework<IUser> {
 class IUser final : public ServiceFramework<IUser> {
 public:
 public:
-    IUser() : ServiceFramework("IUser") {
+    IUser(Module::Interface& nfp_interface)
+        : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface) {
         static const FunctionInfo functions[] = {
         static const FunctionInfo functions[] = {
             {0, &IUser::Initialize, "Initialize"},
             {0, &IUser::Initialize, "Initialize"},
-            {1, nullptr, "Finalize"},
+            {1, &IUser::Finalize, "Finalize"},
             {2, &IUser::ListDevices, "ListDevices"},
             {2, &IUser::ListDevices, "ListDevices"},
-            {3, nullptr, "StartDetection"},
-            {4, nullptr, "StopDetection"},
-            {5, nullptr, "Mount"},
-            {6, nullptr, "Unmount"},
-            {7, nullptr, "OpenApplicationArea"},
-            {8, nullptr, "GetApplicationArea"},
+            {3, &IUser::StartDetection, "StartDetection"},
+            {4, &IUser::StopDetection, "StopDetection"},
+            {5, &IUser::Mount, "Mount"},
+            {6, &IUser::Unmount, "Unmount"},
+            {7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
+            {8, &IUser::GetApplicationArea, "GetApplicationArea"},
             {9, nullptr, "SetApplicationArea"},
             {9, nullptr, "SetApplicationArea"},
             {10, nullptr, "Flush"},
             {10, nullptr, "Flush"},
             {11, nullptr, "Restore"},
             {11, nullptr, "Restore"},
             {12, nullptr, "CreateApplicationArea"},
             {12, nullptr, "CreateApplicationArea"},
-            {13, nullptr, "GetTagInfo"},
-            {14, nullptr, "GetRegisterInfo"},
-            {15, nullptr, "GetCommonInfo"},
-            {16, nullptr, "GetModelInfo"},
+            {13, &IUser::GetTagInfo, "GetTagInfo"},
+            {14, &IUser::GetRegisterInfo, "GetRegisterInfo"},
+            {15, &IUser::GetCommonInfo, "GetCommonInfo"},
+            {16, &IUser::GetModelInfo, "GetModelInfo"},
             {17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
             {17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
             {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
             {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
             {19, &IUser::GetState, "GetState"},
             {19, &IUser::GetState, "GetState"},
             {20, &IUser::GetDeviceState, "GetDeviceState"},
             {20, &IUser::GetDeviceState, "GetDeviceState"},
             {21, &IUser::GetNpadId, "GetNpadId"},
             {21, &IUser::GetNpadId, "GetNpadId"},
-            {22, nullptr, "GetApplicationArea2"},
+            {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
             {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
             {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
             {24, nullptr, "RecreateApplicationArea"},
             {24, nullptr, "RecreateApplicationArea"},
         };
         };
         RegisterHandlers(functions);
         RegisterHandlers(functions);
 
 
         auto& kernel = Core::System::GetInstance().Kernel();
         auto& kernel = Core::System::GetInstance().Kernel();
-        activate_event =
-            Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IUser:ActivateEvent");
         deactivate_event =
         deactivate_event =
             Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IUser:DeactivateEvent");
             Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IUser:DeactivateEvent");
         availability_change_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot,
         availability_change_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot,
@@ -59,6 +70,17 @@ public:
     }
     }
 
 
 private:
 private:
+    struct TagInfo {
+        std::array<u8, 10> uuid;
+        u8 uuid_length; // TODO(ogniK): Figure out if this is actual the uuid length or does it
+                        // mean something else
+        INSERT_PADDING_BYTES(0x15);
+        u32_le protocol;
+        u32_le tag_type;
+        INSERT_PADDING_BYTES(0x2c);
+    };
+    static_assert(sizeof(TagInfo) == 0x54, "TagInfo is an invalid size");
+
     enum class State : u32 {
     enum class State : u32 {
         NonInitialized = 0,
         NonInitialized = 0,
         Initialized = 1,
         Initialized = 1,
@@ -66,15 +88,40 @@ private:
 
 
     enum class DeviceState : u32 {
     enum class DeviceState : u32 {
         Initialized = 0,
         Initialized = 0,
+        SearchingForTag = 1,
+        TagFound = 2,
+        TagRemoved = 3,
+        TagNearby = 4,
+        Unknown5 = 5,
+        Finalized = 6
     };
     };
 
 
+    struct CommonInfo {
+        u16_be last_write_year;
+        u8 last_write_month;
+        u8 last_write_day;
+        u16_be write_counter;
+        u16_be version;
+        u32_be application_area_size;
+        INSERT_PADDING_BYTES(0x34);
+    };
+    static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
+
     void Initialize(Kernel::HLERequestContext& ctx) {
     void Initialize(Kernel::HLERequestContext& ctx) {
-        LOG_WARNING(Service_NFP, "(STUBBED) called");
+        IPC::ResponseBuilder rb{ctx, 2, 0};
+        rb.Push(RESULT_SUCCESS);
 
 
         state = State::Initialized;
         state = State::Initialized;
 
 
-        IPC::ResponseBuilder rb{ctx, 2};
+        LOG_DEBUG(Service_NFC, "called");
+    }
+
+    void GetState(Kernel::HLERequestContext& ctx) {
+        IPC::ResponseBuilder rb{ctx, 3, 0};
         rb.Push(RESULT_SUCCESS);
         rb.Push(RESULT_SUCCESS);
+        rb.PushRaw<u32>(static_cast<u32>(state));
+
+        LOG_DEBUG(Service_NFC, "called");
     }
     }
 
 
     void ListDevices(Kernel::HLERequestContext& ctx) {
     void ListDevices(Kernel::HLERequestContext& ctx) {
@@ -83,80 +130,217 @@ private:
 
 
         ctx.WriteBuffer(&device_handle, sizeof(device_handle));
         ctx.WriteBuffer(&device_handle, sizeof(device_handle));
 
 
-        LOG_WARNING(Service_NFP, "(STUBBED) called, array_size={}", array_size);
+        LOG_DEBUG(Service_NFP, "called, array_size={}", array_size);
 
 
         IPC::ResponseBuilder rb{ctx, 3};
         IPC::ResponseBuilder rb{ctx, 3};
         rb.Push(RESULT_SUCCESS);
         rb.Push(RESULT_SUCCESS);
-        rb.Push<u32>(0);
+        rb.Push<u32>(1);
     }
     }
 
 
-    void AttachActivateEvent(Kernel::HLERequestContext& ctx) {
+    void GetNpadId(Kernel::HLERequestContext& ctx) {
         IPC::RequestParser rp{ctx};
         IPC::RequestParser rp{ctx};
         const u64 dev_handle = rp.Pop<u64>();
         const u64 dev_handle = rp.Pop<u64>();
-        LOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle);
+        LOG_DEBUG(Service_NFP, "called, dev_handle=0x{:X}", dev_handle);
+        IPC::ResponseBuilder rb{ctx, 3};
+        rb.Push(RESULT_SUCCESS);
+        rb.Push<u32>(npad_id);
+    }
 
 
+    void AttachActivateEvent(Kernel::HLERequestContext& ctx) {
+        IPC::RequestParser rp{ctx};
+        const u64 dev_handle = rp.Pop<u64>();
+        LOG_DEBUG(Service_NFP, "called, dev_handle=0x{:X}", dev_handle);
         IPC::ResponseBuilder rb{ctx, 2, 1};
         IPC::ResponseBuilder rb{ctx, 2, 1};
         rb.Push(RESULT_SUCCESS);
         rb.Push(RESULT_SUCCESS);
-        rb.PushCopyObjects(activate_event);
+        rb.PushCopyObjects(nfp_interface.GetNFCEvent());
+        has_attached_handle = true;
     }
     }
 
 
     void AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
     void AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
         IPC::RequestParser rp{ctx};
         IPC::RequestParser rp{ctx};
         const u64 dev_handle = rp.Pop<u64>();
         const u64 dev_handle = rp.Pop<u64>();
-        LOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle);
+        LOG_DEBUG(Service_NFP, "called, dev_handle=0x{:X}", dev_handle);
 
 
         IPC::ResponseBuilder rb{ctx, 2, 1};
         IPC::ResponseBuilder rb{ctx, 2, 1};
         rb.Push(RESULT_SUCCESS);
         rb.Push(RESULT_SUCCESS);
         rb.PushCopyObjects(deactivate_event);
         rb.PushCopyObjects(deactivate_event);
     }
     }
 
 
-    void GetState(Kernel::HLERequestContext& ctx) {
-        LOG_WARNING(Service_NFP, "(STUBBED) called");
-        IPC::ResponseBuilder rb{ctx, 3};
+    void StopDetection(Kernel::HLERequestContext& ctx) {
+        LOG_DEBUG(Service_NFP, "called");
+        switch (device_state) {
+        case DeviceState::TagFound:
+        case DeviceState::TagNearby:
+            deactivate_event->Signal();
+            device_state = DeviceState::Initialized;
+            break;
+        case DeviceState::SearchingForTag:
+        case DeviceState::TagRemoved:
+            device_state = DeviceState::Initialized;
+            break;
+        }
+        IPC::ResponseBuilder rb{ctx, 2};
         rb.Push(RESULT_SUCCESS);
         rb.Push(RESULT_SUCCESS);
-        rb.Push<u32>(static_cast<u32>(state));
     }
     }
 
 
     void GetDeviceState(Kernel::HLERequestContext& ctx) {
     void GetDeviceState(Kernel::HLERequestContext& ctx) {
-        LOG_WARNING(Service_NFP, "(STUBBED) called");
+        LOG_DEBUG(Service_NFP, "called");
+        auto nfc_event = nfp_interface.GetNFCEvent();
+        if (!nfc_event->ShouldWait(Kernel::GetCurrentThread()) && !has_attached_handle) {
+            device_state = DeviceState::TagFound;
+            nfc_event->Clear();
+        }
+
         IPC::ResponseBuilder rb{ctx, 3};
         IPC::ResponseBuilder rb{ctx, 3};
         rb.Push(RESULT_SUCCESS);
         rb.Push(RESULT_SUCCESS);
         rb.Push<u32>(static_cast<u32>(device_state));
         rb.Push<u32>(static_cast<u32>(device_state));
     }
     }
 
 
-    void GetNpadId(Kernel::HLERequestContext& ctx) {
-        IPC::RequestParser rp{ctx};
-        const u64 dev_handle = rp.Pop<u64>();
-        LOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle);
-        IPC::ResponseBuilder rb{ctx, 3};
+    void StartDetection(Kernel::HLERequestContext& ctx) {
+        LOG_DEBUG(Service_NFP, "called");
+
+        if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
+            device_state = DeviceState::SearchingForTag;
+        }
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(RESULT_SUCCESS);
+    }
+
+    void GetTagInfo(Kernel::HLERequestContext& ctx) {
+        LOG_DEBUG(Service_NFP, "called");
+
+        IPC::ResponseBuilder rb{ctx, 2};
+        auto amiibo = nfp_interface.GetAmiiboBuffer();
+        TagInfo tag_info{};
+        std::memcpy(tag_info.uuid.data(), amiibo.uuid.data(), sizeof(tag_info.uuid.size()));
+        tag_info.uuid_length = static_cast<u8>(tag_info.uuid.size());
+
+        tag_info.protocol = 1; // TODO(ogniK): Figure out actual values
+        tag_info.tag_type = 2;
+        ctx.WriteBuffer(&tag_info, sizeof(TagInfo));
+        rb.Push(RESULT_SUCCESS);
+    }
+
+    void Mount(Kernel::HLERequestContext& ctx) {
+        LOG_DEBUG(Service_NFP, "called");
+
+        device_state = DeviceState::TagNearby;
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(RESULT_SUCCESS);
+    }
+
+    void GetModelInfo(Kernel::HLERequestContext& ctx) {
+        LOG_DEBUG(Service_NFP, "called");
+
+        IPC::ResponseBuilder rb{ctx, 2};
+        auto amiibo = nfp_interface.GetAmiiboBuffer();
+        ctx.WriteBuffer(&amiibo.model_info, sizeof(amiibo.model_info));
+        rb.Push(RESULT_SUCCESS);
+    }
+
+    void Unmount(Kernel::HLERequestContext& ctx) {
+        LOG_DEBUG(Service_NFP, "called");
+
+        device_state = DeviceState::TagFound;
+
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(RESULT_SUCCESS);
+    }
+
+    void Finalize(Kernel::HLERequestContext& ctx) {
+        LOG_DEBUG(Service_NFP, "called");
+
+        device_state = DeviceState::Finalized;
+
+        IPC::ResponseBuilder rb{ctx, 2};
         rb.Push(RESULT_SUCCESS);
         rb.Push(RESULT_SUCCESS);
-        rb.Push<u32>(npad_id);
     }
     }
 
 
     void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
     void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
-        IPC::RequestParser rp{ctx};
-        const u64 dev_handle = rp.Pop<u64>();
-        LOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle);
+        LOG_WARNING(Service_NFP, "(STUBBED) called");
 
 
         IPC::ResponseBuilder rb{ctx, 2, 1};
         IPC::ResponseBuilder rb{ctx, 2, 1};
         rb.Push(RESULT_SUCCESS);
         rb.Push(RESULT_SUCCESS);
         rb.PushCopyObjects(availability_change_event);
         rb.PushCopyObjects(availability_change_event);
     }
     }
 
 
-    const u64 device_handle{0xDEAD};
-    const u32 npad_id{0}; // This is the first player controller id
+    void GetRegisterInfo(Kernel::HLERequestContext& ctx) {
+        LOG_WARNING(Service_NFP, "(STUBBED) called");
+
+        // TODO(ogniK): Pull Mii and owner data from amiibo
+
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(RESULT_SUCCESS);
+    }
+
+    void GetCommonInfo(Kernel::HLERequestContext& ctx) {
+        LOG_WARNING(Service_NFP, "(STUBBED) called");
+
+        // TODO(ogniK): Pull common information from amiibo
+
+        CommonInfo common_info{};
+        common_info.application_area_size = 0;
+        ctx.WriteBuffer(&common_info, sizeof(CommonInfo));
+
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(RESULT_SUCCESS);
+    }
+
+    void OpenApplicationArea(Kernel::HLERequestContext& ctx) {
+        LOG_DEBUG(Service_NFP, "called");
+        // We don't need to worry about this since we can just open the file
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(RESULT_SUCCESS);
+    }
+
+    void GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
+        LOG_WARNING(Service_NFP, "(STUBBED) called");
+        // We don't need to worry about this since we can just open the file
+        IPC::ResponseBuilder rb{ctx, 3};
+        rb.Push(RESULT_SUCCESS);
+        rb.PushRaw<u32>(0); // This is from the GetCommonInfo stub
+    }
+
+    void GetApplicationArea(Kernel::HLERequestContext& ctx) {
+        LOG_WARNING(Service_NFP, "(STUBBED) called");
+
+        // TODO(ogniK): Pull application area from amiibo
+
+        IPC::ResponseBuilder rb{ctx, 3};
+        rb.Push(RESULT_SUCCESS);
+        rb.PushRaw<u32>(0); // This is from the GetCommonInfo stub
+    }
+
+    bool has_attached_handle{};
+    const u64 device_handle{Common::MakeMagic('Y', 'U', 'Z', 'U')};
+    const u32 npad_id{0}; // Player 1 controller
     State state{State::NonInitialized};
     State state{State::NonInitialized};
     DeviceState device_state{DeviceState::Initialized};
     DeviceState device_state{DeviceState::Initialized};
-    Kernel::SharedPtr<Kernel::Event> activate_event;
     Kernel::SharedPtr<Kernel::Event> deactivate_event;
     Kernel::SharedPtr<Kernel::Event> deactivate_event;
     Kernel::SharedPtr<Kernel::Event> availability_change_event;
     Kernel::SharedPtr<Kernel::Event> availability_change_event;
+    const Module::Interface& nfp_interface;
 };
 };
 
 
 void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
 void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
     LOG_DEBUG(Service_NFP, "called");
     LOG_DEBUG(Service_NFP, "called");
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
     rb.Push(RESULT_SUCCESS);
     rb.Push(RESULT_SUCCESS);
-    rb.PushIpcInterface<IUser>();
+    rb.PushIpcInterface<IUser>(*this);
+}
+
+void Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
+    std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+    if (buffer.size() < sizeof(AmiiboFile)) {
+        return; // Failed to load file
+    }
+    std::memcpy(&amiibo, buffer.data(), sizeof(amiibo));
+    nfc_tag_load->Signal();
+}
+const Kernel::SharedPtr<Kernel::Event>& Module::Interface::GetNFCEvent() const {
+    return nfc_tag_load;
+}
+const Module::Interface::AmiiboFile& Module::Interface::GetAmiiboBuffer() const {
+    return amiibo;
 }
 }
 
 
 void InstallInterfaces(SM::ServiceManager& service_manager) {
 void InstallInterfaces(SM::ServiceManager& service_manager) {

+ 23 - 0
src/core/hle/service/nfp/nfp.h

@@ -4,6 +4,9 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <array>
+#include <vector>
+#include "core/hle/kernel/event.h"
 #include "core/hle/service/service.h"
 #include "core/hle/service/service.h"
 
 
 namespace Service::NFP {
 namespace Service::NFP {
@@ -15,7 +18,27 @@ public:
         explicit Interface(std::shared_ptr<Module> module, const char* name);
         explicit Interface(std::shared_ptr<Module> module, const char* name);
         ~Interface() override;
         ~Interface() override;
 
 
+        struct ModelInfo {
+            std::array<u8, 0x8> amiibo_identification_block;
+            INSERT_PADDING_BYTES(0x38);
+        };
+        static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
+
+        struct AmiiboFile {
+            std::array<u8, 10> uuid;
+            INSERT_PADDING_BYTES(0x4a);
+            ModelInfo model_info;
+        };
+        static_assert(sizeof(AmiiboFile) == 0x94, "AmiiboFile is an invalid size");
+
         void CreateUserInterface(Kernel::HLERequestContext& ctx);
         void CreateUserInterface(Kernel::HLERequestContext& ctx);
+        void LoadAmiibo(const std::vector<u8>& buffer);
+        const Kernel::SharedPtr<Kernel::Event>& GetNFCEvent() const;
+        const AmiiboFile& GetAmiiboBuffer() const;
+
+    private:
+        Kernel::SharedPtr<Kernel::Event> nfc_tag_load{};
+        AmiiboFile amiibo{};
 
 
     protected:
     protected:
         std::shared_ptr<Module> module;
         std::shared_ptr<Module> module;

+ 1 - 0
src/core/settings.h

@@ -113,6 +113,7 @@ static const std::array<const char*, NumAnalogs> mapping = {{
 struct Values {
 struct Values {
     // System
     // System
     bool use_docked_mode;
     bool use_docked_mode;
+    bool enable_nfc;
     std::string username;
     std::string username;
     int language_index;
     int language_index;
 
 

+ 2 - 0
src/yuzu/configuration/config.cpp

@@ -122,6 +122,7 @@ void Config::ReadValues() {
 
 
     qt_config->beginGroup("System");
     qt_config->beginGroup("System");
     Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool();
     Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool();
+    Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool();
     Settings::values.username = qt_config->value("username", "yuzu").toString().toStdString();
     Settings::values.username = qt_config->value("username", "yuzu").toString().toStdString();
     Settings::values.language_index = qt_config->value("language_index", 1).toInt();
     Settings::values.language_index = qt_config->value("language_index", 1).toInt();
     qt_config->endGroup();
     qt_config->endGroup();
@@ -258,6 +259,7 @@ void Config::SaveValues() {
 
 
     qt_config->beginGroup("System");
     qt_config->beginGroup("System");
     qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode);
     qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode);
+    qt_config->setValue("enable_nfc", Settings::values.enable_nfc);
     qt_config->setValue("username", QString::fromStdString(Settings::values.username));
     qt_config->setValue("username", QString::fromStdString(Settings::values.username));
     qt_config->setValue("language_index", Settings::values.language_index);
     qt_config->setValue("language_index", Settings::values.language_index);
     qt_config->endGroup();
     qt_config->endGroup();

+ 2 - 0
src/yuzu/configuration/configure_general.cpp

@@ -31,6 +31,7 @@ void ConfigureGeneral::setConfiguration() {
     ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
     ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
     ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
     ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
     ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);
     ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);
+    ui->enable_nfc->setChecked(Settings::values.enable_nfc);
 }
 }
 
 
 void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {
 void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {
@@ -45,4 +46,5 @@ void ConfigureGeneral::applyConfiguration() {
 
 
     Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked();
     Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked();
     Settings::values.use_docked_mode = ui->use_docked_mode->isChecked();
     Settings::values.use_docked_mode = ui->use_docked_mode->isChecked();
+    Settings::values.enable_nfc = ui->enable_nfc->isChecked();
 }
 }

+ 18 - 11
src/yuzu/configuration/configure_general.ui

@@ -68,19 +68,26 @@
        <property name="title">
        <property name="title">
         <string>Emulation</string>
         <string>Emulation</string>
        </property>
        </property>
-        <layout class="QHBoxLayout" name="EmulationHorizontalLayout">
+       <layout class="QHBoxLayout" name="EmulationHorizontalLayout">
+        <item>
+         <layout class="QVBoxLayout" name="EmulationVerticalLayout">
+          <item>
+           <widget class="QCheckBox" name="use_docked_mode">
+            <property name="text">
+             <string>Enable docked mode</string>
+            </property>
+           </widget>
+          </item>
           <item>
           <item>
-            <layout class="QVBoxLayout" name="EmulationVerticalLayout">
-              <item>
-                <widget class="QCheckBox" name="use_docked_mode">
-                  <property name="text">
-                    <string>Enable docked mode</string>
-                  </property>
-                </widget>
-              </item>
-            </layout>
+           <widget class="QCheckBox" name="enable_nfc">
+            <property name="text">
+             <string>Enable NFC</string>
+            </property>
+           </widget>
           </item>
           </item>
-        </layout>
+         </layout>
+        </item>
+       </layout>
       </widget>
       </widget>
      </item>
      </item>
      <item>
      <item>

+ 43 - 12
src/yuzu/main.cpp

@@ -60,6 +60,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
 #include "core/hle/kernel/process.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/hle/service/filesystem/fsp_ldr.h"
 #include "core/hle/service/filesystem/fsp_ldr.h"
+#include "core/hle/service/nfp/nfp.h"
+#include "core/hle/service/sm/sm.h"
 #include "core/loader/loader.h"
 #include "core/loader/loader.h"
 #include "core/perf_stats.h"
 #include "core/perf_stats.h"
 #include "core/settings.h"
 #include "core/settings.h"
@@ -424,6 +426,7 @@ void GMainWindow::ConnectMenuEvents() {
     connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this,
     connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this,
             [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::SDMC); });
             [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::SDMC); });
     connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
     connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
+    connect(ui.action_Load_Amiibo, &QAction::triggered, this, &GMainWindow::OnLoadAmiibo);
 
 
     // Emulation
     // Emulation
     connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame);
     connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame);
@@ -692,6 +695,7 @@ void GMainWindow::ShutdownGame() {
     ui.action_Stop->setEnabled(false);
     ui.action_Stop->setEnabled(false);
     ui.action_Restart->setEnabled(false);
     ui.action_Restart->setEnabled(false);
     ui.action_Report_Compatibility->setEnabled(false);
     ui.action_Report_Compatibility->setEnabled(false);
+    ui.action_Load_Amiibo->setEnabled(false);
     render_window->hide();
     render_window->hide();
     game_list->show();
     game_list->show();
     game_list->setFilterFocus();
     game_list->setFilterFocus();
@@ -1191,6 +1195,7 @@ void GMainWindow::OnStartGame() {
     ui.action_Report_Compatibility->setEnabled(true);
     ui.action_Report_Compatibility->setEnabled(true);
 
 
     discord_rpc->Update();
     discord_rpc->Update();
+    ui.action_Load_Amiibo->setEnabled(true);
 }
 }
 
 
 void GMainWindow::OnPauseGame() {
 void GMainWindow::OnPauseGame() {
@@ -1295,6 +1300,27 @@ void GMainWindow::OnConfigure() {
     }
     }
 }
 }
 
 
+void GMainWindow::OnLoadAmiibo() {
+    const QString extensions{"*.bin"};
+    const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
+    const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), "", file_filter);
+    if (!filename.isEmpty()) {
+        Core::System& system{Core::System::GetInstance()};
+        Service::SM::ServiceManager& sm = system.ServiceManager();
+        auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user");
+        if (nfc != nullptr) {
+            auto nfc_file = FileUtil::IOFile(filename.toStdString(), "rb");
+            if (!nfc_file.IsOpen()) {
+                return;
+            }
+            std::vector<u8> amiibo_buffer(nfc_file.GetSize());
+            nfc_file.ReadBytes(amiibo_buffer.data(), amiibo_buffer.size());
+            nfc_file.Close();
+            nfc->LoadAmiibo(amiibo_buffer);
+        }
+    }
+}
+
 void GMainWindow::OnAbout() {
 void GMainWindow::OnAbout() {
     AboutDialog aboutDialog(this);
     AboutDialog aboutDialog(this);
     aboutDialog.exec();
     aboutDialog.exec();
@@ -1335,15 +1361,17 @@ void GMainWindow::UpdateStatusBar() {
 void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) {
 void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) {
     QMessageBox::StandardButton answer;
     QMessageBox::StandardButton answer;
     QString status_message;
     QString status_message;
-    const QString common_message = tr(
-        "The game you are trying to load requires additional files from your Switch to be dumped "
-        "before playing.<br/><br/>For more information on dumping these files, please see the "
-        "following wiki page: <a "
-        "href='https://yuzu-emu.org/wiki/"
-        "dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System "
-        "Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit "
-        "back to the game list? Continuing emulation may result in crashes, corrupted save "
-        "data, or other bugs.");
+    const QString common_message =
+        tr("The game you are trying to load requires additional files from your Switch to be "
+           "dumped "
+           "before playing.<br/><br/>For more information on dumping these files, please see the "
+           "following wiki page: <a "
+           "href='https://yuzu-emu.org/wiki/"
+           "dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System "
+           "Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to "
+           "quit "
+           "back to the game list? Continuing emulation may result in crashes, corrupted save "
+           "data, or other bugs.");
     switch (result) {
     switch (result) {
     case Core::System::ResultStatus::ErrorSystemFiles: {
     case Core::System::ResultStatus::ErrorSystemFiles: {
         QString message = "yuzu was unable to locate a Switch system archive";
         QString message = "yuzu was unable to locate a Switch system archive";
@@ -1374,9 +1402,12 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
             this, tr("Fatal Error"),
             this, tr("Fatal Error"),
             tr("yuzu has encountered a fatal error, please see the log for more details. "
             tr("yuzu has encountered a fatal error, please see the log for more details. "
                "For more information on accessing the log, please see the following page: "
                "For more information on accessing the log, please see the following page: "
-               "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to "
-               "Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? "
-               "Continuing emulation may result in crashes, corrupted save data, or other bugs."),
+               "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How "
+               "to "
+               "Upload the Log File</a>.<br/><br/>Would you like to quit back to the game "
+               "list? "
+               "Continuing emulation may result in crashes, corrupted save data, or other "
+               "bugs."),
             QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
             QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
         status_message = "Fatal Error encountered";
         status_message = "Fatal Error encountered";
         break;
         break;

+ 1 - 0
src/yuzu/main.h

@@ -166,6 +166,7 @@ private slots:
     void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target);
     void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target);
     void OnMenuRecentFile();
     void OnMenuRecentFile();
     void OnConfigure();
     void OnConfigure();
+    void OnLoadAmiibo();
     void OnAbout();
     void OnAbout();
     void OnToggleFilterBar();
     void OnToggleFilterBar();
     void OnDisplayTitleBars(bool);
     void OnDisplayTitleBars(bool);

+ 20 - 7
src/yuzu/main.ui

@@ -57,8 +57,8 @@
       <string>Recent Files</string>
       <string>Recent Files</string>
      </property>
      </property>
     </widget>
     </widget>
-     <addaction name="action_Install_File_NAND" />
-     <addaction name="separator"/>
+    <addaction name="action_Install_File_NAND"/>
+    <addaction name="separator"/>
     <addaction name="action_Load_File"/>
     <addaction name="action_Load_File"/>
     <addaction name="action_Load_Folder"/>
     <addaction name="action_Load_Folder"/>
     <addaction name="separator"/>
     <addaction name="separator"/>
@@ -68,6 +68,8 @@
     <addaction name="action_Select_NAND_Directory"/>
     <addaction name="action_Select_NAND_Directory"/>
     <addaction name="action_Select_SDMC_Directory"/>
     <addaction name="action_Select_SDMC_Directory"/>
     <addaction name="separator"/>
     <addaction name="separator"/>
+    <addaction name="action_Load_Amiibo"/>
+    <addaction name="separator"/>
     <addaction name="action_Exit"/>
     <addaction name="action_Exit"/>
    </widget>
    </widget>
    <widget class="QMenu" name="menu_Emulation">
    <widget class="QMenu" name="menu_Emulation">
@@ -117,11 +119,14 @@
    <addaction name="menu_Tools" />
    <addaction name="menu_Tools" />
    <addaction name="menu_Help"/>
    <addaction name="menu_Help"/>
   </widget>
   </widget>
-   <action name="action_Install_File_NAND">
-     <property name="text">
-       <string>Install File to NAND...</string>
-     </property>
-   </action>
+  <action name="action_Install_File_NAND">
+   <property name="enabled">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>Install File to NAND...</string>
+   </property>
+  </action>
   <action name="action_Load_File">
   <action name="action_Load_File">
    <property name="text">
    <property name="text">
     <string>Load File...</string>
     <string>Load File...</string>
@@ -253,6 +258,14 @@
        <string>Restart</string>
        <string>Restart</string>
      </property>
      </property>
    </action>
    </action>
+  <action name="action_Load_Amiibo">
+    <property name="enabled">
+      <bool>false</bool>
+    </property>
+    <property name="text">
+      <string>Load Amiibo...</string>
+    </property>
+  </action>
    <action name="action_Report_Compatibility">
    <action name="action_Report_Compatibility">
      <property name="enabled">
      <property name="enabled">
        <bool>false</bool>
        <bool>false</bool>

+ 1 - 0
src/yuzu_cmd/config.cpp

@@ -125,6 +125,7 @@ void Config::ReadValues() {
 
 
     // System
     // System
     Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
     Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
+    Settings::values.enable_nfc = sdl2_config->GetBoolean("System", "enable_nfc", true);
     Settings::values.username = sdl2_config->Get("System", "username", "yuzu");
     Settings::values.username = sdl2_config->Get("System", "username", "yuzu");
     if (Settings::values.username.empty()) {
     if (Settings::values.username.empty()) {
         Settings::values.username = "yuzu";
         Settings::values.username = "yuzu";

+ 4 - 0
src/yuzu_cmd/default_ini.h

@@ -174,6 +174,10 @@ use_virtual_sd =
 # 1: Yes, 0 (default): No
 # 1: Yes, 0 (default): No
 use_docked_mode =
 use_docked_mode =
 
 
+# Allow the use of NFC in games
+# 1 (default): Yes, 0 : No
+enable_nfc =
+
 # Sets the account username, max length is 32 characters
 # Sets the account username, max length is 32 characters
 # yuzu (default)
 # yuzu (default)
 username = yuzu
 username = yuzu