From 4c269e5ced47765f35dbb58afa9fcac8138a1f7c Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Thu, 16 Apr 2020 23:27:38 -0400 Subject: [PATCH 1/4] Add support for batch install to NAND This adds support to batch install files to NAND --- src/yuzu/CMakeLists.txt | 6 +- src/yuzu/install_dialog.cpp | 72 +++++++++ src/yuzu/install_dialog.h | 35 +++++ src/yuzu/main.cpp | 299 +++++++++++++++++++++--------------- src/yuzu/main.h | 4 + src/yuzu/main.ui | 2 +- 6 files changed, 292 insertions(+), 126 deletions(-) create mode 100644 src/yuzu/install_dialog.cpp create mode 100644 src/yuzu/install_dialog.h diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 742b72856..f430525f8 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -98,11 +98,13 @@ add_executable(yuzu game_list_p.h game_list_worker.cpp game_list_worker.h + hotkeys.cpp + hotkeys.h + install_dialog.cpp + install_dialog.h loading_screen.cpp loading_screen.h loading_screen.ui - hotkeys.cpp - hotkeys.h main.cpp main.h main.ui diff --git a/src/yuzu/install_dialog.cpp b/src/yuzu/install_dialog.cpp new file mode 100644 index 000000000..fac158c25 --- /dev/null +++ b/src/yuzu/install_dialog.cpp @@ -0,0 +1,72 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include +#include "yuzu/install_dialog.h" +#include "yuzu/uisettings.h" + +InstallDialog::InstallDialog(QWidget* parent, const QStringList& files) : QDialog(parent) { + file_list = new QListWidget(this); + + for (const QString& file : files) { + QListWidgetItem* item = new QListWidgetItem(QFileInfo(file).fileName(), file_list); + item->setData(Qt::UserRole, file); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Checked); + } + + file_list->setMinimumWidth((file_list->sizeHintForColumn(0) * 6) / 5); + + vbox_layout = new QVBoxLayout; + + hbox_layout = new QHBoxLayout; + + description = new QLabel(tr("Please confirm these are the files you wish to install.")); + + overwrite_files = new QCheckBox(tr("Overwrite Existing Files")); + overwrite_files->setCheckState(Qt::Unchecked); + + buttons = new QDialogButtonBox; + buttons->addButton(QDialogButtonBox::Cancel); + buttons->addButton(tr("Install"), QDialogButtonBox::AcceptRole); + + connect(buttons, &QDialogButtonBox::accepted, this, &InstallDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &InstallDialog::reject); + + hbox_layout->addWidget(overwrite_files); + hbox_layout->addWidget(buttons); + + vbox_layout->addWidget(description); + vbox_layout->addWidget(file_list); + vbox_layout->addLayout(hbox_layout); + + setLayout(vbox_layout); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + setWindowTitle(tr("Install Files to NAND")); +} + +InstallDialog::~InstallDialog() = default; + +QStringList InstallDialog::GetFilenames() const { + QStringList filenames; + + for (int i = 0; i < file_list->count(); ++i) { + const QListWidgetItem* item = file_list->item(i); + if (item->checkState() == Qt::Checked) { + filenames.append(item->data(Qt::UserRole).toString()); + } + } + + return filenames; +} + +bool InstallDialog::ShouldOverwriteFiles() const { + return overwrite_files->isChecked(); +} \ No newline at end of file diff --git a/src/yuzu/install_dialog.h b/src/yuzu/install_dialog.h new file mode 100644 index 000000000..3eaa9e60a --- /dev/null +++ b/src/yuzu/install_dialog.h @@ -0,0 +1,35 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +class QCheckBox; +class QDialogButtonBox; +class QHBoxLayout; +class QLabel; +class QListWidget; +class QVBoxLayout; + +class InstallDialog : public QDialog { + Q_OBJECT + +public: + explicit InstallDialog(QWidget* parent, const QStringList& files); + ~InstallDialog() override; + + QStringList GetFilenames() const; + bool ShouldOverwriteFiles() const; + +private: + QListWidget* file_list; + + QVBoxLayout* vbox_layout; + QHBoxLayout* hbox_layout; + + QLabel* description; + QCheckBox* overwrite_files; + QDialogButtonBox* buttons; +}; \ No newline at end of file diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 4d501a8f9..45ddc3baf 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -107,6 +107,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/game_list.h" #include "yuzu/game_list_p.h" #include "yuzu/hotkeys.h" +#include "yuzu/install_dialog.h" #include "yuzu/loading_screen.h" #include "yuzu/main.h" #include "yuzu/uisettings.h" @@ -1596,38 +1597,67 @@ void GMainWindow::OnMenuLoadFolder() { void GMainWindow::OnMenuInstallToNAND() { const QString file_filter = tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive " - "(*.nca);;Nintendo Submissions Package (*.nsp);;NX Cartridge " + "(*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge " "Image (*.xci)"); - QString filename = QFileDialog::getOpenFileName(this, tr("Install File"), - UISettings::values.roms_path, file_filter); + QStringList files = QFileDialog::getOpenFileNames(this, tr("Install Files"), + UISettings::values.roms_path, file_filter); - if (filename.isEmpty()) { + if (files.isEmpty()) { return; } - const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, - const FileSys::VirtualFile& dest, std::size_t block_size) { - if (src == nullptr || dest == nullptr) + InstallDialog installDialog(this, files); + if (installDialog.exec() == QDialog::Rejected) { + return; + } + + const QStringList filenames = installDialog.GetFilenames(); + const bool overwrite_files = installDialog.ShouldOverwriteFiles(); + + int count = 0; + int total_count = filenames.size(); + bool is_progressdialog_created = false; + + const auto qt_raw_copy = [this, &count, &total_count, &is_progressdialog_created]( + const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, + std::size_t block_size) { + if (src == nullptr || dest == nullptr) { return false; - if (!dest->Resize(src->GetSize())) + } + if (!dest->Resize(src->GetSize())) { return false; + } std::array buffer{}; const int progress_maximum = static_cast(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); + if (!is_progressdialog_created) { + ui.action_Install_File_NAND->setEnabled(false); + install_progress = new QProgressDialog( + tr("Installing file \"%1\"...").arg(QString::fromStdString(src->GetName())), + tr("Cancel"), 0, progress_maximum, this); + install_progress->setWindowTitle( + tr("%n file(s) remaining", "", total_count - count - 1)); + install_progress->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint & + ~Qt::WindowMaximizeButtonHint); + install_progress->setAutoClose(false); + is_progressdialog_created = true; + } else { + install_progress->setWindowTitle( + tr("%n file(s) remaining", "", total_count - count - 1)); + install_progress->setLabelText( + tr("Installing file \"%1\"...").arg(QString::fromStdString(src->GetName()))); + install_progress->setMaximum(progress_maximum); + } for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { - if (progress.wasCanceled()) { + if (install_progress->wasCanceled()) { dest->Resize(0); return false; } const int progress_value = static_cast(i / buffer.size()); - progress.setValue(progress_value); + install_progress->setValue(progress_value); const auto read = src->Read(buffer.data(), buffer.size(), i); dest->Write(buffer.data(), read, i); @@ -1636,143 +1666,166 @@ void GMainWindow::OnMenuInstallToNAND() { return true; }; - const auto success = [this]() { + const auto success = [this, &count, &is_progressdialog_created]() { + if (is_progressdialog_created) { + install_progress->close(); + } QMessageBox::information(this, tr("Successfully Installed"), - tr("The file was successfully installed.")); + tr("%n file(s) successfully installed", "", count)); game_list->PopulateAsync(UISettings::values.game_dirs); FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list"); + ui.action_Install_File_NAND->setEnabled(true); }; - const auto failed = [this]() { + const auto failed = [this, &is_progressdialog_created](const QString& file) { + if (is_progressdialog_created) { + install_progress->close(); + } QMessageBox::warning( - this, tr("Failed to Install"), + this, tr("Failed to Install %1").arg(QFileInfo(file).fileName()), 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.")); + game_list->PopulateAsync(UISettings::values.game_dirs); + ui.action_Install_File_NAND->setEnabled(true); }; - 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; + const auto overwrite = [this](const QString& file) { + return QMessageBox::question( + this, tr("Failed to Install %1").arg(QFileInfo(file).fileName()), + tr("The file you are attempting to install already exists " + "in the cache. Would you like to overwrite it?")) == QMessageBox::Yes; }; - if (filename.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || - filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { - std::shared_ptr nsp; - if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { - nsp = std::make_shared( - vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); - if (nsp->IsExtractedType()) - failed(); - } else { - const auto xci = std::make_shared( - vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); - nsp = xci->GetSecurePartitionNSP(); - } + for (const QString& filename : filenames) { + if (filename.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || + filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { + std::shared_ptr nsp; + if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { + nsp = std::make_shared( + vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); + if (nsp->IsExtractedType()) { + failed(filename); + break; + } + } else { + const auto xci = std::make_shared( + vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); + nsp = xci->GetSecurePartitionNSP(); + } - if (nsp->GetStatus() != Loader::ResultStatus::Success) { - failed(); - return; - } - const auto res = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->InstallEntry(*nsp, false, qt_raw_copy); - if (res == FileSys::InstallResult::Success) { - success(); - } else { - if (res == FileSys::InstallResult::ErrorAlreadyExists) { - if (overwrite()) { + if (nsp->GetStatus() != Loader::ResultStatus::Success) { + failed(filename); + break; + } + const auto res = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->InstallEntry(*nsp, false, qt_raw_copy); + if (res == FileSys::InstallResult::Success) { + ++count; + } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { + if (overwrite_files && overwrite(filename)) { const auto res2 = Core::System::GetInstance() .GetFileSystemController() .GetUserNANDContents() ->InstallEntry(*nsp, true, qt_raw_copy); - if (res2 == FileSys::InstallResult::Success) { - success(); - } else { - failed(); + if (res2 != FileSys::InstallResult::Success) { + failed(filename); + break; } + ++count; + } else { + --total_count; } } else { - failed(); + failed(filename); + break; } - } - } else { - const auto nca = std::make_shared( - vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); - const auto id = nca->GetStatus(); - - // Game updates necessary are missing base RomFS - if (id != Loader::ResultStatus::Success && - id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { - 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 is equal to or past Game, add the jump in TitleType. - if (index >= 5) { - index += static_cast(FileSys::TitleType::Application) - - static_cast(FileSys::TitleType::FirmwarePackageB); - } - - FileSys::InstallResult res; - if (index >= static_cast(FileSys::TitleType::Application)) { - res = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->InstallEntry(*nca, static_cast(index), false, - qt_raw_copy); } else { - res = Core::System::GetInstance() - .GetFileSystemController() - .GetSystemNANDContents() - ->InstallEntry(*nca, static_cast(index), false, - qt_raw_copy); - } + const auto nca = std::make_shared( + vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); + const auto id = nca->GetStatus(); - if (res == FileSys::InstallResult::Success) { - success(); - } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { - if (overwrite()) { - const auto res2 = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->InstallEntry(*nca, static_cast(index), - true, qt_raw_copy); - if (res2 == FileSys::InstallResult::Success) { - success(); + // Game updates necessary are missing base RomFS + if (id != Loader::ResultStatus::Success && + id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { + failed(filename); + break; + } + + 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.")); + break; + } + + // If index is equal to or past Game, add the jump in TitleType. + if (index >= 5) { + index += static_cast(FileSys::TitleType::Application) - + static_cast(FileSys::TitleType::FirmwarePackageB); + } + + FileSys::InstallResult res; + if (index >= static_cast(FileSys::TitleType::Application)) { + res = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->InstallEntry(*nca, static_cast(index), false, + qt_raw_copy); + } else { + res = Core::System::GetInstance() + .GetFileSystemController() + .GetSystemNANDContents() + ->InstallEntry(*nca, static_cast(index), false, + qt_raw_copy); + } + + if (res == FileSys::InstallResult::Success) { + ++count; + } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { + if (overwrite_files && overwrite(filename)) { + const auto res2 = + Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->InstallEntry(*nca, static_cast(index), true, + qt_raw_copy); + if (res2 != FileSys::InstallResult::Success) { + failed(filename); + break; + } + ++count; } else { - failed(); + --total_count; } + } else { + failed(filename); + break; } - } else { - failed(); + } + + // Return success only on the last file + if (filename == filenames.last()) { + success(); } } } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 8e3d39c38..55d072e96 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -28,6 +28,7 @@ class MicroProfileDialog; class ProfilerWidget; class QLabel; class QPushButton; +class QProgressDialog; class WaitTreeWidget; enum class GameListOpenTarget; class GameListPlaceholder; @@ -272,6 +273,9 @@ private: HotkeyRegistry hotkey_registry; + // Install to NAND progress dialog + QProgressDialog* install_progress; + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index bee6e107e..c3a1d715e 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -130,7 +130,7 @@ true - Install File to NAND... + Install Files to NAND... From 7f4d96d87332824643d7a8d3ff0fab7ea771b798 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Wed, 1 Jul 2020 16:15:57 -0400 Subject: [PATCH 2/4] Refactor batch installing files Key issues fixed: - Progress dialog showing up as white/hanging/getting stuck/unresponsive. Key changes: - Progress dialog now shows progress as a function of all files instead of per nca within a file. - Overwrite existing files will overwrite all files in the selection. --- src/yuzu/install_dialog.cpp | 16 +- src/yuzu/install_dialog.h | 5 +- src/yuzu/main.cpp | 407 +++++++++++++++++++----------------- src/yuzu/main.h | 14 +- 4 files changed, 242 insertions(+), 200 deletions(-) diff --git a/src/yuzu/install_dialog.cpp b/src/yuzu/install_dialog.cpp index fac158c25..5f3b4c963 100644 --- a/src/yuzu/install_dialog.cpp +++ b/src/yuzu/install_dialog.cpp @@ -22,7 +22,7 @@ InstallDialog::InstallDialog(QWidget* parent, const QStringList& files) : QDialo item->setCheckState(Qt::Checked); } - file_list->setMinimumWidth((file_list->sizeHintForColumn(0) * 6) / 5); + file_list->setMinimumWidth((file_list->sizeHintForColumn(0) * 10) / 9); vbox_layout = new QVBoxLayout; @@ -54,19 +54,23 @@ InstallDialog::InstallDialog(QWidget* parent, const QStringList& files) : QDialo InstallDialog::~InstallDialog() = default; -QStringList InstallDialog::GetFilenames() const { - QStringList filenames; +QStringList InstallDialog::GetFiles() const { + QStringList files; for (int i = 0; i < file_list->count(); ++i) { const QListWidgetItem* item = file_list->item(i); if (item->checkState() == Qt::Checked) { - filenames.append(item->data(Qt::UserRole).toString()); + files.append(item->data(Qt::UserRole).toString()); } } - return filenames; + return files; } bool InstallDialog::ShouldOverwriteFiles() const { return overwrite_files->isChecked(); -} \ No newline at end of file +} + +int InstallDialog::GetMinimumWidth() const { + return file_list->width(); +} diff --git a/src/yuzu/install_dialog.h b/src/yuzu/install_dialog.h index 3eaa9e60a..55a458ba8 100644 --- a/src/yuzu/install_dialog.h +++ b/src/yuzu/install_dialog.h @@ -20,8 +20,9 @@ public: explicit InstallDialog(QWidget* parent, const QStringList& files); ~InstallDialog() override; - QStringList GetFilenames() const; + QStringList GetFiles() const; bool ShouldOverwriteFiles() const; + int GetMinimumWidth() const; private: QListWidget* file_list; @@ -32,4 +33,4 @@ private: QLabel* description; QCheckBox* overwrite_files; QDialogButtonBox* buttons; -}; \ No newline at end of file +}; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 45ddc3baf..4539cbe0d 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1599,28 +1599,107 @@ void GMainWindow::OnMenuInstallToNAND() { tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive " "(*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge " "Image (*.xci)"); - QStringList files = QFileDialog::getOpenFileNames(this, tr("Install Files"), - UISettings::values.roms_path, file_filter); - if (files.isEmpty()) { + QStringList filenames = QFileDialog::getOpenFileNames( + this, tr("Install Files"), UISettings::values.roms_path, file_filter); + + if (filenames.isEmpty()) { return; } - InstallDialog installDialog(this, files); + InstallDialog installDialog(this, filenames); if (installDialog.exec() == QDialog::Rejected) { return; } - const QStringList filenames = installDialog.GetFilenames(); + const QStringList files = installDialog.GetFiles(); const bool overwrite_files = installDialog.ShouldOverwriteFiles(); int count = 0; - int total_count = filenames.size(); - bool is_progressdialog_created = false; + const int total_count = filenames.size(); - const auto qt_raw_copy = [this, &count, &total_count, &is_progressdialog_created]( - const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, - std::size_t block_size) { + QStringList new_files{}; // Newly installed files that do not yet exist in the NAND + QStringList overwritten_files{}; // Files that overwrote those existing in the NAND + QStringList existing_files{}; // Files that were not installed as they already exist in the NAND + QStringList failed_files{}; // Files that failed to install due to errors + + ui.action_Install_File_NAND->setEnabled(false); + + QProgressDialog install_progress(QStringLiteral(""), tr("Cancel"), 0, total_count, this); + install_progress.setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint & + ~Qt::WindowMaximizeButtonHint); + install_progress.setAutoClose(false); + install_progress.setFixedWidth(installDialog.GetMinimumWidth()); + install_progress.show(); + + for (const QString& file : files) { + install_progress.setWindowTitle(tr("%n file(s) remaining", "", total_count - count)); + install_progress.setLabelText( + tr("Installing file \"%1\"...").arg(QFileInfo(file).fileName())); + + QFuture future; + InstallResult result; + + if (file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || + file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { + future = QtConcurrent::run([this, &file, &overwrite_files, &install_progress] { + return InstallNSPXCI(file, overwrite_files, install_progress); + }); + + while (!future.isFinished()) { + QCoreApplication::processEvents(); + } + + result = future.result(); + } else { + result = InstallNCA(file, overwrite_files, install_progress); + } + + switch (result) { + case InstallResult::Success: + new_files.append(QFileInfo(file).fileName()); + break; + case InstallResult::Overwrite: + overwritten_files.append(QFileInfo(file).fileName()); + break; + case InstallResult::AlreadyExists: + existing_files.append(QFileInfo(file).fileName()); + break; + case InstallResult::Failure: + failed_files.append(QFileInfo(file).fileName()); + break; + } + + install_progress.setValue(++count); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + install_progress.close(); + + const QString install_results = + (new_files.isEmpty() ? QStringLiteral("") + : tr("%n file(s) were newly installed\n", "", new_files.size())) + + (overwritten_files.isEmpty() + ? QStringLiteral("") + : tr("%n file(s) were overwritten\n", "", overwritten_files.size())) + + (existing_files.isEmpty() + ? QStringLiteral("") + : tr("%n file(s) already exist in NAND\n", "", existing_files.size())) + + (failed_files.isEmpty() ? QStringLiteral("") + : tr("%n file(s) failed to install\n", "", failed_files.size())); + + QMessageBox::information(this, tr("Install Results"), install_results); + game_list->PopulateAsync(UISettings::values.game_dirs); + FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + + "game_list"); + ui.action_Install_File_NAND->setEnabled(true); +} + +InstallResult GMainWindow::InstallNSPXCI(const QString& filename, bool overwrite_files, + QProgressDialog& install_progress) { + const auto qt_raw_copy = [this, &install_progress](const FileSys::VirtualFile& src, + const FileSys::VirtualFile& dest, + std::size_t block_size) { if (src == nullptr || dest == nullptr) { return false; } @@ -1629,204 +1708,154 @@ void GMainWindow::OnMenuInstallToNAND() { } std::array buffer{}; - const int progress_maximum = static_cast(src->GetSize() / buffer.size()); - - if (!is_progressdialog_created) { - ui.action_Install_File_NAND->setEnabled(false); - install_progress = new QProgressDialog( - tr("Installing file \"%1\"...").arg(QString::fromStdString(src->GetName())), - tr("Cancel"), 0, progress_maximum, this); - install_progress->setWindowTitle( - tr("%n file(s) remaining", "", total_count - count - 1)); - install_progress->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint & - ~Qt::WindowMaximizeButtonHint); - install_progress->setAutoClose(false); - is_progressdialog_created = true; - } else { - install_progress->setWindowTitle( - tr("%n file(s) remaining", "", total_count - count - 1)); - install_progress->setLabelText( - tr("Installing file \"%1\"...").arg(QString::fromStdString(src->GetName()))); - install_progress->setMaximum(progress_maximum); - } for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { - if (install_progress->wasCanceled()) { + if (install_progress.wasCanceled()) { dest->Resize(0); return false; } - const int progress_value = static_cast(i / buffer.size()); - install_progress->setValue(progress_value); + const auto read = src->Read(buffer.data(), buffer.size(), i); + dest->Write(buffer.data(), read, i); + } + return true; + }; + + std::shared_ptr nsp; + if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { + nsp = std::make_shared( + vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); + if (nsp->IsExtractedType()) { + return InstallResult::Failure; + } + } else { + const auto xci = std::make_shared( + vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); + nsp = xci->GetSecurePartitionNSP(); + } + + if (nsp->GetStatus() != Loader::ResultStatus::Success) { + return InstallResult::Failure; + } + const auto res = + Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->InstallEntry( + *nsp, false, qt_raw_copy); + if (res == FileSys::InstallResult::Success) { + return InstallResult::Success; + } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { + if (overwrite_files) { + const auto res2 = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->InstallEntry(*nsp, true, qt_raw_copy); + if (res2 != FileSys::InstallResult::Success) { + return InstallResult::Failure; + } + return InstallResult::Overwrite; + } else { + return InstallResult::AlreadyExists; + } + } else { + return InstallResult::Failure; + } +} + +InstallResult GMainWindow::InstallNCA(const QString& filename, bool overwrite_files, + QProgressDialog& install_progress) { + const auto qt_raw_copy = [this, &install_progress](const FileSys::VirtualFile& src, + const FileSys::VirtualFile& dest, + std::size_t block_size) { + if (src == nullptr || dest == nullptr) { + return false; + } + if (!dest->Resize(src->GetSize())) { + return false; + } + + std::array buffer{}; + + for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { + if (install_progress.wasCanceled()) { + dest->Resize(0); + return false; + } const auto read = src->Read(buffer.data(), buffer.size(), i); dest->Write(buffer.data(), read, i); } - return true; }; - const auto success = [this, &count, &is_progressdialog_created]() { - if (is_progressdialog_created) { - install_progress->close(); - } - QMessageBox::information(this, tr("Successfully Installed"), - tr("%n file(s) successfully installed", "", count)); - game_list->PopulateAsync(UISettings::values.game_dirs); - FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + - DIR_SEP + "game_list"); - ui.action_Install_File_NAND->setEnabled(true); - }; + const auto nca = + std::make_shared(vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); + const auto id = nca->GetStatus(); - const auto failed = [this, &is_progressdialog_created](const QString& file) { - if (is_progressdialog_created) { - install_progress->close(); - } - QMessageBox::warning( - this, tr("Failed to Install %1").arg(QFileInfo(file).fileName()), - 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.")); - game_list->PopulateAsync(UISettings::values.game_dirs); - ui.action_Install_File_NAND->setEnabled(true); - }; + // Game updates necessary are missing base RomFS + if (id != Loader::ResultStatus::Success && + id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { + return InstallResult::Failure; + } - const auto overwrite = [this](const QString& file) { - return QMessageBox::question( - this, tr("Failed to Install %1").arg(QFileInfo(file).fileName()), - tr("The file you are attempting to install already exists " - "in the cache. Would you like to overwrite it?")) == QMessageBox::Yes; - }; + 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); - for (const QString& filename : filenames) { - if (filename.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || - filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { - std::shared_ptr nsp; - if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { - nsp = std::make_shared( - vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); - if (nsp->IsExtractedType()) { - failed(filename); - break; - } - } else { - const auto xci = std::make_shared( - vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); - nsp = xci->GetSecurePartitionNSP(); - } - - if (nsp->GetStatus() != Loader::ResultStatus::Success) { - failed(filename); - break; - } - const auto res = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->InstallEntry(*nsp, false, qt_raw_copy); - if (res == FileSys::InstallResult::Success) { - ++count; - } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { - if (overwrite_files && overwrite(filename)) { - const auto res2 = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->InstallEntry(*nsp, true, qt_raw_copy); - if (res2 != FileSys::InstallResult::Success) { - failed(filename); - break; - } - ++count; - } else { - --total_count; - } - } else { - failed(filename); - break; + 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 InstallResult::Failure; + } + + // If index is equal to or past Game, add the jump in TitleType. + if (index >= 5) { + index += static_cast(FileSys::TitleType::Application) - + static_cast(FileSys::TitleType::FirmwarePackageB); + } + + FileSys::InstallResult res; + if (index >= static_cast(FileSys::TitleType::Application)) { + res = Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->InstallEntry(*nca, static_cast(index), false, qt_raw_copy); + } else { + res = Core::System::GetInstance() + .GetFileSystemController() + .GetSystemNANDContents() + ->InstallEntry(*nca, static_cast(index), false, qt_raw_copy); + } + + if (res == FileSys::InstallResult::Success) { + return InstallResult::Success; + } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { + if (overwrite_files) { + const auto res2 = + Core::System::GetInstance() + .GetFileSystemController() + .GetUserNANDContents() + ->InstallEntry(*nca, static_cast(index), true, qt_raw_copy); + if (res2 != FileSys::InstallResult::Success) { + return InstallResult::Failure; } + return InstallResult::Overwrite; } else { - const auto nca = std::make_shared( - vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); - const auto id = nca->GetStatus(); - - // Game updates necessary are missing base RomFS - if (id != Loader::ResultStatus::Success && - id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { - failed(filename); - break; - } - - 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.")); - break; - } - - // If index is equal to or past Game, add the jump in TitleType. - if (index >= 5) { - index += static_cast(FileSys::TitleType::Application) - - static_cast(FileSys::TitleType::FirmwarePackageB); - } - - FileSys::InstallResult res; - if (index >= static_cast(FileSys::TitleType::Application)) { - res = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->InstallEntry(*nca, static_cast(index), false, - qt_raw_copy); - } else { - res = Core::System::GetInstance() - .GetFileSystemController() - .GetSystemNANDContents() - ->InstallEntry(*nca, static_cast(index), false, - qt_raw_copy); - } - - if (res == FileSys::InstallResult::Success) { - ++count; - } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { - if (overwrite_files && overwrite(filename)) { - const auto res2 = - Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->InstallEntry(*nca, static_cast(index), true, - qt_raw_copy); - if (res2 != FileSys::InstallResult::Success) { - failed(filename); - break; - } - ++count; - } else { - --total_count; - } - } else { - failed(filename); - break; - } - } - - // Return success only on the last file - if (filename == filenames.last()) { - success(); + return InstallResult::AlreadyExists; } + } else { + return InstallResult::Failure; } } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 55d072e96..deea8170d 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -48,6 +48,13 @@ enum class EmulatedDirectoryTarget { SDMC, }; +enum class InstallResult { + Success, + Overwrite, + AlreadyExists, + Failure, +}; + enum class ReinitializeKeyBehavior { NoWarning, Warning, @@ -219,6 +226,10 @@ private slots: private: std::optional SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); + InstallResult InstallNSPXCI(const QString& filename, bool overwrite_files, + QProgressDialog& install_progress); + InstallResult InstallNCA(const QString& filename, bool overwrite_files, + QProgressDialog& install_progress); void UpdateWindowTitle(const std::string& title_name = {}, const std::string& title_version = {}); void UpdateStatusBar(); @@ -273,9 +284,6 @@ private: HotkeyRegistry hotkey_registry; - // Install to NAND progress dialog - QProgressDialog* install_progress; - protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; From 6d8d7ebc66d9deb5adb4edb246590481b1236bbc Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sun, 5 Jul 2020 09:29:39 -0400 Subject: [PATCH 3/4] Update the install and progress dialogs - Remove the overwrite files checkbox, it will always overwrite - The progressbar now reflects the progress in terms of data transferred. --- src/yuzu/install_dialog.cpp | 12 ++-- src/yuzu/install_dialog.h | 2 +- src/yuzu/main.cpp | 118 ++++++++++++++++-------------------- src/yuzu/main.h | 13 ++-- 4 files changed, 65 insertions(+), 80 deletions(-) diff --git a/src/yuzu/install_dialog.cpp b/src/yuzu/install_dialog.cpp index 5f3b4c963..06b0b1874 100644 --- a/src/yuzu/install_dialog.cpp +++ b/src/yuzu/install_dialog.cpp @@ -22,7 +22,7 @@ InstallDialog::InstallDialog(QWidget* parent, const QStringList& files) : QDialo item->setCheckState(Qt::Checked); } - file_list->setMinimumWidth((file_list->sizeHintForColumn(0) * 10) / 9); + file_list->setMinimumWidth((file_list->sizeHintForColumn(0) * 11) / 10); vbox_layout = new QVBoxLayout; @@ -30,8 +30,8 @@ InstallDialog::InstallDialog(QWidget* parent, const QStringList& files) : QDialo description = new QLabel(tr("Please confirm these are the files you wish to install.")); - overwrite_files = new QCheckBox(tr("Overwrite Existing Files")); - overwrite_files->setCheckState(Qt::Unchecked); + update_description = + new QLabel(tr("Installing an Update or DLC will overwrite the previously installed one.")); buttons = new QDialogButtonBox; buttons->addButton(QDialogButtonBox::Cancel); @@ -40,10 +40,10 @@ InstallDialog::InstallDialog(QWidget* parent, const QStringList& files) : QDialo connect(buttons, &QDialogButtonBox::accepted, this, &InstallDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &InstallDialog::reject); - hbox_layout->addWidget(overwrite_files); hbox_layout->addWidget(buttons); vbox_layout->addWidget(description); + vbox_layout->addWidget(update_description); vbox_layout->addWidget(file_list); vbox_layout->addLayout(hbox_layout); @@ -67,10 +67,6 @@ QStringList InstallDialog::GetFiles() const { return files; } -bool InstallDialog::ShouldOverwriteFiles() const { - return overwrite_files->isChecked(); -} - int InstallDialog::GetMinimumWidth() const { return file_list->width(); } diff --git a/src/yuzu/install_dialog.h b/src/yuzu/install_dialog.h index 55a458ba8..e4aba1b06 100644 --- a/src/yuzu/install_dialog.h +++ b/src/yuzu/install_dialog.h @@ -31,6 +31,6 @@ private: QHBoxLayout* hbox_layout; QLabel* description; - QCheckBox* overwrite_files; + QLabel* update_description; QDialogButtonBox* buttons; }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 4539cbe0d..8a57e34c7 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -848,6 +848,9 @@ void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::OpenPerGameGeneralRequested, this, &GMainWindow::OnGameListOpenPerGameProperties); + connect(this, &GMainWindow::UpdateInstallProgress, this, + &GMainWindow::IncrementInstallProgress); + connect(this, &GMainWindow::EmulationStarting, render_window, &GRenderWindow::OnEmulationStarting); connect(this, &GMainWindow::EmulationStopping, render_window, @@ -1594,6 +1597,10 @@ void GMainWindow::OnMenuLoadFolder() { } } +void GMainWindow::IncrementInstallProgress() { + install_progress->setValue(install_progress->value() + 1); +} + void GMainWindow::OnMenuInstallToNAND() { const QString file_filter = tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive " @@ -1613,28 +1620,35 @@ void GMainWindow::OnMenuInstallToNAND() { } const QStringList files = installDialog.GetFiles(); - const bool overwrite_files = installDialog.ShouldOverwriteFiles(); - int count = 0; - const int total_count = filenames.size(); + int remaining = filenames.size(); + + // This would only overflow above 2^43 bytes (8.796 TB) + int total_size = 0; + for (const QString& file : files) { + total_size += static_cast(QFile(file).size() / 0x1000); + } + if (total_size < 0) { + LOG_CRITICAL(Frontend, "Attempting to install too many files, aborting."); + return; + } QStringList new_files{}; // Newly installed files that do not yet exist in the NAND QStringList overwritten_files{}; // Files that overwrote those existing in the NAND - QStringList existing_files{}; // Files that were not installed as they already exist in the NAND - QStringList failed_files{}; // Files that failed to install due to errors + QStringList failed_files{}; // Files that failed to install due to errors ui.action_Install_File_NAND->setEnabled(false); - QProgressDialog install_progress(QStringLiteral(""), tr("Cancel"), 0, total_count, this); - install_progress.setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint & - ~Qt::WindowMaximizeButtonHint); - install_progress.setAutoClose(false); - install_progress.setFixedWidth(installDialog.GetMinimumWidth()); - install_progress.show(); + install_progress = new QProgressDialog(QStringLiteral(""), tr("Cancel"), 0, total_size, this); + install_progress->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint & + ~Qt::WindowMaximizeButtonHint); + install_progress->setAttribute(Qt::WA_DeleteOnClose, true); + install_progress->setFixedWidth(installDialog.GetMinimumWidth() + 40); + install_progress->show(); for (const QString& file : files) { - install_progress.setWindowTitle(tr("%n file(s) remaining", "", total_count - count)); - install_progress.setLabelText( + install_progress->setWindowTitle(tr("%n file(s) remaining", "", remaining)); + install_progress->setLabelText( tr("Installing file \"%1\"...").arg(QFileInfo(file).fileName())); QFuture future; @@ -1642,19 +1656,21 @@ void GMainWindow::OnMenuInstallToNAND() { if (file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { - future = QtConcurrent::run([this, &file, &overwrite_files, &install_progress] { - return InstallNSPXCI(file, overwrite_files, install_progress); - }); + + future = QtConcurrent::run([this, &file] { return InstallNSPXCI(file); }); while (!future.isFinished()) { QCoreApplication::processEvents(); } result = future.result(); + } else { - result = InstallNCA(file, overwrite_files, install_progress); + result = InstallNCA(file); } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + switch (result) { case InstallResult::Success: new_files.append(QFileInfo(file).fileName()); @@ -1662,19 +1678,15 @@ void GMainWindow::OnMenuInstallToNAND() { case InstallResult::Overwrite: overwritten_files.append(QFileInfo(file).fileName()); break; - case InstallResult::AlreadyExists: - existing_files.append(QFileInfo(file).fileName()); - break; case InstallResult::Failure: failed_files.append(QFileInfo(file).fileName()); break; } - install_progress.setValue(++count); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + --remaining; } - install_progress.close(); + install_progress->close(); const QString install_results = (new_files.isEmpty() ? QStringLiteral("") @@ -1682,9 +1694,6 @@ void GMainWindow::OnMenuInstallToNAND() { (overwritten_files.isEmpty() ? QStringLiteral("") : tr("%n file(s) were overwritten\n", "", overwritten_files.size())) + - (existing_files.isEmpty() - ? QStringLiteral("") - : tr("%n file(s) already exist in NAND\n", "", existing_files.size())) + (failed_files.isEmpty() ? QStringLiteral("") : tr("%n file(s) failed to install\n", "", failed_files.size())); @@ -1695,11 +1704,9 @@ void GMainWindow::OnMenuInstallToNAND() { ui.action_Install_File_NAND->setEnabled(true); } -InstallResult GMainWindow::InstallNSPXCI(const QString& filename, bool overwrite_files, - QProgressDialog& install_progress) { - const auto qt_raw_copy = [this, &install_progress](const FileSys::VirtualFile& src, - const FileSys::VirtualFile& dest, - std::size_t block_size) { +InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { + const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, + const FileSys::VirtualFile& dest, std::size_t block_size) { if (src == nullptr || dest == nullptr) { return false; } @@ -1710,11 +1717,13 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename, bool overwrite std::array buffer{}; for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { - if (install_progress.wasCanceled()) { + if (install_progress->wasCanceled()) { dest->Resize(0); return false; } + emit UpdateInstallProgress(); + const auto read = src->Read(buffer.data(), buffer.size(), i); dest->Write(buffer.data(), read, i); } @@ -1739,32 +1748,19 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename, bool overwrite } const auto res = Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->InstallEntry( - *nsp, false, qt_raw_copy); + *nsp, true, qt_raw_copy); if (res == FileSys::InstallResult::Success) { return InstallResult::Success; } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { - if (overwrite_files) { - const auto res2 = Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->InstallEntry(*nsp, true, qt_raw_copy); - if (res2 != FileSys::InstallResult::Success) { - return InstallResult::Failure; - } - return InstallResult::Overwrite; - } else { - return InstallResult::AlreadyExists; - } + return InstallResult::Overwrite; } else { return InstallResult::Failure; } } -InstallResult GMainWindow::InstallNCA(const QString& filename, bool overwrite_files, - QProgressDialog& install_progress) { - const auto qt_raw_copy = [this, &install_progress](const FileSys::VirtualFile& src, - const FileSys::VirtualFile& dest, - std::size_t block_size) { +InstallResult GMainWindow::InstallNCA(const QString& filename) { + const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, + const FileSys::VirtualFile& dest, std::size_t block_size) { if (src == nullptr || dest == nullptr) { return false; } @@ -1775,11 +1771,13 @@ InstallResult GMainWindow::InstallNCA(const QString& filename, bool overwrite_fi std::array buffer{}; for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { - if (install_progress.wasCanceled()) { + if (install_progress->wasCanceled()) { dest->Resize(0); return false; } + emit UpdateInstallProgress(); + const auto read = src->Read(buffer.data(), buffer.size(), i); dest->Write(buffer.data(), read, i); } @@ -1830,30 +1828,18 @@ InstallResult GMainWindow::InstallNCA(const QString& filename, bool overwrite_fi res = Core::System::GetInstance() .GetFileSystemController() .GetUserNANDContents() - ->InstallEntry(*nca, static_cast(index), false, qt_raw_copy); + ->InstallEntry(*nca, static_cast(index), true, qt_raw_copy); } else { res = Core::System::GetInstance() .GetFileSystemController() .GetSystemNANDContents() - ->InstallEntry(*nca, static_cast(index), false, qt_raw_copy); + ->InstallEntry(*nca, static_cast(index), true, qt_raw_copy); } if (res == FileSys::InstallResult::Success) { return InstallResult::Success; } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { - if (overwrite_files) { - const auto res2 = - Core::System::GetInstance() - .GetFileSystemController() - .GetUserNANDContents() - ->InstallEntry(*nca, static_cast(index), true, qt_raw_copy); - if (res2 != FileSys::InstallResult::Success) { - return InstallResult::Failure; - } - return InstallResult::Overwrite; - } else { - return InstallResult::AlreadyExists; - } + return InstallResult::Overwrite; } else { return InstallResult::Failure; } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index deea8170d..adff65fb5 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -51,7 +51,6 @@ enum class EmulatedDirectoryTarget { enum class InstallResult { Success, Overwrite, - AlreadyExists, Failure, }; @@ -110,6 +109,8 @@ signals: // Signal that tells widgets to update icons to use the current theme void UpdateThemedIcons(); + void UpdateInstallProgress(); + void ErrorDisplayFinished(); void ProfileSelectorFinishedSelection(std::optional uuid); @@ -206,6 +207,7 @@ private slots: void OnGameListOpenPerGameProperties(const std::string& file); void OnMenuLoadFile(); void OnMenuLoadFolder(); + void IncrementInstallProgress(); void OnMenuInstallToNAND(); void OnMenuRecentFile(); void OnConfigure(); @@ -226,10 +228,8 @@ private slots: private: std::optional SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); - InstallResult InstallNSPXCI(const QString& filename, bool overwrite_files, - QProgressDialog& install_progress); - InstallResult InstallNCA(const QString& filename, bool overwrite_files, - QProgressDialog& install_progress); + InstallResult InstallNSPXCI(const QString& filename); + InstallResult InstallNCA(const QString& filename); void UpdateWindowTitle(const std::string& title_name = {}, const std::string& title_version = {}); void UpdateStatusBar(); @@ -284,6 +284,9 @@ private: HotkeyRegistry hotkey_registry; + // Install progress dialog + QProgressDialog* install_progress; + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; From 75a01475d174a8dbea7dcf41002cb53abce1d8bb Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Wed, 8 Jul 2020 13:01:25 -0400 Subject: [PATCH 4/4] Add additional empty check for the QStringList returned by the InstallDialog --- src/yuzu/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 8a57e34c7..432379705 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1621,6 +1621,10 @@ void GMainWindow::OnMenuInstallToNAND() { const QStringList files = installDialog.GetFiles(); + if (files.isEmpty()) { + return; + } + int remaining = filenames.size(); // This would only overflow above 2^43 bytes (8.796 TB)