From e8d71712e7054748e7e18de9362de1f5a394b46b Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sat, 24 Sep 2022 19:46:49 -0500
Subject: input_common: Create virtual amiibo driver

---
 src/common/input.h                          |  27 ++++++++
 src/input_common/CMakeLists.txt             |   2 +
 src/input_common/drivers/virtual_amiibo.cpp | 101 ++++++++++++++++++++++++++++
 src/input_common/drivers/virtual_amiibo.h   |  61 +++++++++++++++++
 src/input_common/input_engine.cpp           |  37 ++++++++++
 src/input_common/input_engine.h             |  16 +++++
 6 files changed, 244 insertions(+)
 create mode 100644 src/input_common/drivers/virtual_amiibo.cpp
 create mode 100644 src/input_common/drivers/virtual_amiibo.h

(limited to 'src')

diff --git a/src/common/input.h b/src/common/input.h
index 825b0d650d..8365cc36ed 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -76,6 +76,19 @@ enum class PollingError {
     Unknown,
 };
 
+// Nfc reply from the controller
+enum class NfcState {
+    Success,
+    NewAmiibo,
+    WaitingForAmiibo,
+    AmiiboRemoved,
+    NotAnAmiibo,
+    NotSupported,
+    WrongDeviceState,
+    WriteFailed,
+    Unknown,
+};
+
 // Ir camera reply from the controller
 enum class CameraError {
     None,
@@ -202,6 +215,11 @@ struct CameraStatus {
     std::vector<u8> data{};
 };
 
+struct NfcStatus {
+    NfcState state{};
+    std::vector<u8> data{};
+};
+
 // List of buttons to be passed to Qt that can be translated
 enum class ButtonNames {
     Undefined,
@@ -260,6 +278,7 @@ struct CallbackStatus {
     BatteryStatus battery_status{};
     VibrationStatus vibration_status{};
     CameraStatus camera_status{};
+    NfcStatus nfc_status{};
 };
 
 // Triggered once every input change
@@ -312,6 +331,14 @@ public:
     virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
         return CameraError::NotSupported;
     }
+
+    virtual NfcState SupportsNfc() {
+        return NfcState::NotSupported;
+    }
+
+    virtual NfcState WriteNfcData([[maybe_unused]] const std::vector<u8>& data) {
+        return NfcState::NotSupported;
+    }
 };
 
 /// An abstract class template for a factory that can create input devices.
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 4b91b88cee..2cf9eb97f4 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -18,6 +18,8 @@ add_library(input_common STATIC
     drivers/touch_screen.h
     drivers/udp_client.cpp
     drivers/udp_client.h
+    drivers/virtual_amiibo.cpp
+    drivers/virtual_amiibo.h
     helpers/stick_from_buttons.cpp
     helpers/stick_from_buttons.h
     helpers/touch_from_buttons.cpp
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp
new file mode 100644
index 0000000000..8fadb1322a
--- /dev/null
+++ b/src/input_common/drivers/virtual_amiibo.cpp
@@ -0,0 +1,101 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <cstring>
+#include <fmt/format.h>
+
+#include "common/fs/file.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "input_common/drivers/virtual_amiibo.h"
+
+namespace InputCommon {
+constexpr PadIdentifier identifier = {
+    .guid = Common::UUID{},
+    .port = 0,
+    .pad = 0,
+};
+
+VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move(input_engine_)) {}
+
+VirtualAmiibo::~VirtualAmiibo() {}
+
+Common::Input::PollingError VirtualAmiibo::SetPollingMode(
+    [[maybe_unused]] const PadIdentifier& identifier_,
+    const Common::Input::PollingMode polling_mode_) {
+    polling_mode = polling_mode_;
+
+    if (polling_mode == Common::Input::PollingMode::NFC) {
+        if (state == State::Initialized) {
+            state = State::WaitingForAmiibo;
+        }
+    } else {
+        if (state == State::AmiiboIsOpen) {
+            CloseAmiibo();
+        }
+    }
+
+    return Common::Input::PollingError::None;
+}
+
+Common::Input::NfcState VirtualAmiibo::SupportsNfc(
+    [[maybe_unused]] const PadIdentifier& identifier_) {
+    return Common::Input::NfcState::Success;
+}
+
+Common::Input::NfcState VirtualAmiibo::WriteNfcData(
+    [[maybe_unused]] const PadIdentifier& identifier_, const std::vector<u8>& data) {
+    const Common::FS::IOFile amiibo_file{file_path, Common::FS::FileAccessMode::ReadWrite,
+                                         Common::FS::FileType::BinaryFile};
+
+    if (!amiibo_file.IsOpen()) {
+        LOG_ERROR(Core, "Amiibo is already on use");
+        return Common::Input::NfcState::WriteFailed;
+    }
+
+    if (!amiibo_file.Write(data)) {
+        LOG_ERROR(Service_NFP, "Error writting to file");
+        return Common::Input::NfcState::WriteFailed;
+    }
+
+    return Common::Input::NfcState::Success;
+}
+
+VirtualAmiibo::State VirtualAmiibo::GetCurrentState() const {
+    return state;
+}
+
+VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) {
+    const Common::FS::IOFile amiibo_file{filename, Common::FS::FileAccessMode::Read,
+                                         Common::FS::FileType::BinaryFile};
+
+    if (state != State::WaitingForAmiibo) {
+        return Info::WrongDeviceState;
+    }
+
+    if (!amiibo_file.IsOpen()) {
+        return Info::UnableToLoad;
+    }
+
+    amiibo_data.resize(amiibo_size);
+
+    if (amiibo_file.Read(amiibo_data) < amiibo_size_without_password) {
+        return Info::NotAnAmiibo;
+    }
+
+    file_path = filename;
+    state = State::AmiiboIsOpen;
+    SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_data});
+    return Info::Success;
+}
+
+VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() {
+    state = polling_mode == Common::Input::PollingMode::NFC ? State::WaitingForAmiibo
+                                                            : State::Initialized;
+    SetNfc(identifier, {Common::Input::NfcState::AmiiboRemoved, {}});
+    return Info::Success;
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h
new file mode 100644
index 0000000000..5790e4a1ff
--- /dev/null
+++ b/src/input_common/drivers/virtual_amiibo.h
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <string>
+#include <vector>
+
+#include "common/common_types.h"
+#include "input_common/input_engine.h"
+
+namespace Common::FS {
+class IOFile;
+}
+
+namespace InputCommon {
+
+class VirtualAmiibo final : public InputEngine {
+public:
+    enum class State {
+        Initialized,
+        WaitingForAmiibo,
+        AmiiboIsOpen,
+    };
+
+    enum class Info {
+        Success,
+        UnableToLoad,
+        NotAnAmiibo,
+        WrongDeviceState,
+        Unknown,
+    };
+
+    explicit VirtualAmiibo(std::string input_engine_);
+    ~VirtualAmiibo() override;
+
+    // Sets polling mode to a controller
+    Common::Input::PollingError SetPollingMode(
+        const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override;
+
+    Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) override;
+
+    Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
+                                         const std::vector<u8>& data) override;
+
+    State GetCurrentState() const;
+
+    Info LoadAmiibo(const std::string& amiibo_file);
+    Info CloseAmiibo();
+
+private:
+    static constexpr std::size_t amiibo_size = 0x21C;
+    static constexpr std::size_t amiibo_size_without_password = amiibo_size - 0x8;
+
+    std::string file_path{};
+    State state{State::Initialized};
+    std::vector<u8> amiibo_data;
+    Common::Input::PollingMode polling_mode{Common::Input::PollingMode::Pasive};
+};
+} // namespace InputCommon
diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp
index 6ede0e4b0e..61cfd0911c 100644
--- a/src/input_common/input_engine.cpp
+++ b/src/input_common/input_engine.cpp
@@ -102,6 +102,17 @@ void InputEngine::SetCamera(const PadIdentifier& identifier,
     TriggerOnCameraChange(identifier, value);
 }
 
+void InputEngine::SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value) {
+    {
+        std::scoped_lock lock{mutex};
+        ControllerData& controller = controller_list.at(identifier);
+        if (!configuring) {
+            controller.nfc = value;
+        }
+    }
+    TriggerOnNfcChange(identifier, value);
+}
+
 bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const {
     std::scoped_lock lock{mutex};
     const auto controller_iter = controller_list.find(identifier);
@@ -189,6 +200,18 @@ Common::Input::CameraStatus InputEngine::GetCamera(const PadIdentifier& identifi
     return controller.camera;
 }
 
+Common::Input::NfcStatus InputEngine::GetNfc(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.nfc;
+}
+
 void InputEngine::ResetButtonState() {
     for (const auto& controller : controller_list) {
         for (const auto& button : controller.second.buttons) {
@@ -355,6 +378,20 @@ void InputEngine::TriggerOnCameraChange(const PadIdentifier& identifier,
     }
 }
 
+void InputEngine::TriggerOnNfcChange(const PadIdentifier& identifier,
+                                     [[maybe_unused]] const Common::Input::NfcStatus& 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::Nfc, 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 f6b3c46104..9b8470c6f0 100644
--- a/src/input_common/input_engine.h
+++ b/src/input_common/input_engine.h
@@ -42,6 +42,7 @@ enum class EngineInputType {
     Camera,
     HatButton,
     Motion,
+    Nfc,
 };
 
 namespace std {
@@ -127,6 +128,17 @@ public:
         return Common::Input::CameraError::NotSupported;
     }
 
+    // Request nfc data from a controller
+    virtual Common::Input::NfcState SupportsNfc([[maybe_unused]] const PadIdentifier& identifier) {
+        return Common::Input::NfcState::NotSupported;
+    }
+
+    // Writes data to an nfc tag
+    virtual Common::Input::NfcState WriteNfcData([[maybe_unused]] const PadIdentifier& identifier,
+                                                 [[maybe_unused]] const std::vector<u8>& data) {
+        return Common::Input::NfcState::NotSupported;
+    }
+
     // Returns the engine name
     [[nodiscard]] const std::string& GetEngineName() const;
 
@@ -183,6 +195,7 @@ public:
     Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const;
     BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
     Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const;
+    Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const;
 
     int SetCallback(InputIdentifier input_identifier);
     void SetMappingCallback(MappingCallback callback);
@@ -195,6 +208,7 @@ protected:
     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);
+    void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value);
 
     virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const {
         return "Unknown";
@@ -208,6 +222,7 @@ private:
         std::unordered_map<int, BasicMotion> motions;
         Common::Input::BatteryLevel battery{};
         Common::Input::CameraStatus camera{};
+        Common::Input::NfcStatus nfc{};
     };
 
     void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value);
@@ -218,6 +233,7 @@ private:
                                const BasicMotion& value);
     void TriggerOnCameraChange(const PadIdentifier& identifier,
                                const Common::Input::CameraStatus& value);
+    void TriggerOnNfcChange(const PadIdentifier& identifier, const Common::Input::NfcStatus& value);
 
     bool IsInputIdentifierEqual(const InputIdentifier& input_identifier,
                                 const PadIdentifier& identifier, EngineInputType type,
-- 
cgit v1.2.3-70-g09d2


From da8864d00261894ebac8764786cd7fc51f8c566c Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sat, 24 Sep 2022 20:28:27 -0500
Subject: input_common: Enable virtual amiibo driver

---
 src/input_common/input_poller.cpp | 64 +++++++++++++++++++++++++++++++++++++++
 src/input_common/input_poller.h   | 10 ++++++
 src/input_common/main.cpp         | 21 +++++++++++++
 src/input_common/main.h           |  7 +++++
 4 files changed, 102 insertions(+)

(limited to 'src')

diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index ffb9b945e5..a8eb1442b1 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -705,6 +705,47 @@ private:
     InputEngine* input_engine;
 };
 
+class InputFromNfc final : public Common::Input::InputDevice {
+public:
+    explicit InputFromNfc(PadIdentifier identifier_, InputEngine* input_engine_)
+        : identifier(identifier_), input_engine(input_engine_) {
+        UpdateCallback engine_callback{[this]() { OnChange(); }};
+        const InputIdentifier input_identifier{
+            .identifier = identifier,
+            .type = EngineInputType::Nfc,
+            .index = 0,
+            .callback = engine_callback,
+        };
+        callback_key = input_engine->SetCallback(input_identifier);
+    }
+
+    ~InputFromNfc() override {
+        input_engine->DeleteCallback(callback_key);
+    }
+
+    Common::Input::NfcStatus GetStatus() const {
+        return input_engine->GetNfc(identifier);
+    }
+
+    void ForceUpdate() override {
+        OnChange();
+    }
+
+    void OnChange() {
+        const Common::Input::CallbackStatus status{
+            .type = Common::Input::InputType::Nfc,
+            .nfc_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_)
@@ -727,6 +768,14 @@ public:
         return input_engine->SetCameraFormat(identifier, camera_format);
     }
 
+    Common::Input::NfcState SupportsNfc() override {
+        return input_engine->SupportsNfc(identifier);
+    }
+
+    Common::Input::NfcState WriteNfcData(const std::vector<u8>& data) override {
+        return input_engine->WriteNfcData(identifier, data);
+    }
+
 private:
     const PadIdentifier identifier;
     InputEngine* input_engine;
@@ -978,6 +1027,18 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateCameraDevice(
     return std::make_unique<InputFromCamera>(identifier, input_engine.get());
 }
 
+std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateNfcDevice(
+    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<InputFromNfc>(identifier, input_engine.get());
+}
+
 InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_)
     : input_engine(std::move(input_engine_)) {}
 
@@ -989,6 +1050,9 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
     if (params.Has("camera")) {
         return CreateCameraDevice(params);
     }
+    if (params.Has("nfc")) {
+        return CreateNfcDevice(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 4410a84154..d7db13ce42 100644
--- a/src/input_common/input_poller.h
+++ b/src/input_common/input_poller.h
@@ -222,6 +222,16 @@ private:
     std::unique_ptr<Common::Input::InputDevice> CreateCameraDevice(
         const Common::ParamPackage& params);
 
+    /**
+     * Creates a nfc 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> CreateNfcDevice(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 75a57b9fc6..b2064ef955 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -11,6 +11,7 @@
 #include "input_common/drivers/tas_input.h"
 #include "input_common/drivers/touch_screen.h"
 #include "input_common/drivers/udp_client.h"
+#include "input_common/drivers/virtual_amiibo.h"
 #include "input_common/helpers/stick_from_buttons.h"
 #include "input_common/helpers/touch_from_buttons.h"
 #include "input_common/input_engine.h"
@@ -87,6 +88,15 @@ struct InputSubsystem::Impl {
         Common::Input::RegisterFactory<Common::Input::OutputDevice>(camera->GetEngineName(),
                                                                     camera_output_factory);
 
+        virtual_amiibo = std::make_shared<VirtualAmiibo>("virtual_amiibo");
+        virtual_amiibo->SetMappingCallback(mapping_callback);
+        virtual_amiibo_input_factory = std::make_shared<InputFactory>(virtual_amiibo);
+        virtual_amiibo_output_factory = std::make_shared<OutputFactory>(virtual_amiibo);
+        Common::Input::RegisterFactory<Common::Input::InputDevice>(virtual_amiibo->GetEngineName(),
+                                                                   virtual_amiibo_input_factory);
+        Common::Input::RegisterFactory<Common::Input::OutputDevice>(virtual_amiibo->GetEngineName(),
+                                                                    virtual_amiibo_output_factory);
+
 #ifdef HAVE_SDL2
         sdl = std::make_shared<SDLDriver>("sdl");
         sdl->SetMappingCallback(mapping_callback);
@@ -327,6 +337,7 @@ struct InputSubsystem::Impl {
     std::shared_ptr<TasInput::Tas> tas_input;
     std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
     std::shared_ptr<Camera> camera;
+    std::shared_ptr<VirtualAmiibo> virtual_amiibo;
 
     std::shared_ptr<InputFactory> keyboard_factory;
     std::shared_ptr<InputFactory> mouse_factory;
@@ -335,6 +346,7 @@ struct InputSubsystem::Impl {
     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<InputFactory> virtual_amiibo_input_factory;
 
     std::shared_ptr<OutputFactory> keyboard_output_factory;
     std::shared_ptr<OutputFactory> mouse_output_factory;
@@ -342,6 +354,7 @@ struct InputSubsystem::Impl {
     std::shared_ptr<OutputFactory> udp_client_output_factory;
     std::shared_ptr<OutputFactory> tas_output_factory;
     std::shared_ptr<OutputFactory> camera_output_factory;
+    std::shared_ptr<OutputFactory> virtual_amiibo_output_factory;
 
 #ifdef HAVE_SDL2
     std::shared_ptr<SDLDriver> sdl;
@@ -402,6 +415,14 @@ const Camera* InputSubsystem::GetCamera() const {
     return impl->camera.get();
 }
 
+VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() {
+    return impl->virtual_amiibo.get();
+}
+
+const VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() const {
+    return impl->virtual_amiibo.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 9a969e747b..ced2523839 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -33,6 +33,7 @@ class Camera;
 class Keyboard;
 class Mouse;
 class TouchScreen;
+class VirtualAmiibo;
 struct MappingData;
 } // namespace InputCommon
 
@@ -101,6 +102,12 @@ public:
     /// Retrieves the underlying camera input device.
     [[nodiscard]] const Camera* GetCamera() const;
 
+    /// Retrieves the underlying virtual amiibo input device.
+    [[nodiscard]] VirtualAmiibo* GetVirtualAmiibo();
+
+    /// Retrieves the underlying virtual amiibo input device.
+    [[nodiscard]] const VirtualAmiibo* GetVirtualAmiibo() 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
-- 
cgit v1.2.3-70-g09d2


From f6d57d7dd97751df7ee5fe4bc3667305d594010f Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sat, 24 Sep 2022 20:31:54 -0500
Subject: yuzu: Use virtual amiibo driver instead of nfp service

---
 src/yuzu/main.cpp | 51 ++++++++++++++++++++++++++-------------------------
 1 file changed, 26 insertions(+), 25 deletions(-)

(limited to 'src')

diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index e2c2b92925..632f7c9c9b 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -105,12 +105,12 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
 #include "core/hle/kernel/k_process.h"
 #include "core/hle/service/am/am.h"
 #include "core/hle/service/filesystem/filesystem.h"
-#include "core/hle/service/nfp/nfp.h"
 #include "core/hle/service/sm/sm.h"
 #include "core/loader/loader.h"
 #include "core/perf_stats.h"
 #include "core/telemetry_session.h"
 #include "input_common/drivers/tas_input.h"
+#include "input_common/drivers/virtual_amiibo.h"
 #include "input_common/main.h"
 #include "ui_main.h"
 #include "util/overlay_dialog.h"
@@ -3211,21 +3211,16 @@ void GMainWindow::OnLoadAmiibo() {
         return;
     }
 
-    Service::SM::ServiceManager& sm = system->ServiceManager();
-    auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user");
-    if (nfc == nullptr) {
-        QMessageBox::warning(this, tr("Error"), tr("The current game is not looking for amiibos"));
-        return;
-    }
-    const auto nfc_state = nfc->GetCurrentState();
-    if (nfc_state == Service::NFP::DeviceState::TagFound ||
-        nfc_state == Service::NFP::DeviceState::TagMounted) {
-        nfc->CloseAmiibo();
+    auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
+
+    // Remove amiibo if one is connected
+    if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) {
+        virtual_amiibo->CloseAmiibo();
         QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
         return;
     }
 
-    if (nfc_state != Service::NFP::DeviceState::SearchingForTag) {
+    if (virtual_amiibo->GetCurrentState() != InputCommon::VirtualAmiibo::State::WaitingForAmiibo) {
         QMessageBox::warning(this, tr("Error"), tr("The current game is not looking for amiibos"));
         return;
     }
@@ -3244,24 +3239,30 @@ void GMainWindow::OnLoadAmiibo() {
 }
 
 void GMainWindow::LoadAmiibo(const QString& filename) {
-    Service::SM::ServiceManager& sm = system->ServiceManager();
-    auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user");
-    if (nfc == nullptr) {
-        return;
-    }
-
+    auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
+    const QString title = tr("Error loading Amiibo data");
     // Remove amiibo if one is connected
-    const auto nfc_state = nfc->GetCurrentState();
-    if (nfc_state == Service::NFP::DeviceState::TagFound ||
-        nfc_state == Service::NFP::DeviceState::TagMounted) {
-        nfc->CloseAmiibo();
+    if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) {
+        virtual_amiibo->CloseAmiibo();
         QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
         return;
     }
 
-    if (!nfc->LoadAmiibo(filename.toStdString())) {
-        QMessageBox::warning(this, tr("Error loading Amiibo data"),
-                             tr("Unable to load Amiibo data."));
+    switch (virtual_amiibo->LoadAmiibo(filename.toStdString())) {
+    case InputCommon::VirtualAmiibo::Info::NotAnAmiibo:
+        QMessageBox::warning(this, title, tr("The selected file is not a valid amiibo"));
+        break;
+    case InputCommon::VirtualAmiibo::Info::UnableToLoad:
+        QMessageBox::warning(this, title, tr("The selected file is already on use"));
+        break;
+    case InputCommon::VirtualAmiibo::Info::WrongDeviceState:
+        QMessageBox::warning(this, title, tr("The current game is not looking for amiibos"));
+        break;
+    case InputCommon::VirtualAmiibo::Info::Unknown:
+        QMessageBox::warning(this, title, tr("An unkown error occured"));
+        break;
+    default:
+        break;
     }
 }
 
-- 
cgit v1.2.3-70-g09d2


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: 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(-)

(limited to 'src')

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
-- 
cgit v1.2.3-70-g09d2


From afea5c163fd2bd4b99753f6780c256c7280052a8 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sat, 24 Sep 2022 21:28:06 -0500
Subject: service: nfp: Rewrite and implement applet calls

---
 src/core/CMakeLists.txt                    |    5 +-
 src/core/hle/service/mii/mii_manager.cpp   |   80 +-
 src/core/hle/service/mii/mii_manager.h     |    3 +-
 src/core/hle/service/nfp/amiibo_crypto.cpp |   12 +-
 src/core/hle/service/nfp/amiibo_crypto.h   |    4 +-
 src/core/hle/service/nfp/amiibo_types.h    |  197 -----
 src/core/hle/service/nfp/nfp.cpp           | 1093 +---------------------------
 src/core/hle/service/nfp/nfp.h             |  161 ----
 src/core/hle/service/nfp/nfp_device.cpp    |  572 +++++++++++++++
 src/core/hle/service/nfp/nfp_device.h      |   97 +++
 src/core/hle/service/nfp/nfp_result.h      |   21 +
 src/core/hle/service/nfp/nfp_types.h       |  262 +++++++
 src/core/hle/service/nfp/nfp_user.cpp      |  634 +++++++++++++++-
 src/core/hle/service/nfp/nfp_user.h        |   44 +-
 14 files changed, 1732 insertions(+), 1453 deletions(-)
 delete mode 100644 src/core/hle/service/nfp/amiibo_types.h
 create mode 100644 src/core/hle/service/nfp/nfp_device.cpp
 create mode 100644 src/core/hle/service/nfp/nfp_device.h
 create mode 100644 src/core/hle/service/nfp/nfp_result.h
 create mode 100644 src/core/hle/service/nfp/nfp_types.h

(limited to 'src')

diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index c176623234..7fd2d0276c 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -523,9 +523,12 @@ add_library(core STATIC
     hle/service/nfc/nfc.h
     hle/service/nfp/amiibo_crypto.cpp
     hle/service/nfp/amiibo_crypto.h
-    hle/service/nfp/amiibo_types.h
     hle/service/nfp/nfp.cpp
     hle/service/nfp/nfp.h
+    hle/service/nfp/nfp_device.cpp
+    hle/service/nfp/nfp_device.h
+    hle/service/nfp/nfp_result.h
+    hle/service/nfp/nfp_types.h
     hle/service/nfp/nfp_user.cpp
     hle/service/nfp/nfp_user.h
     hle/service/ngct/ngct.cpp
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index c484a9c8de..3e92152ec0 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -427,12 +427,12 @@ CharInfo MiiManager::BuildDefault(std::size_t index) {
     return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id));
 }
 
-CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
+CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const {
     Service::Mii::MiiManager manager;
     auto mii = manager.BuildDefault(0);
 
     // Check if mii data exist
-    if (mii_v3.mii_name[0] == 0) {
+    if (mii_v3.version == 0) {
         return mii;
     }
 
@@ -443,8 +443,8 @@ CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
     mii.height = mii_v3.height;
     mii.build = mii_v3.build;
 
-    memset(mii.name.data(), 0, sizeof(mii.name));
-    memcpy(mii.name.data(), mii_v3.mii_name.data(), sizeof(mii_v3.mii_name));
+    memset(mii.name.data(), 0, mii.name.size());
+    memcpy(mii.name.data(), mii_v3.mii_name.data(), mii_v3.mii_name.size());
     mii.font_region = mii_v3.region_information.character_set;
 
     mii.faceline_type = mii_v3.appearance_bits1.face_shape;
@@ -504,6 +504,78 @@ CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
     return mii;
 }
 
+Ver3StoreData MiiManager::ConvertCharInfoToV3(const CharInfo& mii) const {
+    Service::Mii::MiiManager manager;
+    Ver3StoreData mii_v3{};
+
+    // TODO: We are ignoring a bunch of data from the mii_v3
+
+    mii_v3.version = 1;
+    mii_v3.mii_information.gender.Assign(mii.gender);
+    mii_v3.mii_information.favorite_color.Assign(mii.favorite_color);
+    mii_v3.height = mii.height;
+    mii_v3.build = mii.build;
+
+    memcpy(mii_v3.mii_name.data(), mii.name.data(), mii.name.size());
+    mii_v3.region_information.character_set.Assign(mii.font_region);
+
+    mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type);
+    mii_v3.appearance_bits1.skin_color.Assign(mii.faceline_color);
+    mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle);
+    mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make);
+
+    mii_v3.hair_style = mii.hair_type;
+    mii_v3.appearance_bits3.hair_color.Assign(mii.hair_color);
+    mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip);
+
+    mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type);
+    mii_v3.appearance_bits4.eye_color.Assign(mii.eye_color);
+    mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale);
+    mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect);
+    mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate);
+    mii_v3.appearance_bits4.eye_spacing.Assign(mii.eye_x);
+    mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y);
+
+    mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type);
+    mii_v3.appearance_bits5.eyebrow_color.Assign(mii.eyebrow_color);
+    mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale);
+    mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect);
+    mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate);
+    mii_v3.appearance_bits5.eyebrow_spacing.Assign(mii.eyebrow_x);
+    mii_v3.appearance_bits5.eyebrow_y_position.Assign(mii.eyebrow_y);
+
+    mii_v3.appearance_bits6.nose_type.Assign(mii.nose_type);
+    mii_v3.appearance_bits6.nose_scale.Assign(mii.nose_scale);
+    mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y);
+
+    mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type);
+    mii_v3.appearance_bits7.mouth_color.Assign(mii.mouth_color);
+    mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale);
+    mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect);
+    mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y);
+
+    mii_v3.appearance_bits8.mustache_type.Assign(mii.mustache_type);
+    mii_v3.appearance_bits9.mustache_scale.Assign(mii.mustache_scale);
+    mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y);
+
+    mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type);
+    mii_v3.appearance_bits9.facial_hair_color.Assign(mii.beard_color);
+
+    mii_v3.appearance_bits10.glasses_type.Assign(mii.glasses_type);
+    mii_v3.appearance_bits10.glasses_color.Assign(mii.glasses_color);
+    mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale);
+    mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y);
+
+    mii_v3.appearance_bits11.mole_enabled.Assign(mii.mole_type);
+    mii_v3.appearance_bits11.mole_scale.Assign(mii.mole_scale);
+    mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x);
+    mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y);
+
+    // TODO: Validate mii_v3 data
+
+    return mii_v3;
+}
+
 ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) {
     std::vector<MiiInfoElement> result;
 
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
index d847de0bda..b68fdd54c0 100644
--- a/src/core/hle/service/mii/mii_manager.h
+++ b/src/core/hle/service/mii/mii_manager.h
@@ -22,7 +22,8 @@ public:
     ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag);
     CharInfo BuildRandom(Age age, Gender gender, Race race);
     CharInfo BuildDefault(std::size_t index);
-    CharInfo ConvertV3ToCharInfo(Ver3StoreData mii_v3) const;
+    CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const;
+    Ver3StoreData ConvertCharInfoToV3(const CharInfo& mii) const;
     ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag);
     Result GetIndex(const CharInfo& info, u32& index);
 
diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp
index 31dd3a307f..c87da5ae42 100644
--- a/src/core/hle/service/nfp/amiibo_crypto.cpp
+++ b/src/core/hle/service/nfp/amiibo_crypto.cpp
@@ -20,13 +20,13 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
     const auto& amiibo_data = ntag_file.user_memory;
     LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock);
     LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", ntag_file.compability_container);
-    LOG_INFO(Service_NFP, "write_count={}", amiibo_data.write_counter);
+    LOG_DEBUG(Service_NFP, "write_count={}", static_cast<u16>(amiibo_data.write_counter));
 
-    LOG_INFO(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
-    LOG_INFO(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
-    LOG_INFO(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
-    LOG_INFO(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number);
-    LOG_INFO(Service_NFP, "series={}", amiibo_data.model_info.series);
+    LOG_DEBUG(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
+    LOG_DEBUG(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
+    LOG_DEBUG(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
+    LOG_DEBUG(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number);
+    LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series);
     LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.constant_value);
 
     LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock);
diff --git a/src/core/hle/service/nfp/amiibo_crypto.h b/src/core/hle/service/nfp/amiibo_crypto.h
index af7335912f..e3fa3d07e4 100644
--- a/src/core/hle/service/nfp/amiibo_crypto.h
+++ b/src/core/hle/service/nfp/amiibo_crypto.h
@@ -5,7 +5,7 @@
 
 #include <array>
 
-#include "core/hle/service/nfp/amiibo_types.h"
+#include "core/hle/service/nfp/nfp_types.h"
 
 struct mbedtls_md_context_t;
 
@@ -22,7 +22,7 @@ using HmacKey = std::array<u8, 0x10>;
 using DrgbOutput = std::array<u8, 0x20>;
 
 struct HashSeed {
-    u16 magic;
+    u16_be magic;
     std::array<u8, 0xE> padding;
     std::array<u8, 0x8> uuid1;
     std::array<u8, 0x8> uuid2;
diff --git a/src/core/hle/service/nfp/amiibo_types.h b/src/core/hle/service/nfp/amiibo_types.h
deleted file mode 100644
index bf2de811ad..0000000000
--- a/src/core/hle/service/nfp/amiibo_types.h
+++ /dev/null
@@ -1,197 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#pragma once
-
-#include <array>
-
-#include "core/hle/service/mii/types.h"
-
-namespace Service::NFP {
-static constexpr std::size_t amiibo_name_length = 0xA;
-
-enum class ServiceType : u32 {
-    User,
-    Debug,
-    System,
-};
-
-enum class State : u32 {
-    NonInitialized,
-    Initialized,
-};
-
-enum class DeviceState : u32 {
-    Initialized,
-    SearchingForTag,
-    TagFound,
-    TagRemoved,
-    TagMounted,
-    Unaviable,
-    Finalized,
-};
-
-enum class ModelType : u32 {
-    Amiibo,
-};
-
-enum class MountTarget : u32 {
-    Rom,
-    Ram,
-    All,
-};
-
-enum class AmiiboType : u8 {
-    Figure,
-    Card,
-    Yarn,
-};
-
-enum class AmiiboSeries : u8 {
-    SuperSmashBros,
-    SuperMario,
-    ChibiRobo,
-    YoshiWoollyWorld,
-    Splatoon,
-    AnimalCrossing,
-    EightBitMario,
-    Skylanders,
-    Unknown8,
-    TheLegendOfZelda,
-    ShovelKnight,
-    Unknown11,
-    Kiby,
-    Pokemon,
-    MarioSportsSuperstars,
-    MonsterHunter,
-    BoxBoy,
-    Pikmin,
-    FireEmblem,
-    Metroid,
-    Others,
-    MegaMan,
-    Diablo,
-};
-
-using TagUuid = std::array<u8, 10>;
-using HashData = std::array<u8, 0x20>;
-using ApplicationArea = std::array<u8, 0xD8>;
-
-struct AmiiboDate {
-    u16 raw_date{};
-
-    u16 GetYear() const {
-        return static_cast<u16>(((raw_date & 0xFE00) >> 9) + 2000);
-    }
-    u8 GetMonth() const {
-        return static_cast<u8>(((raw_date & 0x01E0) >> 5) - 1);
-    }
-    u8 GetDay() const {
-        return static_cast<u8>(raw_date & 0x001F);
-    }
-};
-static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
-
-struct Settings {
-    union {
-        u8 raw{};
-
-        BitField<4, 1, u8> amiibo_initialized;
-        BitField<5, 1, u8> appdata_initialized;
-    };
-};
-static_assert(sizeof(Settings) == 1, "AmiiboDate is an invalid size");
-
-struct AmiiboSettings {
-    Settings settings;
-    u8 country_code_id;
-    u16_be crc_counter; // Incremented each time crc is changed
-    AmiiboDate init_date;
-    AmiiboDate write_date;
-    u32_be crc;
-    std::array<u16_be, amiibo_name_length> amiibo_name; // UTF-16 text
-};
-static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size");
-
-struct AmiiboModelInfo {
-    u16 character_id;
-    u8 character_variant;
-    AmiiboType amiibo_type;
-    u16 model_number;
-    AmiiboSeries series;
-    u8 constant_value;         // Must be 02
-    INSERT_PADDING_BYTES(0x4); // Unknown
-};
-static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size");
-
-struct NTAG215Password {
-    u32 PWD;  // Password to allow write access
-    u16 PACK; // Password acknowledge reply
-    u16 RFUI; // Reserved for future use
-};
-static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size");
-
-#pragma pack(1)
-struct EncryptedAmiiboFile {
-    u8 constant_value;                     // Must be A5
-    u16 write_counter;                     // Number of times the amiibo has been written?
-    INSERT_PADDING_BYTES(0x1);             // Unknown 1
-    AmiiboSettings settings;               // Encrypted amiibo settings
-    HashData hmac_tag;                     // Hash
-    AmiiboModelInfo model_info;            // Encrypted amiibo model info
-    HashData keygen_salt;                  // Salt
-    HashData hmac_data;                    // Hash
-    Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
-    u64_be title_id;                       // Encrypted Game id
-    u16_be applicaton_write_counter;       // Encrypted Counter
-    u32_be application_area_id;            // Encrypted Game id
-    std::array<u8, 0x2> unknown;
-    HashData hash;                    // Probably a SHA256-HMAC hash?
-    ApplicationArea application_area; // Encrypted Game data
-};
-static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
-
-struct NTAG215File {
-    std::array<u8, 0x2> uuid2;
-    u16 static_lock;           // Set defined pages as read only
-    u32 compability_container; // Defines available memory
-    HashData hmac_data;        // Hash
-    u8 constant_value;         // Must be A5
-    u16 write_counter;         // Number of times the amiibo has been written?
-    INSERT_PADDING_BYTES(0x1); // Unknown 1
-    AmiiboSettings settings;
-    Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
-    u64_be title_id;
-    u16_be applicaton_write_counter; // Encrypted Counter
-    u32_be application_area_id;
-    std::array<u8, 0x2> unknown;
-    HashData hash;                    // Probably a SHA256-HMAC hash?
-    ApplicationArea application_area; // Encrypted Game data
-    HashData hmac_tag;                // Hash
-    std::array<u8, 0x8> uuid;
-    AmiiboModelInfo model_info;
-    HashData keygen_salt;     // Salt
-    u32 dynamic_lock;         // Dynamic lock
-    u32 CFG0;                 // Defines memory protected by password
-    u32 CFG1;                 // Defines number of verification attempts
-    NTAG215Password password; // Password data
-};
-static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size");
-static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be trivially copyable.");
-#pragma pack()
-
-struct EncryptedNTAG215File {
-    TagUuid uuid;                    // Unique serial number
-    u16 static_lock;                 // Set defined pages as read only
-    u32 compability_container;       // Defines available memory
-    EncryptedAmiiboFile user_memory; // Writable data
-    u32 dynamic_lock;                // Dynamic lock
-    u32 CFG0;                        // Defines memory protected by password
-    u32 CFG1;                        // Defines number of verification attempts
-    NTAG215Password password;        // Password data
-};
-static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an invalid size");
-static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
-              "EncryptedNTAG215File must be trivially copyable.");
-
-} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 037b866536..0cb55ca49c 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -1,1098 +1,43 @@
 // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
 // 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/logging/log.h"
-#include "common/string_util.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/mii/mii_manager.h"
-#include "core/hle/service/nfp/amiibo_crypto.h"
 #include "core/hle/service/nfp/nfp.h"
 #include "core/hle/service/nfp/nfp_user.h"
 
 namespace Service::NFP {
-namespace ErrCodes {
-constexpr Result DeviceNotFound(ErrorModule::NFP, 64);
-constexpr Result WrongDeviceState(ErrorModule::NFP, 73);
-constexpr Result NfcDisabled(ErrorModule::NFP, 80);
-constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88);
-constexpr Result TagRemoved(ErrorModule::NFP, 97);
-constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
-constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152);
-constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168);
-} // namespace ErrCodes
-
-IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_)
-    : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name},
-      nfp_interface{nfp_interface_} {
-    static const FunctionInfo functions[] = {
-        {0, &IUser::Initialize, "Initialize"},
-        {1, &IUser::Finalize, "Finalize"},
-        {2, &IUser::ListDevices, "ListDevices"},
-        {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, &IUser::SetApplicationArea, "SetApplicationArea"},
-        {10, &IUser::Flush, "Flush"},
-        {11, nullptr, "Restore"},
-        {12, &IUser::CreateApplicationArea, "CreateApplicationArea"},
-        {13, &IUser::GetTagInfo, "GetTagInfo"},
-        {14, &IUser::GetRegisterInfo, "GetRegisterInfo"},
-        {15, &IUser::GetCommonInfo, "GetCommonInfo"},
-        {16, &IUser::GetModelInfo, "GetModelInfo"},
-        {17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
-        {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
-        {19, &IUser::GetState, "GetState"},
-        {20, &IUser::GetDeviceState, "GetDeviceState"},
-        {21, &IUser::GetNpadId, "GetNpadId"},
-        {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
-        {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
-        {24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
-    };
-    RegisterHandlers(functions);
-
-    availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent");
-}
-
-void IUser::Initialize(Kernel::HLERequestContext& ctx) {
-    LOG_INFO(Service_NFC, "called");
-
-    state = State::Initialized;
-
-    // TODO(german77): Loop through all interfaces
-    nfp_interface.Initialize();
-
-    IPC::ResponseBuilder rb{ctx, 2, 0};
-    rb.Push(ResultSuccess);
-}
-
-void IUser::Finalize(Kernel::HLERequestContext& ctx) {
-    LOG_INFO(Service_NFP, "called");
-
-    state = State::NonInitialized;
-
-    // TODO(german77): Loop through all interfaces
-    nfp_interface.Finalize();
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
-}
-
-void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
-    LOG_INFO(Service_NFP, "called");
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    std::vector<u64> devices;
-
-    // TODO(german77): Loop through all interfaces
-    devices.push_back(nfp_interface.GetHandle());
-
-    if (devices.size() == 0) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::DeviceNotFound);
-        return;
-    }
-
-    ctx.WriteBuffer(devices);
-
-    IPC::ResponseBuilder rb{ctx, 3};
-    rb.Push(ResultSuccess);
-    rb.Push(static_cast<s32>(devices.size()));
-}
-
-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_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        const auto result = nfp_interface.StartDetection(nfp_protocol);
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::StopDetection(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        const auto result = nfp_interface.StopDetection();
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::Mount(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    const auto model_type{rp.PopEnum<ModelType>()};
-    const auto mount_target{rp.PopEnum<MountTarget>()};
-    LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle,
-             model_type, mount_target);
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        const auto result = nfp_interface.Mount();
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::Unmount(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        const auto result = nfp_interface.Unmount();
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    const auto access_id{rp.Pop<u32>()};
-    LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, access_id={}", device_handle,
-                access_id);
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        const auto result = nfp_interface.OpenApplicationArea(access_id);
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        ApplicationArea data{};
-        const auto result = nfp_interface.GetApplicationArea(data);
-        ctx.WriteBuffer(data);
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(result);
-        rb.Push(static_cast<u32>(data.size()));
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    const auto data{ctx.ReadBuffer()};
-    LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}", device_handle,
-                data.size());
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        const auto result = nfp_interface.SetApplicationArea(data);
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::Flush(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        const auto result = nfp_interface.Flush();
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    const auto access_id{rp.Pop<u32>()};
-    const auto data{ctx.ReadBuffer()};
-    LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}",
-                device_handle, access_id, data.size());
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        const auto result = nfp_interface.CreateApplicationArea(access_id, data);
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        TagInfo tag_info{};
-        const auto result = nfp_interface.GetTagInfo(tag_info);
-        ctx.WriteBuffer(tag_info);
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetRegisterInfo(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        RegisterInfo register_info{};
-        const auto result = nfp_interface.GetRegisterInfo(register_info);
-        ctx.WriteBuffer(register_info);
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        CommonInfo common_info{};
-        const auto result = nfp_interface.GetCommonInfo(common_info);
-        ctx.WriteBuffer(common_info);
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        ModelInfo model_info{};
-        const auto result = nfp_interface.GetModelInfo(model_info);
-        ctx.WriteBuffer(model_info);
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        IPC::ResponseBuilder rb{ctx, 2, 1};
-        rb.Push(ResultSuccess);
-        rb.PushCopyObjects(nfp_interface.GetActivateEvent());
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        IPC::ResponseBuilder rb{ctx, 2, 1};
-        rb.Push(ResultSuccess);
-        rb.PushCopyObjects(nfp_interface.GetDeactivateEvent());
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetState(Kernel::HLERequestContext& ctx) {
-    LOG_DEBUG(Service_NFC, "called");
-
-    IPC::ResponseBuilder rb{ctx, 3, 0};
-    rb.Push(ResultSuccess);
-    rb.PushEnum(state);
-}
-
-void IUser::GetDeviceState(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(ResultSuccess);
-        rb.PushEnum(nfp_interface.GetCurrentState());
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetNpadId(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(ResultSuccess);
-        rb.PushEnum(nfp_interface.GetNpadId());
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        IPC::ResponseBuilder rb{ctx, 3};
-        rb.Push(ResultSuccess);
-        rb.Push(sizeof(ApplicationArea));
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
-    LOG_DEBUG(Service_NFP, "(STUBBED) called");
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    IPC::ResponseBuilder rb{ctx, 2, 1};
-    rb.Push(ResultSuccess);
-    rb.PushCopyObjects(availability_change_event->GetReadableEvent());
-}
-
-void IUser::RecreateApplicationArea(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp{ctx};
-    const auto device_handle{rp.Pop<u64>()};
-    const auto access_id{rp.Pop<u32>()};
-    const auto data{ctx.ReadBuffer()};
-    LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}",
-                device_handle, access_id, data.size());
-
-    if (state == State::NonInitialized) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(ErrCodes::NfcDisabled);
-        return;
-    }
-
-    // TODO(german77): Loop through all interfaces
-    if (device_handle == nfp_interface.GetHandle()) {
-        const auto result = nfp_interface.RecreateApplicationArea(access_id, data);
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-        return;
-    }
-
-    LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
-    IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ErrCodes::DeviceNotFound);
-}
-
-Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_,
-                             const char* name)
-    : ServiceFramework{system_, name}, module{std::move(module_)},
-      npad_id{Core::HID::NpadIdType::Player1}, service_context{system_, service_name} {
-    activate_event = service_context.CreateEvent("IUser:NFPActivateEvent");
-    deactivate_event = service_context.CreateEvent("IUser:NFPDeactivateEvent");
-}
-
-Module::Interface::~Interface() = default;
-
-void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
-    LOG_DEBUG(Service_NFP, "called");
-
-    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-    rb.Push(ResultSuccess);
-    rb.PushIpcInterface<IUser>(*this, system);
-}
-
-bool Module::Interface::LoadAmiiboFile(const std::string& filename) {
-    constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
-    const Common::FS::IOFile amiibo_file{filename, Common::FS::FileAccessMode::Read,
-                                         Common::FS::FileType::BinaryFile};
-
-    if (!amiibo_file.IsOpen()) {
-        LOG_ERROR(Service_NFP, "Amiibo is already on use");
-        return false;
-    }
-
-    // Workaround for files with missing password data
-    std::array<u8, sizeof(EncryptedNTAG215File)> buffer{};
-    if (amiibo_file.Read(buffer) < tag_size_without_password) {
-        LOG_ERROR(Service_NFP, "Failed to read amiibo file");
-        return false;
-    }
-    memcpy(&encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File));
-
-    if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
-        LOG_INFO(Service_NFP, "Invalid amiibo");
-        return false;
-    }
-
-    file_path = filename;
-    return true;
-}
-
-bool Module::Interface::LoadAmiibo(const std::string& filename) {
-    if (device_state != DeviceState::SearchingForTag) {
-        LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
-        return false;
-    }
-
-    if (!LoadAmiiboFile(filename)) {
-        return false;
-    }
-
-    device_state = DeviceState::TagFound;
-    activate_event->GetWritableEvent().Signal();
-    return true;
-}
-
-void Module::Interface::CloseAmiibo() {
-    LOG_INFO(Service_NFP, "Remove amiibo");
-    device_state = DeviceState::TagRemoved;
-    is_data_decoded = false;
-    is_application_area_initialized = false;
-    encrypted_tag_data = {};
-    tag_data = {};
-    deactivate_event->GetWritableEvent().Signal();
-}
-
-Kernel::KReadableEvent& Module::Interface::GetActivateEvent() const {
-    return activate_event->GetReadableEvent();
-}
-
-Kernel::KReadableEvent& Module::Interface::GetDeactivateEvent() const {
-    return deactivate_event->GetReadableEvent();
-}
-
-void Module::Interface::Initialize() {
-    device_state = DeviceState::Initialized;
-    is_data_decoded = false;
-    is_application_area_initialized = false;
-    encrypted_tag_data = {};
-    tag_data = {};
-}
-
-void Module::Interface::Finalize() {
-    if (device_state == DeviceState::TagMounted) {
-        Unmount();
-    }
-    if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
-        StopDetection();
-    }
-    device_state = DeviceState::Unaviable;
-}
-
-Result Module::Interface::StartDetection(s32 protocol_) {
-    auto npad_device = system.HIDCore().GetEmulatedController(npad_id);
-
-    // TODO(german77): Add callback for when nfc data is available
-
-    if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
-        npad_device->SetPollingMode(Common::Input::PollingMode::NFC);
-        device_state = DeviceState::SearchingForTag;
-        protocol = protocol_;
-        return ResultSuccess;
-    }
-
-    LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
-    return ErrCodes::WrongDeviceState;
-}
-
-Result Module::Interface::StopDetection() {
-    auto npad_device = system.HIDCore().GetEmulatedController(npad_id);
-    npad_device->SetPollingMode(Common::Input::PollingMode::Active);
-
-    if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
-        CloseAmiibo();
-        return ResultSuccess;
-    }
-    if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
-        device_state = DeviceState::Initialized;
-        return ResultSuccess;
-    }
-
-    LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
-    return ErrCodes::WrongDeviceState;
-}
-
-Result Module::Interface::Flush() {
-    // Ignore write command if we can't encrypt the data
-    if (!is_data_decoded) {
-        return ResultSuccess;
-    }
-
-    constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
-    EncryptedNTAG215File tmp_encrypted_tag_data{};
-    const Common::FS::IOFile amiibo_file{file_path, Common::FS::FileAccessMode::ReadWrite,
-                                         Common::FS::FileType::BinaryFile};
-
-    if (!amiibo_file.IsOpen()) {
-        LOG_ERROR(Core, "Amiibo is already on use");
-        return ErrCodes::WriteAmiiboFailed;
-    }
-
-    // Workaround for files with missing password data
-    std::array<u8, sizeof(EncryptedNTAG215File)> buffer{};
-    if (amiibo_file.Read(buffer) < tag_size_without_password) {
-        LOG_ERROR(Core, "Failed to read amiibo file");
-        return ErrCodes::WriteAmiiboFailed;
-    }
-    memcpy(&tmp_encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File));
-
-    if (!AmiiboCrypto::IsAmiiboValid(tmp_encrypted_tag_data)) {
-        LOG_INFO(Service_NFP, "Invalid amiibo");
-        return ErrCodes::WriteAmiiboFailed;
-    }
-
-    bool is_uuid_equal = memcmp(tmp_encrypted_tag_data.uuid.data(), tag_data.uuid.data(), 8) == 0;
-    bool is_character_equal = tmp_encrypted_tag_data.user_memory.model_info.character_id ==
-                              tag_data.model_info.character_id;
-    if (!is_uuid_equal || !is_character_equal) {
-        LOG_ERROR(Service_NFP, "Not the same amiibo");
-        return ErrCodes::WriteAmiiboFailed;
-    }
-
-    if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
-        LOG_ERROR(Service_NFP, "Failed to encode data");
-        return ErrCodes::WriteAmiiboFailed;
-    }
-
-    // Return to the start of the file
-    if (!amiibo_file.Seek(0)) {
-        LOG_ERROR(Service_NFP, "Error writing to file");
-        return ErrCodes::WriteAmiiboFailed;
-    }
-
-    if (!amiibo_file.Write(encrypted_tag_data)) {
-        LOG_ERROR(Service_NFP, "Error writing to file");
-        return ErrCodes::WriteAmiiboFailed;
-    }
-
-    return ResultSuccess;
-}
-
-Result Module::Interface::Mount() {
-    if (device_state != DeviceState::TagFound) {
-        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
-        return ErrCodes::WrongDeviceState;
-    }
 
-    is_data_decoded = AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data);
-    LOG_INFO(Service_NFP, "Is amiibo decoded {}", is_data_decoded);
-
-    is_application_area_initialized = false;
-    device_state = DeviceState::TagMounted;
-    return ResultSuccess;
-}
-
-Result Module::Interface::Unmount() {
-    if (device_state != DeviceState::TagMounted) {
-        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
-        return ErrCodes::WrongDeviceState;
-    }
-
-    is_data_decoded = false;
-    is_application_area_initialized = false;
-    device_state = DeviceState::TagFound;
-    return ResultSuccess;
-}
-
-Result Module::Interface::GetTagInfo(TagInfo& tag_info) const {
-    if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
-        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
-        return ErrCodes::WrongDeviceState;
-    }
-
-    tag_info = {
-        .uuid = encrypted_tag_data.uuid,
-        .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.size()),
-        .protocol = protocol,
-        .tag_type = static_cast<u32>(encrypted_tag_data.user_memory.model_info.amiibo_type),
-    };
-
-    return ResultSuccess;
-}
-
-Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const {
-    if (device_state != DeviceState::TagMounted) {
-        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
-        return ErrCodes::WrongDeviceState;
-    }
-
-    if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) {
-        const auto& settings = tag_data.settings;
-        // TODO: Validate this data
-        common_info = {
-            .last_write_year = settings.write_date.GetYear(),
-            .last_write_month = settings.write_date.GetMonth(),
-            .last_write_day = settings.write_date.GetDay(),
-            .write_counter = settings.crc_counter,
-            .version = 1,
-            .application_area_size = sizeof(ApplicationArea),
-        };
-        return ResultSuccess;
-    }
-
-    // Generate a generic answer
-    common_info = {
-        .last_write_year = 2022,
-        .last_write_month = 2,
-        .last_write_day = 7,
-        .write_counter = 0,
-        .version = 1,
-        .application_area_size = sizeof(ApplicationArea),
-    };
-    return ResultSuccess;
-}
-
-Result Module::Interface::GetModelInfo(ModelInfo& model_info) const {
-    if (device_state != DeviceState::TagMounted) {
-        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
-        return ErrCodes::WrongDeviceState;
-    }
-
-    const auto& model_info_data = encrypted_tag_data.user_memory.model_info;
-    model_info = {
-        .character_id = model_info_data.character_id,
-        .character_variant = model_info_data.character_variant,
-        .amiibo_type = model_info_data.amiibo_type,
-        .model_number = model_info_data.model_number,
-        .series = model_info_data.series,
-        .constant_value = model_info_data.constant_value,
-    };
-    return ResultSuccess;
-}
-
-Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const {
-    if (device_state != DeviceState::TagMounted) {
-        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
-        if (device_state == DeviceState::TagRemoved) {
-            return ErrCodes::TagRemoved;
-        }
-        return ErrCodes::WrongDeviceState;
-    }
-
-    Service::Mii::MiiManager manager;
-
-    if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) {
-        const auto& settings = tag_data.settings;
-
-        // TODO: Validate this data
-        register_info = {
-            .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
-            .first_write_year = settings.init_date.GetYear(),
-            .first_write_month = settings.init_date.GetMonth(),
-            .first_write_day = settings.init_date.GetDay(),
-            .amiibo_name = GetAmiiboName(settings),
-            .font_region = {},
+class IUserManager final : public ServiceFramework<IUserManager> {
+public:
+    explicit IUserManager(Core::System& system_) : ServiceFramework{system_, "nfp:user"} {
+        // clang-format off
+        static const FunctionInfo functions[] = {
+            {0, &IUserManager::CreateUserInterface, "CreateUserInterface"},
         };
+        // clang-format on
 
-        return ResultSuccess;
+        RegisterHandlers(functions);
     }
 
-    // Generate a generic answer
-    register_info = {
-        .mii_char_info = manager.BuildDefault(0),
-        .first_write_year = 2022,
-        .first_write_month = 2,
-        .first_write_day = 7,
-        .amiibo_name = {'Y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o', 0},
-        .font_region = {},
-    };
-    return ResultSuccess;
-}
+private:
+    void CreateUserInterface(Kernel::HLERequestContext& ctx) {
+        LOG_DEBUG(Service_NFP, "called");
 
-Result Module::Interface::OpenApplicationArea(u32 access_id) {
-    if (device_state != DeviceState::TagMounted) {
-        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
-        if (device_state == DeviceState::TagRemoved) {
-            return ErrCodes::TagRemoved;
+        if (user_interface == nullptr) {
+            user_interface = std::make_shared<IUser>(system);
         }
-        return ErrCodes::WrongDeviceState;
-    }
-
-    // Fallback for lack of amiibo keys
-    if (!is_data_decoded) {
-        LOG_WARNING(Service_NFP, "Application area is not initialized");
-        return ErrCodes::ApplicationAreaIsNotInitialized;
-    }
 
-    if (tag_data.settings.settings.appdata_initialized == 0) {
-        LOG_WARNING(Service_NFP, "Application area is not initialized");
-        return ErrCodes::ApplicationAreaIsNotInitialized;
-    }
-
-    if (tag_data.application_area_id != access_id) {
-        LOG_WARNING(Service_NFP, "Wrong application area id");
-        return ErrCodes::WrongApplicationAreaId;
-    }
-
-    is_application_area_initialized = true;
-    return ResultSuccess;
-}
-
-Result Module::Interface::GetApplicationArea(ApplicationArea& data) const {
-    if (device_state != DeviceState::TagMounted) {
-        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
-        if (device_state == DeviceState::TagRemoved) {
-            return ErrCodes::TagRemoved;
-        }
-        return ErrCodes::WrongDeviceState;
-    }
-
-    if (!is_application_area_initialized) {
-        LOG_ERROR(Service_NFP, "Application area is not initialized");
-        return ErrCodes::ApplicationAreaIsNotInitialized;
-    }
-
-    data = tag_data.application_area;
-
-    return ResultSuccess;
-}
-
-Result Module::Interface::SetApplicationArea(const std::vector<u8>& data) {
-    if (device_state != DeviceState::TagMounted) {
-        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
-        if (device_state == DeviceState::TagRemoved) {
-            return ErrCodes::TagRemoved;
-        }
-        return ErrCodes::WrongDeviceState;
-    }
-
-    if (!is_application_area_initialized) {
-        LOG_ERROR(Service_NFP, "Application area is not initialized");
-        return ErrCodes::ApplicationAreaIsNotInitialized;
-    }
-
-    if (data.size() != sizeof(ApplicationArea)) {
-        LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
-        return ResultUnknown;
-    }
-
-    std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
-    return ResultSuccess;
-}
-
-Result Module::Interface::CreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
-    if (device_state != DeviceState::TagMounted) {
-        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
-        if (device_state == DeviceState::TagRemoved) {
-            return ErrCodes::TagRemoved;
-        }
-        return ErrCodes::WrongDeviceState;
-    }
-
-    if (tag_data.settings.settings.appdata_initialized != 0) {
-        LOG_ERROR(Service_NFP, "Application area already exist");
-        return ErrCodes::ApplicationAreaExist;
-    }
-
-    if (data.size() != sizeof(ApplicationArea)) {
-        LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
-        return ResultUnknown;
-    }
-
-    std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
-    tag_data.application_area_id = access_id;
-
-    return ResultSuccess;
-}
-
-Result Module::Interface::RecreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
-    if (device_state != DeviceState::TagMounted) {
-        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
-        if (device_state == DeviceState::TagRemoved) {
-            return ErrCodes::TagRemoved;
-        }
-        return ErrCodes::WrongDeviceState;
-    }
-
-    if (data.size() != sizeof(ApplicationArea)) {
-        LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
-        return ResultUnknown;
-    }
-
-    std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
-    tag_data.application_area_id = access_id;
-
-    return ResultSuccess;
-}
-
-u64 Module::Interface::GetHandle() const {
-    // Generate a handle based of the npad id
-    return static_cast<u64>(npad_id);
-}
-
-DeviceState Module::Interface::GetCurrentState() const {
-    return device_state;
-}
-
-Core::HID::NpadIdType Module::Interface::GetNpadId() const {
-    // Return first connected npad id as a workaround for lack of a single nfc interface per
-    // controller
-    return system.HIDCore().GetFirstNpadId();
-}
-
-AmiiboName Module::Interface::GetAmiiboName(const AmiiboSettings& settings) const {
-    std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
-    AmiiboName amiibo_name{};
-
-    // Convert from big endian to little endian
-    for (std::size_t i = 0; i < amiibo_name_length; i++) {
-        settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]);
+        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+        rb.Push(ResultSuccess);
+        rb.PushIpcInterface<IUser>(user_interface);
     }
 
-    // Convert from utf16 to utf8
-    const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
-    memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
-
-    return amiibo_name;
-}
+    std::shared_ptr<IUser> user_interface;
+};
 
 void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
-    auto module = std::make_shared<Module>();
-    std::make_shared<NFP_User>(module, system)->InstallAsService(service_manager);
+    std::make_shared<IUserManager>(system)->InstallAsService(service_manager);
 }
 
 } // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h
index 0de0b48e71..a25c362b8a 100644
--- a/src/core/hle/service/nfp/nfp.h
+++ b/src/core/hle/service/nfp/nfp.h
@@ -3,170 +3,9 @@
 
 #pragma once
 
-#include <array>
-#include <vector>
-
-#include "common/common_funcs.h"
-#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/mii/types.h"
-#include "core/hle/service/nfp/amiibo_types.h"
 #include "core/hle/service/service.h"
 
-namespace Kernel {
-class KEvent;
-class KReadableEvent;
-} // namespace Kernel
-
-namespace Core::HID {
-enum class NpadIdType : u32;
-} // namespace Core::HID
-
 namespace Service::NFP {
-using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
-
-struct TagInfo {
-    TagUuid uuid;
-    u8 uuid_length;
-    INSERT_PADDING_BYTES(0x15);
-    s32 protocol;
-    u32 tag_type;
-    INSERT_PADDING_BYTES(0x30);
-};
-static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
-
-struct CommonInfo {
-    u16 last_write_year;
-    u8 last_write_month;
-    u8 last_write_day;
-    u16 write_counter;
-    u16 version;
-    u32 application_area_size;
-    INSERT_PADDING_BYTES(0x34);
-};
-static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
-
-struct ModelInfo {
-    u16 character_id;
-    u8 character_variant;
-    AmiiboType amiibo_type;
-    u16 model_number;
-    AmiiboSeries series;
-    u8 constant_value;          // Must be 02
-    INSERT_PADDING_BYTES(0x38); // Unknown
-};
-static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
-
-struct RegisterInfo {
-    Service::Mii::CharInfo mii_char_info;
-    u16 first_write_year;
-    u8 first_write_month;
-    u8 first_write_day;
-    AmiiboName amiibo_name;
-    u8 font_region;
-    INSERT_PADDING_BYTES(0x7A);
-};
-static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
-
-class Module final {
-public:
-    class Interface : public ServiceFramework<Interface> {
-    public:
-        explicit Interface(std::shared_ptr<Module> module_, Core::System& system_,
-                           const char* name);
-        ~Interface() override;
-
-        void CreateUserInterface(Kernel::HLERequestContext& ctx);
-        bool LoadAmiibo(const std::string& filename);
-        bool LoadAmiiboFile(const std::string& filename);
-        void CloseAmiibo();
-
-        void Initialize();
-        void Finalize();
-
-        Result StartDetection(s32 protocol_);
-        Result StopDetection();
-        Result Mount();
-        Result Unmount();
-        Result Flush();
-
-        Result GetTagInfo(TagInfo& tag_info) const;
-        Result GetCommonInfo(CommonInfo& common_info) const;
-        Result GetModelInfo(ModelInfo& model_info) const;
-        Result GetRegisterInfo(RegisterInfo& register_info) const;
-
-        Result OpenApplicationArea(u32 access_id);
-        Result GetApplicationArea(ApplicationArea& data) const;
-        Result SetApplicationArea(const std::vector<u8>& data);
-        Result CreateApplicationArea(u32 access_id, const std::vector<u8>& data);
-        Result RecreateApplicationArea(u32 access_id, const std::vector<u8>& data);
-
-        u64 GetHandle() const;
-        DeviceState GetCurrentState() const;
-        Core::HID::NpadIdType GetNpadId() const;
-
-        Kernel::KReadableEvent& GetActivateEvent() const;
-        Kernel::KReadableEvent& GetDeactivateEvent() const;
-
-    protected:
-        std::shared_ptr<Module> module;
-
-    private:
-        AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
-
-        const Core::HID::NpadIdType npad_id;
-
-        bool is_data_decoded{};
-        bool is_application_area_initialized{};
-        s32 protocol;
-        std::string file_path{};
-        Kernel::KEvent* activate_event;
-        Kernel::KEvent* deactivate_event;
-        DeviceState device_state{DeviceState::Unaviable};
-        KernelHelpers::ServiceContext service_context;
-
-        NTAG215File tag_data{};
-        EncryptedNTAG215File encrypted_tag_data{};
-    };
-};
-
-class IUser final : public ServiceFramework<IUser> {
-public:
-    explicit IUser(Module::Interface& nfp_interface_, Core::System& system_);
-
-private:
-    void Initialize(Kernel::HLERequestContext& ctx);
-    void Finalize(Kernel::HLERequestContext& ctx);
-    void ListDevices(Kernel::HLERequestContext& ctx);
-    void StartDetection(Kernel::HLERequestContext& ctx);
-    void StopDetection(Kernel::HLERequestContext& ctx);
-    void Mount(Kernel::HLERequestContext& ctx);
-    void Unmount(Kernel::HLERequestContext& ctx);
-    void OpenApplicationArea(Kernel::HLERequestContext& ctx);
-    void GetApplicationArea(Kernel::HLERequestContext& ctx);
-    void SetApplicationArea(Kernel::HLERequestContext& ctx);
-    void Flush(Kernel::HLERequestContext& ctx);
-    void CreateApplicationArea(Kernel::HLERequestContext& ctx);
-    void GetTagInfo(Kernel::HLERequestContext& ctx);
-    void GetRegisterInfo(Kernel::HLERequestContext& ctx);
-    void GetCommonInfo(Kernel::HLERequestContext& ctx);
-    void GetModelInfo(Kernel::HLERequestContext& ctx);
-    void AttachActivateEvent(Kernel::HLERequestContext& ctx);
-    void AttachDeactivateEvent(Kernel::HLERequestContext& ctx);
-    void GetState(Kernel::HLERequestContext& ctx);
-    void GetDeviceState(Kernel::HLERequestContext& ctx);
-    void GetNpadId(Kernel::HLERequestContext& ctx);
-    void GetApplicationAreaSize(Kernel::HLERequestContext& ctx);
-    void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
-    void RecreateApplicationArea(Kernel::HLERequestContext& ctx);
-
-    KernelHelpers::ServiceContext service_context;
-
-    // TODO(german77): We should have a vector of interfaces
-    Module::Interface& nfp_interface;
-
-    State state{State::NonInitialized};
-    Kernel::KEvent* availability_change_event;
-};
 
 void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
 
diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp
new file mode 100644
index 0000000000..da7daae882
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_device.cpp
@@ -0,0 +1,572 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// 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"
+#include "common/tiny_mt.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/mii/mii_manager.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"
+
+namespace Service::NFP {
+NfpDevice::NfpDevice(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:NFPActivateEvent");
+    deactivate_event = service_context.CreateEvent("IUser:NFPDeactivateEvent");
+    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);
+}
+
+NfpDevice::~NfpDevice() {
+    if (is_controller_set) {
+        npad_device->DeleteCallback(callback_key);
+        is_controller_set = false;
+    }
+};
+
+void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
+    if (type == Core::HID::ControllerTriggerType::Connected ||
+        type == Core::HID::ControllerTriggerType::Disconnected) {
+        availability_change_event->GetWritableEvent().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:
+        LoadAmiibo(nfc_status.data);
+        break;
+    case Common::Input::NfcState::AmiiboRemoved:
+        if (device_state != DeviceState::SearchingForTag) {
+            CloseAmiibo();
+        }
+        break;
+    default:
+        break;
+    }
+}
+
+bool NfpDevice::LoadAmiibo(const std::vector<u8>& data) {
+    if (device_state != DeviceState::SearchingForTag) {
+        LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
+        return false;
+    }
+
+    if (data.size() != sizeof(EncryptedNTAG215File)) {
+        LOG_ERROR(Service_NFP, "Not an amiibo, size={}", data.size());
+        return false;
+    }
+
+    memcpy(&encrypted_tag_data, data.data(), sizeof(EncryptedNTAG215File));
+
+    if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
+        LOG_INFO(Service_NFP, "Invalid amiibo");
+        return false;
+    }
+
+    device_state = DeviceState::TagFound;
+    activate_event->GetWritableEvent().Signal();
+    return true;
+}
+
+void NfpDevice::CloseAmiibo() {
+    LOG_INFO(Service_NFP, "Remove amiibo");
+
+    if (device_state == DeviceState::TagMounted) {
+        Unmount();
+    }
+
+    device_state = DeviceState::TagRemoved;
+    encrypted_tag_data = {};
+    tag_data = {};
+    deactivate_event->GetWritableEvent().Signal();
+}
+
+Kernel::KReadableEvent& NfpDevice::GetActivateEvent() const {
+    return activate_event->GetReadableEvent();
+}
+
+Kernel::KReadableEvent& NfpDevice::GetDeactivateEvent() const {
+    return deactivate_event->GetReadableEvent();
+}
+
+void NfpDevice::Initialize() {
+    device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable;
+    encrypted_tag_data = {};
+    tag_data = {};
+}
+
+void NfpDevice::Finalize() {
+    if (device_state == DeviceState::TagMounted) {
+        Unmount();
+    }
+    if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
+        StopDetection();
+    }
+    device_state = DeviceState::Unavailable;
+}
+
+Result NfpDevice::StartDetection(s32 protocol_) {
+    // TODO(german77): Add callback for when nfc data is available
+
+    if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
+        npad_device->SetPollingMode(Common::Input::PollingMode::NFC);
+        device_state = DeviceState::SearchingForTag;
+        protocol = protocol_;
+        return ResultSuccess;
+    }
+
+    LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+    return WrongDeviceState;
+}
+
+Result NfpDevice::StopDetection() {
+    npad_device->SetPollingMode(Common::Input::PollingMode::Active);
+
+    if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
+        CloseAmiibo();
+        return ResultSuccess;
+    }
+    if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
+        device_state = DeviceState::Initialized;
+        return ResultSuccess;
+    }
+
+    LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+    return WrongDeviceState;
+}
+
+Result NfpDevice::Flush() {
+    auto& settings = tag_data.settings;
+
+    if (settings.write_date.raw_date != settings.write_date.raw_date) {
+        // TODO: Read current system date
+        settings.write_date.SetYear(2022);
+        settings.write_date.SetMonth(9);
+        settings.write_date.SetDay(9);
+        settings.crc_counter++;
+        // TODO: Find how to calculate the crc check
+        // settings.crc = CalculateCRC(settings);
+    }
+
+    tag_data.write_counter++;
+
+    if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
+        LOG_ERROR(Service_NFP, "Failed to encode data");
+        return WriteAmiiboFailed;
+    }
+
+    std::vector<u8> data(sizeof(encrypted_tag_data));
+    memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
+
+    if (!npad_device->WriteNfc(data)) {
+        LOG_ERROR(Service_NFP, "Error writing to file");
+        return WriteAmiiboFailed;
+    }
+
+    is_data_moddified = false;
+
+    return ResultSuccess;
+}
+
+Result NfpDevice::Mount() {
+    if (device_state != DeviceState::TagFound) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        return WrongDeviceState;
+    }
+
+    if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
+        LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state);
+        return CorruptedData;
+    }
+
+    device_state = DeviceState::TagMounted;
+    return ResultSuccess;
+}
+
+Result NfpDevice::Unmount() {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        return WrongDeviceState;
+    }
+
+    // Save data before unloading the amiibo
+    if (is_data_moddified) {
+        Flush();
+    }
+
+    device_state = DeviceState::TagFound;
+    return ResultSuccess;
+}
+
+Result NfpDevice::GetTagInfo(TagInfo& tag_info) const {
+    if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        return WrongDeviceState;
+    }
+
+    tag_info = {
+        .uuid = encrypted_tag_data.uuid,
+        .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.size()),
+        .protocol = protocol,
+        .tag_type = static_cast<u32>(encrypted_tag_data.user_memory.model_info.amiibo_type),
+    };
+
+    return ResultSuccess;
+}
+
+Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        return WrongDeviceState;
+    }
+
+    const auto& settings = tag_data.settings;
+    const u32 application_area_size =
+        tag_data.settings.settings.appdata_initialized == 0 ? 0 : sizeof(ApplicationArea);
+
+    // TODO: Validate this data
+    common_info = {
+        .last_write_date =
+            {
+                settings.write_date.GetYear(),
+                settings.write_date.GetMonth(),
+                settings.write_date.GetDay(),
+            },
+        .write_counter = tag_data.write_counter,
+        .version = 1,
+        .application_area_size = application_area_size,
+    };
+    return ResultSuccess;
+}
+
+Result NfpDevice::GetModelInfo(ModelInfo& model_info) const {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        return WrongDeviceState;
+    }
+
+    const auto& model_info_data = encrypted_tag_data.user_memory.model_info;
+    model_info = {
+        .character_id = model_info_data.character_id,
+        .character_variant = model_info_data.character_variant,
+        .amiibo_type = model_info_data.amiibo_type,
+        .model_number = model_info_data.model_number,
+        .series = model_info_data.series,
+    };
+    return ResultSuccess;
+}
+
+Result NfpDevice::GetRegisterInfo(RegisterInfo& register_info) const {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        if (device_state == DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    if (tag_data.settings.settings.amiibo_initialized == 0) {
+        return RegistrationIsNotInitialized;
+    }
+
+    Service::Mii::MiiManager manager;
+    const auto& settings = tag_data.settings;
+
+    // TODO: Validate this data
+    register_info = {
+        .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
+        .creation_date =
+            {
+                settings.init_date.GetYear(),
+                settings.init_date.GetMonth(),
+                settings.init_date.GetDay(),
+            },
+        .amiibo_name = GetAmiiboName(settings),
+        .font_region = {},
+    };
+
+    return ResultSuccess;
+}
+
+Result NfpDevice::SetNicknameAndOwner(const AmiiboName& amiibo_name) {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        if (device_state == DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    Service::Mii::MiiManager manager;
+    auto& settings = tag_data.settings;
+
+    // TODO: Read current system date
+    settings.init_date.SetYear(2022);
+    settings.init_date.SetMonth(9);
+    settings.init_date.SetDay(9);
+    settings.write_date.SetYear(2022);
+    settings.write_date.SetMonth(9);
+    settings.write_date.SetDay(9);
+    settings.crc_counter++;
+    // TODO: Find how to calculate the crc check
+    // settings.crc = CalculateCRC(settings);
+
+    SetAmiiboName(settings, amiibo_name);
+    tag_data.owner_mii = manager.ConvertCharInfoToV3(manager.BuildDefault(0));
+    settings.settings.amiibo_initialized.Assign(1);
+
+    return Flush();
+}
+
+Result NfpDevice::RestoreAmiibo() {
+    // TODO: Load amiibo from backup on system
+    LOG_ERROR(Service_NFP, "Not Implemented");
+    return ResultSuccess;
+}
+
+Result NfpDevice::DeleteAllData() {
+    const auto result = DeleteApplicationArea();
+    if (result.IsError()) {
+        return result;
+    }
+
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        if (device_state == DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    Common::TinyMT rng{};
+    rng.GenerateRandomBytes(&tag_data.owner_mii, sizeof(tag_data.owner_mii));
+    tag_data.settings.settings.amiibo_initialized.Assign(0);
+
+    return Flush();
+}
+
+Result NfpDevice::OpenApplicationArea(u32 access_id) {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        if (device_state == DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
+        LOG_WARNING(Service_NFP, "Application area is not initialized");
+        return ApplicationAreaIsNotInitialized;
+    }
+
+    if (tag_data.application_area_id != access_id) {
+        LOG_WARNING(Service_NFP, "Wrong application area id");
+        return WrongApplicationAreaId;
+    }
+
+    return ResultSuccess;
+}
+
+Result NfpDevice::GetApplicationArea(std::vector<u8>& data) const {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        if (device_state == DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
+        LOG_ERROR(Service_NFP, "Application area is not initialized");
+        return ApplicationAreaIsNotInitialized;
+    }
+
+    if (data.size() > sizeof(ApplicationArea)) {
+        LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
+        return ResultUnknown;
+    }
+
+    memcpy(data.data(), tag_data.application_area.data(), data.size());
+
+    return ResultSuccess;
+}
+
+Result NfpDevice::SetApplicationArea(const std::vector<u8>& data) {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        if (device_state == DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
+        LOG_ERROR(Service_NFP, "Application area is not initialized");
+        return ApplicationAreaIsNotInitialized;
+    }
+
+    if (data.size() > sizeof(ApplicationArea)) {
+        LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
+        return ResultUnknown;
+    }
+
+    Common::TinyMT rng{};
+    rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(ApplicationArea));
+    std::memcpy(tag_data.application_area.data(), data.data(), data.size());
+
+    tag_data.applicaton_write_counter++;
+    is_data_moddified = true;
+
+    return ResultSuccess;
+}
+
+Result NfpDevice::CreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        if (device_state == DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    if (tag_data.settings.settings.appdata_initialized.Value() != 0) {
+        LOG_ERROR(Service_NFP, "Application area already exist");
+        return ApplicationAreaExist;
+    }
+
+    return RecreateApplicationArea(access_id, data);
+}
+
+Result NfpDevice::RecreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        if (device_state == DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    if (data.size() > sizeof(ApplicationArea)) {
+        LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
+        return ResultUnknown;
+    }
+
+    Common::TinyMT rng{};
+    std::memcpy(tag_data.application_area.data(), data.data(), data.size());
+    // HW seems to fill excess data with garbage
+    rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
+                            sizeof(ApplicationArea) - data.size());
+
+    // TODO: Investigate why the title id needs to be moddified
+    tag_data.title_id = system.GetCurrentProcessProgramID();
+    tag_data.title_id = tag_data.title_id | 0x30000000ULL;
+    tag_data.settings.settings.appdata_initialized.Assign(1);
+    tag_data.application_area_id = access_id;
+    tag_data.applicaton_write_counter++;
+    tag_data.unknown = {};
+
+    return Flush();
+}
+
+Result NfpDevice::DeleteApplicationArea() {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        if (device_state == DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    Common::TinyMT rng{};
+    rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(ApplicationArea));
+    rng.GenerateRandomBytes(&tag_data.title_id, sizeof(u64));
+    rng.GenerateRandomBytes(&tag_data.application_area_id, sizeof(u32));
+    tag_data.settings.settings.appdata_initialized.Assign(0);
+    tag_data.applicaton_write_counter++;
+    tag_data.unknown = {};
+
+    return Flush();
+}
+
+u64 NfpDevice::GetHandle() const {
+    // Generate a handle based of the npad id
+    return static_cast<u64>(npad_id);
+}
+
+u32 NfpDevice::GetApplicationAreaSize() const {
+    // Investigate if this value is really constant
+    return sizeof(ApplicationArea);
+}
+
+DeviceState NfpDevice::GetCurrentState() const {
+    return device_state;
+}
+
+Core::HID::NpadIdType NfpDevice::GetNpadId() const {
+    return npad_id;
+}
+
+AmiiboName NfpDevice::GetAmiiboName(const AmiiboSettings& settings) const {
+    std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
+    AmiiboName amiibo_name{};
+
+    // Convert from big endian to little endian
+    for (std::size_t i = 0; i < amiibo_name_length; i++) {
+        settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]);
+    }
+
+    // Convert from utf16 to utf8
+    const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
+    memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
+
+    return amiibo_name;
+}
+
+void NfpDevice::SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name) {
+    std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
+
+    // Convert from utf8 to utf16
+    const auto amiibo_name_utf16 = Common::UTF8ToUTF16(amiibo_name.data());
+    memcpy(settings_amiibo_name.data(), amiibo_name_utf16.data(),
+           amiibo_name_utf16.size() * sizeof(char16_t));
+
+    // Convert from little endian to big endian
+    for (std::size_t i = 0; i < amiibo_name_length; i++) {
+        settings.amiibo_name[i] = static_cast<u16_be>(settings_amiibo_name[i]);
+    }
+}
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h
new file mode 100644
index 0000000000..53cc0833fc
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_device.h
@@ -0,0 +1,97 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_funcs.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/mii/types.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::NFP {
+class NfpDevice {
+public:
+    NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
+              KernelHelpers::ServiceContext& service_context_,
+              Kernel::KEvent* availability_change_event_);
+    ~NfpDevice();
+
+    void Initialize();
+    void Finalize();
+
+    Result StartDetection(s32 protocol_);
+    Result StopDetection();
+    Result Mount();
+    Result Unmount();
+    Result Flush();
+
+    Result GetTagInfo(TagInfo& tag_info) const;
+    Result GetCommonInfo(CommonInfo& common_info) const;
+    Result GetModelInfo(ModelInfo& model_info) const;
+    Result GetRegisterInfo(RegisterInfo& register_info) const;
+
+    Result SetNicknameAndOwner(const AmiiboName& amiibo_name);
+    Result RestoreAmiibo();
+    Result DeleteAllData();
+
+    Result OpenApplicationArea(u32 access_id);
+    Result GetApplicationArea(std::vector<u8>& data) const;
+    Result SetApplicationArea(const std::vector<u8>& data);
+    Result CreateApplicationArea(u32 access_id, const std::vector<u8>& data);
+    Result RecreateApplicationArea(u32 access_id, const std::vector<u8>& data);
+    Result DeleteApplicationArea();
+
+    u64 GetHandle() const;
+    u32 GetApplicationAreaSize() const;
+    DeviceState GetCurrentState() const;
+    Core::HID::NpadIdType GetNpadId() const;
+
+    Kernel::KReadableEvent& GetActivateEvent() const;
+    Kernel::KReadableEvent& GetDeactivateEvent() const;
+
+private:
+    void NpadUpdate(Core::HID::ControllerTriggerType type);
+    bool LoadAmiibo(const std::vector<u8>& data);
+    void CloseAmiibo();
+
+    AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
+    void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name);
+
+    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;
+
+    bool is_data_moddified{};
+    s32 protocol{};
+    DeviceState device_state{DeviceState::Unavailable};
+
+    NTAG215File tag_data{};
+    EncryptedNTAG215File encrypted_tag_data{};
+};
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_result.h b/src/core/hle/service/nfp/nfp_result.h
new file mode 100644
index 0000000000..15bc02b15d
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_result.h
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::NFP {
+
+constexpr Result DeviceNotFound(ErrorModule::NFP, 64);
+constexpr Result WrongDeviceState(ErrorModule::NFP, 73);
+constexpr Result NfcDisabled(ErrorModule::NFP, 80);
+constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88);
+constexpr Result TagRemoved(ErrorModule::NFP, 97);
+constexpr Result RegistrationIsNotInitialized(ErrorModule::NFP, 120);
+constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
+constexpr Result CorruptedData(ErrorModule::NFP, 144);
+constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152);
+constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168);
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h
new file mode 100644
index 0000000000..d58657a216
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_types.h
@@ -0,0 +1,262 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "core/hle/service/mii/types.h"
+
+namespace Service::NFP {
+static constexpr std::size_t amiibo_name_length = 0xA;
+
+enum class ServiceType : u32 {
+    User,
+    Debug,
+    System,
+};
+
+enum class State : u32 {
+    NonInitialized,
+    Initialized,
+};
+
+enum class DeviceState : u32 {
+    Initialized,
+    SearchingForTag,
+    TagFound,
+    TagRemoved,
+    TagMounted,
+    Unavailable,
+    Finalized,
+};
+
+enum class ModelType : u32 {
+    Amiibo,
+};
+
+enum class MountTarget : u32 {
+    None,
+    Rom,
+    Ram,
+    All,
+};
+
+enum class AmiiboType : u8 {
+    Figure,
+    Card,
+    Yarn,
+};
+
+enum class AmiiboSeries : u8 {
+    SuperSmashBros,
+    SuperMario,
+    ChibiRobo,
+    YoshiWoollyWorld,
+    Splatoon,
+    AnimalCrossing,
+    EightBitMario,
+    Skylanders,
+    Unknown8,
+    TheLegendOfZelda,
+    ShovelKnight,
+    Unknown11,
+    Kiby,
+    Pokemon,
+    MarioSportsSuperstars,
+    MonsterHunter,
+    BoxBoy,
+    Pikmin,
+    FireEmblem,
+    Metroid,
+    Others,
+    MegaMan,
+    Diablo,
+};
+
+using TagUuid = std::array<u8, 10>;
+using HashData = std::array<u8, 0x20>;
+using ApplicationArea = std::array<u8, 0xD8>;
+using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
+
+struct AmiiboDate {
+    u16_be raw_date{};
+
+    u16 DateRaw() const {
+        return static_cast<u16>(raw_date);
+    }
+
+    u16 GetYear() const {
+        return static_cast<u16>(((DateRaw() & 0xFE00) >> 9) + 2000);
+    }
+    u8 GetMonth() const {
+        return static_cast<u8>(((DateRaw() & 0x01E0) >> 5) - 1);
+    }
+    u8 GetDay() const {
+        return static_cast<u8>(DateRaw() & 0x001F);
+    }
+
+    void SetYear(u16 year) {
+        raw_date = DateRaw() & ~0xFE00;
+        raw_date |= static_cast<u16_be>((year - 2000) << 9);
+    }
+    void SetMonth(u8 month) {
+        raw_date = DateRaw() & ~0x01E0;
+        raw_date |= static_cast<u16_be>((month + 1) << 5);
+    }
+    void SetDay(u8 day) {
+        raw_date = DateRaw() & ~0x001F;
+        raw_date |= static_cast<u16_be>(day);
+    }
+};
+static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
+
+struct Settings {
+    union {
+        u8 raw{};
+
+        BitField<4, 1, u8> amiibo_initialized;
+        BitField<5, 1, u8> appdata_initialized;
+    };
+};
+static_assert(sizeof(Settings) == 1, "AmiiboDate is an invalid size");
+
+struct AmiiboSettings {
+    Settings settings;
+    u8 country_code_id;
+    u16_be crc_counter; // Incremented each time crc is changed
+    AmiiboDate init_date;
+    AmiiboDate write_date;
+    u32_be crc;
+    std::array<u16_be, amiibo_name_length> amiibo_name; // UTF-16 text
+};
+static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size");
+
+struct AmiiboModelInfo {
+    u16 character_id;
+    u8 character_variant;
+    AmiiboType amiibo_type;
+    u16 model_number;
+    AmiiboSeries series;
+    u8 constant_value;         // Must be 02
+    INSERT_PADDING_BYTES(0x4); // Unknown
+};
+static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size");
+
+struct NTAG215Password {
+    u32 PWD;  // Password to allow write access
+    u16 PACK; // Password acknowledge reply
+    u16 RFUI; // Reserved for future use
+};
+static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size");
+
+#pragma pack(1)
+struct EncryptedAmiiboFile {
+    u8 constant_value;                     // Must be A5
+    u16_be write_counter;                  // Number of times the amiibo has been written?
+    INSERT_PADDING_BYTES(0x1);             // Unknown 1
+    AmiiboSettings settings;               // Encrypted amiibo settings
+    HashData hmac_tag;                     // Hash
+    AmiiboModelInfo model_info;            // Encrypted amiibo model info
+    HashData keygen_salt;                  // Salt
+    HashData hmac_data;                    // Hash
+    Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
+    u64_be title_id;                       // Encrypted Game id
+    u16_be applicaton_write_counter;       // Encrypted Counter
+    u32_be application_area_id;            // Encrypted Game id
+    std::array<u8, 0x2> unknown;
+    HashData hash;                    // Probably a SHA256-HMAC hash?
+    ApplicationArea application_area; // Encrypted Game data
+};
+static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
+
+struct NTAG215File {
+    std::array<u8, 0x2> uuid2;
+    u16 static_lock;           // Set defined pages as read only
+    u32 compability_container; // Defines available memory
+    HashData hmac_data;        // Hash
+    u8 constant_value;         // Must be A5
+    u16_be write_counter;      // Number of times the amiibo has been written?
+    INSERT_PADDING_BYTES(0x1); // Unknown 1
+    AmiiboSettings settings;
+    Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
+    u64_be title_id;
+    u16_be applicaton_write_counter; // Encrypted Counter
+    u32_be application_area_id;
+    std::array<u8, 0x2> unknown;
+    HashData hash;                    // Probably a SHA256-HMAC hash?
+    ApplicationArea application_area; // Encrypted Game data
+    HashData hmac_tag;                // Hash
+    std::array<u8, 0x8> uuid;
+    AmiiboModelInfo model_info;
+    HashData keygen_salt;     // Salt
+    u32 dynamic_lock;         // Dynamic lock
+    u32 CFG0;                 // Defines memory protected by password
+    u32 CFG1;                 // Defines number of verification attempts
+    NTAG215Password password; // Password data
+};
+static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size");
+static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be trivially copyable.");
+#pragma pack()
+
+struct EncryptedNTAG215File {
+    TagUuid uuid;                    // Unique serial number
+    u16 static_lock;                 // Set defined pages as read only
+    u32 compability_container;       // Defines available memory
+    EncryptedAmiiboFile user_memory; // Writable data
+    u32 dynamic_lock;                // Dynamic lock
+    u32 CFG0;                        // Defines memory protected by password
+    u32 CFG1;                        // Defines number of verification attempts
+    NTAG215Password password;        // Password data
+};
+static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an invalid size");
+static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
+              "EncryptedNTAG215File must be trivially copyable.");
+
+struct TagInfo {
+    TagUuid uuid;
+    u8 uuid_length;
+    INSERT_PADDING_BYTES(0x15);
+    s32 protocol;
+    u32 tag_type;
+    INSERT_PADDING_BYTES(0x30);
+};
+static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
+
+struct WriteDate {
+    u16 year;
+    u8 month;
+    u8 day;
+};
+static_assert(sizeof(WriteDate) == 0x4, "WriteDate is an invalid size");
+
+struct CommonInfo {
+    WriteDate last_write_date;
+    u16 write_counter;
+    u8 version;
+    INSERT_PADDING_BYTES(0x1);
+    u32 application_area_size;
+    INSERT_PADDING_BYTES(0x34);
+};
+static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
+
+struct ModelInfo {
+    u16 character_id;
+    u8 character_variant;
+    AmiiboType amiibo_type;
+    u16 model_number;
+    AmiiboSeries series;
+    INSERT_PADDING_BYTES(0x39); // Unknown
+};
+static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
+
+struct RegisterInfo {
+    Service::Mii::CharInfo mii_char_info;
+    WriteDate creation_date;
+    AmiiboName amiibo_name;
+    u8 font_region;
+    INSERT_PADDING_BYTES(0x7A);
+};
+static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp
index 2d7b156cfd..f8f1975db1 100644
--- a/src/core/hle/service/nfp/nfp_user.cpp
+++ b/src/core/hle/service/nfp/nfp_user.cpp
@@ -1,18 +1,644 @@
 // 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/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/mii/mii_manager.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"
 
 namespace Service::NFP {
 
-NFP_User::NFP_User(std::shared_ptr<Module> module_, Core::System& system_)
-    : Interface(std::move(module_), system_, "nfp:user") {
+IUser::IUser(Core::System& system_)
+    : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name} {
     static const FunctionInfo functions[] = {
-        {0, &NFP_User::CreateUserInterface, "CreateUserInterface"},
+        {0, &IUser::Initialize, "Initialize"},
+        {1, &IUser::Finalize, "Finalize"},
+        {2, &IUser::ListDevices, "ListDevices"},
+        {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, &IUser::SetApplicationArea, "SetApplicationArea"},
+        {10, &IUser::Flush, "Flush"},
+        {11, &IUser::Restore, "Restore"},
+        {12, &IUser::CreateApplicationArea, "CreateApplicationArea"},
+        {13, &IUser::GetTagInfo, "GetTagInfo"},
+        {14, &IUser::GetRegisterInfo, "GetRegisterInfo"},
+        {15, &IUser::GetCommonInfo, "GetCommonInfo"},
+        {16, &IUser::GetModelInfo, "GetModelInfo"},
+        {17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
+        {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
+        {19, &IUser::GetState, "GetState"},
+        {20, &IUser::GetDeviceState, "GetDeviceState"},
+        {21, &IUser::GetNpadId, "GetNpadId"},
+        {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
+        {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
+        {24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
     };
     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<NfpDevice>(Core::HID::IndexToNpadIdType(device_index), system,
+                                        service_context, availability_change_event);
+    }
+}
+
+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_NFP, "called");
+
+    state = State::NonInitialized;
+
+    for (auto& device : devices) {
+        device->Finalize();
+    }
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(ResultSuccess);
+}
+
+void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
+    LOG_INFO(Service_NFP, "called");
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        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() != DeviceState::Unavailable) {
+            nfp_devices.push_back(device->GetHandle());
+        }
+    }
+
+    if (nfp_devices.size() == 0) {
+        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::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_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(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_NFP, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(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::Mount(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    const auto model_type{rp.PopEnum<ModelType>()};
+    const auto mount_target{rp.PopEnum<MountTarget>()};
+    LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle,
+             model_type, mount_target);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    const auto result = device.value()->Mount();
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
+}
+
+void IUser::Unmount(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    const auto result = device.value()->Unmount();
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
 }
 
-NFP_User::~NFP_User() = default;
+void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    const auto access_id{rp.Pop<u32>()};
+    LOG_INFO(Service_NFP, "called, device_handle={}, access_id={}", device_handle, access_id);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    const auto result = device.value()->OpenApplicationArea(access_id);
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
+}
+
+void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    const auto data_size = ctx.GetWriteBufferSize();
+    LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    std::vector<u8> data(data_size);
+    const auto result = device.value()->GetApplicationArea(data);
+    ctx.WriteBuffer(data);
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(result);
+    rb.Push(static_cast<u32>(data_size));
+}
+
+void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    const auto data{ctx.ReadBuffer()};
+    LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}", device_handle, data.size());
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    const auto result = device.value()->SetApplicationArea(data);
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
+}
+
+void IUser::Flush(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    const auto result = device.value()->Flush();
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
+}
+
+void IUser::Restore(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    const auto result = device.value()->RestoreAmiibo();
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
+}
+
+void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    const auto access_id{rp.Pop<u32>()};
+    const auto data{ctx.ReadBuffer()};
+    LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle,
+             access_id, data.size());
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    const auto result = device.value()->CreateApplicationArea(access_id, data);
+    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_NFP, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    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::GetRegisterInfo(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    RegisterInfo register_info{};
+    const auto result = device.value()->GetRegisterInfo(register_info);
+    ctx.WriteBuffer(register_info);
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
+}
+
+void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    CommonInfo common_info{};
+    const auto result = device.value()->GetCommonInfo(common_info);
+    ctx.WriteBuffer(common_info);
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
+}
+
+void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    ModelInfo model_info{};
+    const auto result = device.value()->GetModelInfo(model_info);
+    ctx.WriteBuffer(model_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_NFP, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(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_NFP, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(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::GetState(Kernel::HLERequestContext& ctx) {
+    LOG_DEBUG(Service_NFC, "called");
+
+    IPC::ResponseBuilder rb{ctx, 3, 0};
+    rb.Push(ResultSuccess);
+    rb.PushEnum(state);
+}
+
+void IUser::GetDeviceState(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+    auto device = GetNfpDevice(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_NFP, "called, device_handle={}", device_handle);
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(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::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+    auto device = GetNfpDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    rb.Push(device.value()->GetApplicationAreaSize());
+}
+
+void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
+    LOG_INFO(Service_NFP, "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::RecreateApplicationArea(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto device_handle{rp.Pop<u64>()};
+    const auto access_id{rp.Pop<u32>()};
+    const auto data{ctx.ReadBuffer()};
+    LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle,
+             access_id, data.size());
+
+    if (state == State::NonInitialized) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(NfcDisabled);
+        return;
+    }
+
+    auto device = GetNfpDevice(device_handle);
+
+    if (!device.has_value()) {
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(DeviceNotFound);
+        return;
+    }
+
+    const auto result = device.value()->RecreateApplicationArea(access_id, data);
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
+}
+
+std::optional<std::shared_ptr<NfpDevice>> IUser::GetNfpDevice(u64 handle) {
+    for (auto& device : devices) {
+        if (device->GetHandle() == handle) {
+            return device;
+        }
+    }
+    return std::nullopt;
+}
 
 } // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h
index 519ff56ee2..68c60ae82e 100644
--- a/src/core/hle/service/nfp/nfp_user.h
+++ b/src/core/hle/service/nfp/nfp_user.h
@@ -3,14 +3,52 @@
 
 #pragma once
 
+#include "core/hle/service/kernel_helpers.h"
 #include "core/hle/service/nfp/nfp.h"
+#include "core/hle/service/nfp/nfp_types.h"
 
 namespace Service::NFP {
+class NfpDevice;
 
-class NFP_User final : public Module::Interface {
+class IUser final : public ServiceFramework<IUser> {
 public:
-    explicit NFP_User(std::shared_ptr<Module> module_, Core::System& system_);
-    ~NFP_User() override;
+    explicit IUser(Core::System& system_);
+
+private:
+    void Initialize(Kernel::HLERequestContext& ctx);
+    void Finalize(Kernel::HLERequestContext& ctx);
+    void ListDevices(Kernel::HLERequestContext& ctx);
+    void StartDetection(Kernel::HLERequestContext& ctx);
+    void StopDetection(Kernel::HLERequestContext& ctx);
+    void Mount(Kernel::HLERequestContext& ctx);
+    void Unmount(Kernel::HLERequestContext& ctx);
+    void OpenApplicationArea(Kernel::HLERequestContext& ctx);
+    void GetApplicationArea(Kernel::HLERequestContext& ctx);
+    void SetApplicationArea(Kernel::HLERequestContext& ctx);
+    void Flush(Kernel::HLERequestContext& ctx);
+    void Restore(Kernel::HLERequestContext& ctx);
+    void CreateApplicationArea(Kernel::HLERequestContext& ctx);
+    void GetTagInfo(Kernel::HLERequestContext& ctx);
+    void GetRegisterInfo(Kernel::HLERequestContext& ctx);
+    void GetCommonInfo(Kernel::HLERequestContext& ctx);
+    void GetModelInfo(Kernel::HLERequestContext& ctx);
+    void AttachActivateEvent(Kernel::HLERequestContext& ctx);
+    void AttachDeactivateEvent(Kernel::HLERequestContext& ctx);
+    void GetState(Kernel::HLERequestContext& ctx);
+    void GetDeviceState(Kernel::HLERequestContext& ctx);
+    void GetNpadId(Kernel::HLERequestContext& ctx);
+    void GetApplicationAreaSize(Kernel::HLERequestContext& ctx);
+    void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
+    void RecreateApplicationArea(Kernel::HLERequestContext& ctx);
+
+    std::optional<std::shared_ptr<NfpDevice>> GetNfpDevice(u64 handle);
+
+    KernelHelpers::ServiceContext service_context;
+
+    std::array<std::shared_ptr<NfpDevice>, 10> devices{};
+
+    State state{State::NonInitialized};
+    Kernel::KEvent* availability_change_event;
 };
 
 } // namespace Service::NFP
-- 
cgit v1.2.3-70-g09d2


From 3ce0ef04ddcb2420b61f8c6d22f8039fb7359856 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sat, 24 Sep 2022 22:52:33 -0500
Subject: service: nfp: address comments

---
 src/common/input.h                          |  2 +-
 src/core/hid/input_converter.cpp            |  2 +-
 src/core/hle/service/nfp/nfp_device.cpp     | 11 ++++++-----
 src/core/hle/service/nfp/nfp_device.h       |  4 ++--
 src/core/hle/service/nfp/nfp_types.h        | 25 +++++++++++++------------
 src/input_common/drivers/virtual_amiibo.cpp |  4 ++--
 src/input_common/drivers/virtual_amiibo.h   |  2 +-
 src/input_common/input_engine.h             |  3 ++-
 src/input_common/input_poller.cpp           |  2 +-
 9 files changed, 29 insertions(+), 26 deletions(-)

(limited to 'src')

diff --git a/src/common/input.h b/src/common/input.h
index 8365cc36ed..bfa0639f5e 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -332,7 +332,7 @@ public:
         return CameraError::NotSupported;
     }
 
-    virtual NfcState SupportsNfc() {
+    virtual NfcState SupportsNfc() const {
         return NfcState::NotSupported;
     }
 
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
index e7b871accb..fe9915abe6 100644
--- a/src/core/hid/input_converter.cpp
+++ b/src/core/hid/input_converter.cpp
@@ -291,7 +291,7 @@ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& cal
     Common::Input::NfcStatus nfc{};
     switch (callback.type) {
     case Common::Input::InputType::Nfc:
-        nfc = callback.nfc_status;
+        return callback.nfc_status;
         break;
     default:
         LOG_ERROR(Input, "Conversion from type {} to NFC not implemented", callback.type);
diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp
index da7daae882..ce3bcccf45 100644
--- a/src/core/hle/service/nfp/nfp_device.cpp
+++ b/src/core/hle/service/nfp/nfp_device.cpp
@@ -42,10 +42,11 @@ NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
 }
 
 NfpDevice::~NfpDevice() {
-    if (is_controller_set) {
-        npad_device->DeleteCallback(callback_key);
-        is_controller_set = false;
+    if (!is_controller_set) {
+        return;
     }
+    npad_device->DeleteCallback(callback_key);
+    is_controller_set = false;
 };
 
 void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
@@ -453,7 +454,7 @@ Result NfpDevice::SetApplicationArea(const std::vector<u8>& data) {
     return ResultSuccess;
 }
 
-Result NfpDevice::CreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
+Result NfpDevice::CreateApplicationArea(u32 access_id, std::span<const u8> data) {
     if (device_state != DeviceState::TagMounted) {
         LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
         if (device_state == DeviceState::TagRemoved) {
@@ -470,7 +471,7 @@ Result NfpDevice::CreateApplicationArea(u32 access_id, const std::vector<u8>& da
     return RecreateApplicationArea(access_id, data);
 }
 
-Result NfpDevice::RecreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
+Result NfpDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> data) {
     if (device_state != DeviceState::TagMounted) {
         LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
         if (device_state == DeviceState::TagRemoved) {
diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h
index 53cc0833fc..9ceb7b8fde 100644
--- a/src/core/hle/service/nfp/nfp_device.h
+++ b/src/core/hle/service/nfp/nfp_device.h
@@ -56,8 +56,8 @@ public:
     Result OpenApplicationArea(u32 access_id);
     Result GetApplicationArea(std::vector<u8>& data) const;
     Result SetApplicationArea(const std::vector<u8>& data);
-    Result CreateApplicationArea(u32 access_id, const std::vector<u8>& data);
-    Result RecreateApplicationArea(u32 access_id, const std::vector<u8>& data);
+    Result CreateApplicationArea(u32 access_id, std::span<const u8> data);
+    Result RecreateApplicationArea(u32 access_id, std::span<const u8> data);
     Result DeleteApplicationArea();
 
     u64 GetHandle() const;
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h
index d58657a216..2685ae8fe5 100644
--- a/src/core/hle/service/nfp/nfp_types.h
+++ b/src/core/hle/service/nfp/nfp_types.h
@@ -5,6 +5,7 @@
 
 #include <array>
 
+#include "common/swap.h"
 #include "core/hle/service/mii/types.h"
 
 namespace Service::NFP {
@@ -80,33 +81,33 @@ using ApplicationArea = std::array<u8, 0xD8>;
 using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
 
 struct AmiiboDate {
-    u16_be raw_date{};
+    u16 raw_date{};
 
-    u16 DateRaw() const {
-        return static_cast<u16>(raw_date);
+    u16 GetValue() const {
+        return Common::swap16(raw_date);
     }
 
     u16 GetYear() const {
-        return static_cast<u16>(((DateRaw() & 0xFE00) >> 9) + 2000);
+        return static_cast<u16>(((GetValue() & 0xFE00) >> 9) + 2000);
     }
     u8 GetMonth() const {
-        return static_cast<u8>(((DateRaw() & 0x01E0) >> 5) - 1);
+        return static_cast<u8>(((GetValue() & 0x01E0) >> 5) - 1);
     }
     u8 GetDay() const {
-        return static_cast<u8>(DateRaw() & 0x001F);
+        return static_cast<u8>(GetValue() & 0x001F);
     }
 
     void SetYear(u16 year) {
-        raw_date = DateRaw() & ~0xFE00;
-        raw_date |= static_cast<u16_be>((year - 2000) << 9);
+        const u16 year_converted = static_cast<u16>((year - 2000) << 9);
+        raw_date = Common::swap16((GetValue() & ~0xFE00) | year_converted);
     }
     void SetMonth(u8 month) {
-        raw_date = DateRaw() & ~0x01E0;
-        raw_date |= static_cast<u16_be>((month + 1) << 5);
+        const u16 month_converted = static_cast<u16>((month + 1) << 5);
+        raw_date = Common::swap16((GetValue() & ~0x01E0) | month_converted);
     }
     void SetDay(u8 day) {
-        raw_date = DateRaw() & ~0x001F;
-        raw_date |= static_cast<u16_be>(day);
+        const u16 day_converted = static_cast<u16>(day);
+        raw_date = Common::swap16((GetValue() & ~0x001F) | day_converted);
     }
 };
 static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp
index 8fadb1322a..0cd5129da5 100644
--- a/src/input_common/drivers/virtual_amiibo.cpp
+++ b/src/input_common/drivers/virtual_amiibo.cpp
@@ -20,7 +20,7 @@ constexpr PadIdentifier identifier = {
 
 VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move(input_engine_)) {}
 
-VirtualAmiibo::~VirtualAmiibo() {}
+VirtualAmiibo::~VirtualAmiibo() = default;
 
 Common::Input::PollingError VirtualAmiibo::SetPollingMode(
     [[maybe_unused]] const PadIdentifier& identifier_,
@@ -41,7 +41,7 @@ Common::Input::PollingError VirtualAmiibo::SetPollingMode(
 }
 
 Common::Input::NfcState VirtualAmiibo::SupportsNfc(
-    [[maybe_unused]] const PadIdentifier& identifier_) {
+    [[maybe_unused]] const PadIdentifier& identifier_) const {
     return Common::Input::NfcState::Success;
 }
 
diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h
index 5790e4a1ff..9eac075445 100644
--- a/src/input_common/drivers/virtual_amiibo.h
+++ b/src/input_common/drivers/virtual_amiibo.h
@@ -39,7 +39,7 @@ public:
     Common::Input::PollingError SetPollingMode(
         const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override;
 
-    Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) override;
+    Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
 
     Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
                                          const std::vector<u8>& data) override;
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h
index 9b8470c6f0..cfbdb26bd7 100644
--- a/src/input_common/input_engine.h
+++ b/src/input_common/input_engine.h
@@ -129,7 +129,8 @@ public:
     }
 
     // Request nfc data from a controller
-    virtual Common::Input::NfcState SupportsNfc([[maybe_unused]] const PadIdentifier& identifier) {
+    virtual Common::Input::NfcState SupportsNfc(
+        [[maybe_unused]] const PadIdentifier& identifier) const {
         return Common::Input::NfcState::NotSupported;
     }
 
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index a8eb1442b1..75705b67e7 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -768,7 +768,7 @@ public:
         return input_engine->SetCameraFormat(identifier, camera_format);
     }
 
-    Common::Input::NfcState SupportsNfc() override {
+    Common::Input::NfcState SupportsNfc() const override {
         return input_engine->SupportsNfc(identifier);
     }
 
-- 
cgit v1.2.3-70-g09d2


From 673de3995b7f08d51fcc281439c05c368b7bd1ae Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Mon, 26 Sep 2022 00:58:36 -0500
Subject: nfp: Multiple fixes against HW

---
 src/core/hle/service/mii/mii_manager.cpp   | 68 +++++++++++++++++++++++++++++-
 src/core/hle/service/mii/mii_manager.h     |  1 +
 src/core/hle/service/nfc/nfc.cpp           |  8 ++--
 src/core/hle/service/nfp/amiibo_crypto.cpp | 55 +++++++++++++-----------
 src/core/hle/service/nfp/amiibo_crypto.h   |  6 ++-
 src/core/hle/service/nfp/nfp_device.cpp    | 60 ++++++++++++++++----------
 src/core/hle/service/nfp/nfp_device.h      |  2 +
 src/core/hle/service/nfp/nfp_result.h      |  1 +
 src/core/hle/service/nfp/nfp_types.h       | 24 ++++++++---
 9 files changed, 163 insertions(+), 62 deletions(-)

(limited to 'src')

diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index 3e92152ec0..4bc8703e19 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -431,8 +431,7 @@ CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const {
     Service::Mii::MiiManager manager;
     auto mii = manager.BuildDefault(0);
 
-    // Check if mii data exist
-    if (mii_v3.version == 0) {
+    if (!ValidateV3Info(mii_v3)) {
         return mii;
     }
 
@@ -576,6 +575,71 @@ Ver3StoreData MiiManager::ConvertCharInfoToV3(const CharInfo& mii) const {
     return mii_v3;
 }
 
+bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const {
+    bool is_valid = mii_v3.version == 0 || mii_v3.version == 3;
+
+    is_valid = is_valid && (mii_v3.mii_name[0] != 0);
+
+    is_valid = is_valid && (mii_v3.mii_information.birth_month < 13);
+    is_valid = is_valid && (mii_v3.mii_information.birth_day < 32);
+    is_valid = is_valid && (mii_v3.mii_information.favorite_color < 12);
+    is_valid = is_valid && (mii_v3.height < 128);
+    is_valid = is_valid && (mii_v3.build < 128);
+
+    is_valid = is_valid && (mii_v3.appearance_bits1.face_shape < 12);
+    is_valid = is_valid && (mii_v3.appearance_bits1.skin_color < 7);
+    is_valid = is_valid && (mii_v3.appearance_bits2.wrinkles < 12);
+    is_valid = is_valid && (mii_v3.appearance_bits2.makeup < 12);
+
+    is_valid = is_valid && (mii_v3.hair_style < 132);
+    is_valid = is_valid && (mii_v3.appearance_bits3.hair_color < 8);
+
+    is_valid = is_valid && (mii_v3.appearance_bits4.eye_type < 60);
+    is_valid = is_valid && (mii_v3.appearance_bits4.eye_color < 6);
+    is_valid = is_valid && (mii_v3.appearance_bits4.eye_scale < 8);
+    is_valid = is_valid && (mii_v3.appearance_bits4.eye_vertical_stretch < 7);
+    is_valid = is_valid && (mii_v3.appearance_bits4.eye_rotation < 8);
+    is_valid = is_valid && (mii_v3.appearance_bits4.eye_spacing < 13);
+    is_valid = is_valid && (mii_v3.appearance_bits4.eye_y_position < 19);
+
+    is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_style < 25);
+    is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_color < 8);
+    is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_scale < 9);
+    is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_yscale < 7);
+    is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_rotation < 12);
+    is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_spacing < 12);
+    is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_y_position < 19);
+
+    is_valid = is_valid && (mii_v3.appearance_bits6.nose_type < 18);
+    is_valid = is_valid && (mii_v3.appearance_bits6.nose_scale < 9);
+    is_valid = is_valid && (mii_v3.appearance_bits6.nose_y_position < 19);
+
+    is_valid = is_valid && (mii_v3.appearance_bits7.mouth_type < 36);
+    is_valid = is_valid && (mii_v3.appearance_bits7.mouth_color < 5);
+    is_valid = is_valid && (mii_v3.appearance_bits7.mouth_scale < 9);
+    is_valid = is_valid && (mii_v3.appearance_bits7.mouth_horizontal_stretch < 7);
+    is_valid = is_valid && (mii_v3.appearance_bits8.mouth_y_position < 19);
+
+    is_valid = is_valid && (mii_v3.appearance_bits8.mustache_type < 6);
+    is_valid = is_valid && (mii_v3.appearance_bits9.mustache_scale < 7);
+    is_valid = is_valid && (mii_v3.appearance_bits9.mustache_y_position < 17);
+
+    is_valid = is_valid && (mii_v3.appearance_bits9.bear_type < 6);
+    is_valid = is_valid && (mii_v3.appearance_bits9.facial_hair_color < 8);
+
+    is_valid = is_valid && (mii_v3.appearance_bits10.glasses_type < 9);
+    is_valid = is_valid && (mii_v3.appearance_bits10.glasses_color < 6);
+    is_valid = is_valid && (mii_v3.appearance_bits10.glasses_scale < 8);
+    is_valid = is_valid && (mii_v3.appearance_bits10.glasses_y_position < 21);
+
+    is_valid = is_valid && (mii_v3.appearance_bits11.mole_enabled < 2);
+    is_valid = is_valid && (mii_v3.appearance_bits11.mole_scale < 9);
+    is_valid = is_valid && (mii_v3.appearance_bits11.mole_x_position < 17);
+    is_valid = is_valid && (mii_v3.appearance_bits11.mole_y_position < 31);
+
+    return is_valid;
+}
+
 ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) {
     std::vector<MiiInfoElement> result;
 
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
index b68fdd54c0..83ad3d3431 100644
--- a/src/core/hle/service/mii/mii_manager.h
+++ b/src/core/hle/service/mii/mii_manager.h
@@ -24,6 +24,7 @@ public:
     CharInfo BuildDefault(std::size_t index);
     CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const;
     Ver3StoreData ConvertCharInfoToV3(const CharInfo& mii) const;
+    bool ValidateV3Info(const Ver3StoreData& mii_v3) const;
     ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag);
     Result GetIndex(const CharInfo& info, u32& index);
 
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index 13a843a28d..046c5f18fd 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.cpp
@@ -106,10 +106,10 @@ public:
             {1, &IUser::FinalizeOld, "FinalizeOld"},
             {2, &IUser::GetStateOld, "GetStateOld"},
             {3, &IUser::IsNfcEnabledOld, "IsNfcEnabledOld"},
-            {400, nullptr, "Initialize"},
-            {401, nullptr, "Finalize"},
-            {402, nullptr, "GetState"},
-            {403, nullptr, "IsNfcEnabled"},
+            {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"},
diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp
index c87da5ae42..9e06970a4d 100644
--- a/src/core/hle/service/nfp/amiibo_crypto.cpp
+++ b/src/core/hle/service/nfp/amiibo_crypto.cpp
@@ -25,7 +25,8 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
     LOG_DEBUG(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
     LOG_DEBUG(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
     LOG_DEBUG(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
-    LOG_DEBUG(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number);
+    LOG_DEBUG(Service_NFP, "model_number=0x{0:x}",
+              static_cast<u16>(amiibo_data.model_info.model_number));
     LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series);
     LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.constant_value);
 
@@ -35,11 +36,12 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
 
     // Validate UUID
     constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3`
-    if ((CT ^ ntag_file.uuid[0] ^ ntag_file.uuid[1] ^ ntag_file.uuid[2]) != ntag_file.uuid[3]) {
+    if ((CT ^ ntag_file.uuid.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) !=
+        ntag_file.uuid.uid[3]) {
         return false;
     }
-    if ((ntag_file.uuid[4] ^ ntag_file.uuid[5] ^ ntag_file.uuid[6] ^ ntag_file.uuid[7]) !=
-        ntag_file.uuid[8]) {
+    if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^
+         ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) {
         return false;
     }
 
@@ -70,7 +72,8 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
 NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
     NTAG215File encoded_data{};
 
-    memcpy(encoded_data.uuid2.data(), nfc_data.uuid.data() + 0x8, sizeof(encoded_data.uuid2));
+    encoded_data.uid = nfc_data.uuid.uid;
+    encoded_data.nintendo_id = nfc_data.uuid.nintendo_id;
     encoded_data.static_lock = nfc_data.static_lock;
     encoded_data.compability_container = nfc_data.compability_container;
     encoded_data.hmac_data = nfc_data.user_memory.hmac_data;
@@ -85,7 +88,7 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
     encoded_data.hash = nfc_data.user_memory.hash;
     encoded_data.application_area = nfc_data.user_memory.application_area;
     encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
-    memcpy(encoded_data.uuid.data(), nfc_data.uuid.data(), sizeof(encoded_data.uuid));
+    encoded_data.lock_bytes = nfc_data.uuid.lock_bytes;
     encoded_data.model_info = nfc_data.user_memory.model_info;
     encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt;
     encoded_data.dynamic_lock = nfc_data.dynamic_lock;
@@ -99,8 +102,9 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
 EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
     EncryptedNTAG215File nfc_data{};
 
-    memcpy(nfc_data.uuid.data() + 0x8, encoded_data.uuid2.data(), sizeof(encoded_data.uuid2));
-    memcpy(nfc_data.uuid.data(), encoded_data.uuid.data(), sizeof(encoded_data.uuid));
+    nfc_data.uuid.uid = encoded_data.uid;
+    nfc_data.uuid.nintendo_id = encoded_data.nintendo_id;
+    nfc_data.uuid.lock_bytes = encoded_data.lock_bytes;
     nfc_data.static_lock = encoded_data.static_lock;
     nfc_data.compability_container = encoded_data.compability_container;
     nfc_data.user_memory.hmac_data = encoded_data.hmac_data;
@@ -127,10 +131,10 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
 
 u32 GetTagPassword(const TagUuid& uuid) {
     // Verifiy that the generated password is correct
-    u32 password = 0xAA ^ (uuid[1] ^ uuid[3]);
-    password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8;
-    password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16;
-    password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24;
+    u32 password = 0xAA ^ (uuid.uid[1] ^ uuid.uid[3]);
+    password &= (0x55 ^ (uuid.uid[2] ^ uuid.uid[4])) << 8;
+    password &= (0xAA ^ (uuid.uid[3] ^ uuid.uid[5])) << 16;
+    password &= (0x55 ^ (uuid.uid[4] ^ uuid.uid[6])) << 24;
     return password;
 }
 
@@ -138,15 +142,13 @@ HashSeed GetSeed(const NTAG215File& data) {
     HashSeed seed{
         .magic = data.write_counter,
         .padding = {},
-        .uuid1 = {},
-        .uuid2 = {},
+        .uid_1 = data.uid,
+        .nintendo_id_1 = data.nintendo_id,
+        .uid_2 = data.uid,
+        .nintendo_id_2 = data.nintendo_id,
         .keygen_salt = data.keygen_salt,
     };
 
-    // Copy the first 8 bytes of uuid
-    memcpy(seed.uuid1.data(), data.uuid.data(), sizeof(seed.uuid1));
-    memcpy(seed.uuid2.data(), data.uuid.data(), sizeof(seed.uuid2));
-
     return seed;
 }
 
@@ -165,8 +167,10 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed
     output.insert(output.end(), key.magic_bytes.begin(),
                   key.magic_bytes.begin() + key.magic_length);
 
-    output.insert(output.end(), seed.uuid1.begin(), seed.uuid1.end());
-    output.insert(output.end(), seed.uuid2.begin(), seed.uuid2.end());
+    output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end());
+    output.emplace_back(seed.nintendo_id_1);
+    output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end());
+    output.emplace_back(seed.nintendo_id_2);
 
     for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) {
         output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i]));
@@ -250,14 +254,15 @@ void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& ou
                           reinterpret_cast<unsigned char*>(&out_data.settings));
 
     // Copy the rest of the data directly
-    out_data.uuid2 = in_data.uuid2;
+    out_data.uid = in_data.uid;
+    out_data.nintendo_id = in_data.nintendo_id;
+    out_data.lock_bytes = in_data.lock_bytes;
     out_data.static_lock = in_data.static_lock;
     out_data.compability_container = in_data.compability_container;
 
     out_data.constant_value = in_data.constant_value;
     out_data.write_counter = in_data.write_counter;
 
-    out_data.uuid = in_data.uuid;
     out_data.model_info = in_data.model_info;
     out_data.keygen_salt = in_data.keygen_salt;
     out_data.dynamic_lock = in_data.dynamic_lock;
@@ -309,7 +314,7 @@ bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& t
     // Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
     constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
     mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
-                    sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid),
+                    sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid),
                     input_length, reinterpret_cast<unsigned char*>(&tag_data.hmac_tag));
 
     // Regenerate data HMAC
@@ -350,7 +355,7 @@ bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_t
     constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
     constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START;
     mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
-                    sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid),
+                    sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid),
                     input_length, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag));
 
     // Init mbedtls HMAC context
@@ -364,7 +369,7 @@ bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_t
                            input_length2); // Data
     mbedtls_md_hmac_update(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
                            sizeof(HashData)); // Tag HMAC
-    mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uuid),
+    mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uid),
                            input_length);
     mbedtls_md_hmac_finish(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data));
 
diff --git a/src/core/hle/service/nfp/amiibo_crypto.h b/src/core/hle/service/nfp/amiibo_crypto.h
index e3fa3d07e4..0175ced919 100644
--- a/src/core/hle/service/nfp/amiibo_crypto.h
+++ b/src/core/hle/service/nfp/amiibo_crypto.h
@@ -24,8 +24,10 @@ using DrgbOutput = std::array<u8, 0x20>;
 struct HashSeed {
     u16_be magic;
     std::array<u8, 0xE> padding;
-    std::array<u8, 0x8> uuid1;
-    std::array<u8, 0x8> uuid2;
+    UniqueSerialNumber uid_1;
+    u8 nintendo_id_1;
+    UniqueSerialNumber uid_2;
+    u8 nintendo_id_2;
     std::array<u8, 0x20> keygen_salt;
 };
 static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size");
diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp
index ce3bcccf45..f0eaa7df2d 100644
--- a/src/core/hle/service/nfp/nfp_device.cpp
+++ b/src/core/hle/service/nfp/nfp_device.cpp
@@ -22,6 +22,9 @@
 #include "core/hle/service/nfp/nfp_device.h"
 #include "core/hle/service/nfp/nfp_result.h"
 #include "core/hle/service/nfp/nfp_user.h"
+#include "core/hle/service/time/time_manager.h"
+#include "core/hle/service/time/time_zone_content_manager.h"
+#include "core/hle/service/time/time_zone_types.h"
 
 namespace Service::NFP {
 NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
@@ -39,6 +42,9 @@ NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
     };
     is_controller_set = true;
     callback_key = npad_device->SetCallback(engine_callback);
+
+    auto& standard_steady_clock{system.GetTimeManager().GetStandardSteadyClockCore()};
+    current_posix_time = standard_steady_clock.GetCurrentTimePoint(system).time_point;
 }
 
 NfpDevice::~NfpDevice() {
@@ -98,6 +104,7 @@ bool NfpDevice::LoadAmiibo(const std::vector<u8>& data) {
     }
 
     device_state = DeviceState::TagFound;
+    deactivate_event->GetReadableEvent().Clear();
     activate_event->GetWritableEvent().Signal();
     return true;
 }
@@ -112,6 +119,7 @@ void NfpDevice::CloseAmiibo() {
     device_state = DeviceState::TagRemoved;
     encrypted_tag_data = {};
     tag_data = {};
+    activate_event->GetReadableEvent().Clear();
     deactivate_event->GetWritableEvent().Signal();
 }
 
@@ -140,8 +148,6 @@ void NfpDevice::Finalize() {
 }
 
 Result NfpDevice::StartDetection(s32 protocol_) {
-    // TODO(german77): Add callback for when nfc data is available
-
     if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
         npad_device->SetPollingMode(Common::Input::PollingMode::NFC);
         device_state = DeviceState::SearchingForTag;
@@ -172,11 +178,9 @@ Result NfpDevice::StopDetection() {
 Result NfpDevice::Flush() {
     auto& settings = tag_data.settings;
 
-    if (settings.write_date.raw_date != settings.write_date.raw_date) {
-        // TODO: Read current system date
-        settings.write_date.SetYear(2022);
-        settings.write_date.SetMonth(9);
-        settings.write_date.SetDay(9);
+    const auto& current_date = GetAmiiboDate(current_posix_time);
+    if (settings.write_date.raw_date != current_date.raw_date) {
+        settings.write_date = current_date;
         settings.crc_counter++;
         // TODO: Find how to calculate the crc check
         // settings.crc = CalculateCRC(settings);
@@ -239,10 +243,10 @@ Result NfpDevice::GetTagInfo(TagInfo& tag_info) const {
     }
 
     tag_info = {
-        .uuid = encrypted_tag_data.uuid,
-        .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.size()),
-        .protocol = protocol,
-        .tag_type = static_cast<u32>(encrypted_tag_data.user_memory.model_info.amiibo_type),
+        .uuid = encrypted_tag_data.uuid.uid,
+        .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
+        .protocol = 1,
+        .tag_type = 2,
     };
 
     return ResultSuccess;
@@ -255,8 +259,6 @@ Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const {
     }
 
     const auto& settings = tag_data.settings;
-    const u32 application_area_size =
-        tag_data.settings.settings.appdata_initialized == 0 ? 0 : sizeof(ApplicationArea);
 
     // TODO: Validate this data
     common_info = {
@@ -267,8 +269,8 @@ Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const {
                 settings.write_date.GetDay(),
             },
         .write_counter = tag_data.write_counter,
-        .version = 1,
-        .application_area_size = application_area_size,
+        .version = 0,
+        .application_area_size = sizeof(ApplicationArea),
     };
     return ResultSuccess;
 }
@@ -334,13 +336,8 @@ Result NfpDevice::SetNicknameAndOwner(const AmiiboName& amiibo_name) {
     Service::Mii::MiiManager manager;
     auto& settings = tag_data.settings;
 
-    // TODO: Read current system date
-    settings.init_date.SetYear(2022);
-    settings.init_date.SetMonth(9);
-    settings.init_date.SetDay(9);
-    settings.write_date.SetYear(2022);
-    settings.write_date.SetMonth(9);
-    settings.write_date.SetDay(9);
+    settings.init_date = GetAmiiboDate(current_posix_time);
+    settings.write_date = GetAmiiboDate(current_posix_time);
     settings.crc_counter++;
     // TODO: Find how to calculate the crc check
     // settings.crc = CalculateCRC(settings);
@@ -570,4 +567,23 @@ void NfpDevice::SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo
     }
 }
 
+AmiiboDate NfpDevice::GetAmiiboDate(s64 posix_time) const {
+    const auto& time_zone_manager =
+        system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
+    Time::TimeZone::CalendarInfo calendar_info{};
+    AmiiboDate amiibo_date{};
+
+    amiibo_date.SetYear(2000);
+    amiibo_date.SetMonth(1);
+    amiibo_date.SetDay(1);
+
+    if (time_zone_manager.ToCalendarTime({}, posix_time, calendar_info) == ResultSuccess) {
+        amiibo_date.SetYear(calendar_info.time.year);
+        amiibo_date.SetMonth(calendar_info.time.month);
+        amiibo_date.SetDay(calendar_info.time.day);
+    }
+
+    return amiibo_date;
+}
+
 } // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h
index 9ceb7b8fde..c020506a6b 100644
--- a/src/core/hle/service/nfp/nfp_device.h
+++ b/src/core/hle/service/nfp/nfp_device.h
@@ -75,6 +75,7 @@ private:
 
     AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
     void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name);
+    AmiiboDate GetAmiiboDate(s64 posix_time) const;
 
     bool is_controller_set{};
     int callback_key;
@@ -88,6 +89,7 @@ private:
 
     bool is_data_moddified{};
     s32 protocol{};
+    s64 current_posix_time{};
     DeviceState device_state{DeviceState::Unavailable};
 
     NTAG215File tag_data{};
diff --git a/src/core/hle/service/nfp/nfp_result.h b/src/core/hle/service/nfp/nfp_result.h
index 15bc02b15d..ac259e2ff7 100644
--- a/src/core/hle/service/nfp/nfp_result.h
+++ b/src/core/hle/service/nfp/nfp_result.h
@@ -17,5 +17,6 @@ constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
 constexpr Result CorruptedData(ErrorModule::NFP, 144);
 constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152);
 constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168);
+constexpr Result NotAnAmiibo(ErrorModule::NFP, 178);
 
 } // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h
index 2685ae8fe5..4487918467 100644
--- a/src/core/hle/service/nfp/nfp_types.h
+++ b/src/core/hle/service/nfp/nfp_types.h
@@ -75,11 +75,19 @@ enum class AmiiboSeries : u8 {
     Diablo,
 };
 
-using TagUuid = std::array<u8, 10>;
+using UniqueSerialNumber = std::array<u8, 7>;
+using LockBytes = std::array<u8, 2>;
 using HashData = std::array<u8, 0x20>;
 using ApplicationArea = std::array<u8, 0xD8>;
 using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
 
+struct TagUuid {
+    UniqueSerialNumber uid;
+    u8 nintendo_id;
+    LockBytes lock_bytes;
+};
+static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size");
+
 struct AmiiboDate {
     u16 raw_date{};
 
@@ -91,7 +99,7 @@ struct AmiiboDate {
         return static_cast<u16>(((GetValue() & 0xFE00) >> 9) + 2000);
     }
     u8 GetMonth() const {
-        return static_cast<u8>(((GetValue() & 0x01E0) >> 5) - 1);
+        return static_cast<u8>((GetValue() & 0x01E0) >> 5);
     }
     u8 GetDay() const {
         return static_cast<u8>(GetValue() & 0x001F);
@@ -102,7 +110,7 @@ struct AmiiboDate {
         raw_date = Common::swap16((GetValue() & ~0xFE00) | year_converted);
     }
     void SetMonth(u8 month) {
-        const u16 month_converted = static_cast<u16>((month + 1) << 5);
+        const u16 month_converted = static_cast<u16>(month << 5);
         raw_date = Common::swap16((GetValue() & ~0x01E0) | month_converted);
     }
     void SetDay(u8 day) {
@@ -137,7 +145,7 @@ struct AmiiboModelInfo {
     u16 character_id;
     u8 character_variant;
     AmiiboType amiibo_type;
-    u16 model_number;
+    u16_be model_number;
     AmiiboSeries series;
     u8 constant_value;         // Must be 02
     INSERT_PADDING_BYTES(0x4); // Unknown
@@ -172,7 +180,7 @@ struct EncryptedAmiiboFile {
 static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
 
 struct NTAG215File {
-    std::array<u8, 0x2> uuid2;
+    LockBytes lock_bytes;      // Tag UUID
     u16 static_lock;           // Set defined pages as read only
     u32 compability_container; // Defines available memory
     HashData hmac_data;        // Hash
@@ -188,7 +196,8 @@ struct NTAG215File {
     HashData hash;                    // Probably a SHA256-HMAC hash?
     ApplicationArea application_area; // Encrypted Game data
     HashData hmac_tag;                // Hash
-    std::array<u8, 0x8> uuid;
+    UniqueSerialNumber uid;           // Unique serial number
+    u8 nintendo_id;                   // Tag UUID
     AmiiboModelInfo model_info;
     HashData keygen_salt;     // Salt
     u32 dynamic_lock;         // Dynamic lock
@@ -215,7 +224,8 @@ static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
               "EncryptedNTAG215File must be trivially copyable.");
 
 struct TagInfo {
-    TagUuid uuid;
+    UniqueSerialNumber uuid;
+    INSERT_PADDING_BYTES(0x3);
     u8 uuid_length;
     INSERT_PADDING_BYTES(0x15);
     s32 protocol;
-- 
cgit v1.2.3-70-g09d2


From d9d566bd3f6cb8fd4f8d3d2d17851e0568ddf946 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Wed, 28 Sep 2022 00:47:51 -0500
Subject: service: nfp: Implement mount target and open application area
 errors, minor fixes

---
 src/core/hle/service/nfp/amiibo_crypto.cpp | 10 +--
 src/core/hle/service/nfp/nfp_device.cpp    | 99 ++++++++++++++++++++++++++++--
 src/core/hle/service/nfp/nfp_device.h      |  8 ++-
 src/core/hle/service/nfp/nfp_types.h       | 24 ++++++--
 src/core/hle/service/nfp/nfp_user.cpp      |  2 +-
 5 files changed, 124 insertions(+), 19 deletions(-)

(limited to 'src')

diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp
index 9e06970a4d..ce0bc3f756 100644
--- a/src/core/hle/service/nfp/amiibo_crypto.cpp
+++ b/src/core/hle/service/nfp/amiibo_crypto.cpp
@@ -58,8 +58,9 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
     if (amiibo_data.model_info.constant_value != 0x02) {
         return false;
     }
-    // dynamic_lock value apparently is not constant
-    // ntag_file.dynamic_lock == 0x0F0001
+    if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001U) {
+        return false;
+    }
     if (ntag_file.CFG0 != 0x04000000U) {
         return false;
     }
@@ -85,7 +86,7 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
     encoded_data.applicaton_write_counter = nfc_data.user_memory.applicaton_write_counter;
     encoded_data.application_area_id = nfc_data.user_memory.application_area_id;
     encoded_data.unknown = nfc_data.user_memory.unknown;
-    encoded_data.hash = nfc_data.user_memory.hash;
+    encoded_data.unknown2 = nfc_data.user_memory.unknown2;
     encoded_data.application_area = nfc_data.user_memory.application_area;
     encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
     encoded_data.lock_bytes = nfc_data.uuid.lock_bytes;
@@ -116,7 +117,7 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
     nfc_data.user_memory.applicaton_write_counter = encoded_data.applicaton_write_counter;
     nfc_data.user_memory.application_area_id = encoded_data.application_area_id;
     nfc_data.user_memory.unknown = encoded_data.unknown;
-    nfc_data.user_memory.hash = encoded_data.hash;
+    nfc_data.user_memory.unknown2 = encoded_data.unknown2;
     nfc_data.user_memory.application_area = encoded_data.application_area;
     nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag;
     nfc_data.user_memory.model_info = encoded_data.model_info;
@@ -181,7 +182,6 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed
 
 void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
                 const std::vector<u8>& seed) {
-
     // Initialize context
     ctx.used = false;
     ctx.counter = 0;
diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp
index f0eaa7df2d..0d4ffd3a5f 100644
--- a/src/core/hle/service/nfp/nfp_device.cpp
+++ b/src/core/hle/service/nfp/nfp_device.cpp
@@ -85,7 +85,7 @@ void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
     }
 }
 
-bool NfpDevice::LoadAmiibo(const std::vector<u8>& data) {
+bool NfpDevice::LoadAmiibo(std::span<const u8> data) {
     if (device_state != DeviceState::SearchingForTag) {
         LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
         return false;
@@ -176,6 +176,19 @@ Result NfpDevice::StopDetection() {
 }
 
 Result NfpDevice::Flush() {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        if (device_state == DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+        return WrongDeviceState;
+    }
+
     auto& settings = tag_data.settings;
 
     const auto& current_date = GetAmiiboDate(current_posix_time);
@@ -206,7 +219,7 @@ Result NfpDevice::Flush() {
     return ResultSuccess;
 }
 
-Result NfpDevice::Mount() {
+Result NfpDevice::Mount(MountTarget mount_target_) {
     if (device_state != DeviceState::TagFound) {
         LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
         return WrongDeviceState;
@@ -218,6 +231,7 @@ Result NfpDevice::Mount() {
     }
 
     device_state = DeviceState::TagMounted;
+    mount_target = mount_target_;
     return ResultSuccess;
 }
 
@@ -233,6 +247,9 @@ Result NfpDevice::Unmount() {
     }
 
     device_state = DeviceState::TagFound;
+    mount_target = MountTarget::None;
+    is_app_area_open = false;
+
     return ResultSuccess;
 }
 
@@ -245,8 +262,8 @@ Result NfpDevice::GetTagInfo(TagInfo& tag_info) const {
     tag_info = {
         .uuid = encrypted_tag_data.uuid.uid,
         .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
-        .protocol = 1,
-        .tag_type = 2,
+        .protocol = TagProtocol::TypeA,
+        .tag_type = TagType::Type2,
     };
 
     return ResultSuccess;
@@ -255,6 +272,14 @@ Result NfpDevice::GetTagInfo(TagInfo& tag_info) const {
 Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const {
     if (device_state != DeviceState::TagMounted) {
         LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        if (device_state == DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
         return WrongDeviceState;
     }
 
@@ -301,6 +326,11 @@ Result NfpDevice::GetRegisterInfo(RegisterInfo& register_info) const {
         return WrongDeviceState;
     }
 
+    if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+        return WrongDeviceState;
+    }
+
     if (tag_data.settings.settings.amiibo_initialized == 0) {
         return RegistrationIsNotInitialized;
     }
@@ -333,6 +363,11 @@ Result NfpDevice::SetNicknameAndOwner(const AmiiboName& amiibo_name) {
         return WrongDeviceState;
     }
 
+    if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+        return WrongDeviceState;
+    }
+
     Service::Mii::MiiManager manager;
     auto& settings = tag_data.settings;
 
@@ -350,6 +385,19 @@ Result NfpDevice::SetNicknameAndOwner(const AmiiboName& amiibo_name) {
 }
 
 Result NfpDevice::RestoreAmiibo() {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+        if (device_state == DeviceState::TagRemoved) {
+            return TagRemoved;
+        }
+        return WrongDeviceState;
+    }
+
+    if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+        return WrongDeviceState;
+    }
+
     // TODO: Load amiibo from backup on system
     LOG_ERROR(Service_NFP, "Not Implemented");
     return ResultSuccess;
@@ -385,6 +433,11 @@ Result NfpDevice::OpenApplicationArea(u32 access_id) {
         return WrongDeviceState;
     }
 
+    if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+        return WrongDeviceState;
+    }
+
     if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
         LOG_WARNING(Service_NFP, "Application area is not initialized");
         return ApplicationAreaIsNotInitialized;
@@ -395,6 +448,8 @@ Result NfpDevice::OpenApplicationArea(u32 access_id) {
         return WrongApplicationAreaId;
     }
 
+    is_app_area_open = true;
+
     return ResultSuccess;
 }
 
@@ -407,6 +462,16 @@ Result NfpDevice::GetApplicationArea(std::vector<u8>& data) const {
         return WrongDeviceState;
     }
 
+    if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+        return WrongDeviceState;
+    }
+
+    if (!is_app_area_open) {
+        LOG_ERROR(Service_NFP, "Application area is not open");
+        return WrongDeviceState;
+    }
+
     if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
         LOG_ERROR(Service_NFP, "Application area is not initialized");
         return ApplicationAreaIsNotInitialized;
@@ -422,7 +487,7 @@ Result NfpDevice::GetApplicationArea(std::vector<u8>& data) const {
     return ResultSuccess;
 }
 
-Result NfpDevice::SetApplicationArea(const std::vector<u8>& data) {
+Result NfpDevice::SetApplicationArea(std::span<const u8> data) {
     if (device_state != DeviceState::TagMounted) {
         LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
         if (device_state == DeviceState::TagRemoved) {
@@ -431,6 +496,16 @@ Result NfpDevice::SetApplicationArea(const std::vector<u8>& data) {
         return WrongDeviceState;
     }
 
+    if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+        return WrongDeviceState;
+    }
+
+    if (!is_app_area_open) {
+        LOG_ERROR(Service_NFP, "Application area is not open");
+        return WrongDeviceState;
+    }
+
     if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
         LOG_ERROR(Service_NFP, "Application area is not initialized");
         return ApplicationAreaIsNotInitialized;
@@ -442,8 +517,10 @@ Result NfpDevice::SetApplicationArea(const std::vector<u8>& data) {
     }
 
     Common::TinyMT rng{};
-    rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(ApplicationArea));
     std::memcpy(tag_data.application_area.data(), data.data(), data.size());
+    // HW seems to fill excess data with garbage
+    rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
+                            sizeof(ApplicationArea) - data.size());
 
     tag_data.applicaton_write_counter++;
     is_data_moddified = true;
@@ -477,6 +554,11 @@ Result NfpDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> dat
         return WrongDeviceState;
     }
 
+    if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+        return WrongDeviceState;
+    }
+
     if (data.size() > sizeof(ApplicationArea)) {
         LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
         return ResultUnknown;
@@ -508,6 +590,11 @@ Result NfpDevice::DeleteApplicationArea() {
         return WrongDeviceState;
     }
 
+    if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+        return WrongDeviceState;
+    }
+
     Common::TinyMT rng{};
     rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(ApplicationArea));
     rng.GenerateRandomBytes(&tag_data.title_id, sizeof(u64));
diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h
index c020506a6b..a5b72cf193 100644
--- a/src/core/hle/service/nfp/nfp_device.h
+++ b/src/core/hle/service/nfp/nfp_device.h
@@ -40,7 +40,7 @@ public:
 
     Result StartDetection(s32 protocol_);
     Result StopDetection();
-    Result Mount();
+    Result Mount(MountTarget mount_target);
     Result Unmount();
     Result Flush();
 
@@ -55,7 +55,7 @@ public:
 
     Result OpenApplicationArea(u32 access_id);
     Result GetApplicationArea(std::vector<u8>& data) const;
-    Result SetApplicationArea(const std::vector<u8>& data);
+    Result SetApplicationArea(std::span<const u8> data);
     Result CreateApplicationArea(u32 access_id, std::span<const u8> data);
     Result RecreateApplicationArea(u32 access_id, std::span<const u8> data);
     Result DeleteApplicationArea();
@@ -70,7 +70,7 @@ public:
 
 private:
     void NpadUpdate(Core::HID::ControllerTriggerType type);
-    bool LoadAmiibo(const std::vector<u8>& data);
+    bool LoadAmiibo(std::span<const u8> data);
     void CloseAmiibo();
 
     AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
@@ -88,8 +88,10 @@ private:
     Kernel::KEvent* availability_change_event = nullptr;
 
     bool is_data_moddified{};
+    bool is_app_area_open{};
     s32 protocol{};
     s64 current_posix_time{};
+    MountTarget mount_target{MountTarget::None};
     DeviceState device_state{DeviceState::Unavailable};
 
     NTAG215File tag_data{};
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h
index 4487918467..dd4525b614 100644
--- a/src/core/hle/service/nfp/nfp_types.h
+++ b/src/core/hle/service/nfp/nfp_types.h
@@ -75,6 +75,22 @@ enum class AmiiboSeries : u8 {
     Diablo,
 };
 
+enum class TagType : u32 {
+    None,
+    Type1, // ISO14443A RW 96-2k bytes 106kbit/s
+    Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
+    Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
+    Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
+    Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
+};
+
+enum class TagProtocol : u32 {
+    None,
+    TypeA, // ISO14443A
+    TypeB, // ISO14443B
+    TypeF, // Sony Felica
+};
+
 using UniqueSerialNumber = std::array<u8, 7>;
 using LockBytes = std::array<u8, 2>;
 using HashData = std::array<u8, 0x20>;
@@ -174,7 +190,7 @@ struct EncryptedAmiiboFile {
     u16_be applicaton_write_counter;       // Encrypted Counter
     u32_be application_area_id;            // Encrypted Game id
     std::array<u8, 0x2> unknown;
-    HashData hash;                    // Probably a SHA256-HMAC hash?
+    std::array<u32, 0x8> unknown2;
     ApplicationArea application_area; // Encrypted Game data
 };
 static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
@@ -193,7 +209,7 @@ struct NTAG215File {
     u16_be applicaton_write_counter; // Encrypted Counter
     u32_be application_area_id;
     std::array<u8, 0x2> unknown;
-    HashData hash;                    // Probably a SHA256-HMAC hash?
+    std::array<u32, 0x8> unknown2;
     ApplicationArea application_area; // Encrypted Game data
     HashData hmac_tag;                // Hash
     UniqueSerialNumber uid;           // Unique serial number
@@ -228,8 +244,8 @@ struct TagInfo {
     INSERT_PADDING_BYTES(0x3);
     u8 uuid_length;
     INSERT_PADDING_BYTES(0x15);
-    s32 protocol;
-    u32 tag_type;
+    TagProtocol protocol;
+    TagType tag_type;
     INSERT_PADDING_BYTES(0x30);
 };
 static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp
index f8f1975db1..c61df94011 100644
--- a/src/core/hle/service/nfp/nfp_user.cpp
+++ b/src/core/hle/service/nfp/nfp_user.cpp
@@ -189,7 +189,7 @@ void IUser::Mount(Kernel::HLERequestContext& ctx) {
         return;
     }
 
-    const auto result = device.value()->Mount();
+    const auto result = device.value()->Mount(mount_target);
     IPC::ResponseBuilder rb{ctx, 2};
     rb.Push(result);
 }
-- 
cgit v1.2.3-70-g09d2


From 1485daff06b7e2aeb7077a4bdb1574956a4c3b82 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Thu, 29 Sep 2022 01:03:47 -0500
Subject: service: mii: Copy only valid name bytes

---
 src/core/hle/service/mii/mii_manager.cpp | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)

(limited to 'src')

diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index 4bc8703e19..3a2fe938f1 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -442,8 +442,15 @@ CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const {
     mii.height = mii_v3.height;
     mii.build = mii_v3.build;
 
-    memset(mii.name.data(), 0, mii.name.size());
-    memcpy(mii.name.data(), mii_v3.mii_name.data(), mii_v3.mii_name.size());
+    // Copy name until string terminator
+    mii.name = {};
+    for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
+        mii.name[index] = mii_v3.mii_name[index];
+        if (mii.name[index] == 0) {
+            break;
+        }
+    }
+
     mii.font_region = mii_v3.region_information.character_set;
 
     mii.faceline_type = mii_v3.appearance_bits1.face_shape;
@@ -515,7 +522,15 @@ Ver3StoreData MiiManager::ConvertCharInfoToV3(const CharInfo& mii) const {
     mii_v3.height = mii.height;
     mii_v3.build = mii.build;
 
-    memcpy(mii_v3.mii_name.data(), mii.name.data(), mii.name.size());
+    // Copy name until string terminator
+    mii_v3.mii_name = {};
+    for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
+        mii_v3.mii_name[index] = mii.name[index];
+        if (mii_v3.mii_name[index] == 0) {
+            break;
+        }
+    }
+
     mii_v3.region_information.character_set.Assign(mii.font_region);
 
     mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type);
-- 
cgit v1.2.3-70-g09d2