diff --git a/Source/Core/Core/WiiUtils.cpp b/Source/Core/Core/WiiUtils.cpp index 4ce3f03103..f721107d76 100644 --- a/Source/Core/Core/WiiUtils.cpp +++ b/Source/Core/Core/WiiUtils.cpp @@ -685,4 +685,89 @@ UpdateResult DoDiscUpdate(UpdateCallback update_callback, const std::string& ima DiscIO::NANDContentManager::Access().ClearCache(); return result; } + +bool CheckNAND(IOS::HLE::Kernel& ios) +{ + bool bad = false; + const auto es = ios.GetES(); + + // Check for NANDs that were used with old Dolphin versions. + if (File::Exists(Common::RootUserPath(Common::FROM_CONFIGURED_ROOT) + "/sys/replace")) + { + ERROR_LOG(CORE, "CheckNAND: NAND was used with old versions, so it is likely to be damaged"); + bad = true; + } + + for (const u64 title_id : es->GetInstalledTitles()) + { + // Check for missing title sub directories. + if (!File::IsDirectory(Common::GetTitleContentPath(title_id, Common::FROM_CONFIGURED_ROOT))) + { + ERROR_LOG(CORE, "CheckNAND: Missing content directory for title %016" PRIx64, title_id); + bad = true; + } + if (!File::IsDirectory(Common::GetTitleDataPath(title_id, Common::FROM_CONFIGURED_ROOT))) + { + ERROR_LOG(CORE, "CheckNAND: Missing data directory for title %016" PRIx64, title_id); + bad = true; + } + + // Check for incomplete title installs (missing ticket, TMD or contents). + const auto ticket = DiscIO::FindSignedTicket(title_id); + if (!IOS::ES::IsDiscTitle(title_id) && !ticket.IsValid()) + { + ERROR_LOG(CORE, "CheckNAND: Missing ticket for title %016" PRIx64, title_id); + bad = true; + } + + const auto tmd = es->FindInstalledTMD(title_id); + if (!tmd.IsValid()) + { + WARN_LOG(CORE, "CheckNAND: Missing TMD for title %016" PRIx64, title_id); + // Further checks require the TMD to be valid. + continue; + } + + const auto installed_contents = es->GetStoredContentsFromTMD(tmd); + const bool is_installed = std::any_of(installed_contents.begin(), installed_contents.end(), + [](const auto& content) { return !content.IsShared(); }); + + if (is_installed && installed_contents != tmd.GetContents() && + (tmd.GetTitleFlags() & IOS::ES::TitleFlags::TITLE_TYPE_WFS_MAYBE) == 0) + { + ERROR_LOG(CORE, "CheckNAND: Missing contents for title %016" PRIx64, title_id); + bad = true; + } + } + + return !bad; +} + +bool RepairNAND(IOS::HLE::Kernel& ios) +{ + const auto es = ios.GetES(); + + // Delete an old, unneeded file + File::Delete(Common::RootUserPath(Common::FROM_CONFIGURED_ROOT) + "/sys/replace"); + + for (const u64 title_id : es->GetInstalledTitles()) + { + // Create missing title sub directories. + const std::string content_dir = + Common::GetTitleContentPath(title_id, Common::FROM_CONFIGURED_ROOT); + const std::string data_dir = Common::GetTitleDataPath(title_id, Common::FROM_CONFIGURED_ROOT); + File::CreateDir(content_dir); + File::CreateDir(data_dir); + + // If there's nothing in the content/data directories and no ticket, + // this title shouldn't exist at all on the NAND. + if (File::ScanDirectoryTree(content_dir, false).children.empty() && + File::ScanDirectoryTree(data_dir, false).children.empty() && + !DiscIO::FindSignedTicket(title_id).IsValid()) + { + es->DeleteTitle(title_id); + } + } + return CheckNAND(ios); +} } diff --git a/Source/Core/Core/WiiUtils.h b/Source/Core/Core/WiiUtils.h index 49e8175bc2..2320b1f154 100644 --- a/Source/Core/Core/WiiUtils.h +++ b/Source/Core/Core/WiiUtils.h @@ -12,6 +12,14 @@ // Small utility functions for common Wii related tasks. +namespace IOS +{ +namespace HLE +{ +class Kernel; +} +} + namespace WiiUtils { bool InstallWAD(const std::string& wad_path); @@ -48,4 +56,8 @@ UpdateResult DoOnlineUpdate(UpdateCallback update_callback, const std::string& r // Perform a disc update with behaviour similar to the System Menu. UpdateResult DoDiscUpdate(UpdateCallback update_callback, const std::string& image_path); + +// Check the emulated NAND for common issues. +bool CheckNAND(IOS::HLE::Kernel& ios); +bool RepairNAND(IOS::HLE::Kernel& ios); } diff --git a/Source/Core/DolphinQt2/MenuBar.cpp b/Source/Core/DolphinQt2/MenuBar.cpp index 6fb6cc79a5..b7b7410e6f 100644 --- a/Source/Core/DolphinQt2/MenuBar.cpp +++ b/Source/Core/DolphinQt2/MenuBar.cpp @@ -22,6 +22,7 @@ #include "Core/IOS/IOS.h" #include "Core/Movie.h" #include "Core/State.h" +#include "Core/WiiUtils.h" #include "DiscIO/NANDImporter.h" #include "DolphinQt2/AboutDialog.h" #include "DolphinQt2/GameList/GameFile.h" @@ -114,7 +115,7 @@ void MenuBar::AddToolsMenu() AddAction(tools_menu, QStringLiteral(""), this, [this] { emit BootWiiSystemMenu(); }); m_import_backup = AddAction(gc_ipl, tr("Import BootMii NAND Backup..."), this, [this] { emit ImportNANDBackup(); }); - + m_check_nand = AddAction(tools_menu, tr("Check NAND..."), this, &MenuBar::CheckNAND); m_extract_certificates = AddAction(tools_menu, tr("Extract Certificates from NAND"), this, &MenuBar::NANDExtractCertificates); @@ -473,6 +474,7 @@ void MenuBar::UpdateToolsMenu(bool emulation_started) m_pal_ipl->setEnabled(!emulation_started && File::Exists(SConfig::GetInstance().GetBootROMPath(EUR_DIR))); m_import_backup->setEnabled(!emulation_started); + m_check_nand->setEnabled(!emulation_started); if (!emulation_started) { @@ -532,6 +534,35 @@ void MenuBar::ExportWiiSaves() CWiiSaveCrypted::ExportAllSaves(); } +void MenuBar::CheckNAND() +{ + IOS::HLE::Kernel ios; + if (WiiUtils::CheckNAND(ios)) + { + QMessageBox::information(this, tr("NAND Check"), tr("No issues have been detected.")); + return; + } + + if (QMessageBox::question( + this, tr("NAND Check"), + tr("The emulated NAND is damaged. System titles such as the Wii Menu and " + "the Wii Shop Channel may not work correctly.\n\n" + "Do you want to try to repair the NAND?")) != QMessageBox::Yes) + { + return; + } + + if (WiiUtils::RepairNAND(ios)) + { + QMessageBox::information(this, tr("NAND Check"), tr("The NAND has been repaired.")); + return; + } + + QMessageBox::critical(this, tr("NAND Check"), + tr("The NAND could not be repaired. It is recommended to back up " + "your current data and start over with a fresh NAND.")); +} + void MenuBar::NANDExtractCertificates() { if (DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX))) diff --git a/Source/Core/DolphinQt2/MenuBar.h b/Source/Core/DolphinQt2/MenuBar.h index c4a8c6a41c..d9745e7e67 100644 --- a/Source/Core/DolphinQt2/MenuBar.h +++ b/Source/Core/DolphinQt2/MenuBar.h @@ -113,6 +113,7 @@ private: void InstallWAD(); void ImportWiiSave(); void ExportWiiSaves(); + void CheckNAND(); void NANDExtractCertificates(); void OnSelectionChanged(QSharedPointer game_file); @@ -131,6 +132,7 @@ private: QAction* m_ntscu_ipl; QAction* m_pal_ipl; QAction* m_import_backup; + QAction* m_check_nand; QAction* m_extract_certificates; // Emulation diff --git a/Source/Core/DolphinWX/Frame.h b/Source/Core/DolphinWX/Frame.h index a7005841ef..24cb56a94b 100644 --- a/Source/Core/DolphinWX/Frame.h +++ b/Source/Core/DolphinWX/Frame.h @@ -348,6 +348,7 @@ private: void OnInstallWAD(wxCommandEvent& event); void OnUninstallWAD(wxCommandEvent& event); void OnImportBootMiiBackup(wxCommandEvent& event); + void OnCheckNAND(wxCommandEvent& event); void OnExtractCertificates(wxCommandEvent& event); void OnPerformOnlineWiiUpdate(wxCommandEvent& event); void OnPerformDiscWiiUpdate(wxCommandEvent& event); diff --git a/Source/Core/DolphinWX/FrameTools.cpp b/Source/Core/DolphinWX/FrameTools.cpp index e8c7b4f487..cda03e9c6d 100644 --- a/Source/Core/DolphinWX/FrameTools.cpp +++ b/Source/Core/DolphinWX/FrameTools.cpp @@ -185,6 +185,7 @@ void CFrame::BindMenuBarEvents() Bind(wxEVT_MENU, &CFrame::OnInstallWAD, this, IDM_MENU_INSTALL_WAD); Bind(wxEVT_MENU, &CFrame::OnLoadWiiMenu, this, IDM_LOAD_WII_MENU); Bind(wxEVT_MENU, &CFrame::OnImportBootMiiBackup, this, IDM_IMPORT_NAND); + Bind(wxEVT_MENU, &CFrame::OnCheckNAND, this, IDM_CHECK_NAND); Bind(wxEVT_MENU, &CFrame::OnExtractCertificates, this, IDM_EXTRACT_CERTIFICATES); for (const int idm : {IDM_PERFORM_ONLINE_UPDATE_CURRENT, IDM_PERFORM_ONLINE_UPDATE_EUR, IDM_PERFORM_ONLINE_UPDATE_JPN, IDM_PERFORM_ONLINE_UPDATE_KOR, @@ -1308,6 +1309,34 @@ void CFrame::OnImportBootMiiBackup(wxCommandEvent& WXUNUSED(event)) UpdateLoadWiiMenuItem(); } +void CFrame::OnCheckNAND(wxCommandEvent&) +{ + IOS::HLE::Kernel ios; + if (WiiUtils::CheckNAND(ios)) + { + wxMessageBox(_("No issues have been detected."), _("NAND Check"), wxOK | wxICON_INFORMATION); + return; + } + + if (wxMessageBox("The emulated NAND is damaged. System titles such as the Wii Menu and " + "the Wii Shop Channel may not work correctly.\n\n" + "Do you want to try to repair the NAND?", + _("NAND Check"), wxYES_NO) != wxYES) + { + return; + } + + if (WiiUtils::RepairNAND(ios)) + { + wxMessageBox(_("The NAND has been repaired."), _("NAND Check"), wxOK | wxICON_INFORMATION); + return; + } + + wxMessageBox(_("The NAND could not be repaired. It is recommended to back up " + "your current data and start over with a fresh NAND."), + _("NAND Check"), wxOK | wxICON_ERROR); +} + void CFrame::OnExtractCertificates(wxCommandEvent& WXUNUSED(event)) { DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX)); diff --git a/Source/Core/DolphinWX/Globals.h b/Source/Core/DolphinWX/Globals.h index dad9550cd7..1975716bce 100644 --- a/Source/Core/DolphinWX/Globals.h +++ b/Source/Core/DolphinWX/Globals.h @@ -105,6 +105,7 @@ enum IDM_LIST_INSTALL_WAD, IDM_LIST_UNINSTALL_WAD, IDM_IMPORT_NAND, + IDM_CHECK_NAND, IDM_EXTRACT_CERTIFICATES, IDM_PERFORM_ONLINE_UPDATE_CURRENT, IDM_PERFORM_ONLINE_UPDATE_EUR, diff --git a/Source/Core/DolphinWX/MainMenuBar.cpp b/Source/Core/DolphinWX/MainMenuBar.cpp index c9b505e891..fdb6b633c6 100644 --- a/Source/Core/DolphinWX/MainMenuBar.cpp +++ b/Source/Core/DolphinWX/MainMenuBar.cpp @@ -235,6 +235,7 @@ wxMenu* MainMenuBar::CreateToolsMenu() const tools_menu->Append(IDM_MENU_INSTALL_WAD, _("Install WAD...")); tools_menu->Append(IDM_LOAD_WII_MENU, dummy_string); tools_menu->Append(IDM_IMPORT_NAND, _("Import BootMii NAND Backup...")); + tools_menu->Append(IDM_CHECK_NAND, _("Check NAND...")); tools_menu->Append(IDM_EXTRACT_CERTIFICATES, _("Extract Certificates from NAND")); auto* const online_update_menu = new wxMenu; online_update_menu->Append(IDM_PERFORM_ONLINE_UPDATE_CURRENT, _("Current Region")); @@ -582,7 +583,7 @@ void MainMenuBar::RefreshWiiToolsLabels() const // inconsistent data. const bool enable_wii_tools = !Core::IsRunning() || !SConfig::GetInstance().bWii; for (const int index : - {IDM_MENU_INSTALL_WAD, IDM_EXPORT_ALL_SAVE, IDM_IMPORT_SAVE, IDM_IMPORT_NAND, + {IDM_MENU_INSTALL_WAD, IDM_EXPORT_ALL_SAVE, IDM_IMPORT_SAVE, IDM_IMPORT_NAND, IDM_CHECK_NAND, IDM_EXTRACT_CERTIFICATES, IDM_LOAD_WII_MENU, IDM_PERFORM_ONLINE_UPDATE_CURRENT, IDM_PERFORM_ONLINE_UPDATE_EUR, IDM_PERFORM_ONLINE_UPDATE_JPN, IDM_PERFORM_ONLINE_UPDATE_KOR, IDM_PERFORM_ONLINE_UPDATE_USA})