diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/yuzu/CMakeLists.txt | 6 | ||||
| -rw-r--r-- | src/yuzu/install_dialog.cpp | 72 | ||||
| -rw-r--r-- | src/yuzu/install_dialog.h | 36 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 358 | ||||
| -rw-r--r-- | src/yuzu/main.h | 15 | ||||
| -rw-r--r-- | src/yuzu/main.ui | 2 |
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 | |||
| 15 | InstallDialog::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 | |||
| 55 | InstallDialog::~InstallDialog() = default; | ||
| 56 | |||
| 57 | QStringList 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 | |||
| 70 | int 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 | |||
| 9 | class QCheckBox; | ||
| 10 | class QDialogButtonBox; | ||
| 11 | class QHBoxLayout; | ||
| 12 | class QLabel; | ||
| 13 | class QListWidget; | ||
| 14 | class QVBoxLayout; | ||
| 15 | |||
| 16 | class InstallDialog : public QDialog { | ||
| 17 | Q_OBJECT | ||
| 18 | |||
| 19 | public: | ||
| 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 | |||
| 27 | private: | ||
| 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 | ||
| 1600 | void GMainWindow::IncrementInstallProgress() { | ||
| 1601 | install_progress->setValue(install_progress->value() + 1); | ||
| 1602 | } | ||
| 1603 | |||
| 1596 | void GMainWindow::OnMenuInstallToNAND() { | 1604 | void 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 | |||
| 1711 | InstallResult 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"), | 1765 | InstallResult 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; | |||
| 28 | class ProfilerWidget; | 28 | class ProfilerWidget; |
| 29 | class QLabel; | 29 | class QLabel; |
| 30 | class QPushButton; | 30 | class QPushButton; |
| 31 | class QProgressDialog; | ||
| 31 | class WaitTreeWidget; | 32 | class WaitTreeWidget; |
| 32 | enum class GameListOpenTarget; | 33 | enum class GameListOpenTarget; |
| 33 | class GameListPlaceholder; | 34 | class GameListPlaceholder; |
| @@ -47,6 +48,12 @@ enum class EmulatedDirectoryTarget { | |||
| 47 | SDMC, | 48 | SDMC, |
| 48 | }; | 49 | }; |
| 49 | 50 | ||
| 51 | enum class InstallResult { | ||
| 52 | Success, | ||
| 53 | Overwrite, | ||
| 54 | Failure, | ||
| 55 | }; | ||
| 56 | |||
| 50 | enum class ReinitializeKeyBehavior { | 57 | enum 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 | ||
| 219 | private: | 229 | private: |
| 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 | |||
| 275 | protected: | 290 | protected: |
| 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"> |