mirror of
https://git.suyu.dev/suyu/suyu.git
synced 2024-11-25 22:57:22 +00:00
Merge pull request #3385 from Morph1984/batch-install
frontend: Add support to batch install files to NAND
This commit is contained in:
commit
f1aabc21ee
6 changed files with 345 additions and 148 deletions
|
@ -98,11 +98,13 @@ add_executable(yuzu
|
||||||
game_list_p.h
|
game_list_p.h
|
||||||
game_list_worker.cpp
|
game_list_worker.cpp
|
||||||
game_list_worker.h
|
game_list_worker.h
|
||||||
|
hotkeys.cpp
|
||||||
|
hotkeys.h
|
||||||
|
install_dialog.cpp
|
||||||
|
install_dialog.h
|
||||||
loading_screen.cpp
|
loading_screen.cpp
|
||||||
loading_screen.h
|
loading_screen.h
|
||||||
loading_screen.ui
|
loading_screen.ui
|
||||||
hotkeys.cpp
|
|
||||||
hotkeys.h
|
|
||||||
main.cpp
|
main.cpp
|
||||||
main.h
|
main.h
|
||||||
main.ui
|
main.ui
|
||||||
|
|
72
src/yuzu/install_dialog.cpp
Normal file
72
src/yuzu/install_dialog.cpp
Normal file
|
@ -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) * 11) / 10);
|
||||||
|
|
||||||
|
vbox_layout = new QVBoxLayout;
|
||||||
|
|
||||||
|
hbox_layout = new QHBoxLayout;
|
||||||
|
|
||||||
|
description = new QLabel(tr("Please confirm these are the files you wish to install."));
|
||||||
|
|
||||||
|
update_description =
|
||||||
|
new QLabel(tr("Installing an Update or DLC will overwrite the previously installed one."));
|
||||||
|
|
||||||
|
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(buttons);
|
||||||
|
|
||||||
|
vbox_layout->addWidget(description);
|
||||||
|
vbox_layout->addWidget(update_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::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) {
|
||||||
|
files.append(item->data(Qt::UserRole).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
int InstallDialog::GetMinimumWidth() const {
|
||||||
|
return file_list->width();
|
||||||
|
}
|
36
src/yuzu/install_dialog.h
Normal file
36
src/yuzu/install_dialog.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// 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 GetFiles() const;
|
||||||
|
bool ShouldOverwriteFiles() const;
|
||||||
|
int GetMinimumWidth() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QListWidget* file_list;
|
||||||
|
|
||||||
|
QVBoxLayout* vbox_layout;
|
||||||
|
QHBoxLayout* hbox_layout;
|
||||||
|
|
||||||
|
QLabel* description;
|
||||||
|
QLabel* update_description;
|
||||||
|
QDialogButtonBox* buttons;
|
||||||
|
};
|
|
@ -107,6 +107,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||||
#include "yuzu/game_list.h"
|
#include "yuzu/game_list.h"
|
||||||
#include "yuzu/game_list_p.h"
|
#include "yuzu/game_list_p.h"
|
||||||
#include "yuzu/hotkeys.h"
|
#include "yuzu/hotkeys.h"
|
||||||
|
#include "yuzu/install_dialog.h"
|
||||||
#include "yuzu/loading_screen.h"
|
#include "yuzu/loading_screen.h"
|
||||||
#include "yuzu/main.h"
|
#include "yuzu/main.h"
|
||||||
#include "yuzu/uisettings.h"
|
#include "yuzu/uisettings.h"
|
||||||
|
@ -847,6 +848,9 @@ void GMainWindow::ConnectWidgetEvents() {
|
||||||
connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
|
connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
|
||||||
&GMainWindow::OnGameListOpenPerGameProperties);
|
&GMainWindow::OnGameListOpenPerGameProperties);
|
||||||
|
|
||||||
|
connect(this, &GMainWindow::UpdateInstallProgress, this,
|
||||||
|
&GMainWindow::IncrementInstallProgress);
|
||||||
|
|
||||||
connect(this, &GMainWindow::EmulationStarting, render_window,
|
connect(this, &GMainWindow::EmulationStarting, render_window,
|
||||||
&GRenderWindow::OnEmulationStarting);
|
&GRenderWindow::OnEmulationStarting);
|
||||||
connect(this, &GMainWindow::EmulationStopping, render_window,
|
connect(this, &GMainWindow::EmulationStopping, render_window,
|
||||||
|
@ -1593,187 +1597,255 @@ void GMainWindow::OnMenuLoadFolder() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::IncrementInstallProgress() {
|
||||||
|
install_progress->setValue(install_progress->value() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
void GMainWindow::OnMenuInstallToNAND() {
|
void GMainWindow::OnMenuInstallToNAND() {
|
||||||
const QString file_filter =
|
const QString file_filter =
|
||||||
tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive "
|
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)");
|
"Image (*.xci)");
|
||||||
QString filename = QFileDialog::getOpenFileName(this, tr("Install File"),
|
|
||||||
UISettings::values.roms_path, file_filter);
|
|
||||||
|
|
||||||
if (filename.isEmpty()) {
|
QStringList filenames = QFileDialog::getOpenFileNames(
|
||||||
|
this, tr("Install Files"), UISettings::values.roms_path, file_filter);
|
||||||
|
|
||||||
|
if (filenames.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InstallDialog installDialog(this, filenames);
|
||||||
|
if (installDialog.exec() == QDialog::Rejected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList files = installDialog.GetFiles();
|
||||||
|
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<int>(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 failed_files{}; // Files that failed to install due to errors
|
||||||
|
|
||||||
|
ui.action_Install_File_NAND->setEnabled(false);
|
||||||
|
|
||||||
|
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", "", remaining));
|
||||||
|
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] { return InstallNSPXCI(file); });
|
||||||
|
|
||||||
|
while (!future.isFinished()) {
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
result = future.result();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
result = InstallNCA(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case InstallResult::Success:
|
||||||
|
new_files.append(QFileInfo(file).fileName());
|
||||||
|
break;
|
||||||
|
case InstallResult::Overwrite:
|
||||||
|
overwritten_files.append(QFileInfo(file).fileName());
|
||||||
|
break;
|
||||||
|
case InstallResult::Failure:
|
||||||
|
failed_files.append(QFileInfo(file).fileName());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
--remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
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())) +
|
||||||
|
(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) {
|
||||||
const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
|
const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
|
||||||
const FileSys::VirtualFile& dest, std::size_t block_size) {
|
const FileSys::VirtualFile& dest, std::size_t block_size) {
|
||||||
if (src == nullptr || dest == nullptr)
|
if (src == nullptr || dest == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
if (!dest->Resize(src->GetSize()))
|
}
|
||||||
|
if (!dest->Resize(src->GetSize())) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::array<u8, 0x1000> buffer{};
|
std::array<u8, 0x1000> buffer{};
|
||||||
const int progress_maximum = static_cast<int>(src->GetSize() / buffer.size());
|
|
||||||
|
|
||||||
QProgressDialog progress(
|
|
||||||
tr("Installing file \"%1\"...").arg(QString::fromStdString(src->GetName())),
|
|
||||||
tr("Cancel"), 0, progress_maximum, this);
|
|
||||||
progress.setWindowModality(Qt::WindowModal);
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
|
for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
|
||||||
if (progress.wasCanceled()) {
|
if (install_progress->wasCanceled()) {
|
||||||
dest->Resize(0);
|
dest->Resize(0);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int progress_value = static_cast<int>(i / buffer.size());
|
emit UpdateInstallProgress();
|
||||||
progress.setValue(progress_value);
|
|
||||||
|
|
||||||
const auto read = src->Read(buffer.data(), buffer.size(), i);
|
const auto read = src->Read(buffer.data(), buffer.size(), i);
|
||||||
dest->Write(buffer.data(), read, i);
|
dest->Write(buffer.data(), read, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto success = [this]() {
|
std::shared_ptr<FileSys::NSP> nsp;
|
||||||
QMessageBox::information(this, tr("Successfully Installed"),
|
if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
|
||||||
tr("The file was successfully installed."));
|
nsp = std::make_shared<FileSys::NSP>(
|
||||||
game_list->PopulateAsync(UISettings::values.game_dirs);
|
vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
|
||||||
FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
|
if (nsp->IsExtractedType()) {
|
||||||
DIR_SEP + "game_list");
|
return InstallResult::Failure;
|
||||||
};
|
|
||||||
|
|
||||||
const auto failed = [this]() {
|
|
||||||
QMessageBox::warning(
|
|
||||||
this, tr("Failed to Install"),
|
|
||||||
tr("There was an error while attempting to install the provided file. It "
|
|
||||||
"could have an incorrect format or be missing metadata. Please "
|
|
||||||
"double-check your file and try again."));
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto overwrite = [this]() {
|
|
||||||
return QMessageBox::question(this, tr("Failed to Install"),
|
|
||||||
tr("The file you are attempting to install already exists "
|
|
||||||
"in the cache. Would you like to overwrite it?")) ==
|
|
||||||
QMessageBox::Yes;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (filename.endsWith(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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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()) {
|
|
||||||
const auto res2 = Core::System::GetInstance()
|
|
||||||
.GetFileSystemController()
|
|
||||||
.GetUserNANDContents()
|
|
||||||
->InstallEntry(*nsp, true, qt_raw_copy);
|
|
||||||
if (res2 == FileSys::InstallResult::Success) {
|
|
||||||
success();
|
|
||||||
} else {
|
|
||||||
failed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
failed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const auto nca = std::make_shared<FileSys::NCA>(
|
const auto xci = std::make_shared<FileSys::XCI>(
|
||||||
vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
|
vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
|
||||||
const auto id = nca->GetStatus();
|
nsp = xci->GetSecurePartitionNSP();
|
||||||
|
}
|
||||||
|
|
||||||
// Game updates necessary are missing base RomFS
|
if (nsp->GetStatus() != Loader::ResultStatus::Success) {
|
||||||
if (id != Loader::ResultStatus::Success &&
|
return InstallResult::Failure;
|
||||||
id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
|
}
|
||||||
failed();
|
const auto res =
|
||||||
return;
|
Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->InstallEntry(
|
||||||
|
*nsp, true, qt_raw_copy);
|
||||||
|
if (res == FileSys::InstallResult::Success) {
|
||||||
|
return InstallResult::Success;
|
||||||
|
} else if (res == FileSys::InstallResult::ErrorAlreadyExists) {
|
||||||
|
return InstallResult::Overwrite;
|
||||||
|
} else {
|
||||||
|
return InstallResult::Failure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (!dest->Resize(src->GetSize())) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QStringList tt_options{tr("System Application"),
|
std::array<u8, 0x1000> buffer{};
|
||||||
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);
|
for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
|
||||||
if (!ok || index == -1) {
|
if (install_progress->wasCanceled()) {
|
||||||
QMessageBox::warning(this, tr("Failed to Install"),
|
dest->Resize(0);
|
||||||
tr("The title type you selected for the NCA is invalid."));
|
return false;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
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();
|
|
||||||
} else {
|
|
||||||
failed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
failed();
|
emit UpdateInstallProgress();
|
||||||
|
|
||||||
|
const auto read = src->Read(buffer.data(), buffer.size(), i);
|
||||||
|
dest->Write(buffer.data(), read, i);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
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), true, qt_raw_copy);
|
||||||
|
} else {
|
||||||
|
res = Core::System::GetInstance()
|
||||||
|
.GetFileSystemController()
|
||||||
|
.GetSystemNANDContents()
|
||||||
|
->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res == FileSys::InstallResult::Success) {
|
||||||
|
return InstallResult::Success;
|
||||||
|
} else if (res == FileSys::InstallResult::ErrorAlreadyExists) {
|
||||||
|
return InstallResult::Overwrite;
|
||||||
|
} else {
|
||||||
|
return InstallResult::Failure;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ class MicroProfileDialog;
|
||||||
class ProfilerWidget;
|
class ProfilerWidget;
|
||||||
class QLabel;
|
class QLabel;
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
|
class QProgressDialog;
|
||||||
class WaitTreeWidget;
|
class WaitTreeWidget;
|
||||||
enum class GameListOpenTarget;
|
enum class GameListOpenTarget;
|
||||||
class GameListPlaceholder;
|
class GameListPlaceholder;
|
||||||
|
@ -47,6 +48,12 @@ enum class EmulatedDirectoryTarget {
|
||||||
SDMC,
|
SDMC,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class InstallResult {
|
||||||
|
Success,
|
||||||
|
Overwrite,
|
||||||
|
Failure,
|
||||||
|
};
|
||||||
|
|
||||||
enum class ReinitializeKeyBehavior {
|
enum class ReinitializeKeyBehavior {
|
||||||
NoWarning,
|
NoWarning,
|
||||||
Warning,
|
Warning,
|
||||||
|
@ -102,6 +109,8 @@ signals:
|
||||||
// Signal that tells widgets to update icons to use the current theme
|
// Signal that tells widgets to update icons to use the current theme
|
||||||
void UpdateThemedIcons();
|
void UpdateThemedIcons();
|
||||||
|
|
||||||
|
void UpdateInstallProgress();
|
||||||
|
|
||||||
void ErrorDisplayFinished();
|
void ErrorDisplayFinished();
|
||||||
|
|
||||||
void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid);
|
void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid);
|
||||||
|
@ -198,6 +207,7 @@ private slots:
|
||||||
void OnGameListOpenPerGameProperties(const std::string& file);
|
void OnGameListOpenPerGameProperties(const std::string& file);
|
||||||
void OnMenuLoadFile();
|
void OnMenuLoadFile();
|
||||||
void OnMenuLoadFolder();
|
void OnMenuLoadFolder();
|
||||||
|
void IncrementInstallProgress();
|
||||||
void OnMenuInstallToNAND();
|
void OnMenuInstallToNAND();
|
||||||
void OnMenuRecentFile();
|
void OnMenuRecentFile();
|
||||||
void OnConfigure();
|
void OnConfigure();
|
||||||
|
@ -218,6 +228,8 @@ private slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
|
std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
|
||||||
|
InstallResult InstallNSPXCI(const QString& filename);
|
||||||
|
InstallResult InstallNCA(const QString& filename);
|
||||||
void UpdateWindowTitle(const std::string& title_name = {},
|
void UpdateWindowTitle(const std::string& title_name = {},
|
||||||
const std::string& title_version = {});
|
const std::string& title_version = {});
|
||||||
void UpdateStatusBar();
|
void UpdateStatusBar();
|
||||||
|
@ -272,6 +284,9 @@ private:
|
||||||
|
|
||||||
HotkeyRegistry hotkey_registry;
|
HotkeyRegistry hotkey_registry;
|
||||||
|
|
||||||
|
// Install progress dialog
|
||||||
|
QProgressDialog* install_progress;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void dropEvent(QDropEvent* event) override;
|
void dropEvent(QDropEvent* event) override;
|
||||||
void dragEnterEvent(QDragEnterEvent* event) override;
|
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||||
|
|
|
@ -130,7 +130,7 @@
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Install File to NAND...</string>
|
<string>Install Files to NAND...</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="action_Load_File">
|
<action name="action_Load_File">
|
||||||
|
|
Loading…
Reference in a new issue