aboutsummaryrefslogtreecommitdiff
path: root/src/yuzu
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu')
-rw-r--r--src/yuzu/CMakeLists.txt3
-rw-r--r--src/yuzu/applets/qt_amiibo_settings.cpp264
-rw-r--r--src/yuzu/applets/qt_amiibo_settings.h83
-rw-r--r--src/yuzu/applets/qt_amiibo_settings.ui494
-rw-r--r--src/yuzu/configuration/config.cpp5
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp38
-rw-r--r--src/yuzu/configuration/configure_graphics.h2
-rw-r--r--src/yuzu/configuration/configure_graphics.ui146
-rw-r--r--src/yuzu/main.cpp23
-rw-r--r--src/yuzu/main.h9
-rw-r--r--src/yuzu/main.ui9
11 files changed, 1076 insertions, 0 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index ec9246e74..0aa109dd3 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_settings.cpp
+ applets/qt_amiibo_settings.h
+ applets/qt_amiibo_settings.ui
applets/qt_controller.cpp
applets/qt_controller.h
applets/qt_controller.ui
diff --git a/src/yuzu/applets/qt_amiibo_settings.cpp b/src/yuzu/applets/qt_amiibo_settings.cpp
new file mode 100644
index 000000000..93ad4b4f9
--- /dev/null
+++ b/src/yuzu/applets/qt_amiibo_settings.cpp
@@ -0,0 +1,264 @@
+// 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"
+#ifdef ENABLE_WEB_SERVICE
+#include "web_service/web_backend.h"
+#endif
+#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) {
+#ifdef ENABLE_WEB_SERVICE
+ // 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);
+#endif
+}
+
+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);
+}
diff --git a/src/yuzu/applets/qt_amiibo_settings.h b/src/yuzu/applets/qt_amiibo_settings.h
new file mode 100644
index 000000000..930c96739
--- /dev/null
+++ b/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;
+};
diff --git a/src/yuzu/applets/qt_amiibo_settings.ui b/src/yuzu/applets/qt_amiibo_settings.ui
new file mode 100644
index 000000000..f377a6e61
--- /dev/null
+++ b/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>
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 343f3b8e5..0c93df428 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -672,6 +672,7 @@ void Config::ReadRendererValues() {
ReadGlobalSetting(Settings::values.aspect_ratio);
ReadGlobalSetting(Settings::values.resolution_setup);
ReadGlobalSetting(Settings::values.scaling_filter);
+ ReadGlobalSetting(Settings::values.fsr_sharpening_slider);
ReadGlobalSetting(Settings::values.anti_aliasing);
ReadGlobalSetting(Settings::values.max_anisotropy);
ReadGlobalSetting(Settings::values.use_speed_limit);
@@ -1282,6 +1283,10 @@ void Config::SaveRendererValues() {
static_cast<u32>(Settings::values.scaling_filter.GetValue(global)),
static_cast<u32>(Settings::values.scaling_filter.GetDefault()),
Settings::values.scaling_filter.UsingGlobal());
+ WriteSetting(QString::fromStdString(Settings::values.fsr_sharpening_slider.GetLabel()),
+ static_cast<u32>(Settings::values.fsr_sharpening_slider.GetValue(global)),
+ static_cast<u32>(Settings::values.fsr_sharpening_slider.GetDefault()),
+ Settings::values.fsr_sharpening_slider.UsingGlobal());
WriteSetting(QString::fromStdString(Settings::values.anti_aliasing.GetLabel()),
static_cast<u32>(Settings::values.anti_aliasing.GetValue(global)),
static_cast<u32>(Settings::values.anti_aliasing.GetDefault()),
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index bd69d04a6..f1385e972 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -63,6 +63,11 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren
ui->api_widget->isEnabled());
ui->bg_label->setVisible(Settings::IsConfiguringGlobal());
ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal());
+
+ connect(ui->fsr_sharpening_slider, &QSlider::valueChanged, this,
+ &ConfigureGraphics::SetFSRIndicatorText);
+ ui->fsr_sharpening_combobox->setVisible(!Settings::IsConfiguringGlobal());
+ ui->fsr_sharpening_label->setVisible(Settings::IsConfiguringGlobal());
}
void ConfigureGraphics::UpdateDeviceSelection(int device) {
@@ -110,6 +115,7 @@ void ConfigureGraphics::SetConfiguration() {
static_cast<int>(Settings::values.resolution_setup.GetValue()));
ui->scaling_filter_combobox->setCurrentIndex(
static_cast<int>(Settings::values.scaling_filter.GetValue()));
+ ui->fsr_sharpening_slider->setValue(Settings::values.fsr_sharpening_slider.GetValue());
ui->anti_aliasing_combobox->setCurrentIndex(
static_cast<int>(Settings::values.anti_aliasing.GetValue()));
} else {
@@ -147,6 +153,15 @@ void ConfigureGraphics::SetConfiguration() {
ConfigurationShared::SetHighlight(ui->anti_aliasing_label,
!Settings::values.anti_aliasing.UsingGlobal());
+ ui->fsr_sharpening_combobox->setCurrentIndex(
+ Settings::values.fsr_sharpening_slider.UsingGlobal() ? 0 : 1);
+ ui->fsr_sharpening_slider->setEnabled(
+ !Settings::values.fsr_sharpening_slider.UsingGlobal());
+ ui->fsr_sharpening_value->setEnabled(!Settings::values.fsr_sharpening_slider.UsingGlobal());
+ ConfigurationShared::SetHighlight(ui->fsr_sharpening_layout,
+ !Settings::values.fsr_sharpening_slider.UsingGlobal());
+ ui->fsr_sharpening_slider->setValue(Settings::values.fsr_sharpening_slider.GetValue());
+
ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1);
ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal());
ConfigurationShared::SetHighlight(ui->bg_layout, !Settings::values.bg_red.UsingGlobal());
@@ -155,6 +170,12 @@ void ConfigureGraphics::SetConfiguration() {
Settings::values.bg_green.GetValue(),
Settings::values.bg_blue.GetValue()));
UpdateAPILayout();
+ SetFSRIndicatorText(ui->fsr_sharpening_slider->sliderPosition());
+}
+
+void ConfigureGraphics::SetFSRIndicatorText(int percentage) {
+ ui->fsr_sharpening_value->setText(
+ tr("%1%", "FSR sharpening percentage (e.g. 50%)").arg(100 - (percentage / 2)));
}
void ConfigureGraphics::ApplyConfiguration() {
@@ -210,6 +231,7 @@ void ConfigureGraphics::ApplyConfiguration() {
if (Settings::values.anti_aliasing.UsingGlobal()) {
Settings::values.anti_aliasing.SetValue(anti_aliasing);
}
+ Settings::values.fsr_sharpening_slider.SetValue(ui->fsr_sharpening_slider->value());
} else {
if (ui->resolution_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
Settings::values.resolution_setup.SetGlobal(true);
@@ -269,6 +291,13 @@ void ConfigureGraphics::ApplyConfiguration() {
Settings::values.bg_green.SetValue(static_cast<u8>(bg_color.green()));
Settings::values.bg_blue.SetValue(static_cast<u8>(bg_color.blue()));
}
+
+ if (ui->fsr_sharpening_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
+ Settings::values.fsr_sharpening_slider.SetGlobal(true);
+ } else {
+ Settings::values.fsr_sharpening_slider.SetGlobal(false);
+ Settings::values.fsr_sharpening_slider.SetValue(ui->fsr_sharpening_slider->value());
+ }
}
}
@@ -380,6 +409,7 @@ void ConfigureGraphics::SetupPerGameUI() {
ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal());
ui->resolution_combobox->setEnabled(Settings::values.resolution_setup.UsingGlobal());
ui->scaling_filter_combobox->setEnabled(Settings::values.scaling_filter.UsingGlobal());
+ ui->fsr_sharpening_slider->setEnabled(Settings::values.fsr_sharpening_slider.UsingGlobal());
ui->anti_aliasing_combobox->setEnabled(Settings::values.anti_aliasing.UsingGlobal());
ui->use_asynchronous_gpu_emulation->setEnabled(
Settings::values.use_asynchronous_gpu_emulation.UsingGlobal());
@@ -387,6 +417,7 @@ void ConfigureGraphics::SetupPerGameUI() {
ui->accelerate_astc->setEnabled(Settings::values.accelerate_astc.UsingGlobal());
ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal());
ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal());
+ ui->fsr_slider_layout->setEnabled(Settings::values.fsr_sharpening_slider.UsingGlobal());
return;
}
@@ -396,6 +427,13 @@ void ConfigureGraphics::SetupPerGameUI() {
ConfigurationShared::SetHighlight(ui->bg_layout, index == 1);
});
+ connect(ui->fsr_sharpening_combobox, qOverload<int>(&QComboBox::activated), this,
+ [this](int index) {
+ ui->fsr_sharpening_slider->setEnabled(index == 1);
+ ui->fsr_sharpening_value->setEnabled(index == 1);
+ ConfigurationShared::SetHighlight(ui->fsr_sharpening_layout, index == 1);
+ });
+
ConfigurationShared::SetColoredTristate(
ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache);
ConfigurationShared::SetColoredTristate(ui->accelerate_astc, Settings::values.accelerate_astc,
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index 70034eb1b..d98d6624e 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -42,6 +42,8 @@ private:
void RetrieveVulkanDevices();
+ void SetFSRIndicatorText(int percentage);
+
void SetupPerGameUI();
Settings::RendererBackend GetCurrentGraphicsBackend() const;
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index fdbb33372..37271f956 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -152,6 +152,12 @@
</item>
<item>
<widget class="QGroupBox" name="groupBox">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
+ </property>
<property name="title">
<string>Graphics Settings</string>
</property>
@@ -482,6 +488,146 @@
</widget>
</item>
<item>
+ <widget class="QWidget" name="fsr_sharpening_layout" native="true">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </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="fsr_sharpening_label_group">
+ <item>
+ <widget class="QComboBox" name="fsr_sharpening_combobox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>Use global FSR Sharpness</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Set FSR Sharpness</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="fsr_sharpening_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>FSR Sharpness:</string>
+ </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>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="fsr_slider_layout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QSlider" name="fsr_sharpening_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximum">
+ <number>200</number>
+ </property>
+ <property name="sliderPosition">
+ <number>25</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="invertedAppearance">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="fsr_sharpening_value">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>100%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<widget class="QWidget" name="bg_layout" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 34c10fd72..2aae746f0 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_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"
@@ -549,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");
@@ -570,6 +577,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);
@@ -1547,6 +1569,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
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index b73f550dd..6a9992d05 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 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(
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index e670acc30..013ba0ceb 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -231,6 +231,9 @@
<property name="text">
<string>Con&amp;figure...</string>
</property>
+ <property name="menuRole">
+ <enum>QAction::PreferencesRole</enum>
+ </property>
</action>
<action name="action_Display_Dock_Widget_Headers">
<property name="checkable">
@@ -363,6 +366,9 @@
<property name="text">
<string>&amp;Configure TAS...</string>
</property>
+ <property name="menuRole">
+ <enum>QAction::NoRole</enum>
+ </property>
</action>
<action name="action_Configure_Current_Game">
<property name="enabled">
@@ -371,6 +377,9 @@
<property name="text">
<string>Configure C&amp;urrent Game...</string>
</property>
+ <property name="menuRole">
+ <enum>QAction::NoRole</enum>
+ </property>
</action>
<action name="action_TAS_Start">
<property name="enabled">