diff options
Diffstat (limited to 'src/yuzu')
29 files changed, 627 insertions, 276 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 475556806..46ed232d8 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -17,6 +17,8 @@ add_executable(yuzu 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 @@ -59,6 +61,7 @@ set(UIS 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 39ed3bccf..a81ad2888 100644 --- a/src/yuzu/about_dialog.cpp +++ b/src/yuzu/about_dialog.cpp @@ -15,4 +15,4 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia 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/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 bf469ee73..0bd46dbac 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -122,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(); @@ -234,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 c8e0b88af..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> @@ -67,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> diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 7fd07539a..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); 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 f66abf870..daa4cc0d9 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -6,18 +6,22 @@ #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(); 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 cb7d3f8bf..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(); 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 3379b7963..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, diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index 9be2c939c..e9ed9c38f 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -35,7 +35,7 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui:: this->setConfiguration(); } -ConfigureSystem::~ConfigureSystem() {} +ConfigureSystem::~ConfigureSystem() = default; void ConfigureSystem::setConfiguration() { enabled = !Core::System::GetInstance().IsPoweredOn(); diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp index ff3efcdaa..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 @@ -382,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 58f9db465..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; diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index f5a5697a0..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; } diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h index 6cbce6856..513b3c45d 100644 --- a/src/yuzu/debugger/wait_tree.h +++ b/src/yuzu/debugger/wait_tree.h @@ -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 71e24a9e2..85cb12594 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -9,9 +9,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 +166,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 +198,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 +263,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 +343,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, @@ -368,21 +375,23 @@ void GameList::LoadInterfaceLayout() { 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() { @@ -394,8 +403,32 @@ void GameList::RefreshGameDirectory() { } 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 { + boost::container::flat_map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map; + + const auto nca_control_callback = + [this, &nca_control_map](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); + + const auto callback = [this, recursion, + &nca_control_map](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) @@ -405,18 +438,48 @@ 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); - - u64 program_id = 0; - loader->ReadProgramId(program_id); + std::vector<u8> icon; + const auto res1 = loader->ReadIcon(icon); + + 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]; + const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS()); + + const auto nacp_file = control_dir->GetFile("control.nacp"); + 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; + } + } + } + } 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)), 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..8fe5e8b80 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,6 +162,7 @@ signals: void Finished(QStringList watch_list); private: + FileSys::VirtualFilesystem vfs; QStringList watch_list; QString dir_path; bool deep_scan; 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 e28679cd1..94fb8ae6a 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -24,6 +24,7 @@ #include "common/string_util.h" #include "core/core.h" #include "core/crypto/key_manager.h" +#include "core/file_sys/vfs_real.h" #include "core/gdbstub/gdbstub.h" #include "core/loader/loader.h" #include "core/settings.h" @@ -81,7 +82,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(); @@ -130,7 +135,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 @@ -206,43 +211,46 @@ void GMainWindow::InitializeRecentFileMenuActions() { } void GMainWindow::InitializeHotkeys() { - RegisterHotkey("Main Window", "Load File", QKeySequence::Open); - RegisterHotkey("Main Window", "Start Emulation"); - RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4)); - RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen); - RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape), - Qt::ApplicationShortcut); - RegisterHotkey("Main Window", "Toggle Speed Limit", QKeySequence("CTRL+Z"), - 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", "Continue/Pause", this), &QShortcut::activated, this, [&] { - if (emulation_running) { - if (emu_thread->IsRunning()) { - OnPauseGame(); - } else { - 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(); - } - }); - connect(GetHotkey("Main Window", "Toggle Speed Limit", this), &QShortcut::activated, this, [&] { - Settings::values.toggle_framelimit = !Settings::values.toggle_framelimit; - UpdateStatusBar(); - }); + 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() { @@ -321,7 +329,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 @@ -400,6 +409,7 @@ bool GMainWindow::LoadROM(const QString& filename) { } Core::System& system{Core::System::GetInstance()}; + system.SetFilesystem(vfs); system.SetGPUDebugContext(debug_context); @@ -414,67 +424,11 @@ 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_ErrorMissingKeys: { - const auto reg_found = Core::Crypto::KeyManager::KeyFileExists(false); - const auto title_found = Core::Crypto::KeyManager::KeyFileExists(true); - - std::string file_text; - - if (!reg_found && !title_found) { - file_text = "A proper key file (prod.keys, dev.keys, or title.keys) could not be " - "found. You will need to dump your keys from your switch to continue."; - } else if (reg_found && title_found) { - file_text = - "Both key files were found in your config directory, but the correct key could" - "not be found. You may be missing a titlekey or general key, depending on " - "the game."; - } else if (reg_found) { - file_text = - "The regular keys file (prod.keys/dev.keys) was found in your config, but the " - "titlekeys file (title.keys) was not. You are either missing the correct " - "titlekey or missing a general key required to decrypt the game."; - } else { - file_text = "The title keys file (title.keys) was found in your config, but " - "the regular keys file (prod.keys/dev.keys) was not. Unfortunately, " - "having the titlekey is not enough, you need additional general keys " - "to properly decrypt the game. You should double-check to make sure " - "your keys are correct."; - } - - QMessageBox::critical( - this, tr("Error while loading ROM!"), - tr(("The game you are trying to load is encrypted and the required keys to load " - "the game could not be found in your configuration. " + - file_text + " Please refer to the yuzu wiki for help.") - .c_str())); - break; - } - case Core::System::ResultStatus::ErrorLoader_ErrorDecrypting: { - QMessageBox::critical( - this, tr("Error while loading ROM!"), - tr("There was a general error while decrypting the game. This means that the keys " - "necessary were found, but were either incorrect, the game itself was not a " - "valid game or the game uses an unhandled cryptographic scheme. Please double " - "check that you have the correct " - "keys.")); - 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 occurred initializing the video core."), @@ -489,9 +443,23 @@ bool GMainWindow::LoadROM(const QString& filename) { break; default: - QMessageBox::critical( - this, tr("Error while loading ROM!"), - tr("An unknown error occurred. 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, Loader::GetMessageForResultStatus(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; @@ -579,11 +547,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]); @@ -595,12 +563,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) { @@ -631,9 +595,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")); + + if (dir_path.isNull()) { + return; + } - QStringList matching_main = dir.entryList(QStringList("main"), QDir::Files); + 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 { @@ -654,9 +624,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. @@ -754,13 +723,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(); if (UISettings::values.theme != old_theme) UpdateUITheme(); + game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); config->Save(); } } @@ -893,7 +863,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) @@ -947,15 +917,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); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 074bba3f9..74487c58c 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: @@ -160,6 +161,9 @@ private: bool emulation_running = false; std::unique_ptr<EmuThread> emu_thread; + // FS + FileSys::VirtualFilesystem vfs; + // Debugger panes ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; @@ -172,6 +176,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/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; |
