summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/yuzu/CMakeLists.txt6
-rw-r--r--src/yuzu/install_dialog.cpp72
-rw-r--r--src/yuzu/install_dialog.h35
-rw-r--r--src/yuzu/main.cpp295
-rw-r--r--src/yuzu/main.h4
-rw-r--r--src/yuzu/main.ui2
6 files changed, 290 insertions, 124 deletions
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 <QCheckBox>
+#include <QDialogButtonBox>
+#include <QFileInfo>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QListWidget>
+#include <QVBoxLayout>
+#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 <QDialog>
+
+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;
+ }
+
+ InstallDialog installDialog(this, files);
+ if (installDialog.exec() == QDialog::Rejected) {
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)
+ 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<u8, 0x1000> buffer{};
const int progress_maximum = static_cast<int>(src->GetSize() / buffer.size());
- QProgressDialog progress(
- tr("Installing file \"%1\"...").arg(QString::fromStdString(src->GetName())),
- tr("Cancel"), 0, progress_maximum, this);
- progress.setWindowModality(Qt::WindowModal);
+ 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<int>(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<FileSys::NSP> nsp;
- if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
- nsp = std::make_shared<FileSys::NSP>(
- vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
- if (nsp->IsExtractedType())
- failed();
- } else {
- const auto xci = std::make_shared<FileSys::XCI>(
- 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<FileSys::NSP> nsp;
+ if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
+ nsp = std::make_shared<FileSys::NSP>(
+ vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
+ if (nsp->IsExtractedType()) {
+ failed(filename);
+ break;
+ }
+ } else {
+ const auto xci = std::make_shared<FileSys::XCI>(
+ 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<FileSys::NCA>(
- 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;
- }
+ } else {
+ const auto nca = std::make_shared<FileSys::NCA>(
+ vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
+ const auto id = nca->GetStatus();
- 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;
- }
+ // Game updates necessary are missing base RomFS
+ if (id != Loader::ResultStatus::Success &&
+ id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
+ failed(filename);
+ break;
+ }
- // If index is equal to or past Game, add the jump in TitleType.
- if (index >= 5) {
- index += static_cast<size_t>(FileSys::TitleType::Application) -
- static_cast<size_t>(FileSys::TitleType::FirmwarePackageB);
- }
+ 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;
+ }
- FileSys::InstallResult res;
- if (index >= static_cast<s32>(FileSys::TitleType::Application)) {
- res = Core::System::GetInstance()
- .GetFileSystemController()
- .GetUserNANDContents()
- ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false,
- qt_raw_copy);
- } else {
- res = Core::System::GetInstance()
- .GetFileSystemController()
- .GetSystemNANDContents()
- ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false,
- qt_raw_copy);
- }
+ // If index is equal to or past Game, add the jump in TitleType.
+ if (index >= 5) {
+ index += static_cast<size_t>(FileSys::TitleType::Application) -
+ static_cast<size_t>(FileSys::TitleType::FirmwarePackageB);
+ }
- 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<FileSys::TitleType>(index),
- true, qt_raw_copy);
- if (res2 == FileSys::InstallResult::Success) {
- success();
+ FileSys::InstallResult res;
+ if (index >= static_cast<s32>(FileSys::TitleType::Application)) {
+ res = Core::System::GetInstance()
+ .GetFileSystemController()
+ .GetUserNANDContents()
+ ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false,
+ qt_raw_copy);
+ } else {
+ res = Core::System::GetInstance()
+ .GetFileSystemController()
+ .GetSystemNANDContents()
+ ->InstallEntry(*nca, static_cast<FileSys::TitleType>(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<FileSys::TitleType>(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 @@
<bool>true</bool>
</property>
<property name="text">
- <string>Install File to NAND...</string>
+ <string>Install Files to NAND...</string>
</property>
</action>
<action name="action_Load_File">