WiiUtils: Attempt to fix the NAND more aggressively

Change the repair logic to fix issues more aggressively by deleting bad
titles. This is necessary because of a bug in Dolphin's WAD boot code.

The UI code was updated to inform the user about titles that will be
deleted if they continue a repair, before deleting anything.
This commit is contained in:
Léo Lam 2017-10-06 21:45:28 +02:00
parent e1c0b8d011
commit 02e17594b0
4 changed files with 83 additions and 29 deletions

View file

@ -686,16 +686,16 @@ UpdateResult DoDiscUpdate(UpdateCallback update_callback, const std::string& ima
return result; return result;
} }
bool CheckNAND(IOS::HLE::Kernel& ios) NANDCheckResult CheckNAND(IOS::HLE::Kernel& ios)
{ {
bool bad = false; NANDCheckResult result;
const auto es = ios.GetES(); const auto es = ios.GetES();
// Check for NANDs that were used with old Dolphin versions. // Check for NANDs that were used with old Dolphin versions.
if (File::Exists(Common::RootUserPath(Common::FROM_CONFIGURED_ROOT) + "/sys/replace")) 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"); ERROR_LOG(CORE, "CheckNAND: NAND was used with old versions, so it is likely to be damaged");
bad = true; result.bad = true;
} }
for (const u64 title_id : es->GetInstalledTitles()) for (const u64 title_id : es->GetInstalledTitles())
@ -704,12 +704,12 @@ bool CheckNAND(IOS::HLE::Kernel& ios)
if (!File::IsDirectory(Common::GetTitleContentPath(title_id, Common::FROM_CONFIGURED_ROOT))) if (!File::IsDirectory(Common::GetTitleContentPath(title_id, Common::FROM_CONFIGURED_ROOT)))
{ {
ERROR_LOG(CORE, "CheckNAND: Missing content directory for title %016" PRIx64, title_id); ERROR_LOG(CORE, "CheckNAND: Missing content directory for title %016" PRIx64, title_id);
bad = true; result.bad = true;
} }
if (!File::IsDirectory(Common::GetTitleDataPath(title_id, Common::FROM_CONFIGURED_ROOT))) if (!File::IsDirectory(Common::GetTitleDataPath(title_id, Common::FROM_CONFIGURED_ROOT)))
{ {
ERROR_LOG(CORE, "CheckNAND: Missing data directory for title %016" PRIx64, title_id); ERROR_LOG(CORE, "CheckNAND: Missing data directory for title %016" PRIx64, title_id);
bad = true; result.bad = true;
} }
// Check for incomplete title installs (missing ticket, TMD or contents). // Check for incomplete title installs (missing ticket, TMD or contents).
@ -717,13 +717,26 @@ bool CheckNAND(IOS::HLE::Kernel& ios)
if (!IOS::ES::IsDiscTitle(title_id) && !ticket.IsValid()) if (!IOS::ES::IsDiscTitle(title_id) && !ticket.IsValid())
{ {
ERROR_LOG(CORE, "CheckNAND: Missing ticket for title %016" PRIx64, title_id); ERROR_LOG(CORE, "CheckNAND: Missing ticket for title %016" PRIx64, title_id);
bad = true; result.titles_to_remove.insert(title_id);
result.bad = true;
} }
const std::string content_dir =
Common::GetTitleContentPath(title_id, Common::FROM_CONFIGURED_ROOT);
const auto tmd = es->FindInstalledTMD(title_id); const auto tmd = es->FindInstalledTMD(title_id);
if (!tmd.IsValid()) if (!tmd.IsValid())
{
if (File::ScanDirectoryTree(content_dir, false).children.empty())
{ {
WARN_LOG(CORE, "CheckNAND: Missing TMD for title %016" PRIx64, title_id); WARN_LOG(CORE, "CheckNAND: Missing TMD for title %016" PRIx64, title_id);
}
else
{
ERROR_LOG(CORE, "CheckNAND: Missing TMD for title %016" PRIx64, title_id);
result.titles_to_remove.insert(title_id);
result.bad = true;
}
// Further checks require the TMD to be valid. // Further checks require the TMD to be valid.
continue; continue;
} }
@ -736,11 +749,12 @@ bool CheckNAND(IOS::HLE::Kernel& ios)
(tmd.GetTitleFlags() & IOS::ES::TitleFlags::TITLE_TYPE_WFS_MAYBE) == 0) (tmd.GetTitleFlags() & IOS::ES::TitleFlags::TITLE_TYPE_WFS_MAYBE) == 0)
{ {
ERROR_LOG(CORE, "CheckNAND: Missing contents for title %016" PRIx64, title_id); ERROR_LOG(CORE, "CheckNAND: Missing contents for title %016" PRIx64, title_id);
bad = true; result.titles_to_remove.insert(title_id);
result.bad = true;
} }
} }
return !bad; return result;
} }
bool RepairNAND(IOS::HLE::Kernel& ios) bool RepairNAND(IOS::HLE::Kernel& ios)
@ -759,15 +773,18 @@ bool RepairNAND(IOS::HLE::Kernel& ios)
File::CreateDir(content_dir); File::CreateDir(content_dir);
File::CreateDir(data_dir); File::CreateDir(data_dir);
// If there's nothing in the content/data directories and no ticket, // If there's nothing in the content directory and no ticket,
// this title shouldn't exist at all on the NAND. // this title shouldn't exist at all on the NAND.
if (File::ScanDirectoryTree(content_dir, false).children.empty() && // WARNING: This will delete associated save data!
File::ScanDirectoryTree(data_dir, false).children.empty() && const auto content_files = File::ScanDirectoryTree(content_dir, false).children;
!DiscIO::FindSignedTicket(title_id).IsValid()) const bool has_no_tmd_but_contents =
!es->FindInstalledTMD(title_id).IsValid() && !content_files.empty();
if (has_no_tmd_but_contents || !DiscIO::FindSignedTicket(title_id).IsValid())
{ {
es->DeleteTitle(title_id); const std::string title_dir = Common::GetTitlePath(title_id, Common::FROM_CONFIGURED_ROOT);
File::DeleteDirRecursively(title_dir);
} }
} }
return CheckNAND(ios); return !CheckNAND(ios).bad;
} }
} }

View file

@ -7,6 +7,7 @@
#include <cstddef> #include <cstddef>
#include <functional> #include <functional>
#include <string> #include <string>
#include <unordered_set>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
@ -58,6 +59,11 @@ UpdateResult DoOnlineUpdate(UpdateCallback update_callback, const std::string& r
UpdateResult DoDiscUpdate(UpdateCallback update_callback, const std::string& image_path); UpdateResult DoDiscUpdate(UpdateCallback update_callback, const std::string& image_path);
// Check the emulated NAND for common issues. // Check the emulated NAND for common issues.
bool CheckNAND(IOS::HLE::Kernel& ios); struct NANDCheckResult
{
bool bad = false;
std::unordered_set<u64> titles_to_remove;
};
NANDCheckResult CheckNAND(IOS::HLE::Kernel& ios);
bool RepairNAND(IOS::HLE::Kernel& ios); bool RepairNAND(IOS::HLE::Kernel& ios);
} }

View file

@ -22,6 +22,7 @@
#include "Core/IOS/IOS.h" #include "Core/IOS/IOS.h"
#include "Core/Movie.h" #include "Core/Movie.h"
#include "Core/State.h" #include "Core/State.h"
#include "Core/TitleDatabase.h"
#include "Core/WiiUtils.h" #include "Core/WiiUtils.h"
#include "DiscIO/NANDImporter.h" #include "DiscIO/NANDImporter.h"
#include "DolphinQt2/AboutDialog.h" #include "DolphinQt2/AboutDialog.h"
@ -537,20 +538,36 @@ void MenuBar::ExportWiiSaves()
void MenuBar::CheckNAND() void MenuBar::CheckNAND()
{ {
IOS::HLE::Kernel ios; IOS::HLE::Kernel ios;
if (WiiUtils::CheckNAND(ios)) WiiUtils::NANDCheckResult result = WiiUtils::CheckNAND(ios);
if (!result.bad)
{ {
QMessageBox::information(this, tr("NAND Check"), tr("No issues have been detected.")); QMessageBox::information(this, tr("NAND Check"), tr("No issues have been detected."));
return; return;
} }
if (QMessageBox::question( QString message = tr("The emulated NAND is damaged. System titles such as the Wii Menu and "
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" "the Wii Shop Channel may not work correctly.\n\n"
"Do you want to try to repair the NAND?")) != QMessageBox::Yes) "Do you want to try to repair the NAND?");
if (!result.titles_to_remove.empty())
{ {
return; message += tr("\n\nWARNING: Fixing this NAND requires the deletion of titles that have "
"incomplete data on the NAND, including all associated save data. "
"By continuing, the following title(s) will be removed:\n\n");
Core::TitleDatabase title_db;
for (const u64 title_id : result.titles_to_remove)
{
const std::string name = title_db.GetTitleName(title_id);
message += !name.empty() ?
QStringLiteral("%1 (%2)")
.arg(QString::fromStdString(name))
.arg(title_id, 16, 16, QLatin1Char('0')) :
QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char('0'));
message += QStringLiteral("\n");
} }
}
if (QMessageBox::question(this, tr("NAND Check"), message) != QMessageBox::Yes)
return;
if (WiiUtils::RepairNAND(ios)) if (WiiUtils::RepairNAND(ios))
{ {

View file

@ -1312,19 +1312,33 @@ void CFrame::OnImportBootMiiBackup(wxCommandEvent& WXUNUSED(event))
void CFrame::OnCheckNAND(wxCommandEvent&) void CFrame::OnCheckNAND(wxCommandEvent&)
{ {
IOS::HLE::Kernel ios; IOS::HLE::Kernel ios;
if (WiiUtils::CheckNAND(ios)) WiiUtils::NANDCheckResult result = WiiUtils::CheckNAND(ios);
if (!result.bad)
{ {
wxMessageBox(_("No issues have been detected."), _("NAND Check"), wxOK | wxICON_INFORMATION); wxMessageBox(_("No issues have been detected."), _("NAND Check"), wxOK | wxICON_INFORMATION);
return; return;
} }
if (wxMessageBox("The emulated NAND is damaged. System titles such as the Wii Menu and " wxString message = _("The emulated NAND is damaged. System titles such as the Wii Menu and "
"the Wii Shop Channel may not work correctly.\n\n" "the Wii Shop Channel may not work correctly.\n\n"
"Do you want to try to repair the NAND?", "Do you want to try to repair the NAND?");
_("NAND Check"), wxYES_NO) != wxYES) if (!result.titles_to_remove.empty())
{ {
return; message += _("\n\nWARNING: Fixing this NAND requires the deletion of titles that have "
"incomplete data on the NAND, including all associated save data. "
"By continuing, the following title(s) will be removed:\n\n");
Core::TitleDatabase title_db;
for (const u64 title_id : result.titles_to_remove)
{
const std::string name = title_db.GetTitleName(title_id);
message += !name.empty() ? StringFromFormat("%s (%016" PRIx64 ")", name.c_str(), title_id) :
StringFromFormat("%016" PRIx64, title_id);
message += "\n";
} }
}
if (wxMessageBox(message, _("NAND Check"), wxYES_NO) != wxYES)
return;
if (WiiUtils::RepairNAND(ios)) if (WiiUtils::RepairNAND(ios))
{ {