From 501e3ae05abbb6ae1cb8811113e1c889d33e5858 Mon Sep 17 00:00:00 2001 From: Andrew Pilley Date: Sat, 17 Feb 2024 23:33:55 +1100 Subject: [PATCH 1/5] Implement In-app firmware installation. --- src/yuzu/main.cpp | 113 ++++++++++++++++++++++++++++++++++++++++++++++ src/yuzu/main.h | 1 + src/yuzu/main.ui | 18 +++++++- 3 files changed, 130 insertions(+), 2 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index dfa50006ab..58e23fc693 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1603,6 +1603,7 @@ void GMainWindow::ConnectMenuEvents() { // Help connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder); connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents); + connect_menu(ui->action_Install_Firmware, &GMainWindow::OnInstallFirmware); connect_menu(ui->action_About, &GMainWindow::OnAbout); } @@ -1631,6 +1632,8 @@ void GMainWindow::UpdateMenuState() { action->setEnabled(emulation_running); } + ui->action_Install_Firmware->setEnabled(!emulation_running); + for (QAction* action : applet_actions) { action->setEnabled(is_firmware_available && !emulation_running); } @@ -4150,6 +4153,116 @@ void GMainWindow::OnVerifyInstalledContents() { } } +void GMainWindow::OnInstallFirmware() { + // Don't do this while emulation is running, that'd probably be a bad idea. + if (emu_thread != nullptr && emu_thread->IsRunning()) { + return; + } + + // Check for installed keys, error out, suggest restart? + if (!ContentManager::AreKeysPresent()) { + QMessageBox::information(this, tr("Keys not installed"), + tr("Install decryption keys and restart yuzu before attempting to install firmware.")); + return; + } + + QString firmware_source_location = QFileDialog::getExistingDirectory(this, + tr("Select Dumped Firmware Source Location"), QString::fromStdString(""), QFileDialog::ShowDirsOnly); + if (firmware_source_location.isEmpty()) { + return; + } + + QProgressDialog progress(tr("Installing Firmware..."), tr("Cancel"), 0, 100, this); + progress.setWindowModality(Qt::WindowModal); + progress.setMinimumDuration(100); + progress.setAutoClose(false); + progress.setAutoReset(false); + progress.show(); + + // Declare progress callback. + auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { + progress.setValue(static_cast((processed_size * 100) / total_size)); + return progress.wasCanceled(); + }; + + LOG_INFO(Frontend, "Installing firmware from {}", firmware_source_location.toStdString()); + + // Check for a resonable number of .nca files (don't hardcode them, just see if there's some in there. + std::filesystem::path firmware_source_path = firmware_source_location.toStdString(); + if (!Common::FS::IsDir(firmware_source_path)) { + progress.close(); + return; + } + + std::vector out; + + const Common::FS::DirEntryCallable callback = [&out](const std::filesystem::directory_entry& entry) { + if (entry.path().has_extension() && entry.path().extension() == ".nca") + out.emplace_back(entry.path()); + + return true; + }; + + QtProgressCallback(100, 10); + + Common::FS::IterateDirEntries(firmware_source_path, callback, Common::FS::DirEntryFilter::File); + if (out.size() <= 0) { + progress.close(); + QMessageBox::warning(this, tr("Firmware install failed"), + tr("Unable to locate potential firmware NCA files")); + return; + } + + // Locate and erase the content of nand/system/Content/registered/*.nca, if any. + auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory(); + if (sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) { + LOG_INFO(Frontend, + "Cleaned nand/system/Content/registered folder in preparation for new firmware."); + } + + QtProgressCallback(100, 20); + + auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered"); + + bool success = true; + bool cancelled = false; + int i = 0; + for (const auto& firmware_src_path : out) { + i++; + auto firmware_src_vfile = + vfs->OpenFile(firmware_src_path.generic_string(), FileSys::OpenMode::Read); + auto firmware_dst_vfile = firmware_vdir->CreateFileRelative(firmware_src_path.filename().string()); + + if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) { + LOG_ERROR(Frontend, "Failed to copy firmware file {} to {} in registered folder!", + firmware_src_path.generic_string(), firmware_src_path.filename().string()); + success = false; + } + + if (QtProgressCallback(100, 20 + (int)(((float)(i) / (float)out.size()) * 70.0))) + { + success = false; + cancelled = true; + break; + } + } + + if (!success && !cancelled) { + progress.close(); + QMessageBox::critical(this, tr("Firmware install failed"), + tr("One or more firmware files failed to copy into NAND.")); + return; + } else if (cancelled) { + progress.close(); + QMessageBox::warning(this, tr("Firmware install failed"), + tr("Firmware installation cancelled, firmware may be in bad state, restart yuzu or re-install firmware.")); + return; + } + + progress.close(); + OnCheckFirmwareDecryption(); +} + void GMainWindow::OnAbout() { AboutDialog aboutDialog(this); aboutDialog.exec(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index aba61e3882..1f0e35c678 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -380,6 +380,7 @@ private slots: void OnLoadAmiibo(); void OnOpenYuzuFolder(); void OnVerifyInstalledContents(); + void OnInstallFirmware(); void OnAbout(); void OnToggleFilterBar(); void OnToggleStatusBar(); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 6a6b0821f6..6ff444a22c 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -25,7 +25,16 @@ - + + 0 + + + 0 + + + 0 + + 0 @@ -156,8 +165,8 @@ - + @@ -455,6 +464,11 @@ Open &Controller Menu + + + Install Firmware + + From 9eba64adce6d6d4af66d6335ba061e9df810892a Mon Sep 17 00:00:00 2001 From: Andrew Pilley Date: Sun, 18 Feb 2024 07:38:47 +1100 Subject: [PATCH 2/5] Improve behavior when one or more firmware files can't be deleted. --- src/yuzu/main.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 58e23fc693..d3517b8056 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -4187,7 +4187,7 @@ void GMainWindow::OnInstallFirmware() { LOG_INFO(Frontend, "Installing firmware from {}", firmware_source_location.toStdString()); - // Check for a resonable number of .nca files (don't hardcode them, just see if there's some in there. + // Check for a resonable number of .nca files (don't hardcode them, just see if there's some in there.) std::filesystem::path firmware_source_path = firmware_source_location.toStdString(); if (!Common::FS::IsDir(firmware_source_path)) { progress.close(); @@ -4195,7 +4195,6 @@ void GMainWindow::OnInstallFirmware() { } std::vector out; - const Common::FS::DirEntryCallable callback = [&out](const std::filesystem::directory_entry& entry) { if (entry.path().has_extension() && entry.path().extension() == ".nca") out.emplace_back(entry.path()); @@ -4215,11 +4214,15 @@ void GMainWindow::OnInstallFirmware() { // Locate and erase the content of nand/system/Content/registered/*.nca, if any. auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory(); - if (sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) { - LOG_INFO(Frontend, - "Cleaned nand/system/Content/registered folder in preparation for new firmware."); + if (!sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) { + progress.close(); + QMessageBox::critical(this, tr("Firmware install failed"), tr("Failed to delete one or more firmware file.")); + return; } + LOG_INFO(Frontend, + "Cleaned nand/system/Content/registered folder in preparation for new firmware."); + QtProgressCallback(100, 20); auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered"); @@ -4239,7 +4242,7 @@ void GMainWindow::OnInstallFirmware() { success = false; } - if (QtProgressCallback(100, 20 + (int)(((float)(i) / (float)out.size()) * 70.0))) + if (QtProgressCallback(100, 20 + (int)(((float)(i) / (float)out.size()) * 80.0))) { success = false; cancelled = true; From 59ede32f8e0f528d96ade4da8f0c79ccda1cc4e8 Mon Sep 17 00:00:00 2001 From: Andrew Pilley Date: Sun, 18 Feb 2024 07:41:24 +1100 Subject: [PATCH 3/5] cleanup by clang-format. --- src/yuzu/main.cpp | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index d3517b8056..79741e7215 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -4161,13 +4161,15 @@ void GMainWindow::OnInstallFirmware() { // Check for installed keys, error out, suggest restart? if (!ContentManager::AreKeysPresent()) { - QMessageBox::information(this, tr("Keys not installed"), - tr("Install decryption keys and restart yuzu before attempting to install firmware.")); + QMessageBox::information( + this, tr("Keys not installed"), + tr("Install decryption keys and restart yuzu before attempting to install firmware.")); return; } - QString firmware_source_location = QFileDialog::getExistingDirectory(this, - tr("Select Dumped Firmware Source Location"), QString::fromStdString(""), QFileDialog::ShowDirsOnly); + QString firmware_source_location = + QFileDialog::getExistingDirectory(this, tr("Select Dumped Firmware Source Location"), + QString::fromStdString(""), QFileDialog::ShowDirsOnly); if (firmware_source_location.isEmpty()) { return; } @@ -4187,7 +4189,8 @@ void GMainWindow::OnInstallFirmware() { LOG_INFO(Frontend, "Installing firmware from {}", firmware_source_location.toStdString()); - // Check for a resonable number of .nca files (don't hardcode them, just see if there's some in there.) + // Check for a resonable number of .nca files (don't hardcode them, just see if there's some in + // there.) std::filesystem::path firmware_source_path = firmware_source_location.toStdString(); if (!Common::FS::IsDir(firmware_source_path)) { progress.close(); @@ -4195,12 +4198,13 @@ void GMainWindow::OnInstallFirmware() { } std::vector out; - const Common::FS::DirEntryCallable callback = [&out](const std::filesystem::directory_entry& entry) { - if (entry.path().has_extension() && entry.path().extension() == ".nca") - out.emplace_back(entry.path()); + const Common::FS::DirEntryCallable callback = + [&out](const std::filesystem::directory_entry& entry) { + if (entry.path().has_extension() && entry.path().extension() == ".nca") + out.emplace_back(entry.path()); - return true; - }; + return true; + }; QtProgressCallback(100, 10); @@ -4216,7 +4220,8 @@ void GMainWindow::OnInstallFirmware() { auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory(); if (!sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) { progress.close(); - QMessageBox::critical(this, tr("Firmware install failed"), tr("Failed to delete one or more firmware file.")); + QMessageBox::critical(this, tr("Firmware install failed"), + tr("Failed to delete one or more firmware file.")); return; } @@ -4234,7 +4239,8 @@ void GMainWindow::OnInstallFirmware() { i++; auto firmware_src_vfile = vfs->OpenFile(firmware_src_path.generic_string(), FileSys::OpenMode::Read); - auto firmware_dst_vfile = firmware_vdir->CreateFileRelative(firmware_src_path.filename().string()); + auto firmware_dst_vfile = + firmware_vdir->CreateFileRelative(firmware_src_path.filename().string()); if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) { LOG_ERROR(Frontend, "Failed to copy firmware file {} to {} in registered folder!", @@ -4242,8 +4248,7 @@ void GMainWindow::OnInstallFirmware() { success = false; } - if (QtProgressCallback(100, 20 + (int)(((float)(i) / (float)out.size()) * 80.0))) - { + if (QtProgressCallback(100, 20 + (int)(((float)(i) / (float)out.size()) * 80.0))) { success = false; cancelled = true; break; @@ -4258,7 +4263,8 @@ void GMainWindow::OnInstallFirmware() { } else if (cancelled) { progress.close(); QMessageBox::warning(this, tr("Firmware install failed"), - tr("Firmware installation cancelled, firmware may be in bad state, restart yuzu or re-install firmware.")); + tr("Firmware installation cancelled, firmware may be in bad state, " + "restart yuzu or re-install firmware.")); return; } From e31c926bf0d9ee8f587ef41707548b3202dfcdeb Mon Sep 17 00:00:00 2001 From: Andrew Pilley Date: Sun, 18 Feb 2024 07:58:41 +1100 Subject: [PATCH 4/5] >.> spelling --- src/yuzu/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 79741e7215..fc6f0d3818 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -4189,7 +4189,7 @@ void GMainWindow::OnInstallFirmware() { LOG_INFO(Frontend, "Installing firmware from {}", firmware_source_location.toStdString()); - // Check for a resonable number of .nca files (don't hardcode them, just see if there's some in + // Check for a reasonable number of .nca files (don't hardcode them, just see if there's some in // there.) std::filesystem::path firmware_source_path = firmware_source_location.toStdString(); if (!Common::FS::IsDir(firmware_source_path)) { From cb2e312f138a832224c994adbf3591c399931b6b Mon Sep 17 00:00:00 2001 From: Andrew Pilley Date: Sun, 18 Feb 2024 12:31:14 +1100 Subject: [PATCH 5/5] Add check for corrupted firmware files after install. --- src/frontend_common/content_manager.h | 5 +++-- src/yuzu/main.cpp | 23 ++++++++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/frontend_common/content_manager.h b/src/frontend_common/content_manager.h index f3efe34652..c4e97a47b8 100644 --- a/src/frontend_common/content_manager.h +++ b/src/frontend_common/content_manager.h @@ -251,11 +251,12 @@ inline InstallResult InstallNCA(FileSys::VfsFilesystem& vfs, const std::string& * \param callback Callback to report the progress of the installation. The first size_t * parameter is the total size of the installed contents and the second is the current progress. If * you return true to the callback, it will cancel the installation as soon as possible. + * \param firmware_only Set to true to only scan system nand NCAs (firmware), post firmware install. * \return A list of entries that failed to install. Returns an empty vector if successful. */ inline std::vector VerifyInstalledContents( Core::System& system, FileSys::ManualContentProvider& provider, - const std::function& callback) { + const std::function& callback, bool firmware_only = false) { // Get content registries. auto bis_contents = system.GetFileSystemController().GetSystemNANDContents(); auto user_contents = system.GetFileSystemController().GetUserNANDContents(); @@ -264,7 +265,7 @@ inline std::vector VerifyInstalledContents( if (bis_contents) { content_providers.push_back(bis_contents); } - if (user_contents) { + if (user_contents && !firmware_only) { content_providers.push_back(user_contents); } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index fc6f0d3818..0d16bfd651 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -4248,7 +4248,7 @@ void GMainWindow::OnInstallFirmware() { success = false; } - if (QtProgressCallback(100, 20 + (int)(((float)(i) / (float)out.size()) * 80.0))) { + if (QtProgressCallback(100, 20 + (int)(((float)(i) / (float)out.size()) * 70.0))) { success = false; cancelled = true; break; @@ -4268,6 +4268,27 @@ void GMainWindow::OnInstallFirmware() { return; } + // Re-scan VFS for the newly placed firmware files. + system->GetFileSystemController().CreateFactories(*vfs); + + auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) { + progress.setValue(90 + static_cast((processed_size * 10) / total_size)); + return progress.wasCanceled(); + }; + + auto result = + ContentManager::VerifyInstalledContents(*system, *provider, VerifyFirmwareCallback, true); + + if (result.size() > 0) { + const auto failed_names = + QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); + progress.close(); + QMessageBox::critical( + this, tr("Firmware integrity verification failed!"), + tr("Verification failed for the following files:\n\n%1").arg(failed_names)); + return; + } + progress.close(); OnCheckFirmwareDecryption(); }