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

Merge pull request #9238 from german77/cabinet_applet

service: am: Implement cabinet applet
bunnei 3 лет назад
Родитель
Сommit
57a05b1653

+ 4 - 0
src/core/CMakeLists.txt

@@ -120,6 +120,8 @@ add_library(core STATIC
     file_sys/vfs_vector.h
     file_sys/xts_archive.cpp
     file_sys/xts_archive.h
+    frontend/applets/cabinet.cpp
+    frontend/applets/cabinet.h
     frontend/applets/controller.cpp
     frontend/applets/controller.h
     frontend/applets/error.cpp
@@ -312,6 +314,8 @@ add_library(core STATIC
     hle/service/am/applet_ae.h
     hle/service/am/applet_oe.cpp
     hle/service/am/applet_oe.h
+    hle/service/am/applets/applet_cabinet.cpp
+    hle/service/am/applets/applet_cabinet.h
     hle/service/am/applets/applet_controller.cpp
     hle/service/am/applets/applet_controller.h
     hle/service/am/applets/applet_error.cpp

+ 20 - 0
src/core/frontend/applets/cabinet.cpp

@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "core/frontend/applets/cabinet.h"
+
+#include <thread>
+
+namespace Core::Frontend {
+
+CabinetApplet::~CabinetApplet() = default;
+
+void DefaultCabinetApplet::ShowCabinetApplet(
+    const CabinetCallback& callback, const CabinetParameters& parameters,
+    std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const {
+    LOG_WARNING(Service_AM, "(STUBBED) called");
+    callback(false, {});
+}
+
+} // namespace Core::Frontend

+ 37 - 0
src/core/frontend/applets/cabinet.h

@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <functional>
+#include "core/hle/service/nfp/nfp_types.h"
+
+namespace Service::NFP {
+class NfpDevice;
+} // namespace Service::NFP
+
+namespace Core::Frontend {
+
+struct CabinetParameters {
+    Service::NFP::TagInfo tag_info;
+    Service::NFP::RegisterInfo register_info;
+    Service::NFP::CabinetMode mode;
+};
+
+using CabinetCallback = std::function<void(bool, const std::string&)>;
+
+class CabinetApplet {
+public:
+    virtual ~CabinetApplet();
+    virtual void ShowCabinetApplet(const CabinetCallback& callback,
+                                   const CabinetParameters& parameters,
+                                   std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const = 0;
+};
+
+class DefaultCabinetApplet final : public CabinetApplet {
+public:
+    void ShowCabinetApplet(const CabinetCallback& callback, const CabinetParameters& parameters,
+                           std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const override;
+};
+
+} // namespace Core::Frontend

+ 177 - 0
src/core/hle/service/am/applets/applet_cabinet.cpp

@@ -0,0 +1,177 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/frontend/applets/cabinet.h"
+#include "core/hid/hid_core.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/service/am/am.h"
+#include "core/hle/service/am/applets/applet_cabinet.h"
+#include "core/hle/service/mii/mii_manager.h"
+#include "core/hle/service/nfp/nfp_device.h"
+
+namespace Service::AM::Applets {
+
+Cabinet::Cabinet(Core::System& system_, LibraryAppletMode applet_mode_,
+                 const Core::Frontend::CabinetApplet& frontend_)
+    : Applet{system_, applet_mode_}, frontend{frontend_}, system{system_}, service_context{
+                                                                               system_,
+                                                                               "CabinetApplet"} {
+
+    availability_change_event =
+        service_context.CreateEvent("CabinetApplet:AvailabilityChangeEvent");
+}
+
+Cabinet::~Cabinet() = default;
+
+void Cabinet::Initialize() {
+    Applet::Initialize();
+
+    LOG_INFO(Service_HID, "Initializing Cabinet Applet.");
+
+    LOG_DEBUG(Service_HID,
+              "Initializing Applet with common_args: arg_version={}, lib_version={}, "
+              "play_startup_sound={}, size={}, system_tick={}, theme_color={}",
+              common_args.arguments_version, common_args.library_version,
+              common_args.play_startup_sound, common_args.size, common_args.system_tick,
+              common_args.theme_color);
+
+    const auto storage = broker.PopNormalDataToApplet();
+    ASSERT(storage != nullptr);
+
+    const auto applet_input_data = storage->GetData();
+    ASSERT(applet_input_data.size() >= sizeof(StartParamForAmiiboSettings));
+
+    std::memcpy(&applet_input_common, applet_input_data.data(),
+                sizeof(StartParamForAmiiboSettings));
+}
+
+bool Cabinet::TransactionComplete() const {
+    return is_complete;
+}
+
+Result Cabinet::GetStatus() const {
+    return ResultSuccess;
+}
+
+void Cabinet::ExecuteInteractive() {
+    ASSERT_MSG(false, "Attempted to call interactive execution on non-interactive applet.");
+}
+
+void Cabinet::Execute() {
+    if (is_complete) {
+        return;
+    }
+
+    const auto callback = [this](bool apply_changes, const std::string& amiibo_name) {
+        DisplayCompleted(apply_changes, amiibo_name);
+    };
+
+    // TODO: listen on all controllers
+    if (nfp_device == nullptr) {
+        nfp_device = std::make_shared<Service::NFP::NfpDevice>(
+            system.HIDCore().GetFirstNpadId(), system, service_context, availability_change_event);
+        nfp_device->Initialize();
+        nfp_device->StartDetection(Service::NFP::TagProtocol::All);
+    }
+
+    const Core::Frontend::CabinetParameters parameters{
+        .tag_info = applet_input_common.tag_info,
+        .register_info = applet_input_common.register_info,
+        .mode = applet_input_common.applet_mode,
+    };
+
+    switch (applet_input_common.applet_mode) {
+    case Service::NFP::CabinetMode::StartNicknameAndOwnerSettings:
+    case Service::NFP::CabinetMode::StartGameDataEraser:
+    case Service::NFP::CabinetMode::StartRestorer:
+    case Service::NFP::CabinetMode::StartFormatter:
+        frontend.ShowCabinetApplet(callback, parameters, nfp_device);
+        break;
+    default:
+        UNIMPLEMENTED_MSG("Unknown CabinetMode={}", applet_input_common.applet_mode);
+        DisplayCompleted(false, {});
+        break;
+    }
+}
+
+void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name) {
+    Service::Mii::MiiManager manager;
+    ReturnValueForAmiiboSettings applet_output{};
+
+    if (!apply_changes) {
+        Cancel();
+    }
+
+    if (nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagFound &&
+        nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagMounted) {
+        Cancel();
+    }
+
+    if (nfp_device->GetCurrentState() == Service::NFP::DeviceState::TagFound) {
+        nfp_device->Mount(Service::NFP::MountTarget::All);
+    }
+
+    switch (applet_input_common.applet_mode) {
+    case Service::NFP::CabinetMode::StartNicknameAndOwnerSettings: {
+        Service::NFP::AmiiboName name{};
+        std::memcpy(name.data(), amiibo_name.data(), std::min(amiibo_name.size(), name.size() - 1));
+        nfp_device->SetNicknameAndOwner(name);
+        break;
+    }
+    case Service::NFP::CabinetMode::StartGameDataEraser:
+        nfp_device->DeleteApplicationArea();
+        break;
+    case Service::NFP::CabinetMode::StartRestorer:
+        nfp_device->RestoreAmiibo();
+        break;
+    case Service::NFP::CabinetMode::StartFormatter:
+        nfp_device->DeleteAllData();
+        break;
+    default:
+        UNIMPLEMENTED_MSG("Unknown CabinetMode={}", applet_input_common.applet_mode);
+        break;
+    }
+
+    applet_output.device_handle = applet_input_common.device_handle;
+    applet_output.result = CabinetResult::Cancel;
+    const auto reg_result = nfp_device->GetRegisterInfo(applet_output.register_info);
+    const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info);
+    nfp_device->Finalize();
+
+    if (reg_result.IsSuccess()) {
+        applet_output.result |= CabinetResult::RegisterInfo;
+    }
+
+    if (tag_result.IsSuccess()) {
+        applet_output.result |= CabinetResult::TagInfo;
+    }
+
+    std::vector<u8> out_data(sizeof(ReturnValueForAmiiboSettings));
+    std::memcpy(out_data.data(), &applet_output, sizeof(ReturnValueForAmiiboSettings));
+
+    is_complete = true;
+
+    broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data)));
+    broker.SignalStateChanged();
+}
+
+void Cabinet::Cancel() {
+    ReturnValueForAmiiboSettings applet_output{};
+    applet_output.device_handle = applet_input_common.device_handle;
+    applet_output.result = CabinetResult::Cancel;
+    nfp_device->Finalize();
+
+    std::vector<u8> out_data(sizeof(ReturnValueForAmiiboSettings));
+    std::memcpy(out_data.data(), &applet_output, sizeof(ReturnValueForAmiiboSettings));
+
+    is_complete = true;
+
+    broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data)));
+    broker.SignalStateChanged();
+}
+
+} // namespace Service::AM::Applets

+ 104 - 0
src/core/hle/service/am/applets/applet_cabinet.h

@@ -0,0 +1,104 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "core/hle/result.h"
+#include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nfp/nfp_types.h"
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace Service::NFP {
+class NfpDevice;
+}
+
+namespace Service::AM::Applets {
+
+enum class CabinetAppletVersion : u32 {
+    Version1 = 0x1,
+};
+
+enum class CabinetResult : u8 {
+    Cancel = 0,
+    TagInfo = 1 << 1,
+    RegisterInfo = 1 << 2,
+    All = TagInfo | RegisterInfo,
+};
+DECLARE_ENUM_FLAG_OPERATORS(CabinetResult)
+
+// This is nn::nfp::AmiiboSettingsStartParam
+struct AmiiboSettingsStartParam {
+    u64 device_handle;
+    std::array<u8, 0x20> param_1;
+    u8 param_2;
+};
+static_assert(sizeof(AmiiboSettingsStartParam) == 0x30,
+              "AmiiboSettingsStartParam is an invalid size");
+
+#pragma pack(push, 1)
+// This is nn::nfp::StartParamForAmiiboSettings
+struct StartParamForAmiiboSettings {
+    u8 param_1;
+    Service::NFP::CabinetMode applet_mode;
+    u8 flags;
+    u8 amiibo_settings_1;
+    u64 device_handle;
+    Service::NFP::TagInfo tag_info;
+    Service::NFP::RegisterInfo register_info;
+    std::array<u8, 0x20> amiibo_settings_3;
+    INSERT_PADDING_BYTES(0x24);
+};
+static_assert(sizeof(StartParamForAmiiboSettings) == 0x1A8,
+              "StartParamForAmiiboSettings is an invalid size");
+
+// This is nn::nfp::ReturnValueForAmiiboSettings
+struct ReturnValueForAmiiboSettings {
+    CabinetResult result;
+    INSERT_PADDING_BYTES(0x3);
+    u64 device_handle;
+    Service::NFP::TagInfo tag_info;
+    Service::NFP::RegisterInfo register_info;
+    INSERT_PADDING_BYTES(0x24);
+};
+static_assert(sizeof(ReturnValueForAmiiboSettings) == 0x188,
+              "ReturnValueForAmiiboSettings is an invalid size");
+#pragma pack(pop)
+
+class Cabinet final : public Applet {
+public:
+    explicit Cabinet(Core::System& system_, LibraryAppletMode applet_mode_,
+                     const Core::Frontend::CabinetApplet& frontend_);
+    ~Cabinet() override;
+
+    void Initialize() override;
+
+    bool TransactionComplete() const override;
+    Result GetStatus() const override;
+    void ExecuteInteractive() override;
+    void Execute() override;
+    void DisplayCompleted(bool apply_changes, std::string_view amiibo_name);
+    void Cancel();
+
+private:
+    const Core::Frontend::CabinetApplet& frontend;
+    Core::System& system;
+
+    bool is_complete{false};
+    std::shared_ptr<Service::NFP::NfpDevice> nfp_device;
+    Kernel::KEvent* availability_change_event;
+    KernelHelpers::ServiceContext service_context;
+    StartParamForAmiiboSettings applet_input_common{};
+};
+
+} // namespace Service::AM::Applets

+ 17 - 3
src/core/hle/service/am/applets/applets.cpp

@@ -5,6 +5,7 @@
 
 #include "common/assert.h"
 #include "core/core.h"
+#include "core/frontend/applets/cabinet.h"
 #include "core/frontend/applets/controller.h"
 #include "core/frontend/applets/error.h"
 #include "core/frontend/applets/general_frontend.h"
@@ -16,6 +17,7 @@
 #include "core/hle/service/am/am.h"
 #include "core/hle/service/am/applet_ae.h"
 #include "core/hle/service/am/applet_oe.h"
+#include "core/hle/service/am/applets/applet_cabinet.h"
 #include "core/hle/service/am/applets/applet_controller.h"
 #include "core/hle/service/am/applets/applet_error.h"
 #include "core/hle/service/am/applets/applet_general_backend.h"
@@ -171,13 +173,15 @@ void Applet::Initialize() {
 
 AppletFrontendSet::AppletFrontendSet() = default;
 
-AppletFrontendSet::AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
+AppletFrontendSet::AppletFrontendSet(CabinetApplet cabinet_applet,
+                                     ControllerApplet controller_applet, ErrorApplet error_applet,
                                      MiiEdit mii_edit_,
                                      ParentalControlsApplet parental_controls_applet,
                                      PhotoViewer photo_viewer_, ProfileSelect profile_select_,
                                      SoftwareKeyboard software_keyboard_, WebBrowser web_browser_)
-    : controller{std::move(controller_applet)}, error{std::move(error_applet)},
-      mii_edit{std::move(mii_edit_)}, parental_controls{std::move(parental_controls_applet)},
+    : cabinet{std::move(cabinet_applet)}, controller{std::move(controller_applet)},
+      error{std::move(error_applet)}, mii_edit{std::move(mii_edit_)},
+      parental_controls{std::move(parental_controls_applet)},
       photo_viewer{std::move(photo_viewer_)}, profile_select{std::move(profile_select_)},
       software_keyboard{std::move(software_keyboard_)}, web_browser{std::move(web_browser_)} {}
 
@@ -196,6 +200,10 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const {
 }
 
 void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
+    if (set.cabinet != nullptr) {
+        frontend.cabinet = std::move(set.cabinet);
+    }
+
     if (set.controller != nullptr) {
         frontend.controller = std::move(set.controller);
     }
@@ -235,6 +243,10 @@ void AppletManager::SetDefaultAppletFrontendSet() {
 }
 
 void AppletManager::SetDefaultAppletsIfMissing() {
+    if (frontend.cabinet == nullptr) {
+        frontend.cabinet = std::make_unique<Core::Frontend::DefaultCabinetApplet>();
+    }
+
     if (frontend.controller == nullptr) {
         frontend.controller =
             std::make_unique<Core::Frontend::DefaultControllerApplet>(system.HIDCore());
@@ -279,6 +291,8 @@ std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, LibraryAppletMode
     switch (id) {
     case AppletId::Auth:
         return std::make_shared<Auth>(system, mode, *frontend.parental_controls);
+    case AppletId::Cabinet:
+        return std::make_shared<Cabinet>(system, mode, *frontend.cabinet);
     case AppletId::Controller:
         return std::make_shared<Controller>(system, mode, *frontend.controller);
     case AppletId::Error:

+ 8 - 4
src/core/hle/service/am/applets/applets.h

@@ -16,6 +16,7 @@ class System;
 }
 
 namespace Core::Frontend {
+class CabinetApplet;
 class ControllerApplet;
 class ECommerceApplet;
 class ErrorApplet;
@@ -176,6 +177,7 @@ protected:
 };
 
 struct AppletFrontendSet {
+    using CabinetApplet = std::unique_ptr<Core::Frontend::CabinetApplet>;
     using ControllerApplet = std::unique_ptr<Core::Frontend::ControllerApplet>;
     using ErrorApplet = std::unique_ptr<Core::Frontend::ErrorApplet>;
     using MiiEdit = std::unique_ptr<Core::Frontend::MiiEditApplet>;
@@ -186,10 +188,11 @@ struct AppletFrontendSet {
     using WebBrowser = std::unique_ptr<Core::Frontend::WebBrowserApplet>;
 
     AppletFrontendSet();
-    AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet,
-                      MiiEdit mii_edit_, ParentalControlsApplet parental_controls_applet,
-                      PhotoViewer photo_viewer_, ProfileSelect profile_select_,
-                      SoftwareKeyboard software_keyboard_, WebBrowser web_browser_);
+    AppletFrontendSet(CabinetApplet cabinet_applet, ControllerApplet controller_applet,
+                      ErrorApplet error_applet, MiiEdit mii_edit_,
+                      ParentalControlsApplet parental_controls_applet, PhotoViewer photo_viewer_,
+                      ProfileSelect profile_select_, SoftwareKeyboard software_keyboard_,
+                      WebBrowser web_browser_);
     ~AppletFrontendSet();
 
     AppletFrontendSet(const AppletFrontendSet&) = delete;
@@ -198,6 +201,7 @@ struct AppletFrontendSet {
     AppletFrontendSet(AppletFrontendSet&&) noexcept;
     AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept;
 
+    CabinetApplet cabinet;
     ControllerApplet controller;
     ErrorApplet error;
     MiiEdit mii_edit;

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

@@ -77,6 +77,9 @@ void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
         LoadAmiibo(nfc_status.data);
         break;
     case Common::Input::NfcState::AmiiboRemoved:
+        if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
+            break;
+        }
         if (device_state != DeviceState::SearchingForTag) {
             CloseAmiibo();
         }
@@ -97,6 +100,8 @@ bool NfpDevice::LoadAmiibo(std::span<const u8> data) {
         return false;
     }
 
+    // TODO: Filter by allowed_protocols here
+
     memcpy(&encrypted_tag_data, data.data(), sizeof(EncryptedNTAG215File));
 
     device_state = DeviceState::TagFound;
@@ -143,7 +148,7 @@ void NfpDevice::Finalize() {
     device_state = DeviceState::Unavailable;
 }
 
-Result NfpDevice::StartDetection(s32 protocol_) {
+Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {
     if (device_state != DeviceState::Initialized && device_state != DeviceState::TagRemoved) {
         LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
         return WrongDeviceState;
@@ -155,7 +160,7 @@ Result NfpDevice::StartDetection(s32 protocol_) {
     }
 
     device_state = DeviceState::SearchingForTag;
-    protocol = protocol_;
+    allowed_protocols = allowed_protocol;
     return ResultSuccess;
 }
 
@@ -469,6 +474,32 @@ Result NfpDevice::OpenApplicationArea(u32 access_id) {
     return ResultSuccess;
 }
 
+Result NfpDevice::GetApplicationAreaId(u32& application_area_id) const {
+    application_area_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 (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;
+    }
+
+    application_area_id = tag_data.application_area_id;
+
+    return ResultSuccess;
+}
+
 Result NfpDevice::GetApplicationArea(std::vector<u8>& data) const {
     if (device_state != DeviceState::TagMounted) {
         LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);

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

@@ -4,6 +4,7 @@
 #pragma once
 
 #include <array>
+#include <span>
 #include <vector>
 
 #include "common/common_funcs.h"
@@ -37,7 +38,7 @@ public:
     void Initialize();
     void Finalize();
 
-    Result StartDetection(s32 protocol_);
+    Result StartDetection(TagProtocol allowed_protocol);
     Result StopDetection();
     Result Mount(MountTarget mount_target);
     Result Unmount();
@@ -53,6 +54,7 @@ public:
     Result DeleteAllData();
 
     Result OpenApplicationArea(u32 access_id);
+    Result GetApplicationAreaId(u32& application_area_id) const;
     Result GetApplicationArea(std::vector<u8>& data) const;
     Result SetApplicationArea(std::span<const u8> data);
     Result CreateApplicationArea(u32 access_id, std::span<const u8> data);
@@ -88,7 +90,7 @@ private:
 
     bool is_data_moddified{};
     bool is_app_area_open{};
-    s32 protocol{};
+    TagProtocol allowed_protocols{};
     s64 current_posix_time{};
     MountTarget mount_target{MountTarget::None};
     DeviceState device_state{DeviceState::Unavailable};

+ 14 - 3
src/core/hle/service/nfp/nfp_types.h

@@ -88,11 +88,22 @@ enum class PackedTagType : u8 {
     Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
 };
 
+// Verify this enum. It might be completely wrong default protocol is 0x48
 enum class TagProtocol : u32 {
     None,
-    TypeA, // ISO14443A
-    TypeB, // ISO14443B
-    TypeF, // Sony Felica
+    TypeA = 1U << 0, // ISO14443A
+    TypeB = 1U << 1, // ISO14443B
+    TypeF = 1U << 2, // Sony Felica
+    Unknown1 = 1U << 3,
+    Unknown2 = 1U << 5,
+    All = 0xFFFFFFFFU,
+};
+
+enum class CabinetMode : u8 {
+    StartNicknameAndOwnerSettings,
+    StartGameDataEraser,
+    StartRestorer,
+    StartFormatter,
 };
 
 using UniqueSerialNumber = std::array<u8, 7>;

+ 1 - 1
src/core/hle/service/nfp/nfp_user.cpp

@@ -130,7 +130,7 @@ void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
 void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     const auto device_handle{rp.Pop<u64>()};
-    const auto nfp_protocol{rp.Pop<s32>()};
+    const auto nfp_protocol{rp.PopEnum<TagProtocol>()};
     LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
 
     if (state == State::NonInitialized) {

+ 15 - 0
src/input_common/drivers/virtual_amiibo.cpp

@@ -60,6 +60,8 @@ Common::Input::NfcState VirtualAmiibo::WriteNfcData(
         return Common::Input::NfcState::WriteFailed;
     }
 
+    amiibo_data = data;
+
     return Common::Input::NfcState::Success;
 }
 
@@ -91,6 +93,15 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) {
     return Info::Success;
 }
 
+VirtualAmiibo::Info VirtualAmiibo::ReloadAmiibo() {
+    if (state == State::AmiiboIsOpen) {
+        SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_data});
+        return Info::Success;
+    }
+
+    return LoadAmiibo(file_path);
+}
+
 VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() {
     state = polling_mode == Common::Input::PollingMode::NFC ? State::WaitingForAmiibo
                                                             : State::Initialized;
@@ -98,4 +109,8 @@ VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() {
     return Info::Success;
 }
 
+std::string VirtualAmiibo::GetLastFilePath() const {
+    return file_path;
+}
+
 } // namespace InputCommon

+ 3 - 0
src/input_common/drivers/virtual_amiibo.h

@@ -47,8 +47,11 @@ public:
     State GetCurrentState() const;
 
     Info LoadAmiibo(const std::string& amiibo_file);
+    Info ReloadAmiibo();
     Info CloseAmiibo();
 
+    std::string GetLastFilePath() const;
+
 private:
     static constexpr std::size_t amiibo_size = 0x21C;
     static constexpr std::size_t amiibo_size_without_password = amiibo_size - 0x8;

+ 1 - 1
src/input_common/input_engine.h

@@ -133,7 +133,7 @@ public:
         return Common::Input::CameraError::NotSupported;
     }
 
-    // Request nfc data from a controller
+    // Returns success if nfc is supported
     virtual Common::Input::NfcState SupportsNfc(
         [[maybe_unused]] const PadIdentifier& identifier) const {
         return Common::Input::NfcState::NotSupported;

+ 3 - 0
src/yuzu/CMakeLists.txt

@@ -18,6 +18,9 @@ add_executable(yuzu
     about_dialog.cpp
     about_dialog.h
     aboutdialog.ui
+    applets/qt_amiibo_settings.cpp
+    applets/qt_amiibo_settings.h
+    applets/qt_amiibo_settings.ui
     applets/qt_controller.cpp
     applets/qt_controller.h
     applets/qt_controller.ui

+ 260 - 0
src/yuzu/applets/qt_amiibo_settings.cpp

@@ -0,0 +1,260 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <thread>
+#include <fmt/format.h>
+#include <nlohmann/json.hpp>
+
+#include "common/assert.h"
+#include "common/string_util.h"
+#include "core/hle/service/nfp/nfp_device.h"
+#include "core/hle/service/nfp/nfp_result.h"
+#include "input_common/drivers/virtual_amiibo.h"
+#include "input_common/main.h"
+#include "ui_qt_amiibo_settings.h"
+#include "web_service/web_backend.h"
+#include "yuzu/applets/qt_amiibo_settings.h"
+#include "yuzu/main.h"
+
+QtAmiiboSettingsDialog::QtAmiiboSettingsDialog(QWidget* parent,
+                                               Core::Frontend::CabinetParameters parameters_,
+                                               InputCommon::InputSubsystem* input_subsystem_,
+                                               std::shared_ptr<Service::NFP::NfpDevice> nfp_device_)
+    : QDialog(parent), ui(std::make_unique<Ui::QtAmiiboSettingsDialog>()),
+      input_subsystem{input_subsystem_}, nfp_device{std::move(nfp_device_)},
+      parameters(std::move(parameters_)) {
+    ui->setupUi(this);
+
+    LoadInfo();
+
+    resize(0, 0);
+}
+
+QtAmiiboSettingsDialog::~QtAmiiboSettingsDialog() = default;
+
+int QtAmiiboSettingsDialog::exec() {
+    if (!is_initalized) {
+        return QDialog::Rejected;
+    }
+    return QDialog::exec();
+}
+
+std::string QtAmiiboSettingsDialog::GetName() const {
+    return ui->amiiboCustomNameValue->text().toStdString();
+}
+
+void QtAmiiboSettingsDialog::LoadInfo() {
+    if (input_subsystem->GetVirtualAmiibo()->ReloadAmiibo() !=
+        InputCommon::VirtualAmiibo::Info::Success) {
+        return;
+    }
+
+    if (nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagFound &&
+        nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagMounted) {
+        return;
+    }
+    nfp_device->Mount(Service::NFP::MountTarget::All);
+
+    LoadAmiiboInfo();
+    LoadAmiiboData();
+    LoadAmiiboGameInfo();
+
+    ui->amiiboDirectoryValue->setText(
+        QString::fromStdString(input_subsystem->GetVirtualAmiibo()->GetLastFilePath()));
+
+    SetSettingsDescription();
+    is_initalized = true;
+}
+
+void QtAmiiboSettingsDialog::LoadAmiiboInfo() {
+    Service::NFP::ModelInfo model_info{};
+    const auto model_result = nfp_device->GetModelInfo(model_info);
+
+    if (model_result.IsFailure()) {
+        ui->amiiboImageLabel->setVisible(false);
+        ui->amiiboInfoGroup->setVisible(false);
+        return;
+    }
+
+    const auto amiibo_id =
+        fmt::format("{:04x}{:02x}{:02x}{:04x}{:02x}02", Common::swap16(model_info.character_id),
+                    model_info.character_variant, model_info.amiibo_type, model_info.model_number,
+                    model_info.series);
+
+    LOG_DEBUG(Frontend, "Loading amiibo id {}", amiibo_id);
+    // Note: This function is not being used until we host the images on our server
+    // LoadAmiiboApiInfo(amiibo_id);
+    ui->amiiboImageLabel->setVisible(false);
+    ui->amiiboInfoGroup->setVisible(false);
+}
+
+void QtAmiiboSettingsDialog::LoadAmiiboApiInfo(std::string_view amiibo_id) {
+    // TODO: Host this data on our website
+    WebService::Client client{"https://amiiboapi.com", {}, {}};
+    WebService::Client image_client{"https://raw.githubusercontent.com", {}, {}};
+    const auto url_path = fmt::format("/api/amiibo/?id={}", amiibo_id);
+
+    const auto amiibo_json = client.GetJson(url_path, true).returned_data;
+    if (amiibo_json.empty()) {
+        ui->amiiboImageLabel->setVisible(false);
+        ui->amiiboInfoGroup->setVisible(false);
+        return;
+    }
+
+    std::string amiibo_series{};
+    std::string amiibo_name{};
+    std::string amiibo_image_url{};
+    std::string amiibo_type{};
+
+    const auto parsed_amiibo_json_json = nlohmann::json::parse(amiibo_json).at("amiibo");
+    parsed_amiibo_json_json.at("amiiboSeries").get_to(amiibo_series);
+    parsed_amiibo_json_json.at("name").get_to(amiibo_name);
+    parsed_amiibo_json_json.at("image").get_to(amiibo_image_url);
+    parsed_amiibo_json_json.at("type").get_to(amiibo_type);
+
+    ui->amiiboSeriesValue->setText(QString::fromStdString(amiibo_series));
+    ui->amiiboNameValue->setText(QString::fromStdString(amiibo_name));
+    ui->amiiboTypeValue->setText(QString::fromStdString(amiibo_type));
+
+    if (amiibo_image_url.size() < 34) {
+        ui->amiiboImageLabel->setVisible(false);
+    }
+
+    const auto image_url_path = amiibo_image_url.substr(34, amiibo_image_url.size() - 34);
+    const auto image_data = image_client.GetImage(image_url_path, true).returned_data;
+
+    if (image_data.empty()) {
+        ui->amiiboImageLabel->setVisible(false);
+    }
+
+    QPixmap pixmap;
+    pixmap.loadFromData(reinterpret_cast<const u8*>(image_data.data()),
+                        static_cast<uint>(image_data.size()));
+    pixmap = pixmap.scaled(250, 350, Qt::AspectRatioMode::KeepAspectRatio,
+                           Qt::TransformationMode::SmoothTransformation);
+    ui->amiiboImageLabel->setPixmap(pixmap);
+}
+
+void QtAmiiboSettingsDialog::LoadAmiiboData() {
+    Service::NFP::RegisterInfo register_info{};
+    Service::NFP::CommonInfo common_info{};
+    const auto register_result = nfp_device->GetRegisterInfo(register_info);
+    const auto common_result = nfp_device->GetCommonInfo(common_info);
+
+    if (register_result.IsFailure()) {
+        ui->creationDateValue->setDisabled(true);
+        ui->modificationDateValue->setDisabled(true);
+        ui->amiiboCustomNameValue->setReadOnly(false);
+        ui->amiiboOwnerValue->setReadOnly(false);
+        return;
+    }
+
+    if (parameters.mode == Service::NFP::CabinetMode::StartNicknameAndOwnerSettings) {
+        ui->creationDateValue->setDisabled(true);
+        ui->modificationDateValue->setDisabled(true);
+    }
+
+    const auto amiibo_name = std::string(register_info.amiibo_name.data());
+    const auto owner_name = Common::UTF16ToUTF8(register_info.mii_char_info.name.data());
+    const auto creation_date =
+        QDate(register_info.creation_date.year, register_info.creation_date.month,
+              register_info.creation_date.day);
+
+    ui->amiiboCustomNameValue->setText(QString::fromStdString(amiibo_name));
+    ui->amiiboOwnerValue->setText(QString::fromStdString(owner_name));
+    ui->amiiboCustomNameValue->setReadOnly(true);
+    ui->amiiboOwnerValue->setReadOnly(true);
+    ui->creationDateValue->setDate(creation_date);
+
+    if (common_result.IsFailure()) {
+        ui->modificationDateValue->setDisabled(true);
+        return;
+    }
+
+    const auto modification_date =
+        QDate(common_info.last_write_date.year, common_info.last_write_date.month,
+              common_info.last_write_date.day);
+    ui->modificationDateValue->setDate(modification_date);
+}
+
+void QtAmiiboSettingsDialog::LoadAmiiboGameInfo() {
+    u32 application_area_id{};
+    const auto application_result = nfp_device->GetApplicationAreaId(application_area_id);
+
+    if (application_result.IsFailure()) {
+        ui->gameIdValue->setVisible(false);
+        ui->gameIdLabel->setText(tr("No game data present"));
+        return;
+    }
+
+    SetGameDataName(application_area_id);
+}
+
+void QtAmiiboSettingsDialog::SetGameDataName(u32 application_area_id) {
+    static constexpr std::array<std::pair<u32, const char*>, 12> game_name_list = {
+        // 3ds, wii u
+        std::pair<u32, const char*>{0x10110E00, "Super Smash Bros (3DS/WiiU)"},
+        {0x00132600, "Mario & Luigi: Paper Jam"},
+        {0x0014F000, "Animal Crossing: Happy Home Designer"},
+        {0x00152600, "Chibi-Robo!: Zip Lash"},
+        {0x10161f00, "Mario Party 10"},
+        {0x1019C800, "The Legend of Zelda: Twilight Princess HD"},
+        // switch
+        {0x10162B00, "Splatoon 2"},
+        {0x1016e100, "Shovel Knight: Treasure Trove"},
+        {0x1019C800, "The Legend of Zelda: Breath of the Wild"},
+        {0x34F80200, "Super Smash Bros. Ultimate"},
+        {0x38600500, "Splatoon 3"},
+        {0x3B440400, "The Legend of Zelda: Link's Awakening"},
+    };
+
+    for (const auto& [game_id, game_name] : game_name_list) {
+        if (application_area_id == game_id) {
+            ui->gameIdValue->setText(QString::fromStdString(game_name));
+            return;
+        }
+    }
+
+    const auto application_area_string = fmt::format("{:016x}", application_area_id);
+    ui->gameIdValue->setText(QString::fromStdString(application_area_string));
+}
+
+void QtAmiiboSettingsDialog::SetSettingsDescription() {
+    switch (parameters.mode) {
+    case Service::NFP::CabinetMode::StartFormatter:
+        ui->cabinetActionDescriptionLabel->setText(
+            tr("The following amiibo data will be formatted:"));
+        break;
+    case Service::NFP::CabinetMode::StartGameDataEraser:
+        ui->cabinetActionDescriptionLabel->setText(tr("The following game data will removed:"));
+        break;
+    case Service::NFP::CabinetMode::StartNicknameAndOwnerSettings:
+        ui->cabinetActionDescriptionLabel->setText(tr("Set nickname and owner:"));
+        break;
+    case Service::NFP::CabinetMode::StartRestorer:
+        ui->cabinetActionDescriptionLabel->setText(tr("Do you wish to restore this amiibo?"));
+        break;
+    }
+}
+
+QtAmiiboSettings::QtAmiiboSettings(GMainWindow& parent) {
+    connect(this, &QtAmiiboSettings::MainWindowShowAmiiboSettings, &parent,
+            &GMainWindow::AmiiboSettingsShowDialog, Qt::QueuedConnection);
+    connect(&parent, &GMainWindow::AmiiboSettingsFinished, this,
+            &QtAmiiboSettings::MainWindowFinished, Qt::QueuedConnection);
+}
+
+QtAmiiboSettings::~QtAmiiboSettings() = default;
+
+void QtAmiiboSettings::ShowCabinetApplet(
+    const Core::Frontend::CabinetCallback& callback_,
+    const Core::Frontend::CabinetParameters& parameters,
+    std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const {
+    callback = std::move(callback_);
+    emit MainWindowShowAmiiboSettings(parameters, nfp_device);
+}
+
+void QtAmiiboSettings::MainWindowFinished(bool is_success, const std::string& name) {
+    callback(is_success, name);
+}

+ 83 - 0
src/yuzu/applets/qt_amiibo_settings.h

@@ -0,0 +1,83 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <QDialog>
+#include "core/frontend/applets/cabinet.h"
+
+class GMainWindow;
+class QCheckBox;
+class QComboBox;
+class QDialogButtonBox;
+class QGroupBox;
+class QLabel;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace Ui {
+class QtAmiiboSettingsDialog;
+}
+
+namespace Service::NFP {
+class NfpDevice;
+} // namespace Service::NFP
+
+class QtAmiiboSettingsDialog final : public QDialog {
+    Q_OBJECT
+
+public:
+    explicit QtAmiiboSettingsDialog(QWidget* parent, Core::Frontend::CabinetParameters parameters_,
+                                    InputCommon::InputSubsystem* input_subsystem_,
+                                    std::shared_ptr<Service::NFP::NfpDevice> nfp_device_);
+    ~QtAmiiboSettingsDialog() override;
+
+    int exec() override;
+
+    std::string GetName() const;
+
+private:
+    void LoadInfo();
+    void LoadAmiiboInfo();
+    void LoadAmiiboApiInfo(std::string_view amiibo_id);
+    void LoadAmiiboData();
+    void LoadAmiiboGameInfo();
+    void SetGameDataName(u32 application_area_id);
+    void SetSettingsDescription();
+
+    std::unique_ptr<Ui::QtAmiiboSettingsDialog> ui;
+
+    InputCommon::InputSubsystem* input_subsystem;
+    std::shared_ptr<Service::NFP::NfpDevice> nfp_device;
+
+    // Parameters sent in from the backend HLE applet.
+    Core::Frontend::CabinetParameters parameters;
+
+    // If false amiibo settings failed to load
+    bool is_initalized{};
+};
+
+class QtAmiiboSettings final : public QObject, public Core::Frontend::CabinetApplet {
+    Q_OBJECT
+
+public:
+    explicit QtAmiiboSettings(GMainWindow& parent);
+    ~QtAmiiboSettings() override;
+
+    void ShowCabinetApplet(const Core::Frontend::CabinetCallback& callback_,
+                           const Core::Frontend::CabinetParameters& parameters,
+                           std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const override;
+
+signals:
+    void MainWindowShowAmiiboSettings(const Core::Frontend::CabinetParameters& parameters,
+                                      std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const;
+
+private:
+    void MainWindowFinished(bool is_success, const std::string& name);
+
+    mutable Core::Frontend::CabinetCallback callback;
+};

+ 494 - 0
src/yuzu/applets/qt_amiibo_settings.ui

@@ -0,0 +1,494 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtAmiiboSettingsDialog</class>
+ <widget class="QDialog" name="QtAmiiboSettingsDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>839</width>
+    <height>500</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Amiibo Settings</string>
+  </property>
+  <property name="styleSheet">
+   <string notr="true"/>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout" stretch="0">
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QWidget" name="mainControllerApplet" native="true">
+     <layout class="QVBoxLayout" name="verticalLayout_1" stretch="0,3,0">
+      <property name="spacing">
+       <number>0</number>
+      </property>
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QWidget" name="topControllerApplet" native="true">
+        <layout class="QHBoxLayout" name="horizontalLayout">
+         <property name="spacing">
+          <number>10</number>
+         </property>
+         <property name="leftMargin">
+          <number>20</number>
+         </property>
+         <property name="topMargin">
+          <number>15</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>15</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="cabinetActionDescriptionLabel">
+           <property name="font">
+            <font>
+             <pointsize>12</pointsize>
+             <weight>75</weight>
+             <bold>true</bold>
+            </font>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+           </property>
+           <property name="wordWrap">
+            <bool>false</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer_2">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </widget>
+      </item>
+      <item>
+       <widget class="QWidget" name="middleControllerApplet" native="true">
+        <layout class="QVBoxLayout" name="verticalLayout_3">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_2">
+           <property name="spacing">
+            <number>20</number>
+           </property>
+           <property name="leftMargin">
+            <number>15</number>
+           </property>
+           <property name="rightMargin">
+            <number>15</number>
+           </property>
+           <item>
+            <widget class="QLabel" name="amiiboImageLabel">
+             <property name="minimumSize">
+              <size>
+               <width>250</width>
+               <height>350</height>
+              </size>
+             </property>
+             <property name="maximumSize">
+              <size>
+               <width>236</width>
+               <height>350</height>
+              </size>
+             </property>
+             <property name="text">
+              <string/>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignCenter</set>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <layout class="QVBoxLayout" name="verticalLayout_4">
+             <property name="leftMargin">
+              <number>0</number>
+             </property>
+             <property name="topMargin">
+              <number>8</number>
+             </property>
+             <property name="bottomMargin">
+              <number>15</number>
+             </property>
+             <item>
+              <widget class="QGroupBox" name="amiiboInfoGroup">
+               <property name="title">
+                <string>Amiibo Info</string>
+               </property>
+               <layout class="QVBoxLayout" name="verticalLayout_5">
+                <item>
+                 <layout class="QGridLayout" name="gridLayout_1">
+                  <item row="0" column="0">
+                   <widget class="QLabel" name="amiiboSeriesLabel">
+                    <property name="text">
+                     <string>Series</string>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="0" column="1">
+                   <widget class="QLineEdit" name="amiiboSeriesValue">
+                    <property name="sizePolicy">
+                     <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+                      <horstretch>0</horstretch>
+                      <verstretch>0</verstretch>
+                     </sizepolicy>
+                    </property>
+                    <property name="readOnly">
+                     <bool>true</bool>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="1" column="0">
+                   <widget class="QLabel" name="amiiboTypeLabel">
+                    <property name="text">
+                     <string>Type</string>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="1" column="1">
+                   <widget class="QLineEdit" name="amiiboTypeValue">
+                    <property name="sizePolicy">
+                     <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+                      <horstretch>0</horstretch>
+                      <verstretch>0</verstretch>
+                     </sizepolicy>
+                    </property>
+                    <property name="readOnly">
+                     <bool>true</bool>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="2" column="0">
+                   <widget class="QLabel" name="amiiboNameLabel">
+                    <property name="text">
+                     <string>Name</string>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="2" column="1">
+                   <widget class="QLineEdit" name="amiiboNameValue">
+                    <property name="sizePolicy">
+                     <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+                      <horstretch>0</horstretch>
+                      <verstretch>0</verstretch>
+                     </sizepolicy>
+                    </property>
+                    <property name="readOnly">
+                     <bool>true</bool>
+                    </property>
+                   </widget>
+                  </item>
+                 </layout>
+                </item>
+               </layout>
+              </widget>
+             </item>
+             <item>
+              <widget class="QGroupBox" name="amiiboDataGroup">
+               <property name="title">
+                <string>Amiibo Data</string>
+               </property>
+               <layout class="QVBoxLayout" name="verticalLayout_6">
+                <item>
+                 <layout class="QGridLayout" name="gridLayout_2">
+                  <item row="0" column="0">
+                   <widget class="QLabel" name="amiiboCustomNameLabel">
+                    <property name="text">
+                     <string>Custom Name</string>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="0" column="1">
+                   <widget class="QLineEdit" name="amiiboCustomNameValue">
+                    <property name="sizePolicy">
+                     <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+                      <horstretch>0</horstretch>
+                      <verstretch>0</verstretch>
+                     </sizepolicy>
+                    </property>
+                    <property name="maxLength">
+                     <number>10</number>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="1" column="0">
+                   <widget class="QLabel" name="amiiboOwnerLabel">
+                    <property name="text">
+                     <string>Owner</string>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="1" column="1">
+                   <widget class="QLineEdit" name="amiiboOwnerValue">
+                    <property name="sizePolicy">
+                     <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+                      <horstretch>0</horstretch>
+                      <verstretch>0</verstretch>
+                     </sizepolicy>
+                    </property>
+                    <property name="maxLength">
+                     <number>10</number>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="2" column="0">
+                   <widget class="QLabel" name="creationDateLabel">
+                    <property name="text">
+                     <string>Creation Date</string>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="2" column="1">
+                   <widget class="QDateTimeEdit" name="creationDateValue">
+                    <property name="readOnly">
+                     <bool>true</bool>
+                    </property>
+                    <property name="minimumDate">
+                     <date>
+                      <year>1970</year>
+                      <month>1</month>
+                      <day>1</day>
+                     </date>
+                    </property>
+                    <property name="displayFormat">
+                     <string>dd/MM/yyyy</string>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="3" column="0">
+                   <widget class="QLabel" name="modificationDateLabel">
+                    <property name="text">
+                     <string>Modification Date</string>
+                    </property>
+                   </widget>
+                  </item>
+                  <item row="3" column="1">
+                   <widget class="QDateTimeEdit" name="modificationDateValue">
+                    <property name="readOnly">
+                     <bool>true</bool>
+                    </property>
+                    <property name="minimumDate">
+                     <date>
+                      <year>1970</year>
+                      <month>1</month>
+                      <day>1</day>
+                     </date>
+                    </property>
+                    <property name="displayFormat">
+                     <string>dd/MM/yyyy </string>
+                    </property>
+                   </widget>
+                  </item>
+                 </layout>
+                </item>
+               </layout>
+              </widget>
+             </item>
+             <item>
+              <widget class="QGroupBox" name="gameDataGroup">
+               <property name="minimumSize">
+                <size>
+                 <width>500</width>
+                 <height>0</height>
+                </size>
+               </property>
+               <property name="title">
+                <string>Game Data</string>
+               </property>
+               <layout class="QGridLayout" name="gridLayout_3">
+                <item row="0" column="0">
+                 <widget class="QLabel" name="gameIdLabel">
+                  <property name="text">
+                   <string>Game Id</string>
+                  </property>
+                 </widget>
+                </item>
+                <item row="0" column="1">
+                 <widget class="QLineEdit" name="gameIdValue">
+                  <property name="sizePolicy">
+                   <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+                    <horstretch>0</horstretch>
+                    <verstretch>0</verstretch>
+                   </sizepolicy>
+                  </property>
+                  <property name="readOnly">
+                   <bool>true</bool>
+                  </property>
+                 </widget>
+                </item>
+               </layout>
+              </widget>
+             </item>
+             <item>
+              <widget class="QGroupBox" name="MountAmiiboGroup">
+               <property name="minimumSize">
+                <size>
+                 <width>500</width>
+                 <height>0</height>
+                </size>
+               </property>
+               <property name="title">
+                <string>Mount Amiibo</string>
+               </property>
+               <layout class="QGridLayout" name="gridLayout_4">
+                <item row="0" column="3">
+                 <widget class="QToolButton" name="amiiboDirectoryButton">
+                  <property name="text">
+                   <string>...</string>
+                  </property>
+                 </widget>
+                </item>
+                <item row="0" column="1">
+                 <spacer name="horizontalSpacer">
+                  <property name="orientation">
+                   <enum>Qt::Horizontal</enum>
+                  </property>
+                  <property name="sizeType">
+                   <enum>QSizePolicy::Maximum</enum>
+                  </property>
+                  <property name="sizeHint" stdset="0">
+                   <size>
+                    <width>60</width>
+                    <height>20</height>
+                   </size>
+                  </property>
+                 </spacer>
+                </item>
+                <item row="0" column="0">
+                 <widget class="QLabel" name="amiiboDirectoryLabel">
+                  <property name="text">
+                   <string>File Path</string>
+                  </property>
+                 </widget>
+                </item>
+                <item row="0" column="2">
+                 <widget class="QLineEdit" name="amiiboDirectoryValue"/>
+                </item>
+               </layout>
+              </widget>
+             </item>
+             <item>
+              <spacer name="verticalSpacer">
+               <property name="orientation">
+                <enum>Qt::Vertical</enum>
+               </property>
+               <property name="sizeHint" stdset="0">
+                <size>
+                 <width>20</width>
+                 <height>40</height>
+                </size>
+               </property>
+              </spacer>
+             </item>
+            </layout>
+           </item>
+          </layout>
+         </item>
+        </layout>
+       </widget>
+      </item>
+      <item>
+       <widget class="QWidget" name="bottomControllerApplet" native="true">
+        <layout class="QHBoxLayout" name="horizontalLayout_6">
+         <property name="spacing">
+          <number>15</number>
+         </property>
+         <property name="leftMargin">
+          <number>15</number>
+         </property>
+         <property name="topMargin">
+          <number>8</number>
+         </property>
+         <property name="rightMargin">
+          <number>20</number>
+         </property>
+         <property name="bottomMargin">
+          <number>8</number>
+         </property>
+         <item alignment="Qt::AlignBottom">
+          <widget class="QDialogButtonBox" name="buttonBox">
+           <property name="enabled">
+            <bool>true</bool>
+           </property>
+           <property name="standardButtons">
+            <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>QtAmiiboSettingsDialog</receiver>
+   <slot>accept()</slot>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>QtAmiiboSettingsDialog</receiver>
+   <slot>reject()</slot>
+  </connection>
+ </connections>
+</ui>

+ 23 - 0
src/yuzu/main.cpp

@@ -15,6 +15,7 @@
 #endif
 
 // VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
+#include "applets/qt_amiibo_settings.h"
 #include "applets/qt_controller.h"
 #include "applets/qt_error.h"
 #include "applets/qt_profile_select.h"
@@ -26,6 +27,7 @@
 #include "configuration/configure_tas.h"
 #include "core/file_sys/vfs.h"
 #include "core/file_sys/vfs_real.h"
+#include "core/frontend/applets/cabinet.h"
 #include "core/frontend/applets/controller.h"
 #include "core/frontend/applets/general_frontend.h"
 #include "core/frontend/applets/mii_edit.h"
@@ -548,6 +550,11 @@ void GMainWindow::RegisterMetaTypes() {
 
     // Register applet types
 
+    // Cabinet Applet
+    qRegisterMetaType<Core::Frontend::CabinetParameters>("Core::Frontend::CabinetParameters");
+    qRegisterMetaType<std::shared_ptr<Service::NFP::NfpDevice>>(
+        "std::shared_ptr<Service::NFP::NfpDevice>");
+
     // Controller Applet
     qRegisterMetaType<Core::Frontend::ControllerParameters>("Core::Frontend::ControllerParameters");
 
@@ -569,6 +576,21 @@ void GMainWindow::RegisterMetaTypes() {
     qRegisterMetaType<Core::SystemResultStatus>("Core::SystemResultStatus");
 }
 
+void GMainWindow::AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters,
+                                           std::shared_ptr<Service::NFP::NfpDevice> nfp_device) {
+    QtAmiiboSettingsDialog dialog(this, parameters, input_subsystem.get(), nfp_device);
+
+    dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
+                          Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
+    dialog.setWindowModality(Qt::WindowModal);
+    if (dialog.exec() == QDialog::Rejected) {
+        emit AmiiboSettingsFinished(false, {});
+        return;
+    }
+
+    emit AmiiboSettingsFinished(true, dialog.GetName());
+}
+
 void GMainWindow::ControllerSelectorReconfigureControllers(
     const Core::Frontend::ControllerParameters& parameters) {
     QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get(), *system);
@@ -1546,6 +1568,7 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
     system->SetFilesystem(vfs);
 
     system->SetAppletFrontendSet({
+        std::make_unique<QtAmiiboSettings>(*this),     // Amiibo Settings
         std::make_unique<QtControllerSelector>(*this), // Controller Selector
         std::make_unique<QtErrorDisplay>(*this),       // Error Display
         nullptr,                                       // Mii Editor

+ 9 - 0
src/yuzu/main.h

@@ -55,6 +55,7 @@ class System;
 } // namespace Core
 
 namespace Core::Frontend {
+struct CabinetParameters;
 struct ControllerParameters;
 struct InlineAppearParameters;
 struct InlineTextParameters;
@@ -82,6 +83,10 @@ enum class SwkbdReplyType : u32;
 enum class WebExitReason : u32;
 } // namespace Service::AM::Applets
 
+namespace Service::NFP {
+class NfpDevice;
+} // namespace Service::NFP
+
 namespace Ui {
 class MainWindow;
 }
@@ -149,6 +154,8 @@ signals:
 
     void UpdateInstallProgress();
 
+    void AmiiboSettingsFinished(bool is_success, const std::string& name);
+
     void ControllerSelectorReconfigureFinished();
 
     void ErrorDisplayFinished();
@@ -170,6 +177,8 @@ public slots:
     void OnExecuteProgram(std::size_t program_index);
     void OnExit();
     void OnSaveConfig();
+    void AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters,
+                                  std::shared_ptr<Service::NFP::NfpDevice> nfp_device);
     void ControllerSelectorReconfigureControllers(
         const Core::Frontend::ControllerParameters& parameters);
     void SoftwareKeyboardInitialize(