|
@@ -1603,6 +1603,7 @@ void GMainWindow::ConnectMenuEvents() {
|
|
|
// Help
|
|
// Help
|
|
|
connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder);
|
|
connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder);
|
|
|
connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents);
|
|
connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents);
|
|
|
|
|
+ connect_menu(ui->action_Install_Firmware, &GMainWindow::OnInstallFirmware);
|
|
|
connect_menu(ui->action_About, &GMainWindow::OnAbout);
|
|
connect_menu(ui->action_About, &GMainWindow::OnAbout);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1631,6 +1632,8 @@ void GMainWindow::UpdateMenuState() {
|
|
|
action->setEnabled(emulation_running);
|
|
action->setEnabled(emulation_running);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ ui->action_Install_Firmware->setEnabled(!emulation_running);
|
|
|
|
|
+
|
|
|
for (QAction* action : applet_actions) {
|
|
for (QAction* action : applet_actions) {
|
|
|
action->setEnabled(is_firmware_available && !emulation_running);
|
|
action->setEnabled(is_firmware_available && !emulation_running);
|
|
|
}
|
|
}
|
|
@@ -4150,6 +4153,146 @@ 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<int>((processed_size * 100) / total_size));
|
|
|
|
|
+ return progress.wasCanceled();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ LOG_INFO(Frontend, "Installing firmware from {}", firmware_source_location.toStdString());
|
|
|
|
|
+
|
|
|
|
|
+ // 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)) {
|
|
|
|
|
+ progress.close();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::vector<std::filesystem::path> 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")) {
|
|
|
|
|
+ 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");
|
|
|
|
|
+
|
|
|
|
|
+ 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;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 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<int>((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();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
void GMainWindow::OnAbout() {
|
|
void GMainWindow::OnAbout() {
|
|
|
AboutDialog aboutDialog(this);
|
|
AboutDialog aboutDialog(this);
|
|
|
aboutDialog.exec();
|
|
aboutDialog.exec();
|