From f06aef4f8382d258e16bac2ea8a19aa48dda72a0 Mon Sep 17 00:00:00 2001 From: Tillmann Karras Date: Fri, 3 Apr 2026 23:50:59 +0100 Subject: Improve NAND import progress dialog Now with cancel button and an actual progress bar. For simplicity, we do two passes on the progress bar, one for loading the NAND into memory and one for extracting it. The user directory is likely on an SSD, making the extraction pass invisibly fast. --- Source/Core/DiscIO/NANDImporter.cpp | 35 ++++++++++++++++++++++++----------- Source/Core/DiscIO/NANDImporter.h | 17 ++++++++++++++--- Source/Core/DolphinQt/MainWindow.cpp | 24 ++++++++++++++---------- 3 files changed, 52 insertions(+), 24 deletions(-) (limited to 'Source/Core') diff --git a/Source/Core/DiscIO/NANDImporter.cpp b/Source/Core/DiscIO/NANDImporter.cpp index 54122d0626..f4c94bbb2c 100644 --- a/Source/Core/DiscIO/NANDImporter.cpp +++ b/Source/Core/DiscIO/NANDImporter.cpp @@ -23,8 +23,7 @@ NANDImporter::NANDImporter() : m_nand_root(File::GetUserPath(D_WIIROOT_IDX)) } NANDImporter::~NANDImporter() = default; -void NANDImporter::ImportNANDBin(const std::string& path_to_bin, - std::function update_callback, +void NANDImporter::ImportNANDBin(const std::string& path_to_bin, UpdateCallback update_callback, const std::function& get_otp_dump_path) { m_update_callback = std::move(update_callback); @@ -35,8 +34,13 @@ void NANDImporter::ImportNANDBin(const std::string& path_to_bin, return; ExportKeys(); - ProcessEntry(0, ""); + + if (!ExtractFiles()) + return; + ExtractCertificates(); + + std::ignore = m_update_callback(Step::Extracting, m_progress_cur, m_progress_max); } bool NANDImporter::ReadNANDBin(const std::string& path_to_bin, @@ -60,10 +64,8 @@ bool NANDImporter::ReadNANDBin(const std::string& path_to_bin, for (size_t i = 0; i < NAND_TOTAL_BLOCKS; i++) { - // Instead of updating on every cycle, we only update every 1000 cycles for a balance between - // not updating fast enough vs updating too fast - if (i % 1000 == 0) - m_update_callback(); + if (m_update_callback(Step::Loading, int(i), int(NAND_TOTAL_BLOCKS))) + return false; file.ReadBytes(&m_nand[i * NAND_BLOCK_SIZE], NAND_BLOCK_SIZE); @@ -121,6 +123,14 @@ bool NANDImporter::FindSuperblock() return true; } +bool NANDImporter::ExtractFiles() +{ + constexpr u16 CLUSTER_CHAIN_END = 0xFFFB; + m_progress_cur = 0; + m_progress_max = std::ranges::count(m_superblock->fat, CLUSTER_CHAIN_END); + return ProcessEntry(0, ""); +} + std::string NANDImporter::GetPath(const NANDFSTEntry& entry, const std::string& parent_path) { std::string name(entry.name, strnlen(entry.name, sizeof(NANDFSTEntry::name))); @@ -131,25 +141,26 @@ std::string NANDImporter::GetPath(const NANDFSTEntry& entry, const std::string& return parent_path + '/' + name; } -void NANDImporter::ProcessEntry(u16 entry_number, const std::string& parent_path) +bool NANDImporter::ProcessEntry(u16 entry_number, const std::string& parent_path) { while (entry_number != 0xffff) { if (entry_number >= m_superblock->fst.size()) { ERROR_LOG_FMT(DISCIO, "FST entry number {} out of range", entry_number); - return; + return false; } const NANDFSTEntry entry = m_superblock->fst[entry_number]; const std::string path = GetPath(entry, parent_path); INFO_LOG_FMT(DISCIO, "Entry: {} Path: {}", entry, path); - m_update_callback(); Type type = static_cast(entry.mode & 3); if (type == Type::File) { + if (m_update_callback(Step::Extracting, m_progress_cur++, m_progress_max)) + return false; std::vector data = GetEntryData(entry); File::IOFile file(m_nand_root + path, "wb"); file.WriteBytes(data.data(), data.size()); @@ -157,7 +168,8 @@ void NANDImporter::ProcessEntry(u16 entry_number, const std::string& parent_path else if (type == Type::Directory) { File::CreateDir(m_nand_root + path); - ProcessEntry(entry.sub, path); + if (!ProcessEntry(entry.sub, path)) + return false; } else { @@ -166,6 +178,7 @@ void NANDImporter::ProcessEntry(u16 entry_number, const std::string& parent_path entry_number = entry.sib; } + return true; } std::vector NANDImporter::GetEntryData(const NANDFSTEntry& entry) const diff --git a/Source/Core/DiscIO/NANDImporter.h b/Source/Core/DiscIO/NANDImporter.h index 164d009f5a..f22f043709 100644 --- a/Source/Core/DiscIO/NANDImporter.h +++ b/Source/Core/DiscIO/NANDImporter.h @@ -23,10 +23,18 @@ public: NANDImporter(); ~NANDImporter(); + enum class Step + { + Loading, + Extracting, + }; + // Return true to cancel. + using UpdateCallback = std::function; + // Extract a NAND image to the configured NAND root. // If the associated OTP/SEEPROM dump (keys.bin) is not included in the image, // get_otp_dump_path will be called to get a path to it. - void ImportNANDBin(const std::string& path_to_bin, std::function update_callback, + void ImportNANDBin(const std::string& path_to_bin, UpdateCallback update_callback, const std::function& get_otp_dump_path); bool ExtractCertificates(); @@ -67,9 +75,10 @@ private: bool ReadNANDBin(const std::string& path_to_bin, const std::function& get_otp_dump_path); bool FindSuperblock(); + bool ExtractFiles(); std::string GetPath(const NANDFSTEntry& entry, const std::string& parent_path); std::string FormatDebugString(const NANDFSTEntry& entry); - void ProcessEntry(u16 entry_number, const std::string& parent_path); + bool ProcessEntry(u16 entry_number, const std::string& parent_path); std::vector GetEntryData(const NANDFSTEntry& entry) const; void ExportKeys(); @@ -78,7 +87,9 @@ private: std::vector m_nand_keys; std::unique_ptr m_aes_ctx; std::unique_ptr m_superblock; - std::function m_update_callback; + UpdateCallback m_update_callback; + u16 m_progress_cur = 0; + u16 m_progress_max = 0; }; } // namespace DiscIO diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index c3f5b4148c..2713157a74 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -1881,20 +1881,24 @@ void MainWindow::OnImportNANDBackup() return; ParallelProgressDialog dialog(this); - dialog.GetRaw()->setMinimum(0); - dialog.GetRaw()->setMaximum(0); - dialog.GetRaw()->setLabelText(tr("Importing NAND backup")); - dialog.GetRaw()->setCancelButton(nullptr); - - auto beginning = QDateTime::currentDateTime().toMSecsSinceEpoch(); + dialog.GetRaw()->setWindowTitle(tr("Importing NAND backup")); std::future result = std::async(std::launch::async, [&] { DiscIO::NANDImporter().ImportNANDBin( file.toStdString(), - [&dialog, beginning] { - dialog.SetLabelText( - tr("Importing NAND backup\n Time elapsed: %1s") - .arg((QDateTime::currentDateTime().toMSecsSinceEpoch() - beginning) / 1000)); + [&dialog](DiscIO::NANDImporter::Step step, u32 cur, u32 max) { + switch (step) + { + case DiscIO::NANDImporter::Step::Loading: + dialog.SetLabelText(tr("Loading NAND...")); + break; + case DiscIO::NANDImporter::Step::Extracting: + dialog.SetLabelText(tr("Extracting NAND...")); + break; + } + dialog.SetValue(cur); + dialog.SetMaximum(max); + return dialog.WasCanceled(); }, [this] { std::optional keys_file = RunOnObject(this, [this] { -- cgit v1.2.3