summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorph <39850852+Morph1984@users.noreply.github.com>2020-07-01 22:15:57 +0200
committerMorph <39850852+Morph1984@users.noreply.github.com>2020-07-10 06:38:28 +0200
commit7f4d96d87332824643d7a8d3ff0fab7ea771b798 (patch)
tree2d1febc79d0411df29625e2d0d3fc0d0b2954a17
parentAdd support for batch install to NAND (diff)
downloadyuzu-7f4d96d87332824643d7a8d3ff0fab7ea771b798.tar
yuzu-7f4d96d87332824643d7a8d3ff0fab7ea771b798.tar.gz
yuzu-7f4d96d87332824643d7a8d3ff0fab7ea771b798.tar.bz2
yuzu-7f4d96d87332824643d7a8d3ff0fab7ea771b798.tar.lz
yuzu-7f4d96d87332824643d7a8d3ff0fab7ea771b798.tar.xz
yuzu-7f4d96d87332824643d7a8d3ff0fab7ea771b798.tar.zst
yuzu-7f4d96d87332824643d7a8d3ff0fab7ea771b798.zip
-rw-r--r--src/yuzu/install_dialog.cpp16
-rw-r--r--src/yuzu/install_dialog.h5
-rw-r--r--src/yuzu/main.cpp399
-rw-r--r--src/yuzu/main.h14
4 files changed, 238 insertions, 196 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();
+
+ 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<InstallResult> 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);
+ }
- 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) {
+ 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<u8, 0x1000> buffer{};
- const int progress_maximum = static_cast<int>(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<int>(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;
};
- 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 failed = [this, &is_progressdialog_created](const QString& file) {
- if (is_progressdialog_created) {
- install_progress->close();
+ 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()) {
+ return InstallResult::Failure;
}
- 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);
- };
-
- 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;
- };
-
- 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(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;
+ } 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) {
+ 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 {
- 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(filename);
- break;
- }
+ return InstallResult::AlreadyExists;
+ }
+ } else {
+ return InstallResult::Failure;
+ }
+}
- 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;
- }
+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;
+ }
- // 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);
- }
+ std::array<u8, 0x1000> buffer{};
- 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);
+ for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
+ if (install_progress.wasCanceled()) {
+ dest->Resize(0);
+ return false;
}
- 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 {
- --total_count;
- }
- } else {
- failed(filename);
- break;
- }
+ const auto read = src->Read(buffer.data(), buffer.size(), i);
+ dest->Write(buffer.data(), read, i);
}
+ return true;
+ };
- // Return success only on the last file
- if (filename == filenames.last()) {
- success();
+ 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) {
+ return InstallResult::Failure;
+ }
+
+ 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 InstallResult::Failure;
+ }
+
+ // 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);
+ }
+
+ 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) {
+ return InstallResult::Success;
+ } else if (res == FileSys::InstallResult::ErrorAlreadyExists) {
+ if (overwrite_files) {
+ const auto res2 =
+ Core::System::GetInstance()
+ .GetFileSystemController()
+ .GetUserNANDContents()
+ ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy);
+ if (res2 != FileSys::InstallResult::Success) {
+ return InstallResult::Failure;
+ }
+ return InstallResult::Overwrite;
+ } else {
+ 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<u64> 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;