mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-29 09:09:52 -06:00
Merge pull request #6096 from leoetlino/wii-fsck
Add a way to check the NAND for issues and fix them
This commit is contained in:
@ -67,6 +67,17 @@ bool Content::IsOptional() const
|
||||
return (type & 0x4000) != 0;
|
||||
}
|
||||
|
||||
bool operator==(const Content& lhs, const Content& rhs)
|
||||
{
|
||||
auto fields = [](const Content& c) { return std::tie(c.id, c.index, c.type, c.size, c.sha1); };
|
||||
return fields(lhs) == fields(rhs);
|
||||
}
|
||||
|
||||
bool operator!=(const Content& lhs, const Content& rhs)
|
||||
{
|
||||
return !operator==(lhs, rhs);
|
||||
}
|
||||
|
||||
SignedBlobReader::SignedBlobReader(const std::vector<u8>& bytes) : m_bytes(bytes)
|
||||
{
|
||||
}
|
||||
|
@ -93,6 +93,8 @@ struct Content
|
||||
std::array<u8, 20> sha1;
|
||||
};
|
||||
static_assert(sizeof(Content) == 36, "Content has the wrong size");
|
||||
bool operator==(const Content&, const Content&);
|
||||
bool operator!=(const Content&, const Content&);
|
||||
|
||||
struct TimeLimit
|
||||
{
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/IOS/ES/Formats.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
|
||||
namespace Core
|
||||
@ -165,6 +166,14 @@ std::string TitleDatabase::GetTitleName(const std::string& game_id, TitleType ty
|
||||
return iterator != map.end() ? iterator->second : "";
|
||||
}
|
||||
|
||||
std::string TitleDatabase::GetTitleName(u64 title_id) const
|
||||
{
|
||||
const std::string id{
|
||||
{static_cast<char>((title_id >> 24) & 0xff), static_cast<char>((title_id >> 16) & 0xff),
|
||||
static_cast<char>((title_id >> 8) & 0xff), static_cast<char>(title_id & 0xff)}};
|
||||
return GetTitleName(id, IOS::ES::IsChannel(title_id) ? TitleType::Channel : TitleType::Other);
|
||||
}
|
||||
|
||||
std::string TitleDatabase::Describe(const std::string& game_id, TitleType type) const
|
||||
{
|
||||
const std::string title_name = GetTitleName(game_id, type);
|
||||
|
@ -7,6 +7,8 @@
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace Core
|
||||
{
|
||||
// Reader for title database files.
|
||||
@ -25,6 +27,7 @@ public:
|
||||
// Get a user friendly title name for a game ID.
|
||||
// This falls back to returning an empty string if none could be found.
|
||||
std::string GetTitleName(const std::string& game_id, TitleType = TitleType::Other) const;
|
||||
std::string GetTitleName(u64 title_id) const;
|
||||
|
||||
// Get a description for a game ID (title name if available + game ID).
|
||||
std::string Describe(const std::string& game_id, TitleType = TitleType::Other) const;
|
||||
|
@ -685,4 +685,106 @@ UpdateResult DoDiscUpdate(UpdateCallback update_callback, const std::string& ima
|
||||
DiscIO::NANDContentManager::Access().ClearCache();
|
||||
return result;
|
||||
}
|
||||
|
||||
NANDCheckResult CheckNAND(IOS::HLE::Kernel& ios)
|
||||
{
|
||||
NANDCheckResult result;
|
||||
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");
|
||||
result.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);
|
||||
result.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);
|
||||
result.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);
|
||||
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);
|
||||
if (!tmd.IsValid())
|
||||
{
|
||||
if (File::ScanDirectoryTree(content_dir, false).children.empty())
|
||||
{
|
||||
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.
|
||||
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);
|
||||
result.titles_to_remove.insert(title_id);
|
||||
result.bad = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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 directory and no ticket,
|
||||
// this title shouldn't exist at all on the NAND.
|
||||
// WARNING: This will delete associated save data!
|
||||
const auto content_files = File::ScanDirectoryTree(content_dir, false).children;
|
||||
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())
|
||||
{
|
||||
const std::string title_dir = Common::GetTitlePath(title_id, Common::FROM_CONFIGURED_ROOT);
|
||||
File::DeleteDirRecursively(title_dir);
|
||||
}
|
||||
}
|
||||
return !CheckNAND(ios).bad;
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,20 @@
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
// 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 +57,13 @@ 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.
|
||||
struct NANDCheckResult
|
||||
{
|
||||
bool bad = false;
|
||||
std::unordered_set<u64> titles_to_remove;
|
||||
};
|
||||
NANDCheckResult CheckNAND(IOS::HLE::Kernel& ios);
|
||||
bool RepairNAND(IOS::HLE::Kernel& ios);
|
||||
}
|
||||
|
Reference in New Issue
Block a user