Merge pull request #12047 from AdmiralCurtiss/nand-stats-user

Add file size stats to NAND Check.
This commit is contained in:
JosJuice 2023-07-30 21:51:07 +02:00 committed by GitHub
commit a912a2c3f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 125 additions and 21 deletions

View File

@ -107,6 +107,33 @@ struct Metadata
u16 fst_index; 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 struct NandStats
{ {
u32 cluster_size; u32 cluster_size;
@ -124,6 +151,14 @@ struct DirectoryStats
u32 used_inodes; 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 struct FileStatus
{ {
u32 offset; u32 offset;
@ -252,6 +287,9 @@ public:
/// Get usage information about a directory (used cluster and inode counts). /// Get usage information about a directory (used cluster and inode counts).
virtual Result<DirectoryStats> GetDirectoryStats(const std::string& path) = 0; 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; virtual void SetNandRedirects(std::vector<NandRedirect> nand_redirects) = 0;
}; };

View File

@ -29,21 +29,6 @@ namespace IOS::HLE::FS
{ {
constexpr u32 BUFFER_CHUNK_SIZE = 65536; 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 HostFileSystem::HostFilename HostFileSystem::BuildFilename(const std::string& wii_path) const
{ {
for (const auto& redirect : m_nand_redirects) for (const auto& redirect : m_nand_redirects)
@ -818,11 +803,24 @@ Result<NandStats> HostFileSystem::GetNandStats()
} }
Result<DirectoryStats> HostFileSystem::GetDirectoryStats(const std::string& wii_path) 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)) if (!IsValidPath(wii_path))
return ResultCode::Invalid; return ResultCode::Invalid;
DirectoryStats stats{}; ExtendedDirectoryStats stats{};
std::string path(BuildFilename(wii_path).host_path); std::string path(BuildFilename(wii_path).host_path);
File::FileInfo info(path); File::FileInfo info(path);
if (!info.Exists()) if (!info.Exists())
@ -835,10 +833,8 @@ Result<DirectoryStats> HostFileSystem::GetDirectoryStats(const std::string& wii_
FixupDirectoryEntries(&parent_dir, wii_path == "/"); FixupDirectoryEntries(&parent_dir, wii_path == "/");
// add one for the folder itself // add one for the folder itself
stats.used_inodes = static_cast<u32>(std::min<u64>(1 + parent_dir.size, TOTAL_INODES)); stats.used_inodes = 1 + parent_dir.size;
stats.used_clusters = ComputeUsedClusters(parent_dir);
const u64 clusters = ComputeUsedClusters(parent_dir);
stats.used_clusters = static_cast<u32>(std::min<u64>(clusters, USABLE_CLUSTERS));
} }
else else
{ {

View File

@ -55,6 +55,7 @@ public:
Result<NandStats> GetNandStats() override; Result<NandStats> GetNandStats() override;
Result<DirectoryStats> GetDirectoryStats(const std::string& path) 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; void SetNandRedirects(std::vector<NandRedirect> nand_redirects) override;

View File

@ -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; return result;
} }

View File

@ -101,6 +101,10 @@ struct NANDCheckResult
{ {
bool bad = false; bool bad = false;
std::unordered_set<u64> titles_to_remove; 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); NANDCheckResult CheckNAND(IOS::HLE::Kernel& ios);
bool RepairNAND(IOS::HLE::Kernel& ios); bool RepairNAND(IOS::HLE::Kernel& ios);

View File

@ -15,6 +15,7 @@
#include <QMap> #include <QMap>
#include <QUrl> #include <QUrl>
#include "Common/Align.h"
#include "Common/CommonPaths.h" #include "Common/CommonPaths.h"
#include "Common/FileUtil.h" #include "Common/FileUtil.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
@ -32,6 +33,7 @@
#include "Core/HW/WiiSave.h" #include "Core/HW/WiiSave.h"
#include "Core/HW/Wiimote.h" #include "Core/HW/Wiimote.h"
#include "Core/IOS/ES/ES.h" #include "Core/IOS/ES/ES.h"
#include "Core/IOS/FS/FileSystem.h"
#include "Core/IOS/IOS.h" #include "Core/IOS/IOS.h"
#include "Core/IOS/USB/Bluetooth/BTEmu.h" #include "Core/IOS/USB/Bluetooth/BTEmu.h"
#include "Core/Movie.h" #include "Core/Movie.h"
@ -1137,7 +1139,42 @@ void MenuBar::CheckNAND()
WiiUtils::NANDCheckResult result = WiiUtils::CheckNAND(ios); WiiUtils::NANDCheckResult result = WiiUtils::CheckNAND(ios);
if (!result.bad) 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; return;
} }