From 9afadca5dc922ac05c7b1557159b277327f40945 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Sun, 13 Nov 2022 13:43:03 -0600
Subject: [PATCH] yuzu: Implement cabinet applet frontend

---
 src/yuzu/CMakeLists.txt                |   3 +
 src/yuzu/applets/qt_amiibo_manager.cpp | 247 +++++++++++++
 src/yuzu/applets/qt_amiibo_manager.h   |  93 +++++
 src/yuzu/applets/qt_amiibo_manager.ui  | 491 +++++++++++++++++++++++++
 src/yuzu/main.cpp                      |  23 +-
 src/yuzu/main.h                        |   9 +
 6 files changed, 865 insertions(+), 1 deletion(-)
 create mode 100644 src/yuzu/applets/qt_amiibo_manager.cpp
 create mode 100644 src/yuzu/applets/qt_amiibo_manager.h
 create mode 100644 src/yuzu/applets/qt_amiibo_manager.ui

diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 5cc1fbf326..8571d9c7ce 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -18,6 +18,9 @@ add_executable(yuzu
     about_dialog.cpp
     about_dialog.h
     aboutdialog.ui
+    applets/qt_amiibo_manager.cpp
+    applets/qt_amiibo_manager.h
+    applets/qt_amiibo_manager.ui
     applets/qt_controller.cpp
     applets/qt_controller.h
     applets/qt_controller.ui
diff --git a/src/yuzu/applets/qt_amiibo_manager.cpp b/src/yuzu/applets/qt_amiibo_manager.cpp
new file mode 100644
index 0000000000..9c7b15d065
--- /dev/null
+++ b/src/yuzu/applets/qt_amiibo_manager.cpp
@@ -0,0 +1,247 @@
+// 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_manager.h"
+#include "web_service/web_backend.h"
+#include "yuzu/applets/qt_amiibo_manager.h"
+#include "yuzu/main.h"
+
+QtAmiiboManagerDialog::QtAmiiboManagerDialog(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::QtAmiiboManagerDialog>()),
+      input_subsystem{input_subsystem_}, nfp_device{nfp_device_},
+      parameters(std::move(parameters_)) {
+    ui->setupUi(this);
+
+    LoadInfo();
+
+    resize(0, 0);
+}
+
+QtAmiiboManagerDialog::~QtAmiiboManagerDialog() = default;
+
+int QtAmiiboManagerDialog::exec() {
+    if (!is_initalized) {
+        return QDialog::Rejected;
+    }
+    return QDialog::exec();
+}
+
+std::string QtAmiiboManagerDialog::GetName() {
+    return ui->amiiboCustomNameValue->text().toStdString();
+}
+
+void QtAmiiboManagerDialog::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);
+
+    Service::NFP::ModelInfo model_info{};
+    const auto model_result = nfp_device->GetModelInfo(model_info);
+
+    if (model_result.IsSuccess()) {
+        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_ERROR(Input, "{}", amiibo_id);
+        LoadAmiiboApiInfo(amiibo_id);
+    }
+
+    LoadAmiiboData();
+    LoadAmiiboGameInfo();
+
+    ui->amiiboDirectoryValue->setText(
+        QString::fromStdString(input_subsystem->GetVirtualAmiibo()->GetLastFilePath()));
+
+    SetManagerDescription();
+    is_initalized = true;
+}
+
+void QtAmiiboManagerDialog::LoadAmiiboApiInfo(std::string amiibo_id) {
+    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 QtAmiiboManagerDialog::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 QtAmiiboManagerDialog::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 QtAmiiboManagerDialog::SetGameDataName(u32 application_area_id) {
+    const std::array<std::pair<u32, QString>, 12> game_name_list = {
+        // 3ds, wii u
+        std::pair<u32, QString>{0x10110E00, QStringLiteral("Super Smash Bros (3DS/WiiU)")},
+        {0x00132600, QStringLiteral("Mario & Luigi: Paper Jam")},
+        {0x0014F000, QStringLiteral("Animal Crossing: Happy Home Designer")},
+        {0x00152600, QStringLiteral("Chibi-Robo!: Zip Lash")},
+        {0x10161f00, QStringLiteral("Mario Party 10")},
+        {0x1019C800, QStringLiteral("The Legend of Zelda: Twilight Princess HD")},
+        // switch
+        {0x10162B00, QStringLiteral("Splatoon 2")},
+        {0x1016e100, QStringLiteral("Shovel Knight: Treasure Trove")},
+        {0x1019C800, QStringLiteral("The Legend of Zelda: Breath of the Wild")},
+        {0x34F80200, QStringLiteral("Super Smash Bros. Ultimate")},
+        {0x38600500, QStringLiteral("Splatoon 3")},
+        {0x3B440400, QStringLiteral("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(game_name);
+            return;
+        }
+    }
+
+    const auto application_area_string = fmt::format("{:016x}", application_area_id);
+    ui->gameIdValue->setText(QString::fromStdString(application_area_string));
+}
+
+void QtAmiiboManagerDialog::SetManagerDescription() {
+    switch (parameters.mode) {
+    case Service::NFP::CabinetMode::StartFormatter:
+        ui->cabinetActionDescriptionLabel->setText(
+            tr("The following amiibo data will be formated:"));
+        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;
+    }
+}
+
+QtAmiiboManager::QtAmiiboManager(GMainWindow& parent) {
+    connect(this, &QtAmiiboManager::MainWindowShowAmiiboManager, &parent,
+            &GMainWindow::AmiiboManagerShowDialog, Qt::QueuedConnection);
+    connect(&parent, &GMainWindow::AmiiboManagerFinished, this,
+            &QtAmiiboManager::MainWindowFinished, Qt::QueuedConnection);
+}
+
+QtAmiiboManager::~QtAmiiboManager() = default;
+
+void QtAmiiboManager::ShowCabinetApplet(std::function<void(bool, const std::string&)> callback_,
+                                        const Core::Frontend::CabinetParameters& parameters,
+                                        std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const {
+    callback = std::move(callback_);
+    emit MainWindowShowAmiiboManager(parameters, nfp_device);
+}
+
+void QtAmiiboManager::MainWindowFinished(bool is_success, std::string name) {
+    callback(is_success, name);
+}
diff --git a/src/yuzu/applets/qt_amiibo_manager.h b/src/yuzu/applets/qt_amiibo_manager.h
new file mode 100644
index 0000000000..3f5866ed7c
--- /dev/null
+++ b/src/yuzu/applets/qt_amiibo_manager.h
@@ -0,0 +1,93 @@
+// 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;
+
+class InputProfiles;
+
+namespace InputCommon {
+class InputSubsystem;
+}
+
+namespace Ui {
+class QtAmiiboManagerDialog;
+}
+
+namespace Core {
+class System;
+}
+
+namespace Core::HID {
+class HIDCore;
+enum class NpadStyleIndex : u8;
+} // namespace Core::HID
+
+namespace Service::NFP {
+class NfpDevice;
+} // namespace Service::NFP
+
+class QtAmiiboManagerDialog final : public QDialog {
+    Q_OBJECT
+
+public:
+    explicit QtAmiiboManagerDialog(QWidget* parent, Core::Frontend::CabinetParameters parameters_,
+                                   InputCommon::InputSubsystem* input_subsystem_,
+                                   std::shared_ptr<Service::NFP::NfpDevice> nfp_device_);
+    ~QtAmiiboManagerDialog() override;
+
+    int exec() override;
+
+    std::string GetName();
+
+private:
+    void LoadInfo();
+    void LoadAmiiboApiInfo(std::string amiibo_id);
+    void LoadAmiiboData();
+    void LoadAmiiboGameInfo();
+    void SetGameDataName(u32 application_area_id);
+    void SetManagerDescription();
+
+    std::unique_ptr<Ui::QtAmiiboManagerDialog> 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 manager failed to load
+    bool is_initalized{};
+};
+
+class QtAmiiboManager final : public QObject, public Core::Frontend::CabinetApplet {
+    Q_OBJECT
+
+public:
+    explicit QtAmiiboManager(GMainWindow& parent);
+    ~QtAmiiboManager() override;
+
+    void ShowCabinetApplet(std::function<void(bool, const std::string&)> callback_,
+                           const Core::Frontend::CabinetParameters& parameters,
+                           std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const override;
+
+signals:
+    void MainWindowShowAmiiboManager(const Core::Frontend::CabinetParameters& parameters,
+                                     std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const;
+
+private:
+    void MainWindowFinished(bool is_success, std::string name);
+
+    mutable std::function<void(bool, const std::string&)> callback;
+};
diff --git a/src/yuzu/applets/qt_amiibo_manager.ui b/src/yuzu/applets/qt_amiibo_manager.ui
new file mode 100644
index 0000000000..eb6eabe591
--- /dev/null
+++ b/src/yuzu/applets/qt_amiibo_manager.ui
@@ -0,0 +1,491 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtAmiiboManagerDialog</class>
+ <widget class="QDialog" name="QtAmiiboManagerDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>839</width>
+    <height>500</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Amiibo Manager</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="leftMargin">
+            <number>20</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>20</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>QtAmiiboManagerDialog</receiver>
+   <slot>accept()</slot>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>QtAmiiboManagerDialog</receiver>
+   <slot>reject()</slot>
+  </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 27c9e1f329..27266cae3c 100644
--- a/src/yuzu/main.cpp
+++ b/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_manager.h"
 #include "applets/qt_controller.h"
 #include "applets/qt_error.h"
 #include "applets/qt_profile_select.h"
@@ -550,6 +551,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");
 
@@ -571,6 +577,21 @@ void GMainWindow::RegisterMetaTypes() {
     qRegisterMetaType<Core::SystemResultStatus>("Core::SystemResultStatus");
 }
 
+void GMainWindow::AmiiboManagerShowDialog(const Core::Frontend::CabinetParameters& parameters,
+                                          std::shared_ptr<Service::NFP::NfpDevice> nfp_device) {
+    QtAmiiboManagerDialog 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 AmiiboManagerFinished(false, {});
+        return;
+    }
+
+    emit AmiiboManagerFinished(true, dialog.GetName());
+}
+
 void GMainWindow::ControllerSelectorReconfigureControllers(
     const Core::Frontend::ControllerParameters& parameters) {
     QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get(), *system);
@@ -1548,7 +1569,7 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
     system->SetFilesystem(vfs);
 
     system->SetAppletFrontendSet({
-        nullptr,                                       // Amiibo Manager
+        std::make_unique<QtAmiiboManager>(*this),      // Amiibo Manager
         std::make_unique<QtControllerSelector>(*this), // Controller Selector
         std::make_unique<QtErrorDisplay>(*this),       // Error Display
         nullptr,                                       // Mii Editor
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index b73f550dda..2724ecd521 100644
--- a/src/yuzu/main.h
+++ b/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 AmiiboManagerFinished(bool is_success, 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 AmiiboManagerShowDialog(const Core::Frontend::CabinetParameters& parameters,
+                                 std::shared_ptr<Service::NFP::NfpDevice> nfp_device);
     void ControllerSelectorReconfigureControllers(
         const Core::Frontend::ControllerParameters& parameters);
     void SoftwareKeyboardInitialize(