summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/yuzu/CMakeLists.txt6
-rw-r--r--src/yuzu/install_dialog.cpp72
-rw-r--r--src/yuzu/install_dialog.h36
-rw-r--r--src/yuzu/main.cpp358
-rw-r--r--src/yuzu/main.h15
-rw-r--r--src/yuzu/main.ui2
6 files changed, 343 insertions, 146 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 6b25a7fa0..ff7d9c1fa 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -98,11 +98,13 @@ add_executable(yuzu
98 game_list_p.h 98 game_list_p.h
99 game_list_worker.cpp 99 game_list_worker.cpp
100 game_list_worker.h 100 game_list_worker.h
101 hotkeys.cpp
102 hotkeys.h
103 install_dialog.cpp
104 install_dialog.h
101 loading_screen.cpp 105 loading_screen.cpp
102 loading_screen.h 106 loading_screen.h
103 loading_screen.ui 107 loading_screen.ui
104 hotkeys.cpp
105 hotkeys.h
106 main.cpp 108 main.cpp
107 main.h 109 main.h
108 main.ui 110 main.ui
diff --git a/src/yuzu/install_dialog.cpp b/src/yuzu/install_dialog.cpp
new file mode 100644
index 000000000..06b0b1874
--- /dev/null
+++ b/src/yuzu/install_dialog.cpp
@@ -0,0 +1,72 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QCheckBox>
6#include <QDialogButtonBox>
7#include <QFileInfo>
8#include <QHBoxLayout>
9#include <QLabel>
10#include <QListWidget>
11#include <QVBoxLayout>
12#include "yuzu/install_dialog.h"
13#include "yuzu/uisettings.h"
14
15InstallDialog::InstallDialog(QWidget* parent, const QStringList& files) : QDialog(parent) {
16 file_list = new QListWidget(this);
17
18 for (const QString& file : files) {
19 QListWidgetItem* item = new QListWidgetItem(QFileInfo(file).fileName(), file_list);
20 item->setData(Qt::UserRole, file);
21 item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
22 item->setCheckState(Qt::Checked);
23 }
24
25 file_list->setMinimumWidth((file_list->sizeHintForColumn(0) * 11) / 10);
26
27 vbox_layout = new QVBoxLayout;
28
29 hbox_layout = new QHBoxLayout;
30
31 description = new QLabel(tr("Please confirm these are the files you wish to install."));
32
33 update_description =
34 new QLabel(tr("Installing an Update or DLC will overwrite the previously installed one."));
35
36 buttons = new QDialogButtonBox;
37 buttons->addButton(QDialogButtonBox::Cancel);
38 buttons->addButton(tr("Install"), QDialogButtonBox::AcceptRole);
39
40 connect(buttons, &QDialogButtonBox::accepted, this, &InstallDialog::accept);
41 connect(buttons, &QDialogButtonBox::rejected, this, &InstallDialog::reject);
42
43 hbox_layout->addWidget(buttons);
44
45 vbox_layout->addWidget(description);
46 vbox_layout->addWidget(update_description);
47 vbox_layout->addWidget(file_list);
48 vbox_layout->addLayout(hbox_layout);
49
50 setLayout(vbox_layout);
51 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
52 setWindowTitle(tr("Install Files to NAND"));
53}
54
55InstallDialog::~InstallDialog() = default;
56
57QStringList InstallDialog::GetFiles() const {
58 QStringList files;
59
60 for (int i = 0; i < file_list->count(); ++i) {
61 const QListWidgetItem* item = file_list->item(i);
62 if (item->checkState() == Qt::Checked) {
63 files.append(item->data(Qt::UserRole).toString());
64 }
65 }
66
67 return files;
68}
69
70int InstallDialog::GetMinimumWidth() const {
71 return file_list->width();
72}
diff --git a/src/yuzu/install_dialog.h b/src/yuzu/install_dialog.h
new file mode 100644
index 000000000..e4aba1b06
--- /dev/null
+++ b/src/yuzu/install_dialog.h
@@ -0,0 +1,36 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <QDialog>
8
9class QCheckBox;
10class QDialogButtonBox;
11class QHBoxLayout;
12class QLabel;
13class QListWidget;
14class QVBoxLayout;
15
16class InstallDialog : public QDialog {
17 Q_OBJECT
18
19public:
20 explicit InstallDialog(QWidget* parent, const QStringList& files);
21 ~InstallDialog() override;
22
23 QStringList GetFiles() const;
24 bool ShouldOverwriteFiles() const;
25 int GetMinimumWidth() const;
26
27private:
28 QListWidget* file_list;
29
30 QVBoxLayout* vbox_layout;
31 QHBoxLayout* hbox_layout;
32
33 QLabel* description;
34 QLabel* update_description;
35 QDialogButtonBox* buttons;
36};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 4d501a8f9..432379705 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -107,6 +107,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
107#include "yuzu/game_list.h" 107#include "yuzu/game_list.h"
108#include "yuzu/game_list_p.h" 108#include "yuzu/game_list_p.h"
109#include "yuzu/hotkeys.h" 109#include "yuzu/hotkeys.h"
110#include "yuzu/install_dialog.h"
110#include "yuzu/loading_screen.h" 111#include "yuzu/loading_screen.h"
111#include "yuzu/main.h" 112#include "yuzu/main.h"
112#include "yuzu/uisettings.h" 113#include "yuzu/uisettings.h"
@@ -847,6 +848,9 @@ void GMainWindow::ConnectWidgetEvents() {
847 connect(game_list, &GameList::OpenPerGameGeneralRequested, this, 848 connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
848 &GMainWindow::OnGameListOpenPerGameProperties); 849 &GMainWindow::OnGameListOpenPerGameProperties);
849 850
851 connect(this, &GMainWindow::UpdateInstallProgress, this,
852 &GMainWindow::IncrementInstallProgress);
853
850 connect(this, &GMainWindow::EmulationStarting, render_window, 854 connect(this, &GMainWindow::EmulationStarting, render_window,
851 &GRenderWindow::OnEmulationStarting); 855 &GRenderWindow::OnEmulationStarting);
852 connect(this, &GMainWindow::EmulationStopping, render_window, 856 connect(this, &GMainWindow::EmulationStopping, render_window,
@@ -1593,187 +1597,255 @@ void GMainWindow::OnMenuLoadFolder() {
1593 } 1597 }
1594} 1598}
1595 1599
1600void GMainWindow::IncrementInstallProgress() {
1601 install_progress->setValue(install_progress->value() + 1);
1602}
1603
1596void GMainWindow::OnMenuInstallToNAND() { 1604void GMainWindow::OnMenuInstallToNAND() {
1597 const QString file_filter = 1605 const QString file_filter =
1598 tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive " 1606 tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive "
1599 "(*.nca);;Nintendo Submissions Package (*.nsp);;NX Cartridge " 1607 "(*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge "
1600 "Image (*.xci)"); 1608 "Image (*.xci)");
1601 QString filename = QFileDialog::getOpenFileName(this, tr("Install File"),
1602 UISettings::values.roms_path, file_filter);
1603 1609
1604 if (filename.isEmpty()) { 1610 QStringList filenames = QFileDialog::getOpenFileNames(
1611 this, tr("Install Files"), UISettings::values.roms_path, file_filter);
1612
1613 if (filenames.isEmpty()) {
1605 return; 1614 return;
1606 } 1615 }
1607 1616
1617 InstallDialog installDialog(this, filenames);
1618 if (installDialog.exec() == QDialog::Rejected) {
1619 return;
1620 }
1621
1622 const QStringList files = installDialog.GetFiles();
1623
1624 if (files.isEmpty()) {
1625 return;
1626 }
1627
1628 int remaining = filenames.size();
1629
1630 // This would only overflow above 2^43 bytes (8.796 TB)
1631 int total_size = 0;
1632 for (const QString& file : files) {
1633 total_size += static_cast<int>(QFile(file).size() / 0x1000);
1634 }
1635 if (total_size < 0) {
1636 LOG_CRITICAL(Frontend, "Attempting to install too many files, aborting.");
1637 return;
1638 }
1639
1640 QStringList new_files{}; // Newly installed files that do not yet exist in the NAND
1641 QStringList overwritten_files{}; // Files that overwrote those existing in the NAND
1642 QStringList failed_files{}; // Files that failed to install due to errors
1643
1644 ui.action_Install_File_NAND->setEnabled(false);
1645
1646 install_progress = new QProgressDialog(QStringLiteral(""), tr("Cancel"), 0, total_size, this);
1647 install_progress->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint &
1648 ~Qt::WindowMaximizeButtonHint);
1649 install_progress->setAttribute(Qt::WA_DeleteOnClose, true);
1650 install_progress->setFixedWidth(installDialog.GetMinimumWidth() + 40);
1651 install_progress->show();
1652
1653 for (const QString& file : files) {
1654 install_progress->setWindowTitle(tr("%n file(s) remaining", "", remaining));
1655 install_progress->setLabelText(
1656 tr("Installing file \"%1\"...").arg(QFileInfo(file).fileName()));
1657
1658 QFuture<InstallResult> future;
1659 InstallResult result;
1660
1661 if (file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) ||
1662 file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
1663
1664 future = QtConcurrent::run([this, &file] { return InstallNSPXCI(file); });
1665
1666 while (!future.isFinished()) {
1667 QCoreApplication::processEvents();
1668 }
1669
1670 result = future.result();
1671
1672 } else {
1673 result = InstallNCA(file);
1674 }
1675
1676 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1677
1678 switch (result) {
1679 case InstallResult::Success:
1680 new_files.append(QFileInfo(file).fileName());
1681 break;
1682 case InstallResult::Overwrite:
1683 overwritten_files.append(QFileInfo(file).fileName());
1684 break;
1685 case InstallResult::Failure:
1686 failed_files.append(QFileInfo(file).fileName());
1687 break;
1688 }
1689
1690 --remaining;
1691 }
1692
1693 install_progress->close();
1694
1695 const QString install_results =
1696 (new_files.isEmpty() ? QStringLiteral("")
1697 : tr("%n file(s) were newly installed\n", "", new_files.size())) +
1698 (overwritten_files.isEmpty()
1699 ? QStringLiteral("")
1700 : tr("%n file(s) were overwritten\n", "", overwritten_files.size())) +
1701 (failed_files.isEmpty() ? QStringLiteral("")
1702 : tr("%n file(s) failed to install\n", "", failed_files.size()));
1703
1704 QMessageBox::information(this, tr("Install Results"), install_results);
1705 game_list->PopulateAsync(UISettings::values.game_dirs);
1706 FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
1707 "game_list");
1708 ui.action_Install_File_NAND->setEnabled(true);
1709}
1710
1711InstallResult GMainWindow::InstallNSPXCI(const QString& filename) {
1608 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, 1712 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
1609 const FileSys::VirtualFile& dest, std::size_t block_size) { 1713 const FileSys::VirtualFile& dest, std::size_t block_size) {
1610 if (src == nullptr || dest == nullptr) 1714 if (src == nullptr || dest == nullptr) {
1611 return false; 1715 return false;
1612 if (!dest->Resize(src->GetSize())) 1716 }
1717 if (!dest->Resize(src->GetSize())) {
1613 return false; 1718 return false;
1719 }
1614 1720
1615 std::array<u8, 0x1000> buffer{}; 1721 std::array<u8, 0x1000> buffer{};
1616 const int progress_maximum = static_cast<int>(src->GetSize() / buffer.size());
1617
1618 QProgressDialog progress(
1619 tr("Installing file \"%1\"...").arg(QString::fromStdString(src->GetName())),
1620 tr("Cancel"), 0, progress_maximum, this);
1621 progress.setWindowModality(Qt::WindowModal);
1622 1722
1623 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { 1723 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
1624 if (progress.wasCanceled()) { 1724 if (install_progress->wasCanceled()) {
1625 dest->Resize(0); 1725 dest->Resize(0);
1626 return false; 1726 return false;
1627 } 1727 }
1628 1728
1629 const int progress_value = static_cast<int>(i / buffer.size()); 1729 emit UpdateInstallProgress();
1630 progress.setValue(progress_value);
1631 1730
1632 const auto read = src->Read(buffer.data(), buffer.size(), i); 1731 const auto read = src->Read(buffer.data(), buffer.size(), i);
1633 dest->Write(buffer.data(), read, i); 1732 dest->Write(buffer.data(), read, i);
1634 } 1733 }
1635
1636 return true; 1734 return true;
1637 }; 1735 };
1638 1736
1639 const auto success = [this]() { 1737 std::shared_ptr<FileSys::NSP> nsp;
1640 QMessageBox::information(this, tr("Successfully Installed"), 1738 if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
1641 tr("The file was successfully installed.")); 1739 nsp = std::make_shared<FileSys::NSP>(
1642 game_list->PopulateAsync(UISettings::values.game_dirs); 1740 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
1643 FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + 1741 if (nsp->IsExtractedType()) {
1644 DIR_SEP + "game_list"); 1742 return InstallResult::Failure;
1645 };
1646
1647 const auto failed = [this]() {
1648 QMessageBox::warning(
1649 this, tr("Failed to Install"),
1650 tr("There was an error while attempting to install the provided file. It "
1651 "could have an incorrect format or be missing metadata. Please "
1652 "double-check your file and try again."));
1653 };
1654
1655 const auto overwrite = [this]() {
1656 return QMessageBox::question(this, tr("Failed to Install"),
1657 tr("The file you are attempting to install already exists "
1658 "in the cache. Would you like to overwrite it?")) ==
1659 QMessageBox::Yes;
1660 };
1661
1662 if (filename.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) ||
1663 filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
1664 std::shared_ptr<FileSys::NSP> nsp;
1665 if (filename.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) {
1666 nsp = std::make_shared<FileSys::NSP>(
1667 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
1668 if (nsp->IsExtractedType())
1669 failed();
1670 } else {
1671 const auto xci = std::make_shared<FileSys::XCI>(
1672 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
1673 nsp = xci->GetSecurePartitionNSP();
1674 }
1675
1676 if (nsp->GetStatus() != Loader::ResultStatus::Success) {
1677 failed();
1678 return;
1679 }
1680 const auto res = Core::System::GetInstance()
1681 .GetFileSystemController()
1682 .GetUserNANDContents()
1683 ->InstallEntry(*nsp, false, qt_raw_copy);
1684 if (res == FileSys::InstallResult::Success) {
1685 success();
1686 } else {
1687 if (res == FileSys::InstallResult::ErrorAlreadyExists) {
1688 if (overwrite()) {
1689 const auto res2 = Core::System::GetInstance()
1690 .GetFileSystemController()
1691 .GetUserNANDContents()
1692 ->InstallEntry(*nsp, true, qt_raw_copy);
1693 if (res2 == FileSys::InstallResult::Success) {
1694 success();
1695 } else {
1696 failed();
1697 }
1698 }
1699 } else {
1700 failed();
1701 }
1702 } 1743 }
1703 } else { 1744 } else {
1704 const auto nca = std::make_shared<FileSys::NCA>( 1745 const auto xci = std::make_shared<FileSys::XCI>(
1705 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); 1746 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
1706 const auto id = nca->GetStatus(); 1747 nsp = xci->GetSecurePartitionNSP();
1748 }
1707 1749
1708 // Game updates necessary are missing base RomFS 1750 if (nsp->GetStatus() != Loader::ResultStatus::Success) {
1709 if (id != Loader::ResultStatus::Success && 1751 return InstallResult::Failure;
1710 id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { 1752 }
1711 failed(); 1753 const auto res =
1712 return; 1754 Core::System::GetInstance().GetFileSystemController().GetUserNANDContents()->InstallEntry(
1713 } 1755 *nsp, true, qt_raw_copy);
1756 if (res == FileSys::InstallResult::Success) {
1757 return InstallResult::Success;
1758 } else if (res == FileSys::InstallResult::ErrorAlreadyExists) {
1759 return InstallResult::Overwrite;
1760 } else {
1761 return InstallResult::Failure;
1762 }
1763}
1714 1764
1715 const QStringList tt_options{tr("System Application"), 1765InstallResult GMainWindow::InstallNCA(const QString& filename) {
1716 tr("System Archive"), 1766 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
1717 tr("System Application Update"), 1767 const FileSys::VirtualFile& dest, std::size_t block_size) {
1718 tr("Firmware Package (Type A)"), 1768 if (src == nullptr || dest == nullptr) {
1719 tr("Firmware Package (Type B)"), 1769 return false;
1720 tr("Game"),
1721 tr("Game Update"),
1722 tr("Game DLC"),
1723 tr("Delta Title")};
1724 bool ok;
1725 const auto item = QInputDialog::getItem(
1726 this, tr("Select NCA Install Type..."),
1727 tr("Please select the type of title you would like to install this NCA as:\n(In "
1728 "most instances, the default 'Game' is fine.)"),
1729 tt_options, 5, false, &ok);
1730
1731 auto index = tt_options.indexOf(item);
1732 if (!ok || index == -1) {
1733 QMessageBox::warning(this, tr("Failed to Install"),
1734 tr("The title type you selected for the NCA is invalid."));
1735 return;
1736 } 1770 }
1737 1771 if (!dest->Resize(src->GetSize())) {
1738 // If index is equal to or past Game, add the jump in TitleType. 1772 return false;
1739 if (index >= 5) {
1740 index += static_cast<size_t>(FileSys::TitleType::Application) -
1741 static_cast<size_t>(FileSys::TitleType::FirmwarePackageB);
1742 } 1773 }
1743 1774
1744 FileSys::InstallResult res; 1775 std::array<u8, 0x1000> buffer{};
1745 if (index >= static_cast<s32>(FileSys::TitleType::Application)) {
1746 res = Core::System::GetInstance()
1747 .GetFileSystemController()
1748 .GetUserNANDContents()
1749 ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false,
1750 qt_raw_copy);
1751 } else {
1752 res = Core::System::GetInstance()
1753 .GetFileSystemController()
1754 .GetSystemNANDContents()
1755 ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), false,
1756 qt_raw_copy);
1757 }
1758 1776
1759 if (res == FileSys::InstallResult::Success) { 1777 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
1760 success(); 1778 if (install_progress->wasCanceled()) {
1761 } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { 1779 dest->Resize(0);
1762 if (overwrite()) { 1780 return false;
1763 const auto res2 = Core::System::GetInstance()
1764 .GetFileSystemController()
1765 .GetUserNANDContents()
1766 ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index),
1767 true, qt_raw_copy);
1768 if (res2 == FileSys::InstallResult::Success) {
1769 success();
1770 } else {
1771 failed();
1772 }
1773 } 1781 }
1774 } else { 1782
1775 failed(); 1783 emit UpdateInstallProgress();
1784
1785 const auto read = src->Read(buffer.data(), buffer.size(), i);
1786 dest->Write(buffer.data(), read, i);
1776 } 1787 }
1788 return true;
1789 };
1790
1791 const auto nca =
1792 std::make_shared<FileSys::NCA>(vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
1793 const auto id = nca->GetStatus();
1794
1795 // Game updates necessary are missing base RomFS
1796 if (id != Loader::ResultStatus::Success &&
1797 id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
1798 return InstallResult::Failure;
1799 }
1800
1801 const QStringList tt_options{tr("System Application"),
1802 tr("System Archive"),
1803 tr("System Application Update"),
1804 tr("Firmware Package (Type A)"),
1805 tr("Firmware Package (Type B)"),
1806 tr("Game"),
1807 tr("Game Update"),
1808 tr("Game DLC"),
1809 tr("Delta Title")};
1810 bool ok;
1811 const auto item = QInputDialog::getItem(
1812 this, tr("Select NCA Install Type..."),
1813 tr("Please select the type of title you would like to install this NCA as:\n(In "
1814 "most instances, the default 'Game' is fine.)"),
1815 tt_options, 5, false, &ok);
1816
1817 auto index = tt_options.indexOf(item);
1818 if (!ok || index == -1) {
1819 QMessageBox::warning(this, tr("Failed to Install"),
1820 tr("The title type you selected for the NCA is invalid."));
1821 return InstallResult::Failure;
1822 }
1823
1824 // If index is equal to or past Game, add the jump in TitleType.
1825 if (index >= 5) {
1826 index += static_cast<size_t>(FileSys::TitleType::Application) -
1827 static_cast<size_t>(FileSys::TitleType::FirmwarePackageB);
1828 }
1829
1830 FileSys::InstallResult res;
1831 if (index >= static_cast<s32>(FileSys::TitleType::Application)) {
1832 res = Core::System::GetInstance()
1833 .GetFileSystemController()
1834 .GetUserNANDContents()
1835 ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy);
1836 } else {
1837 res = Core::System::GetInstance()
1838 .GetFileSystemController()
1839 .GetSystemNANDContents()
1840 ->InstallEntry(*nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy);
1841 }
1842
1843 if (res == FileSys::InstallResult::Success) {
1844 return InstallResult::Success;
1845 } else if (res == FileSys::InstallResult::ErrorAlreadyExists) {
1846 return InstallResult::Overwrite;
1847 } else {
1848 return InstallResult::Failure;
1777 } 1849 }
1778} 1850}
1779 1851
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 8e3d39c38..adff65fb5 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -28,6 +28,7 @@ class MicroProfileDialog;
28class ProfilerWidget; 28class ProfilerWidget;
29class QLabel; 29class QLabel;
30class QPushButton; 30class QPushButton;
31class QProgressDialog;
31class WaitTreeWidget; 32class WaitTreeWidget;
32enum class GameListOpenTarget; 33enum class GameListOpenTarget;
33class GameListPlaceholder; 34class GameListPlaceholder;
@@ -47,6 +48,12 @@ enum class EmulatedDirectoryTarget {
47 SDMC, 48 SDMC,
48}; 49};
49 50
51enum class InstallResult {
52 Success,
53 Overwrite,
54 Failure,
55};
56
50enum class ReinitializeKeyBehavior { 57enum class ReinitializeKeyBehavior {
51 NoWarning, 58 NoWarning,
52 Warning, 59 Warning,
@@ -102,6 +109,8 @@ signals:
102 // Signal that tells widgets to update icons to use the current theme 109 // Signal that tells widgets to update icons to use the current theme
103 void UpdateThemedIcons(); 110 void UpdateThemedIcons();
104 111
112 void UpdateInstallProgress();
113
105 void ErrorDisplayFinished(); 114 void ErrorDisplayFinished();
106 115
107 void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid); 116 void ProfileSelectorFinishedSelection(std::optional<Common::UUID> uuid);
@@ -198,6 +207,7 @@ private slots:
198 void OnGameListOpenPerGameProperties(const std::string& file); 207 void OnGameListOpenPerGameProperties(const std::string& file);
199 void OnMenuLoadFile(); 208 void OnMenuLoadFile();
200 void OnMenuLoadFolder(); 209 void OnMenuLoadFolder();
210 void IncrementInstallProgress();
201 void OnMenuInstallToNAND(); 211 void OnMenuInstallToNAND();
202 void OnMenuRecentFile(); 212 void OnMenuRecentFile();
203 void OnConfigure(); 213 void OnConfigure();
@@ -218,6 +228,8 @@ private slots:
218 228
219private: 229private:
220 std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id); 230 std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
231 InstallResult InstallNSPXCI(const QString& filename);
232 InstallResult InstallNCA(const QString& filename);
221 void UpdateWindowTitle(const std::string& title_name = {}, 233 void UpdateWindowTitle(const std::string& title_name = {},
222 const std::string& title_version = {}); 234 const std::string& title_version = {});
223 void UpdateStatusBar(); 235 void UpdateStatusBar();
@@ -272,6 +284,9 @@ private:
272 284
273 HotkeyRegistry hotkey_registry; 285 HotkeyRegistry hotkey_registry;
274 286
287 // Install progress dialog
288 QProgressDialog* install_progress;
289
275protected: 290protected:
276 void dropEvent(QDropEvent* event) override; 291 void dropEvent(QDropEvent* event) override;
277 void dragEnterEvent(QDragEnterEvent* event) override; 292 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 @@
130 <bool>true</bool> 130 <bool>true</bool>
131 </property> 131 </property>
132 <property name="text"> 132 <property name="text">
133 <string>Install File to NAND...</string> 133 <string>Install Files to NAND...</string>
134 </property> 134 </property>
135 </action> 135 </action>
136 <action name="action_Load_File"> 136 <action name="action_Load_File">