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; | 
