diff options
Diffstat (limited to 'src/yuzu')
41 files changed, 1229 insertions, 298 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 7de919a8e..46ed232d8 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -11,10 +11,14 @@ add_executable(yuzu bootmanager.h configuration/config.cpp configuration/config.h + configuration/configure_audio.cpp + configuration/configure_audio.h configuration/configure_debug.cpp configuration/configure_debug.h configuration/configure_dialog.cpp configuration/configure_dialog.h + configuration/configure_gamelist.cpp + configuration/configure_gamelist.h configuration/configure_general.cpp configuration/configure_general.h configuration/configure_graphics.cpp @@ -55,7 +59,9 @@ add_executable(yuzu set(UIS aboutdialog.ui configuration/configure.ui + configuration/configure_audio.ui configuration/configure_debug.ui + configuration/configure_gamelist.ui configuration/configure_general.ui configuration/configure_graphics.ui configuration/configure_input.ui diff --git a/src/yuzu/about_dialog.cpp b/src/yuzu/about_dialog.cpp index d6647eeea..a81ad2888 100644 --- a/src/yuzu/about_dialog.cpp +++ b/src/yuzu/about_dialog.cpp @@ -10,8 +10,9 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) { ui->setupUi(this); ui->labelLogo->setPixmap(QIcon::fromTheme("yuzu").pixmap(200)); - ui->labelBuildInfo->setText(ui->labelBuildInfo->text().arg( - Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); + ui->labelBuildInfo->setText( + ui->labelBuildInfo->text().arg(Common::g_build_name, Common::g_scm_branch, + Common::g_scm_desc, QString(Common::g_build_date).left(10))); } -AboutDialog::~AboutDialog() {} +AboutDialog::~AboutDialog() = default; diff --git a/src/yuzu/about_dialog.h b/src/yuzu/about_dialog.h index 2eb6e28f5..18e8c11a7 100644 --- a/src/yuzu/about_dialog.h +++ b/src/yuzu/about_dialog.h @@ -16,7 +16,7 @@ class AboutDialog : public QDialog { public: explicit AboutDialog(QWidget* parent); - ~AboutDialog(); + ~AboutDialog() override; private: std::unique_ptr<Ui::AboutDialog> ui; diff --git a/src/yuzu/aboutdialog.ui b/src/yuzu/aboutdialog.ui index 2680480cc..f122ba39d 100644 --- a/src/yuzu/aboutdialog.ui +++ b/src/yuzu/aboutdialog.ui @@ -70,7 +70,7 @@ </sizepolicy> </property> <property name="text"> - <string><html><head/><body><p>%1 | %2-%3</p></body></html></string> + <string><html><head/><body><p>%1 | %2-%3 (%4)</p></body></html></string> </property> </widget> </item> @@ -115,7 +115,7 @@ p, li { white-space: pre-wrap; } <item> <widget class="QLabel" name="labelLinks"> <property name="text"> - <string><html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#0000ff;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#0000ff;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#0000ff;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#0000ff;">License</span></a></p></body></html></string> + <string><html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html></string> </property> <property name="openExternalLinks"> <bool>true</bool> diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 130bc613b..f133bfadf 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -101,12 +101,12 @@ signals: void ErrorThrown(Core::System::ResultStatus, std::string); }; -class GRenderWindow : public QWidget, public EmuWindow { +class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow { Q_OBJECT public: GRenderWindow(QWidget* parent, EmuThread* emu_thread); - ~GRenderWindow(); + ~GRenderWindow() override; // EmuWindow implementation void SwapBuffers() override; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 98969fe10..0bd46dbac 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -92,16 +92,26 @@ void Config::ReadValues() { Settings::values.bg_blue = qt_config->value("bg_blue", 0.0).toFloat(); qt_config->endGroup(); + qt_config->beginGroup("Audio"); + Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString(); + Settings::values.audio_device_id = + qt_config->value("output_device", "auto").toString().toStdString(); + Settings::values.volume = qt_config->value("volume", 1).toFloat(); + qt_config->endGroup(); + qt_config->beginGroup("Data Storage"); Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool(); qt_config->endGroup(); qt_config->beginGroup("System"); Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool(); + Settings::values.username = qt_config->value("username", "yuzu").toString().toStdString(); + Settings::values.language_index = qt_config->value("language_index", 1).toInt(); qt_config->endGroup(); qt_config->beginGroup("Miscellaneous"); Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString(); + Settings::values.use_dev_keys = qt_config->value("use_dev_keys", false).toBool(); qt_config->endGroup(); qt_config->beginGroup("Debugging"); @@ -112,6 +122,13 @@ void Config::ReadValues() { qt_config->beginGroup("UI"); UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); + qt_config->beginGroup("UIGameList"); + UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool(); + UISettings::values.icon_size = qt_config->value("icon_size", 48).toUInt(); + UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 0).toUInt(); + UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 3).toUInt(); + qt_config->endGroup(); + qt_config->beginGroup("UILayout"); UISettings::values.geometry = qt_config->value("geometry").toByteArray(); UISettings::values.state = qt_config->value("state").toByteArray(); @@ -195,16 +212,25 @@ void Config::SaveValues() { qt_config->setValue("bg_blue", (double)Settings::values.bg_blue); qt_config->endGroup(); + qt_config->beginGroup("Audio"); + qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id)); + qt_config->setValue("output_device", QString::fromStdString(Settings::values.audio_device_id)); + qt_config->setValue("volume", Settings::values.volume); + qt_config->endGroup(); + qt_config->beginGroup("Data Storage"); qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd); qt_config->endGroup(); qt_config->beginGroup("System"); qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode); + qt_config->setValue("username", QString::fromStdString(Settings::values.username)); + qt_config->setValue("language_index", Settings::values.language_index); qt_config->endGroup(); qt_config->beginGroup("Miscellaneous"); qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter)); + qt_config->setValue("use_dev_keys", Settings::values.use_dev_keys); qt_config->endGroup(); qt_config->beginGroup("Debugging"); @@ -215,6 +241,13 @@ void Config::SaveValues() { qt_config->beginGroup("UI"); qt_config->setValue("theme", UISettings::values.theme); + qt_config->beginGroup("UIGameList"); + qt_config->setValue("show_unknown", UISettings::values.show_unknown); + qt_config->setValue("icon_size", UISettings::values.icon_size); + qt_config->setValue("row_1_text_id", UISettings::values.row_1_text_id); + qt_config->setValue("row_2_text_id", UISettings::values.row_2_text_id); + qt_config->endGroup(); + qt_config->beginGroup("UILayout"); qt_config->setValue("geometry", UISettings::values.geometry); qt_config->setValue("state", UISettings::values.state); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index c5303851c..20f120134 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -24,6 +24,11 @@ <string>General</string> </attribute> </widget> + <widget class="ConfigureGameList" name="gameListTab"> + <attribute name="title"> + <string>Game List</string> + </attribute> + </widget> <widget class="ConfigureSystem" name="systemTab"> <attribute name="title"> <string>System</string> @@ -34,11 +39,16 @@ <string>Input</string> </attribute> </widget> - <widget class="ConfigureGraphics" name="graphicsTab"> - <attribute name="title"> - <string>Graphics</string> - </attribute> - </widget> + <widget class="ConfigureGraphics" name="graphicsTab"> + <attribute name="title"> + <string>Graphics</string> + </attribute> + </widget> + <widget class="ConfigureAudio" name="audioTab"> + <attribute name="title"> + <string>Audio</string> + </attribute> + </widget> <widget class="ConfigureDebug" name="debugTab"> <attribute name="title"> <string>Debug</string> @@ -62,6 +72,12 @@ <header>configuration/configure_general.h</header> <container>1</container> </customwidget> + <customwidget> + <class>ConfigureGameList</class> + <extends>QWidget</extends> + <header>configuration/configure_gamelist.h</header> + <container>1</container> + </customwidget> <customwidget> <class>ConfigureSystem</class> <extends>QWidget</extends> @@ -69,6 +85,12 @@ <container>1</container> </customwidget> <customwidget> + <class>ConfigureAudio</class> + <extends>QWidget</extends> + <header>configuration/configure_audio.h</header> + <container>1</container> + </customwidget> + <customwidget> <class>ConfigureDebug</class> <extends>QWidget</extends> <header>configuration/configure_debug.h</header> diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp new file mode 100644 index 000000000..fbb813f6c --- /dev/null +++ b/src/yuzu/configuration/configure_audio.cpp @@ -0,0 +1,90 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> + +#include "audio_core/sink.h" +#include "audio_core/sink_details.h" +#include "core/core.h" +#include "core/settings.h" +#include "ui_configure_audio.h" +#include "yuzu/configuration/configure_audio.h" + +ConfigureAudio::ConfigureAudio(QWidget* parent) + : QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()) { + ui->setupUi(this); + + ui->output_sink_combo_box->clear(); + ui->output_sink_combo_box->addItem("auto"); + for (const auto& sink_detail : AudioCore::g_sink_details) { + ui->output_sink_combo_box->addItem(sink_detail.id); + } + + connect(ui->volume_slider, &QSlider::valueChanged, [this] { + ui->volume_indicator->setText(tr("%1 %").arg(ui->volume_slider->sliderPosition())); + }); + + this->setConfiguration(); + connect(ui->output_sink_combo_box, + static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, + &ConfigureAudio::updateAudioDevices); + + ui->output_sink_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn()); + ui->audio_device_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn()); +} + +ConfigureAudio::~ConfigureAudio() = default; + +void ConfigureAudio::setConfiguration() { + int new_sink_index = 0; + for (int index = 0; index < ui->output_sink_combo_box->count(); index++) { + if (ui->output_sink_combo_box->itemText(index).toStdString() == Settings::values.sink_id) { + new_sink_index = index; + break; + } + } + ui->output_sink_combo_box->setCurrentIndex(new_sink_index); + + // The device list cannot be pre-populated (nor listed) until the output sink is known. + updateAudioDevices(new_sink_index); + + int new_device_index = -1; + for (int index = 0; index < ui->audio_device_combo_box->count(); index++) { + if (ui->audio_device_combo_box->itemText(index).toStdString() == + Settings::values.audio_device_id) { + new_device_index = index; + break; + } + } + ui->audio_device_combo_box->setCurrentIndex(new_device_index); + + ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum()); + ui->volume_indicator->setText(tr("%1 %").arg(ui->volume_slider->sliderPosition())); +} + +void ConfigureAudio::applyConfiguration() { + Settings::values.sink_id = + ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) + .toStdString(); + Settings::values.audio_device_id = + ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) + .toStdString(); + Settings::values.volume = + static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum(); +} + +void ConfigureAudio::updateAudioDevices(int sink_index) { + ui->audio_device_combo_box->clear(); + ui->audio_device_combo_box->addItem(AudioCore::auto_device_name); + + std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); + std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices(); + for (const auto& device : device_list) { + ui->audio_device_combo_box->addItem(device.c_str()); + } +} + +void ConfigureAudio::retranslateUi() { + ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h new file mode 100644 index 000000000..4f0af4163 --- /dev/null +++ b/src/yuzu/configuration/configure_audio.h @@ -0,0 +1,31 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QWidget> + +namespace Ui { +class ConfigureAudio; +} + +class ConfigureAudio : public QWidget { + Q_OBJECT + +public: + explicit ConfigureAudio(QWidget* parent = nullptr); + ~ConfigureAudio(); + + void applyConfiguration(); + void retranslateUi(); + +public slots: + void updateAudioDevices(int sink_index); + +private: + void setConfiguration(); + + std::unique_ptr<Ui::ConfigureAudio> ui; +}; diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui new file mode 100644 index 000000000..ef67890dc --- /dev/null +++ b/src/yuzu/configuration/configure_audio.ui @@ -0,0 +1,130 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureAudio</class> + <widget class="QWidget" name="ConfigureAudio"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>188</width> + <height>246</height> + </rect> + </property> + <layout class="QVBoxLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Audio</string> + </property> + <layout class="QVBoxLayout"> + <item> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Output Engine:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="output_sink_combo_box"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Audio Device:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="audio_device_combo_box"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Volume:</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QSlider" name="volume_slider"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="pageStep"> + <number>10</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="volume_indicator"> + <property name="minimumSize"> + <size> + <width>32</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0 %</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>167</width> + <height>55</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 5e66239ff..45d84f19a 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -24,7 +24,7 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co }); } -ConfigureDebug::~ConfigureDebug() {} +ConfigureDebug::~ConfigureDebug() = default; void ConfigureDebug::setConfiguration() { ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub); @@ -44,5 +44,4 @@ void ConfigureDebug::applyConfiguration() { Log::Filter filter; filter.ParseFilterString(Settings::values.log_filter); Log::SetGlobalFilter(filter); - Settings::Apply(); } diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 118e91cf1..5ae7276bd 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -23,13 +23,6 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_3"> <item> - <widget class="QLabel" name="label_1"> - <property name="text"> - <string>The GDB Stub only works correctly when the CPU JIT is off.</string> - </property> - </widget> - </item> - <item> <layout class="QHBoxLayout" name="horizontalLayout_1"> <item> <widget class="QCheckBox" name="toggle_gdbstub"> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 358f33005..daa4cc0d9 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -6,21 +6,26 @@ #include "ui_configure.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_dialog.h" +#include "yuzu/hotkeys.h" -ConfigureDialog::ConfigureDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ConfigureDialog) { +ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry) + : QDialog(parent), ui(new Ui::ConfigureDialog) { ui->setupUi(this); + ui->generalTab->PopulateHotkeyList(registry); this->setConfiguration(); } -ConfigureDialog::~ConfigureDialog() {} +ConfigureDialog::~ConfigureDialog() = default; void ConfigureDialog::setConfiguration() {} void ConfigureDialog::applyConfiguration() { ui->generalTab->applyConfiguration(); + ui->gameListTab->applyConfiguration(); ui->systemTab->applyConfiguration(); ui->inputTab->applyConfiguration(); ui->graphicsTab->applyConfiguration(); + ui->audioTab->applyConfiguration(); ui->debugTab->applyConfiguration(); Settings::Apply(); } diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index 21fa1f501..bbbdacc29 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -7,6 +7,8 @@ #include <memory> #include <QDialog> +class HotkeyRegistry; + namespace Ui { class ConfigureDialog; } @@ -15,7 +17,7 @@ class ConfigureDialog : public QDialog { Q_OBJECT public: - explicit ConfigureDialog(QWidget* parent); + explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry); ~ConfigureDialog(); void applyConfiguration(); diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_gamelist.cpp new file mode 100644 index 000000000..1ae3423cf --- /dev/null +++ b/src/yuzu/configuration/configure_gamelist.cpp @@ -0,0 +1,63 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/settings.h" +#include "ui_configure_gamelist.h" +#include "ui_settings.h" +#include "yuzu/configuration/configure_gamelist.h" + +ConfigureGameList::ConfigureGameList(QWidget* parent) + : QWidget(parent), ui(new Ui::ConfigureGameList) { + ui->setupUi(this); + + static const std::vector<std::pair<u32, std::string>> default_icon_sizes{ + std::make_pair(0, "None"), std::make_pair(32, "Small"), + std::make_pair(64, "Standard"), std::make_pair(128, "Large"), + std::make_pair(256, "Full Size"), + }; + + for (const auto& size : default_icon_sizes) { + ui->icon_size_combobox->addItem(QString::fromStdString(size.second + " (" + + std::to_string(size.first) + "x" + + std::to_string(size.first) + ")"), + size.first); + } + + static const std::vector<std::string> row_text_names{ + "Filename", + "Filetype", + "Title ID", + "Title Name", + }; + + for (size_t i = 0; i < row_text_names.size(); ++i) { + ui->row_1_text_combobox->addItem(QString::fromStdString(row_text_names[i]), + QVariant::fromValue(i)); + ui->row_2_text_combobox->addItem(QString::fromStdString(row_text_names[i]), + QVariant::fromValue(i)); + } + + this->setConfiguration(); +} + +ConfigureGameList::~ConfigureGameList() {} + +void ConfigureGameList::setConfiguration() { + ui->show_unknown->setChecked(UISettings::values.show_unknown); + ui->icon_size_combobox->setCurrentIndex( + ui->icon_size_combobox->findData(UISettings::values.icon_size)); + ui->row_1_text_combobox->setCurrentIndex( + ui->row_1_text_combobox->findData(UISettings::values.row_1_text_id)); + ui->row_2_text_combobox->setCurrentIndex( + ui->row_2_text_combobox->findData(UISettings::values.row_2_text_id)); +} + +void ConfigureGameList::applyConfiguration() { + UISettings::values.show_unknown = ui->show_unknown->isChecked(); + UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt(); + UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); + UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); + Settings::Apply(); +} diff --git a/src/yuzu/configuration/configure_gamelist.h b/src/yuzu/configuration/configure_gamelist.h new file mode 100644 index 000000000..94fba6373 --- /dev/null +++ b/src/yuzu/configuration/configure_gamelist.h @@ -0,0 +1,28 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QWidget> + +namespace Ui { +class ConfigureGameList; +} + +class ConfigureGameList : public QWidget { + Q_OBJECT + +public: + explicit ConfigureGameList(QWidget* parent = nullptr); + ~ConfigureGameList(); + + void applyConfiguration(); + +private: + void setConfiguration(); + +private: + std::unique_ptr<Ui::ConfigureGameList> ui; +}; diff --git a/src/yuzu/configuration/configure_gamelist.ui b/src/yuzu/configuration/configure_gamelist.ui new file mode 100644 index 000000000..7471fdb60 --- /dev/null +++ b/src/yuzu/configuration/configure_gamelist.ui @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureGameList</class> + <widget class="QWidget" name="ConfigureGeneral"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>300</width> + <height>377</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QHBoxLayout" name="HorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="VerticalLayout"> + <item> + <widget class="QGroupBox" name="GeneralGroupBox"> + <property name="title"> + <string>General</string> + </property> + <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="GeneralVerticalLayout"> + <item> + <widget class="QCheckBox" name="show_unknown"> + <property name="text"> + <string>Show files with type 'Unknown'</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="IconSizeGroupBox"> + <property name="title"> + <string>Icon Size</string> + </property> + <layout class="QHBoxLayout" name="icon_size_qhbox_layout"> + <item> + <layout class="QVBoxLayout" name="icon_size_qvbox_layout"> + <item> + <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2"> + <item> + <widget class="QLabel" name="icon_size_label"> + <property name="text"> + <string>Icon Size:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="icon_size_combobox"/> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="RowGroupBox"> + <property name="title"> + <string>Row Text</string> + </property> + <layout class="QHBoxLayout" name="RowHorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="RowVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="row_1_qhbox_layout"> + <item> + <widget class="QLabel" name="row_1_label"> + <property name="text"> + <string>Row 1 Text:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="row_1_text_combobox"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="row_2_qhbox_layout"> + <item> + <widget class="QLabel" name="row_2_label"> + <property name="text"> + <string>Row 2 Text:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="row_2_text_combobox"/> + </item> + </layout> + </item> + </layout> + </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> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index baa558667..d8caee1e8 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -24,7 +24,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) ui->use_docked_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn()); } -ConfigureGeneral::~ConfigureGeneral() {} +ConfigureGeneral::~ConfigureGeneral() = default; void ConfigureGeneral::setConfiguration() { ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan); @@ -35,6 +35,10 @@ void ConfigureGeneral::setConfiguration() { ui->use_docked_mode->setChecked(Settings::values.use_docked_mode); } +void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) { + ui->widget->Populate(registry); +} + void ConfigureGeneral::applyConfiguration() { UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked(); UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); @@ -44,5 +48,4 @@ void ConfigureGeneral::applyConfiguration() { Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked(); Settings::values.use_multi_core = ui->use_multi_core->isChecked(); Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); - Settings::Apply(); } diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h index 447552d8c..4770034cc 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h @@ -7,6 +7,8 @@ #include <memory> #include <QWidget> +class HotkeyRegistry; + namespace Ui { class ConfigureGeneral; } @@ -18,11 +20,11 @@ public: explicit ConfigureGeneral(QWidget* parent = nullptr); ~ConfigureGeneral(); + void PopulateHotkeyList(const HotkeyRegistry& registry); void applyConfiguration(); private: void setConfiguration(); -private: std::unique_ptr<Ui::ConfigureGeneral> ui; }; diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 7664880d5..4afe0f81b 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -14,7 +14,7 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) this->setConfiguration(); } -ConfigureGraphics::~ConfigureGraphics() {} +ConfigureGraphics::~ConfigureGraphics() = default; enum class Resolution : int { Auto, @@ -67,5 +67,4 @@ void ConfigureGraphics::applyConfiguration() { ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex())); Settings::values.toggle_framelimit = ui->toggle_framelimit->isChecked(); Settings::values.use_accurate_framebuffers = ui->use_accurate_framebuffers->isChecked(); - Settings::Apply(); } diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 78559e2bb..5e7badedf 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -191,8 +191,6 @@ void ConfigureInput::applyConfiguration() { [](const Common::ParamPackage& param) { return param.Serialize(); }); std::transform(analogs_param.begin(), analogs_param.end(), Settings::values.analogs.begin(), [](const Common::ParamPackage& param) { return param.Serialize(); }); - - Settings::Apply(); } void ConfigureInput::loadConfiguration() { diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index d09505a0f..e9ed9c38f 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -4,9 +4,10 @@ #include <QMessageBox> #include "core/core.h" +#include "core/settings.h" #include "ui_configure_system.h" #include "yuzu/configuration/configure_system.h" -#include "yuzu/ui_settings.h" +#include "yuzu/main.h" static const std::array<int, 12> days_in_month = {{ 31, @@ -34,10 +35,12 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui:: this->setConfiguration(); } -ConfigureSystem::~ConfigureSystem() {} +ConfigureSystem::~ConfigureSystem() = default; void ConfigureSystem::setConfiguration() { enabled = !Core::System::GetInstance().IsPoweredOn(); + ui->edit_username->setText(QString::fromStdString(Settings::values.username)); + ui->combo_language->setCurrentIndex(Settings::values.language_index); } void ConfigureSystem::ReadSystemSettings() {} @@ -45,6 +48,9 @@ void ConfigureSystem::ReadSystemSettings() {} void ConfigureSystem::applyConfiguration() { if (!enabled) return; + Settings::values.username = ui->edit_username->text().toStdString(); + Settings::values.language_index = ui->combo_language->currentIndex(); + Settings::Apply(); } void ConfigureSystem::updateBirthdayComboBox(int birthmonth_index) { diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index 8caf49623..f3f8db038 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui @@ -38,7 +38,7 @@ </sizepolicy> </property> <property name="maxLength"> - <number>10</number> + <number>32</number> </property> </widget> </item> @@ -164,7 +164,7 @@ </item> <item> <property name="text"> - <string>Simplified Chinese (简体中文)</string> + <string>Chinese</string> </property> </item> <item> @@ -187,6 +187,31 @@ <string>Russian (Русский)</string> </property> </item> + <item> + <property name="text"> + <string>Taiwanese</string> + </property> + </item> + <item> + <property name="text"> + <string>British English</string> + </property> + </item> + <item> + <property name="text"> + <string>Canadian French</string> + </property> + </item> + <item> + <property name="text"> + <string>Latin American Spanish</string> + </property> + </item> + <item> + <property name="text"> + <string>Simplified Chinese</string> + </property> + </item> <item> <property name="text"> <string>Traditional Chinese (正體中文)</string> diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp index d6d61a739..5f459ccfb 100644 --- a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp +++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.cpp @@ -10,12 +10,12 @@ BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Tegra::DebugConte : QDockWidget(title, parent), BreakPointObserver(debug_context) { qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event"); - connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + connect(this, &BreakPointObserverDock::Resumed, this, &BreakPointObserverDock::OnResumed); // NOTE: This signal is emitted from a non-GUI thread, but connect() takes // care of delaying its handling to the GUI thread. - connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this, - SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection); + connect(this, &BreakPointObserverDock::BreakPointHit, this, + &BreakPointObserverDock::OnBreakPointHit, Qt::BlockingQueuedConnection); } void BreakPointObserverDock::OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) { diff --git a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h index 9d05493cf..ab32f0115 100644 --- a/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h +++ b/src/yuzu/debugger/graphics/graphics_breakpoint_observer.h @@ -23,11 +23,11 @@ public: void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; void OnMaxwellResume() override; -private slots: - virtual void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) = 0; - virtual void OnResumed() = 0; - signals: void Resumed(); void BreakPointHit(Tegra::DebugContext::Event event, void* data); + +private: + virtual void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) = 0; + virtual void OnResumed() = 0; }; diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp index f98cc8152..eb16a38a0 100644 --- a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp +++ b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp @@ -144,21 +144,25 @@ GraphicsBreakPointsWidget::GraphicsBreakPointsWidget( qRegisterMetaType<Tegra::DebugContext::Event>("Tegra::DebugContext::Event"); - connect(breakpoint_list, SIGNAL(doubleClicked(const QModelIndex&)), this, - SLOT(OnItemDoubleClicked(const QModelIndex&))); + connect(breakpoint_list, &QTreeView::doubleClicked, this, + &GraphicsBreakPointsWidget::OnItemDoubleClicked); - connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested())); + connect(resume_button, &QPushButton::clicked, this, + &GraphicsBreakPointsWidget::OnResumeRequested); - connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), this, - SLOT(OnBreakPointHit(Tegra::DebugContext::Event, void*)), Qt::BlockingQueuedConnection); - connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + connect(this, &GraphicsBreakPointsWidget::BreakPointHit, this, + &GraphicsBreakPointsWidget::OnBreakPointHit, Qt::BlockingQueuedConnection); + connect(this, &GraphicsBreakPointsWidget::Resumed, this, &GraphicsBreakPointsWidget::OnResumed); - connect(this, SIGNAL(BreakPointHit(Tegra::DebugContext::Event, void*)), breakpoint_model, - SLOT(OnBreakPointHit(Tegra::DebugContext::Event)), Qt::BlockingQueuedConnection); - connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed())); + connect(this, &GraphicsBreakPointsWidget::BreakPointHit, breakpoint_model, + &BreakPointModel::OnBreakPointHit, Qt::BlockingQueuedConnection); + connect(this, &GraphicsBreakPointsWidget::Resumed, breakpoint_model, + &BreakPointModel::OnResumed); - connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&, const QModelIndex&)), - breakpoint_model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&))); + connect(this, &GraphicsBreakPointsWidget::BreakPointsChanged, + [this](const QModelIndex& top_left, const QModelIndex& bottom_right) { + breakpoint_model->dataChanged(top_left, bottom_right); + }); QWidget* main_widget = new QWidget; auto main_layout = new QVBoxLayout; diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.h b/src/yuzu/debugger/graphics/graphics_breakpoints.h index ae0ede2e8..a920a2ae5 100644 --- a/src/yuzu/debugger/graphics/graphics_breakpoints.h +++ b/src/yuzu/debugger/graphics/graphics_breakpoints.h @@ -26,18 +26,17 @@ public: void OnMaxwellBreakPointHit(Tegra::DebugContext::Event event, void* data) override; void OnMaxwellResume() override; -public slots: - void OnBreakPointHit(Tegra::DebugContext::Event event, void* data); - void OnItemDoubleClicked(const QModelIndex&); - void OnResumeRequested(); - void OnResumed(); - signals: void Resumed(); void BreakPointHit(Tegra::DebugContext::Event event, void* data); void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); private: + void OnBreakPointHit(Tegra::DebugContext::Event event, void* data); + void OnItemDoubleClicked(const QModelIndex&); + void OnResumeRequested(); + void OnResumed(); + QLabel* status_text; QPushButton* resume_button; diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints_p.h b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h index 35a6876ae..7112b87e6 100644 --- a/src/yuzu/debugger/graphics/graphics_breakpoints_p.h +++ b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h @@ -25,7 +25,6 @@ public: bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; -public slots: void OnBreakPointHit(Tegra::DebugContext::Event event); void OnResumed(); diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp index c41ff693b..e037223c2 100644 --- a/src/yuzu/debugger/graphics/graphics_surface.cpp +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -34,7 +34,8 @@ static Tegra::Texture::TextureFormat ConvertToTextureFormat( SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) : QLabel(parent), surface_widget(surface_widget_) {} -SurfacePicture::~SurfacePicture() {} + +SurfacePicture::~SurfacePicture() = default; void SurfacePicture::mousePressEvent(QMouseEvent* event) { // Only do something while the left mouse button is held down @@ -153,22 +154,24 @@ GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save")); // Connections - connect(this, SIGNAL(Update()), this, SLOT(OnUpdate())); - connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this, - SLOT(OnSurfaceSourceChanged(int))); - connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this, - SLOT(OnSurfaceAddressChanged(qint64))); - connect(surface_width_control, SIGNAL(valueChanged(int)), this, - SLOT(OnSurfaceWidthChanged(int))); - connect(surface_height_control, SIGNAL(valueChanged(int)), this, - SLOT(OnSurfaceHeightChanged(int))); - connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this, - SLOT(OnSurfaceFormatChanged(int))); - connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this, - SLOT(OnSurfacePickerXChanged(int))); - connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this, - SLOT(OnSurfacePickerYChanged(int))); - connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface())); + connect(this, &GraphicsSurfaceWidget::Update, this, &GraphicsSurfaceWidget::OnUpdate); + connect(surface_source_list, + static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, + &GraphicsSurfaceWidget::OnSurfaceSourceChanged); + connect(surface_address_control, &CSpinBox::ValueChanged, this, + &GraphicsSurfaceWidget::OnSurfaceAddressChanged); + connect(surface_width_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), + this, &GraphicsSurfaceWidget::OnSurfaceWidthChanged); + connect(surface_height_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), + this, &GraphicsSurfaceWidget::OnSurfaceHeightChanged); + connect(surface_format_control, + static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, + &GraphicsSurfaceWidget::OnSurfaceFormatChanged); + connect(surface_picker_x_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), + this, &GraphicsSurfaceWidget::OnSurfacePickerXChanged); + connect(surface_picker_y_control, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), + this, &GraphicsSurfaceWidget::OnSurfacePickerYChanged); + connect(save_surface, &QPushButton::clicked, this, &GraphicsSurfaceWidget::SaveSurface); auto main_widget = new QWidget; auto main_layout = new QVBoxLayout; @@ -380,8 +383,10 @@ void GraphicsSurfaceWidget::OnUpdate() { QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); boost::optional<VAddr> address = gpu.memory_manager->GpuToCpuAddress(surface_address); - auto unswizzled_data = - Tegra::Texture::UnswizzleTexture(*address, surface_format, surface_width, surface_height); + // TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles. + // Needs to be fixed if we plan to use this feature more, otherwise we may remove it. + auto unswizzled_data = Tegra::Texture::UnswizzleTexture( + *address, 1, Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height); auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format, surface_width, surface_height); diff --git a/src/yuzu/debugger/graphics/graphics_surface.h b/src/yuzu/debugger/graphics/graphics_surface.h index 6a344bdfc..323e39d94 100644 --- a/src/yuzu/debugger/graphics/graphics_surface.h +++ b/src/yuzu/debugger/graphics/graphics_surface.h @@ -22,11 +22,11 @@ class SurfacePicture : public QLabel { public: explicit SurfacePicture(QWidget* parent = nullptr, GraphicsSurfaceWidget* surface_widget = nullptr); - ~SurfacePicture(); + ~SurfacePicture() override; protected slots: - virtual void mouseMoveEvent(QMouseEvent* event); - virtual void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; private: GraphicsSurfaceWidget* surface_widget; @@ -65,16 +65,15 @@ public slots: void OnSurfacePickerYChanged(int new_value); void OnUpdate(); -private slots: +signals: + void Update(); + +private: void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override; void OnResumed() override; void SaveSurface(); -signals: - void Update(); - -private: QComboBox* surface_source_list; CSpinBox* surface_address_control; QSpinBox* surface_width_control; diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 2b45b8573..d0926d723 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -14,7 +14,7 @@ #include "core/hle/kernel/timer.h" #include "core/hle/kernel/wait_object.h" -WaitTreeItem::~WaitTreeItem() {} +WaitTreeItem::~WaitTreeItem() = default; QColor WaitTreeItem::GetColor() const { return QColor(Qt::GlobalColor::black); @@ -316,7 +316,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeEvent::GetChildren() const { list.push_back(std::make_unique<WaitTreeText>( tr("reset type = %1") - .arg(GetResetTypeQString(static_cast<const Kernel::Event&>(object).reset_type)))); + .arg(GetResetTypeQString(static_cast<const Kernel::Event&>(object).GetResetType())))); return list; } @@ -328,11 +328,11 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const { const auto& timer = static_cast<const Kernel::Timer&>(object); list.push_back(std::make_unique<WaitTreeText>( - tr("reset type = %1").arg(GetResetTypeQString(timer.reset_type)))); + tr("reset type = %1").arg(GetResetTypeQString(timer.GetResetType())))); list.push_back( - std::make_unique<WaitTreeText>(tr("initial delay = %1").arg(timer.initial_delay))); + std::make_unique<WaitTreeText>(tr("initial delay = %1").arg(timer.GetInitialDelay()))); list.push_back( - std::make_unique<WaitTreeText>(tr("interval delay = %1").arg(timer.interval_delay))); + std::make_unique<WaitTreeText>(tr("interval delay = %1").arg(timer.GetIntervalDelay()))); return list; } diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h index 10fc9e968..513b3c45d 100644 --- a/src/yuzu/debugger/wait_tree.h +++ b/src/yuzu/debugger/wait_tree.h @@ -9,7 +9,7 @@ #include <QTreeView> #include <boost/container/flat_set.hpp> #include "core/core.h" -#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/object.h" class EmuThread; @@ -25,11 +25,13 @@ class WaitTreeThread; class WaitTreeItem : public QObject { Q_OBJECT public: + ~WaitTreeItem() override; + virtual bool IsExpandable() const; virtual std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const; virtual QString GetText() const = 0; virtual QColor GetColor() const; - virtual ~WaitTreeItem(); + void Expand(); WaitTreeItem* Parent() const; const std::vector<std::unique_ptr<WaitTreeItem>>& Children() const; diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 99e6634a1..f867118d9 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <regex> #include <QApplication> #include <QDir> #include <QFileInfo> @@ -9,9 +10,13 @@ #include <QKeyEvent> #include <QMenu> #include <QThreadPool> +#include <boost/container/flat_map.hpp> #include "common/common_paths.h" #include "common/logging/log.h" #include "common/string_util.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/romfs.h" #include "core/file_sys/vfs_real.h" #include "core/loader/loader.h" #include "game_list.h" @@ -162,15 +167,15 @@ void GameList::onTextChanged(const QString& newText) { } search_field->setFilterResult(rowCount, rowCount); } else { - QStandardItem* child_file; - QString file_path, file_name, file_title, file_programmid; int result_count = 0; for (int i = 0; i < rowCount; ++i) { - child_file = item_model->item(i, 0); - file_path = child_file->data(GameListItemPath::FullPathRole).toString().toLower(); - file_name = file_path.mid(file_path.lastIndexOf("/") + 1); - file_title = child_file->data(GameListItemPath::TitleRole).toString().toLower(); - file_programmid = + const QStandardItem* child_file = item_model->item(i, 0); + const QString file_path = + child_file->data(GameListItemPath::FullPathRole).toString().toLower(); + QString file_name = file_path.mid(file_path.lastIndexOf('/') + 1); + const QString file_title = + child_file->data(GameListItemPath::TitleRole).toString().toLower(); + const QString file_programmid = child_file->data(GameListItemPath::ProgramIdRole).toString().toLower(); // Only items which filename in combination with its title contains all words @@ -194,7 +199,8 @@ void GameList::onFilterCloseClicked() { main_window->filterBarSetChecked(false); } -GameList::GameList(GMainWindow* parent) : QWidget{parent} { +GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent) + : QWidget{parent}, vfs(std::move(vfs)) { watcher = new QFileSystemWatcher(this); connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); @@ -258,18 +264,20 @@ void GameList::AddEntry(const QList<QStandardItem*>& entry_items) { void GameList::ValidateEntry(const QModelIndex& item) { // We don't care about the individual QStandardItem that was selected, but its row. - int row = item_model->itemFromIndex(item)->row(); - QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); - QString file_path = child_file->data(GameListItemPath::FullPathRole).toString(); + const int row = item_model->itemFromIndex(item)->row(); + const QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); + const QString file_path = child_file->data(GameListItemPath::FullPathRole).toString(); if (file_path.isEmpty()) return; - std::string std_file_path(file_path.toStdString()); - if (!FileUtil::Exists(std_file_path)) + + if (!QFileInfo::exists(file_path)) return; - if (FileUtil::IsDirectory(std_file_path)) { - QDir dir(std_file_path.c_str()); - QStringList matching_main = dir.entryList(QStringList("main"), QDir::Files); + + const QFileInfo file_info{file_path}; + if (file_info.isDir()) { + const QDir dir{file_path}; + const QStringList matching_main = dir.entryList(QStringList("main"), QDir::Files); if (matching_main.size() == 1) { emit GameChosen(dir.path() + DIR_SEP + matching_main[0]); } @@ -336,7 +344,7 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { emit ShouldCancelWorker(); - GameListWorker* worker = new GameListWorker(dir_path, deep_scan); + GameListWorker* worker = new GameListWorker(vfs, dir_path, deep_scan); connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, @@ -365,24 +373,26 @@ void GameList::LoadInterfaceLayout() { item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); } -const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca"}; +const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci"}; static bool HasSupportedFileExtension(const std::string& file_name) { - QFileInfo file = QFileInfo(file_name.c_str()); + const QFileInfo file = QFileInfo(QString::fromStdString(file_name)); return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); } static bool IsExtractedNCAMain(const std::string& file_name) { - return QFileInfo(file_name.c_str()).fileName() == "main"; + return QFileInfo(QString::fromStdString(file_name)).fileName() == "main"; } static QString FormatGameName(const std::string& physical_name) { - QFileInfo file_info(physical_name.c_str()); + const QString physical_name_as_qstring = QString::fromStdString(physical_name); + const QFileInfo file_info(physical_name_as_qstring); + if (IsExtractedNCAMain(physical_name)) { return file_info.dir().path(); - } else { - return QString::fromStdString(physical_name); } + + return physical_name_as_qstring; } void GameList::RefreshGameDirectory() { @@ -393,6 +403,91 @@ void GameList::RefreshGameDirectory() { } } +static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca, + std::vector<u8>& icon, std::string& name) { + const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS()); + if (control_dir == nullptr) + return; + + const auto nacp_file = control_dir->GetFile("control.nacp"); + if (nacp_file == nullptr) + return; + FileSys::NACP nacp(nacp_file); + name = nacp.GetApplicationName(); + + FileSys::VirtualFile icon_file = nullptr; + for (const auto& language : FileSys::LANGUAGE_NAMES) { + icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat"); + if (icon_file != nullptr) { + icon = icon_file->ReadAllBytes(); + break; + } + } +} + +void GameListWorker::AddInstalledTitlesToGameList() { + const auto usernand = Service::FileSystem::GetUserNANDContents(); + const auto installed_games = usernand->ListEntriesFilter(FileSys::TitleType::Application, + FileSys::ContentRecordType::Program); + + for (const auto& game : installed_games) { + const auto& file = usernand->GetEntryRaw(game); + std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file); + if (!loader) + continue; + + std::vector<u8> icon; + std::string name; + u64 program_id; + loader->ReadProgramId(program_id); + + const auto& control = + usernand->GetEntry(game.title_id, FileSys::ContentRecordType::Control); + if (control != nullptr) + GetMetadataFromControlNCA(control, icon, name); + emit EntryReady({ + new GameListItemPath( + FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name), + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), + program_id), + new GameListItem( + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), + new GameListItemSize(file->GetSize()), + }); + } + + const auto control_data = usernand->ListEntriesFilter(FileSys::TitleType::Application, + FileSys::ContentRecordType::Control); + + for (const auto& entry : control_data) { + const auto nca = usernand->GetEntry(entry); + if (nca != nullptr) + nca_control_map.insert_or_assign(entry.title_id, nca); + } +} + +void GameListWorker::FillControlMap(const std::string& dir_path) { + const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { + std::string physical_name = directory + DIR_SEP + virtual_name; + + if (stop_processing) + return false; // Breaks the callback loop. + + bool is_dir = FileUtil::IsDirectory(physical_name); + QFileInfo file_info(physical_name.c_str()); + if (!is_dir && file_info.suffix().toStdString() == "nca") { + auto nca = + std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read)); + if (nca->GetType() == FileSys::NCAContentType::Control) + nca_control_map.insert_or_assign(nca->GetTitleId(), nca); + } + return true; + }; + + FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback); +} + void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory, const std::string& virtual_name) -> bool { @@ -405,18 +500,35 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign if (!is_dir && (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { std::unique_ptr<Loader::AppLoader> loader = - Loader::GetLoader(std::make_shared<FileSys::RealVfsFile>(physical_name)); - if (!loader) + Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read)); + if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown || + loader->GetFileType() == Loader::FileType::Error) && + !UISettings::values.show_unknown)) return true; - std::vector<u8> smdh; - loader->ReadIcon(smdh); + std::vector<u8> icon; + const auto res1 = loader->ReadIcon(icon); - u64 program_id = 0; - loader->ReadProgramId(program_id); + u64 program_id; + const auto res2 = loader->ReadProgramId(program_id); + + std::string name = " "; + const auto res3 = loader->ReadTitle(name); + + if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success && + res2 == Loader::ResultStatus::Success) { + // Use from metadata pool. + if (nca_control_map.find(program_id) != nca_control_map.end()) { + const auto nca = nca_control_map[program_id]; + GetMetadataFromControlNCA(nca, icon, name); + } + } emit EntryReady({ - new GameListItemPath(FormatGameName(physical_name), smdh, program_id), + new GameListItemPath( + FormatGameName(physical_name), icon, QString::fromStdString(name), + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), + program_id), new GameListItem( QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), new GameListItemSize(FileUtil::GetSize(physical_name)), @@ -435,7 +547,10 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign void GameListWorker::run() { stop_processing = false; watch_list.append(dir_path); + FillControlMap(dir_path.toStdString()); + AddInstalledTitlesToGameList(); AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); + nca_control_map.clear(); emit Finished(watch_list); } diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 3bc14f07f..afe624b32 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -59,7 +59,7 @@ public: QToolButton* button_filter_close = nullptr; }; - explicit GameList(GMainWindow* parent = nullptr); + explicit GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent = nullptr); ~GameList() override; void clearFilter(); @@ -90,6 +90,7 @@ private: void PopupContextMenu(const QPoint& menu_location); void RefreshGameDirectory(); + FileSys::VirtualFilesystem vfs; SearchField* search_field; GMainWindow* main_window = nullptr; QVBoxLayout* layout = nullptr; diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index a758b77aa..10c2ef075 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -4,12 +4,15 @@ #pragma once +#include <array> #include <atomic> +#include <utility> #include <QImage> #include <QRunnable> #include <QStandardItem> #include <QString> #include "common/string_util.h" +#include "ui_settings.h" #include "yuzu/util/util.h" /** @@ -17,8 +20,7 @@ * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) * @return QPixmap default icon */ -static QPixmap GetDefaultIcon(bool large) { - int size = large ? 48 : 24; +static QPixmap GetDefaultIcon(u32 size) { QPixmap icon(size, size); icon.fill(Qt::transparent); return icon; @@ -27,9 +29,8 @@ static QPixmap GetDefaultIcon(bool large) { class GameListItem : public QStandardItem { public: - GameListItem() : QStandardItem() {} - GameListItem(const QString& string) : QStandardItem(string) {} - virtual ~GameListItem() override {} + GameListItem() = default; + explicit GameListItem(const QString& string) : QStandardItem(string) {} }; /** @@ -39,17 +40,29 @@ public: * If this class receives valid SMDH data, it will also display game icons and titles. */ class GameListItemPath : public GameListItem { - public: static const int FullPathRole = Qt::UserRole + 1; static const int TitleRole = Qt::UserRole + 2; static const int ProgramIdRole = Qt::UserRole + 3; + static const int FileTypeRole = Qt::UserRole + 4; - GameListItemPath() : GameListItem() {} - GameListItemPath(const QString& game_path, const std::vector<u8>& smdh_data, u64 program_id) - : GameListItem() { + GameListItemPath() = default; + GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data, + const QString& game_name, const QString& game_type, u64 program_id) { setData(game_path, FullPathRole); + setData(game_name, TitleRole); setData(qulonglong(program_id), ProgramIdRole); + setData(game_type, FileTypeRole); + + const u32 size = UISettings::values.icon_size; + + QPixmap picture; + if (!picture.loadFromData(picture_data.data(), static_cast<u32>(picture_data.size()))) { + picture = GetDefaultIcon(size); + } + picture = picture.scaled(size, size); + + setData(picture, Qt::DecorationRole); } QVariant data(int role) const override { @@ -57,11 +70,26 @@ public: std::string filename; Common::SplitPath(data(FullPathRole).toString().toStdString(), nullptr, &filename, nullptr); - QString title = data(TitleRole).toString(); - return QString::fromStdString(filename) + (title.isEmpty() ? "" : "\n " + title); - } else { - return GameListItem::data(role); + + const std::array<QString, 4> row_data{{ + QString::fromStdString(filename), + data(FileTypeRole).toString(), + QString::fromStdString(fmt::format("0x{:016X}", data(ProgramIdRole).toULongLong())), + data(TitleRole).toString(), + }}; + + const auto& row1 = row_data.at(UISettings::values.row_1_text_id); + const auto& row2 = row_data.at(UISettings::values.row_2_text_id); + + if (row1.isEmpty() || row1 == row2) + return row2; + if (row2.isEmpty()) + return row1; + + return row1 + "\n " + row2; } + + return GameListItem::data(role); } }; @@ -75,8 +103,8 @@ class GameListItemSize : public GameListItem { public: static const int SizeRole = Qt::UserRole + 1; - GameListItemSize() : GameListItem() {} - GameListItemSize(const qulonglong size_bytes) : GameListItem() { + GameListItemSize() = default; + explicit GameListItemSize(const qulonglong size_bytes) { setData(size_bytes, SizeRole); } @@ -110,8 +138,8 @@ class GameListWorker : public QObject, public QRunnable { Q_OBJECT public: - GameListWorker(QString dir_path, bool deep_scan) - : QObject(), QRunnable(), dir_path(dir_path), deep_scan(deep_scan) {} + GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan) + : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan) {} public slots: /// Starts the processing of directory tree information. @@ -134,10 +162,14 @@ signals: void Finished(QStringList watch_list); private: + FileSys::VirtualFilesystem vfs; + std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map; QStringList watch_list; QString dir_path; bool deep_scan; std::atomic_bool stop_processing; + void AddInstalledTitlesToGameList(); + void FillControlMap(const std::string& dir_path); void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); }; diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp index 61acb38ee..dce399774 100644 --- a/src/yuzu/hotkeys.cpp +++ b/src/yuzu/hotkeys.cpp @@ -10,58 +10,53 @@ #include "yuzu/hotkeys.h" #include "yuzu/ui_settings.h" -struct Hotkey { - Hotkey() : shortcut(nullptr), context(Qt::WindowShortcut) {} +HotkeyRegistry::HotkeyRegistry() = default; +HotkeyRegistry::~HotkeyRegistry() = default; - QKeySequence keyseq; - QShortcut* shortcut; - Qt::ShortcutContext context; -}; - -typedef std::map<QString, Hotkey> HotkeyMap; -typedef std::map<QString, HotkeyMap> HotkeyGroupMap; - -HotkeyGroupMap hotkey_groups; - -void SaveHotkeys() { - UISettings::values.shortcuts.clear(); - for (auto group : hotkey_groups) { - for (auto hotkey : group.second) { - UISettings::values.shortcuts.emplace_back( - UISettings::Shortcut(group.first + "/" + hotkey.first, - UISettings::ContextualShortcut(hotkey.second.keyseq.toString(), - hotkey.second.context))); - } - } -} - -void LoadHotkeys() { +void HotkeyRegistry::LoadHotkeys() { // Make sure NOT to use a reference here because it would become invalid once we call // beginGroup() for (auto shortcut : UISettings::values.shortcuts) { - QStringList cat = shortcut.first.split("/"); + const QStringList cat = shortcut.first.split('/'); Q_ASSERT(cat.size() >= 2); // RegisterHotkey assigns default keybindings, so use old values as default parameters Hotkey& hk = hotkey_groups[cat[0]][cat[1]]; if (!shortcut.second.first.isEmpty()) { hk.keyseq = QKeySequence::fromString(shortcut.second.first); - hk.context = (Qt::ShortcutContext)shortcut.second.second; + hk.context = static_cast<Qt::ShortcutContext>(shortcut.second.second); } if (hk.shortcut) hk.shortcut->setKey(hk.keyseq); } } -void RegisterHotkey(const QString& group, const QString& action, const QKeySequence& default_keyseq, - Qt::ShortcutContext default_context) { - if (hotkey_groups[group].find(action) == hotkey_groups[group].end()) { - hotkey_groups[group][action].keyseq = default_keyseq; - hotkey_groups[group][action].context = default_context; +void HotkeyRegistry::SaveHotkeys() { + UISettings::values.shortcuts.clear(); + for (const auto& group : hotkey_groups) { + for (const auto& hotkey : group.second) { + UISettings::values.shortcuts.emplace_back( + UISettings::Shortcut(group.first + '/' + hotkey.first, + UISettings::ContextualShortcut(hotkey.second.keyseq.toString(), + hotkey.second.context))); + } } } -QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget) { +void HotkeyRegistry::RegisterHotkey(const QString& group, const QString& action, + const QKeySequence& default_keyseq, + Qt::ShortcutContext default_context) { + auto& hotkey_group = hotkey_groups[group]; + if (hotkey_group.find(action) != hotkey_group.end()) { + return; + } + + auto& hotkey_action = hotkey_groups[group][action]; + hotkey_action.keyseq = default_keyseq; + hotkey_action.context = default_context; +} + +QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) { Hotkey& hk = hotkey_groups[group][action]; if (!hk.shortcut) @@ -72,10 +67,12 @@ QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widge GHotkeysDialog::GHotkeysDialog(QWidget* parent) : QWidget(parent) { ui.setupUi(this); +} - for (auto group : hotkey_groups) { +void GHotkeysDialog::Populate(const HotkeyRegistry& registry) { + for (const auto& group : registry.hotkey_groups) { QTreeWidgetItem* toplevel_item = new QTreeWidgetItem(QStringList(group.first)); - for (auto hotkey : group.second) { + for (const auto& hotkey : group.second) { QStringList columns; columns << hotkey.first << hotkey.second.keyseq.toString(); QTreeWidgetItem* item = new QTreeWidgetItem(columns); diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h index a4ccc193b..f38e6c002 100644 --- a/src/yuzu/hotkeys.h +++ b/src/yuzu/hotkeys.h @@ -4,6 +4,7 @@ #pragma once +#include <map> #include "ui_hotkeys.h" class QDialog; @@ -11,47 +12,69 @@ class QKeySequence; class QSettings; class QShortcut; -/** - * Register a hotkey. - * - * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger") - * @param action Name of the action (e.g. "Start Emulation", "Load Image") - * @param default_keyseq Default key sequence to assign if the hotkey wasn't present in the settings - * file before - * @param default_context Default context to assign if the hotkey wasn't present in the settings - * file before - * @warning Both the group and action strings will be displayed in the hotkey settings dialog - */ -void RegisterHotkey(const QString& group, const QString& action, - const QKeySequence& default_keyseq = QKeySequence(), - Qt::ShortcutContext default_context = Qt::WindowShortcut); - -/** - * Returns a QShortcut object whose activated() signal can be connected to other QObjects' slots. - * - * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger"). - * @param action Name of the action (e.g. "Start Emulation", "Load Image"). - * @param widget Parent widget of the returned QShortcut. - * @warning If multiple QWidgets' call this function for the same action, the returned QShortcut - * will be the same. Thus, you shouldn't rely on the caller really being the QShortcut's parent. - */ -QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget); - -/** - * Saves all registered hotkeys to the settings file. - * - * @note Each hotkey group will be stored a settings group; For each hotkey inside that group, a - * settings group will be created to store the key sequence and the hotkey context. - */ -void SaveHotkeys(); - -/** - * Loads hotkeys from the settings file. - * - * @note Yet unregistered hotkeys which are present in the settings will automatically be - * registered. - */ -void LoadHotkeys(); +class HotkeyRegistry final { +public: + friend class GHotkeysDialog; + + explicit HotkeyRegistry(); + ~HotkeyRegistry(); + + /** + * Loads hotkeys from the settings file. + * + * @note Yet unregistered hotkeys which are present in the settings will automatically be + * registered. + */ + void LoadHotkeys(); + + /** + * Saves all registered hotkeys to the settings file. + * + * @note Each hotkey group will be stored a settings group; For each hotkey inside that group, a + * settings group will be created to store the key sequence and the hotkey context. + */ + void SaveHotkeys(); + + /** + * Returns a QShortcut object whose activated() signal can be connected to other QObjects' + * slots. + * + * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger"). + * @param action Name of the action (e.g. "Start Emulation", "Load Image"). + * @param widget Parent widget of the returned QShortcut. + * @warning If multiple QWidgets' call this function for the same action, the returned QShortcut + * will be the same. Thus, you shouldn't rely on the caller really being the + * QShortcut's parent. + */ + QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget); + + /** + * Register a hotkey. + * + * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger") + * @param action Name of the action (e.g. "Start Emulation", "Load Image") + * @param default_keyseq Default key sequence to assign if the hotkey wasn't present in the + * settings file before + * @param default_context Default context to assign if the hotkey wasn't present in the settings + * file before + * @warning Both the group and action strings will be displayed in the hotkey settings dialog + */ + void RegisterHotkey(const QString& group, const QString& action, + const QKeySequence& default_keyseq = {}, + Qt::ShortcutContext default_context = Qt::WindowShortcut); + +private: + struct Hotkey { + QKeySequence keyseq; + QShortcut* shortcut = nullptr; + Qt::ShortcutContext context = Qt::WindowShortcut; + }; + + using HotkeyMap = std::map<QString, Hotkey>; + using HotkeyGroupMap = std::map<QString, HotkeyMap>; + + HotkeyGroupMap hotkey_groups; +}; class GHotkeysDialog : public QWidget { Q_OBJECT @@ -59,6 +82,8 @@ class GHotkeysDialog : public QWidget { public: explicit GHotkeysDialog(QWidget* parent = nullptr); + void Populate(const HotkeyRegistry& registry); + private: Ui::hotkeys ui; }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 3c2726498..2df65023a 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -6,7 +6,10 @@ #include <clocale> #include <memory> #include <thread> + +#include <fmt/ostream.h> #include <glad/glad.h> + #define QT_NO_OPENGL #include <QDesktopWidget> #include <QFileDialog> @@ -23,6 +26,9 @@ #include "common/scope_exit.h" #include "common/string_util.h" #include "core/core.h" +#include "core/crypto/key_manager.h" +#include "core/file_sys/card_image.h" +#include "core/file_sys/vfs_real.h" #include "core/gdbstub/gdbstub.h" #include "core/loader/loader.h" #include "core/settings.h" @@ -80,7 +86,11 @@ static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { void GMainWindow::ShowCallouts() {} -GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { +const int GMainWindow::max_recent_files_item; + +GMainWindow::GMainWindow() + : config(new Config()), emu_thread(nullptr), + vfs(std::make_shared<FileSys::RealVfsFilesystem>()) { debug_context = Tegra::DebugContext::Construct(); @@ -101,11 +111,16 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { ConnectMenuEvents(); ConnectWidgetEvents(); + LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_name, Common::g_scm_branch, + Common::g_scm_desc); setWindowTitle(QString("yuzu %1| %2-%3") .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); show(); + // Necessary to load titles from nand in gamelist. + Service::FileSystem::RegisterBIS(std::make_unique<FileSys::BISFactory>(vfs->OpenDirectory( + FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite))); game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); // Show one-time "callout" messages to the user @@ -127,7 +142,7 @@ void GMainWindow::InitializeWidgets() { render_window = new GRenderWindow(this, emu_thread.get()); render_window->hide(); - game_list = new GameList(this); + game_list = new GameList(vfs, this); ui.horizontalLayout->addWidget(game_list); // Create status bar @@ -203,27 +218,46 @@ void GMainWindow::InitializeRecentFileMenuActions() { } void GMainWindow::InitializeHotkeys() { - RegisterHotkey("Main Window", "Load File", QKeySequence::Open); - RegisterHotkey("Main Window", "Start Emulation"); - RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen); - RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape), - Qt::ApplicationShortcut); - LoadHotkeys(); - - connect(GetHotkey("Main Window", "Load File", this), &QShortcut::activated, this, - &GMainWindow::OnMenuLoadFile); - connect(GetHotkey("Main Window", "Start Emulation", this), &QShortcut::activated, this, - &GMainWindow::OnStartGame); - connect(GetHotkey("Main Window", "Fullscreen", render_window), &QShortcut::activated, - ui.action_Fullscreen, &QAction::trigger); - connect(GetHotkey("Main Window", "Fullscreen", render_window), &QShortcut::activatedAmbiguously, - ui.action_Fullscreen, &QAction::trigger); - connect(GetHotkey("Main Window", "Exit Fullscreen", this), &QShortcut::activated, this, [&] { - if (emulation_running) { - ui.action_Fullscreen->setChecked(false); - ToggleFullscreen(); - } - }); + hotkey_registry.RegisterHotkey("Main Window", "Load File", QKeySequence::Open); + hotkey_registry.RegisterHotkey("Main Window", "Start Emulation"); + hotkey_registry.RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4)); + hotkey_registry.RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen); + hotkey_registry.RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape), + Qt::ApplicationShortcut); + hotkey_registry.RegisterHotkey("Main Window", "Toggle Speed Limit", QKeySequence("CTRL+Z"), + Qt::ApplicationShortcut); + hotkey_registry.LoadHotkeys(); + + connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated, + this, &GMainWindow::OnMenuLoadFile); + connect(hotkey_registry.GetHotkey("Main Window", "Start Emulation", this), + &QShortcut::activated, this, &GMainWindow::OnStartGame); + connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause", this), &QShortcut::activated, + this, [&] { + if (emulation_running) { + if (emu_thread->IsRunning()) { + OnPauseGame(); + } else { + OnStartGame(); + } + } + }); + connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window), + &QShortcut::activated, ui.action_Fullscreen, &QAction::trigger); + connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window), + &QShortcut::activatedAmbiguously, ui.action_Fullscreen, &QAction::trigger); + connect(hotkey_registry.GetHotkey("Main Window", "Exit Fullscreen", this), + &QShortcut::activated, this, [&] { + if (emulation_running) { + ui.action_Fullscreen->setChecked(false); + ToggleFullscreen(); + } + }); + connect(hotkey_registry.GetHotkey("Main Window", "Toggle Speed Limit", this), + &QShortcut::activated, this, [&] { + Settings::values.toggle_framelimit = !Settings::values.toggle_framelimit; + UpdateStatusBar(); + }); } void GMainWindow::SetDefaultUIGeometry() { @@ -282,6 +316,8 @@ void GMainWindow::ConnectMenuEvents() { // File connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile); connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); + connect(ui.action_Install_File_NAND, &QAction::triggered, this, + &GMainWindow::OnMenuInstallToNAND); connect(ui.action_Select_Game_List_Root, &QAction::triggered, this, &GMainWindow::OnMenuSelectGameListRoot); connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close); @@ -302,7 +338,8 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); // Fullscreen - ui.action_Fullscreen->setShortcut(GetHotkey("Main Window", "Fullscreen", this)->key()); + ui.action_Fullscreen->setShortcut( + hotkey_registry.GetHotkey("Main Window", "Fullscreen", this)->key()); connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen); // Help @@ -381,10 +418,11 @@ bool GMainWindow::LoadROM(const QString& filename) { } Core::System& system{Core::System::GetInstance()}; + system.SetFilesystem(vfs); system.SetGPUDebugContext(debug_context); - const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())}; + const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; render_window->DoneCurrent(); @@ -395,39 +433,14 @@ bool GMainWindow::LoadROM(const QString& filename) { QMessageBox::critical(this, tr("Error while loading ROM!"), tr("The ROM format is not supported.")); break; - case Core::System::ResultStatus::ErrorUnsupportedArch: - LOG_CRITICAL(Frontend, "Unsupported architecture detected!", filename.toStdString()); - QMessageBox::critical(this, tr("Error while loading ROM!"), - tr("The ROM uses currently unusable 32-bit architecture")); - break; case Core::System::ResultStatus::ErrorSystemMode: LOG_CRITICAL(Frontend, "Failed to load ROM!"); QMessageBox::critical(this, tr("Error while loading ROM!"), tr("Could not determine the system mode.")); break; - - case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: { - QMessageBox::critical( - this, tr("Error while loading ROM!"), - tr("The game that you are trying to load must be decrypted before being used with " - "yuzu. A real Switch is required.<br/><br/>" - "For more information on dumping and decrypting games, please see the following " - "wiki pages: <ul>" - "<li><a href='https://yuzu-emu.org/wiki/dumping-game-cartridges/'>Dumping Game " - "Cartridges</a></li>" - "<li><a href='https://yuzu-emu.org/wiki/dumping-installed-titles/'>Dumping " - "Installed Titles</a></li>" - "</ul>")); - break; - } - case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: - QMessageBox::critical(this, tr("Error while loading ROM!"), - tr("The ROM format is not supported.")); - break; - case Core::System::ResultStatus::ErrorVideoCore: QMessageBox::critical( - this, tr("An error occured in the video core."), + this, tr("An error occurred initializing the video core."), tr("yuzu has encountered an error while running the video core, please see the " "log for more details." "For more information on accessing the log, please see the following page: " @@ -439,9 +452,23 @@ bool GMainWindow::LoadROM(const QString& filename) { break; default: - QMessageBox::critical( - this, tr("Error while loading ROM!"), - tr("An unknown error occured. Please see the log for more details.")); + if (static_cast<u32>(result) > + static_cast<u32>(Core::System::ResultStatus::ErrorLoader)) { + LOG_CRITICAL(Frontend, "Failed to load ROM!"); + const u16 loader_id = static_cast<u16>(Core::System::ResultStatus::ErrorLoader); + const u16 error_id = static_cast<u16>(result) - loader_id; + QMessageBox::critical( + this, tr("Error while loading ROM!"), + QString::fromStdString(fmt::format( + "While attempting to load the ROM requested, an error occured. Please " + "refer to the yuzu wiki for more information or the yuzu discord for " + "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}", + loader_id, error_id, static_cast<Loader::ResultStatus>(error_id)))); + } else { + QMessageBox::critical( + this, tr("Error while loading ROM!"), + tr("An unknown error occurred. Please see the log for more details.")); + } break; } return false; @@ -529,11 +556,11 @@ void GMainWindow::StoreRecentFile(const QString& filename) { } void GMainWindow::UpdateRecentFiles() { - unsigned int num_recent_files = - std::min(UISettings::values.recent_files.size(), static_cast<int>(max_recent_files_item)); + const int num_recent_files = + std::min(UISettings::values.recent_files.size(), max_recent_files_item); - for (unsigned int i = 0; i < num_recent_files; i++) { - QString text = QString("&%1. %2").arg(i + 1).arg( + for (int i = 0; i < num_recent_files; i++) { + const QString text = QString("&%1. %2").arg(i + 1).arg( QFileInfo(UISettings::values.recent_files[i]).fileName()); actions_recent_files[i]->setText(text); actions_recent_files[i]->setData(UISettings::values.recent_files[i]); @@ -545,12 +572,8 @@ void GMainWindow::UpdateRecentFiles() { actions_recent_files[j]->setVisible(false); } - // Grey out the recent files menu if the list is empty - if (num_recent_files == 0) { - ui.menu_recent_files->setEnabled(false); - } else { - ui.menu_recent_files->setEnabled(true); - } + // Enable the recent files menu if the list isn't empty + ui.menu_recent_files->setEnabled(num_recent_files != 0); } void GMainWindow::OnGameListLoadFile(QString game_path) { @@ -581,9 +604,15 @@ void GMainWindow::OnMenuLoadFile() { } void GMainWindow::OnMenuLoadFolder() { - QDir dir = QFileDialog::getExistingDirectory(this, tr("Open Extracted ROM Directory")); + const QString dir_path = + QFileDialog::getExistingDirectory(this, tr("Open Extracted ROM Directory")); - QStringList matching_main = dir.entryList(QStringList("main"), QDir::Files); + if (dir_path.isNull()) { + return; + } + + const QDir dir{dir_path}; + const QStringList matching_main = dir.entryList(QStringList("main"), QDir::Files); if (matching_main.size() == 1) { BootGame(dir.path() + DIR_SEP + matching_main[0]); } else { @@ -592,6 +621,148 @@ void GMainWindow::OnMenuLoadFolder() { } } +void GMainWindow::OnMenuInstallToNAND() { + const QString file_filter = + tr("Installable Switch File (*.nca *.xci);;Nintendo Content Archive (*.nca);;NX Cartridge " + "Image (*.xci)"); + QString filename = QFileDialog::getOpenFileName(this, tr("Install File"), + UISettings::values.roms_path, file_filter); + + if (filename.isEmpty()) { + return; + } + + const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) { + if (src == nullptr || dest == nullptr) + return false; + if (!dest->Resize(src->GetSize())) + return false; + + std::array<u8, 0x1000> buffer{}; + const int progress_maximum = static_cast<int>(src->GetSize() / buffer.size()); + + QProgressDialog progress( + tr("Installing file \"%1\"...").arg(QString::fromStdString(src->GetName())), + tr("Cancel"), 0, progress_maximum, this); + progress.setWindowModality(Qt::WindowModal); + + for (size_t i = 0; i < src->GetSize(); i += buffer.size()) { + if (progress.wasCanceled()) { + dest->Resize(0); + return false; + } + + const int progress_value = static_cast<int>(i / buffer.size()); + progress.setValue(progress_value); + + const auto read = src->Read(buffer.data(), buffer.size(), i); + dest->Write(buffer.data(), read, i); + } + + return true; + }; + + const auto success = [this]() { + QMessageBox::information(this, tr("Successfully Installed"), + tr("The file was successfully installed.")); + game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); + }; + + const auto failed = [this]() { + QMessageBox::warning( + this, tr("Failed to Install"), + tr("There was an error while attempting to install the provided file. It " + "could have an incorrect format or be missing metadata. Please " + "double-check your file and try again.")); + }; + + const auto overwrite = [this]() { + return QMessageBox::question(this, tr("Failed to Install"), + tr("The file you are attempting to install already exists " + "in the cache. Would you like to overwrite it?")) == + QMessageBox::Yes; + }; + + if (filename.endsWith("xci", Qt::CaseInsensitive)) { + const auto xci = std::make_shared<FileSys::XCI>( + vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); + if (xci->GetStatus() != Loader::ResultStatus::Success) { + failed(); + return; + } + const auto res = + Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, false, qt_raw_copy); + if (res == FileSys::InstallResult::Success) { + success(); + } else { + if (res == FileSys::InstallResult::ErrorAlreadyExists) { + if (overwrite()) { + const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( + xci, true, qt_raw_copy); + if (res2 == FileSys::InstallResult::Success) { + success(); + } else { + failed(); + } + } + } else { + failed(); + } + } + } else { + const auto nca = std::make_shared<FileSys::NCA>( + vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); + if (nca->GetStatus() != Loader::ResultStatus::Success) { + failed(); + return; + } + + const QStringList tt_options{tr("System Application"), + tr("System Archive"), + tr("System Application Update"), + tr("Firmware Package (Type A)"), + tr("Firmware Package (Type B)"), + tr("Game"), + tr("Game Update"), + tr("Game DLC"), + tr("Delta Title")}; + bool ok; + const auto item = QInputDialog::getItem( + this, tr("Select NCA Install Type..."), + tr("Please select the type of title you would like to install this NCA as:\n(In " + "most instances, the default 'Game' is fine.)"), + tt_options, 5, false, &ok); + + auto index = tt_options.indexOf(item); + if (!ok || index == -1) { + QMessageBox::warning(this, tr("Failed to Install"), + tr("The title type you selected for the NCA is invalid.")); + return; + } + + if (index >= 5) + index += 0x7B; + + const auto res = Service::FileSystem::GetUserNANDContents()->InstallEntry( + nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); + if (res == FileSys::InstallResult::Success) { + success(); + } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { + if (overwrite()) { + const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( + nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy); + if (res2 == FileSys::InstallResult::Success) { + success(); + } else { + failed(); + } + } + } else { + failed(); + } + } +} + void GMainWindow::OnMenuSelectGameListRoot() { QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); if (!dir_path.isEmpty()) { @@ -604,9 +775,8 @@ void GMainWindow::OnMenuRecentFile() { QAction* action = qobject_cast<QAction*>(sender()); assert(action); - QString filename = action->data().toString(); - QFileInfo file_info(filename); - if (file_info.exists()) { + const QString filename = action->data().toString(); + if (QFileInfo::exists(filename)) { BootGame(filename); } else { // Display an error message and remove the file from the list. @@ -704,11 +874,14 @@ void GMainWindow::ToggleWindowMode() { } void GMainWindow::OnConfigure() { - ConfigureDialog configureDialog(this); + ConfigureDialog configureDialog(this, hotkey_registry); + auto old_theme = UISettings::values.theme; auto result = configureDialog.exec(); if (result == QDialog::Accepted) { configureDialog.applyConfiguration(); - UpdateUITheme(); + if (UISettings::values.theme != old_theme) + UpdateUITheme(); + game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); config->Save(); } } @@ -841,7 +1014,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { UISettings::values.first_start = false; game_list->SaveInterfaceLayout(); - SaveHotkeys(); + hotkey_registry.SaveHotkeys(); // Shutdown session if the emu thread is active... if (emu_thread != nullptr) @@ -895,15 +1068,14 @@ void GMainWindow::UpdateUITheme() { QStringList theme_paths(default_theme_paths); if (UISettings::values.theme != UISettings::themes[0].second && !UISettings::values.theme.isEmpty()) { - QString theme_uri(":" + UISettings::values.theme + "/style.qss"); + const QString theme_uri(":" + UISettings::values.theme + "/style.qss"); QFile f(theme_uri); - if (!f.exists()) { - LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found"); - } else { - f.open(QFile::ReadOnly | QFile::Text); + if (f.open(QFile::ReadOnly | QFile::Text)) { QTextStream ts(&f); qApp->setStyleSheet(ts.readAll()); GMainWindow::setStyleSheet(ts.readAll()); + } else { + LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found"); } theme_paths.append(QStringList{":/icons/default", ":/icons/" + UISettings::values.theme}); QIcon::setThemeName(":/icons/" + UISettings::values.theme); @@ -939,7 +1111,6 @@ int main(int argc, char* argv[]) { QCoreApplication::setOrganizationName("yuzu team"); QCoreApplication::setApplicationName("yuzu"); - QApplication::setAttribute(Qt::AA_X11InitThreads); QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); QApplication app(argc, argv); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 074bba3f9..5f4d2ab9a 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -9,6 +9,7 @@ #include <QTimer> #include "core/core.h" #include "ui_main.h" +#include "yuzu/hotkeys.h" class Config; class EmuThread; @@ -43,7 +44,7 @@ public: void filterBarSetChecked(bool state); void UpdateUITheme(); GMainWindow(); - ~GMainWindow(); + ~GMainWindow() override; signals: @@ -124,6 +125,7 @@ private slots: void OnGameListOpenSaveFolder(u64 program_id); void OnMenuLoadFile(); void OnMenuLoadFolder(); + void OnMenuInstallToNAND(); /// Called whenever a user selects the "File->Select Game List Root" menu item void OnMenuSelectGameListRoot(); void OnMenuRecentFile(); @@ -160,6 +162,9 @@ private: bool emulation_running = false; std::unique_ptr<EmuThread> emu_thread; + // FS + FileSys::VirtualFilesystem vfs; + // Debugger panes ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; @@ -172,6 +177,8 @@ private: // stores default icon theme search paths for the platform QStringList default_theme_paths; + HotkeyRegistry hotkey_registry; + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 22c4cad08..a3bfb2af3 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -57,6 +57,8 @@ <string>Recent Files</string> </property> </widget> + <addaction name="action_Install_File_NAND" /> + <addaction name="separator"/> <addaction name="action_Load_File"/> <addaction name="action_Load_Folder"/> <addaction name="separator"/> @@ -102,6 +104,11 @@ <addaction name="menu_View"/> <addaction name="menu_Help"/> </widget> + <action name="action_Install_File_NAND"> + <property name="text"> + <string>Install File to NAND...</string> + </property> + </action> <action name="action_Load_File"> <property name="text"> <string>Load File...</string> diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index 2286c2559..051494bc5 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -54,6 +54,12 @@ struct Values { // logging bool show_console; + + // Game List + bool show_unknown; + uint32_t icon_size; + uint8_t row_1_text_id; + uint8_t row_2_text_id; }; extern Values values; |
