Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add file size stats to NAND Check. #12047

Merged
merged 3 commits into from Jul 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 38 additions & 0 deletions Source/Core/Core/IOS/FS/FileSystem.h
Expand Up @@ -107,6 +107,33 @@ struct Metadata
u16 fst_index;
};

// size of a single cluster in the NAND in bytes
constexpr u16 CLUSTER_SIZE = 16384;

// total number of clusters available in the NAND
constexpr u16 TOTAL_CLUSTERS = 0x7ec0;

// number of clusters reserved for bad blocks and similar, not accessible to normal writes
constexpr u16 RESERVED_CLUSTERS = 0x0300;

// number of clusters actually usable by the file system
constexpr u16 USABLE_CLUSTERS = TOTAL_CLUSTERS - RESERVED_CLUSTERS;

// size of a single 'block' as defined by the Wii System Menu in clusters
constexpr u16 CLUSTERS_PER_BLOCK = 8;

// total number of user-accessible blocks in the NAND
constexpr u16 USER_BLOCKS = 2176;

// total number of user-accessible clusters in the NAND
constexpr u16 USER_CLUSTERS = USER_BLOCKS * CLUSTERS_PER_BLOCK;

// the inverse of that, the amount of usable clusters reserved for system files
constexpr u16 SYSTEM_CLUSTERS = USABLE_CLUSTERS - USER_CLUSTERS;

// total number of inodes available in the NAND
constexpr u16 TOTAL_INODES = 0x17ff;

struct NandStats
{
u32 cluster_size;
Expand All @@ -124,6 +151,14 @@ struct DirectoryStats
u32 used_inodes;
};

// Not a real Wii data struct, but useful for calculating how full the user's NAND is even if it's
// way larger than it should be.
struct ExtendedDirectoryStats
{
u64 used_clusters;
u64 used_inodes;
};

struct FileStatus
{
u32 offset;
Expand Down Expand Up @@ -252,6 +287,9 @@ class FileSystem
/// Get usage information about a directory (used cluster and inode counts).
virtual Result<DirectoryStats> GetDirectoryStats(const std::string& path) = 0;

/// Like GetDirectoryStats() but not limited to the actual 512 MB NAND limit.
virtual Result<ExtendedDirectoryStats> GetExtendedDirectoryStats(const std::string& path) = 0;

virtual void SetNandRedirects(std::vector<NandRedirect> nand_redirects) = 0;
};

Expand Down
36 changes: 16 additions & 20 deletions Source/Core/Core/IOS/FS/HostBackend/FS.cpp
Expand Up @@ -29,21 +29,6 @@ namespace IOS::HLE::FS
{
constexpr u32 BUFFER_CHUNK_SIZE = 65536;

// size of a single cluster in the NAND
constexpr u16 CLUSTER_SIZE = 16384;

// total number of clusters available in the NAND
constexpr u16 TOTAL_CLUSTERS = 0x7ec0;

// number of clusters reserved for bad blocks and similar, not accessible to normal writes
constexpr u16 RESERVED_CLUSTERS = 0x0300;

// number of clusters actually usable by the file system
constexpr u16 USABLE_CLUSTERS = TOTAL_CLUSTERS - RESERVED_CLUSTERS;

// total number of inodes available in the NAND
constexpr u16 TOTAL_INODES = 0x17ff;

HostFileSystem::HostFilename HostFileSystem::BuildFilename(const std::string& wii_path) const
{
for (const auto& redirect : m_nand_redirects)
Expand Down Expand Up @@ -818,11 +803,24 @@ Result<NandStats> HostFileSystem::GetNandStats()
}

Result<DirectoryStats> HostFileSystem::GetDirectoryStats(const std::string& wii_path)
{
const auto result = GetExtendedDirectoryStats(wii_path);
if (!result)
return result.Error();

DirectoryStats stats{};
stats.used_inodes = static_cast<u32>(std::min<u64>(result->used_inodes, TOTAL_INODES));
stats.used_clusters = static_cast<u32>(std::min<u64>(result->used_clusters, USABLE_CLUSTERS));
return stats;
}

Result<ExtendedDirectoryStats>
HostFileSystem::GetExtendedDirectoryStats(const std::string& wii_path)
{
if (!IsValidPath(wii_path))
return ResultCode::Invalid;

DirectoryStats stats{};
ExtendedDirectoryStats stats{};
std::string path(BuildFilename(wii_path).host_path);
File::FileInfo info(path);
if (!info.Exists())
Expand All @@ -835,10 +833,8 @@ Result<DirectoryStats> HostFileSystem::GetDirectoryStats(const std::string& wii_
FixupDirectoryEntries(&parent_dir, wii_path == "/");

// add one for the folder itself
stats.used_inodes = static_cast<u32>(std::min<u64>(1 + parent_dir.size, TOTAL_INODES));

const u64 clusters = ComputeUsedClusters(parent_dir);
stats.used_clusters = static_cast<u32>(std::min<u64>(clusters, USABLE_CLUSTERS));
stats.used_inodes = 1 + parent_dir.size;
stats.used_clusters = ComputeUsedClusters(parent_dir);
}
else
{
Expand Down
1 change: 1 addition & 0 deletions Source/Core/Core/IOS/FS/HostBackend/FS.h
Expand Up @@ -55,6 +55,7 @@ class HostFileSystem final : public FileSystem

Result<NandStats> GetNandStats() override;
Result<DirectoryStats> GetDirectoryStats(const std::string& path) override;
Result<ExtendedDirectoryStats> GetExtendedDirectoryStats(const std::string& path) override;

void SetNandRedirects(std::vector<NandRedirect> nand_redirects) override;

Expand Down
28 changes: 28 additions & 0 deletions Source/Core/Core/WiiUtils.cpp
Expand Up @@ -961,6 +961,34 @@ static NANDCheckResult CheckNAND(IOS::HLE::Kernel& ios, bool repair)
}
}

// Get some storage stats.
const auto fs = ios.GetFS();
const auto root_stats = fs->GetExtendedDirectoryStats("/");

// The Wii System Menu's save/channel management only considers a specific subset of the Wii NAND
// user-accessible and will only use those folders when calculating the amount of free blocks it
// displays. This can have weird side-effects where the other parts of the NAND contain more data
// than reserved and it will display free blocks even though there isn't any space left. To avoid
// confusion, report the 'user' and 'system' data separately to the user.
u64 used_clusters_user = 0;
u64 used_inodes_user = 0;
for (std::string user_path : {"/meta", "/ticket", "/title/00010000", "/title/00010001",
"/title/00010003", "/title/00010004", "/title/00010005",
"/title/00010006", "/title/00010007", "/shared2/title"})
{
const auto dir_stats = fs->GetExtendedDirectoryStats(user_path);
if (dir_stats)
{
used_clusters_user += dir_stats->used_clusters;
used_inodes_user += dir_stats->used_inodes;
}
}

result.used_clusters_user = used_clusters_user;
result.used_clusters_system = root_stats ? (root_stats->used_clusters - used_clusters_user) : 0;
result.used_inodes_user = used_inodes_user;
result.used_inodes_system = root_stats ? (root_stats->used_inodes - used_inodes_user) : 0;

return result;
}

Expand Down
4 changes: 4 additions & 0 deletions Source/Core/Core/WiiUtils.h
Expand Up @@ -101,6 +101,10 @@ struct NANDCheckResult
{
bool bad = false;
std::unordered_set<u64> titles_to_remove;
u64 used_clusters_user = 0;
u64 used_clusters_system = 0;
u64 used_inodes_user = 0;
u64 used_inodes_system = 0;
};
NANDCheckResult CheckNAND(IOS::HLE::Kernel& ios);
bool RepairNAND(IOS::HLE::Kernel& ios);
Expand Down
39 changes: 38 additions & 1 deletion Source/Core/DolphinQt/MenuBar.cpp
Expand Up @@ -15,6 +15,7 @@
#include <QMap>
#include <QUrl>

#include "Common/Align.h"
#include "Common/CommonPaths.h"
#include "Common/FileUtil.h"
#include "Common/StringUtil.h"
Expand All @@ -32,6 +33,7 @@
#include "Core/HW/WiiSave.h"
#include "Core/HW/Wiimote.h"
#include "Core/IOS/ES/ES.h"
#include "Core/IOS/FS/FileSystem.h"
#include "Core/IOS/IOS.h"
#include "Core/IOS/USB/Bluetooth/BTEmu.h"
#include "Core/Movie.h"
Expand Down Expand Up @@ -1137,7 +1139,42 @@ void MenuBar::CheckNAND()
WiiUtils::NANDCheckResult result = WiiUtils::CheckNAND(ios);
if (!result.bad)
{
ModalMessageBox::information(this, tr("NAND Check"), tr("No issues have been detected."));
const bool overfull = result.used_clusters_user > IOS::HLE::FS::USER_CLUSTERS ||
result.used_clusters_system > IOS::HLE::FS::SYSTEM_CLUSTERS;
const QString user_cluster_message =
tr("The user-accessible part of your NAND contains %1 blocks (%2 KiB) "
"of data, out of an allowed maximum of %3 blocks (%4 KiB).")
.arg(Common::AlignUp(result.used_clusters_user, IOS::HLE::FS::CLUSTERS_PER_BLOCK) /
IOS::HLE::FS::CLUSTERS_PER_BLOCK)
.arg((result.used_clusters_user * IOS::HLE::FS::CLUSTER_SIZE) / 1024)
.arg(IOS::HLE::FS::USER_CLUSTERS / IOS::HLE::FS::CLUSTERS_PER_BLOCK)
.arg((IOS::HLE::FS::USER_CLUSTERS * IOS::HLE::FS::CLUSTER_SIZE) / 1024);
const QString system_cluster_message =
tr("The system-reserved part of your NAND contains %1 blocks (%2 KiB) "
"of data, out of an allowed maximum of %3 blocks (%4 KiB).")
.arg(Common::AlignUp(result.used_clusters_system, IOS::HLE::FS::CLUSTERS_PER_BLOCK) /
IOS::HLE::FS::CLUSTERS_PER_BLOCK)
.arg((result.used_clusters_system * IOS::HLE::FS::CLUSTER_SIZE) / 1024)
.arg(IOS::HLE::FS::SYSTEM_CLUSTERS / IOS::HLE::FS::CLUSTERS_PER_BLOCK)
.arg((IOS::HLE::FS::SYSTEM_CLUSTERS * IOS::HLE::FS::CLUSTER_SIZE) / 1024);

if (overfull)
{
ModalMessageBox::warning(this, tr("NAND Check"),
QStringLiteral("<b>%1</b><br/><br/>%2<br/><br/>%3")
.arg(tr("Your NAND contains more data than allowed. Wii "
"software may behave incorrectly or not allow saving."))
.arg(user_cluster_message)
.arg(system_cluster_message));
}
else
{
ModalMessageBox::information(this, tr("NAND Check"),
QStringLiteral("<b>%1</b><br/><br/>%2<br/><br/>%3")
.arg(tr("No issues have been detected."))
.arg(user_cluster_message)
.arg(system_cluster_message));
}
return;
}

Expand Down