Merge pull request #11711 from AdmiralCurtiss/ios-fs-stats
IOS/FS: More accurate emulation of GetDirectoryStats() and GetNandStats().
This commit is contained in:
commit
fdc1ff1dad
|
@ -134,10 +134,13 @@ struct FileStatus
|
||||||
constexpr size_t MaxPathDepth = 8;
|
constexpr size_t MaxPathDepth = 8;
|
||||||
/// The maximum number of characters a path can have.
|
/// The maximum number of characters a path can have.
|
||||||
constexpr size_t MaxPathLength = 64;
|
constexpr size_t MaxPathLength = 64;
|
||||||
|
/// The maximum number of characters a filename can have.
|
||||||
|
constexpr size_t MaxFilenameLength = 12;
|
||||||
|
|
||||||
/// Returns whether a Wii path is valid.
|
/// Returns whether a Wii path is valid.
|
||||||
bool IsValidPath(std::string_view path);
|
bool IsValidPath(std::string_view path);
|
||||||
bool IsValidNonRootPath(std::string_view path);
|
bool IsValidNonRootPath(std::string_view path);
|
||||||
|
bool IsValidFilename(std::string_view filename);
|
||||||
|
|
||||||
struct SplitPathResult
|
struct SplitPathResult
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include "Core/IOS/FS/FileSystem.h"
|
#include "Core/IOS/FS/FileSystem.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "Common/Assert.h"
|
#include "Common/Assert.h"
|
||||||
#include "Common/FileUtil.h"
|
#include "Common/FileUtil.h"
|
||||||
#include "Core/IOS/Device.h"
|
#include "Core/IOS/Device.h"
|
||||||
|
@ -21,6 +23,12 @@ bool IsValidNonRootPath(std::string_view path)
|
||||||
path.back() != '/';
|
path.back() != '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsValidFilename(std::string_view filename)
|
||||||
|
{
|
||||||
|
return filename.length() <= MaxFilenameLength &&
|
||||||
|
!std::any_of(filename.begin(), filename.end(), [](char c) { return c == '/'; });
|
||||||
|
}
|
||||||
|
|
||||||
SplitPathResult SplitPathAndBasename(std::string_view path)
|
SplitPathResult SplitPathAndBasename(std::string_view path)
|
||||||
{
|
{
|
||||||
const auto last_separator = path.rfind('/');
|
const auto last_separator = path.rfind('/');
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "Core/IOS/FS/HostBackend/FS.h"
|
#include "Core/IOS/FS/HostBackend/FS.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
@ -11,6 +12,7 @@
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include "Common/Align.h"
|
||||||
#include "Common/Assert.h"
|
#include "Common/Assert.h"
|
||||||
#include "Common/ChunkFile.h"
|
#include "Common/ChunkFile.h"
|
||||||
#include "Common/FileUtil.h"
|
#include "Common/FileUtil.h"
|
||||||
|
@ -27,6 +29,21 @@ 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)
|
||||||
|
@ -47,21 +64,6 @@ HostFileSystem::HostFilename HostFileSystem::BuildFilename(const std::string& wi
|
||||||
return HostFilename{m_root_path, false};
|
return HostFilename{m_root_path, false};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get total filesize of contents of a directory (recursive)
|
|
||||||
// Only used for ES_GetUsage atm, could be useful elsewhere?
|
|
||||||
static u64 ComputeTotalFileSize(const File::FSTEntry& parent_entry)
|
|
||||||
{
|
|
||||||
u64 sizeOfFiles = 0;
|
|
||||||
for (const File::FSTEntry& entry : parent_entry.children)
|
|
||||||
{
|
|
||||||
if (entry.isDirectory)
|
|
||||||
sizeOfFiles += ComputeTotalFileSize(entry);
|
|
||||||
else
|
|
||||||
sizeOfFiles += entry.size;
|
|
||||||
}
|
|
||||||
return sizeOfFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
struct SerializedFstEntry
|
struct SerializedFstEntry
|
||||||
|
@ -102,6 +104,45 @@ auto GetNamePredicate(const std::string& name)
|
||||||
{
|
{
|
||||||
return [&name](const auto& entry) { return entry.name == name; };
|
return [&name](const auto& entry) { return entry.name == name; };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert the host directory entries into ones that can be exposed to the emulated system.
|
||||||
|
static u64 FixupDirectoryEntries(File::FSTEntry* dir, bool is_root)
|
||||||
|
{
|
||||||
|
u64 removed_entries = 0;
|
||||||
|
for (auto it = dir->children.begin(); it != dir->children.end();)
|
||||||
|
{
|
||||||
|
// Drop files in the root of the Wii NAND folder, since we store extra files there that the
|
||||||
|
// emulated system shouldn't know about.
|
||||||
|
if (is_root && !it->isDirectory)
|
||||||
|
{
|
||||||
|
++removed_entries;
|
||||||
|
it = dir->children.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode escaped invalid file system characters so that games (such as Harry Potter and the
|
||||||
|
// Half-Blood Prince) can find what they expect.
|
||||||
|
if (it->virtualName.find("__") != std::string::npos)
|
||||||
|
it->virtualName = Common::UnescapeFileName(it->virtualName);
|
||||||
|
|
||||||
|
// Drop files that have too long filenames.
|
||||||
|
if (!IsValidFilename(it->virtualName))
|
||||||
|
{
|
||||||
|
if (it->isDirectory)
|
||||||
|
removed_entries += it->size;
|
||||||
|
++removed_entries;
|
||||||
|
it = dir->children.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dir->isDirectory)
|
||||||
|
removed_entries += FixupDirectoryEntries(&*it, false);
|
||||||
|
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
dir->size -= removed_entries;
|
||||||
|
return removed_entries;
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool HostFileSystem::FstEntry::CheckPermission(Uid caller_uid, Gid caller_gid,
|
bool HostFileSystem::FstEntry::CheckPermission(Uid caller_uid, Gid caller_gid,
|
||||||
|
@ -645,12 +686,7 @@ Result<std::vector<std::string>> HostFileSystem::ReadDirectory(Uid uid, Gid gid,
|
||||||
|
|
||||||
const std::string host_path = BuildFilename(path).host_path;
|
const std::string host_path = BuildFilename(path).host_path;
|
||||||
File::FSTEntry host_entry = File::ScanDirectoryTree(host_path, false);
|
File::FSTEntry host_entry = File::ScanDirectoryTree(host_path, false);
|
||||||
for (File::FSTEntry& child : host_entry.children)
|
FixupDirectoryEntries(&host_entry, path == "/");
|
||||||
{
|
|
||||||
// Decode escaped invalid file system characters so that games (such as
|
|
||||||
// Harry Potter and the Half-Blood Prince) can find what they expect.
|
|
||||||
child.virtualName = Common::UnescapeFileName(child.virtualName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort files according to their order in the FST tree (issue 10234).
|
// Sort files according to their order in the FST tree (issue 10234).
|
||||||
// The result should look like this:
|
// The result should look like this:
|
||||||
|
@ -747,19 +783,33 @@ ResultCode HostFileSystem::SetMetadata(Uid caller_uid, const std::string& path,
|
||||||
return ResultCode::Success;
|
return ResultCode::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static u64 ComputeUsedClusters(const File::FSTEntry& parent_entry)
|
||||||
|
{
|
||||||
|
u64 clusters = 0;
|
||||||
|
for (const File::FSTEntry& entry : parent_entry.children)
|
||||||
|
{
|
||||||
|
if (entry.isDirectory)
|
||||||
|
clusters += ComputeUsedClusters(entry);
|
||||||
|
else
|
||||||
|
clusters += Common::AlignUp(entry.size, CLUSTER_SIZE) / CLUSTER_SIZE;
|
||||||
|
}
|
||||||
|
return clusters;
|
||||||
|
}
|
||||||
|
|
||||||
Result<NandStats> HostFileSystem::GetNandStats()
|
Result<NandStats> HostFileSystem::GetNandStats()
|
||||||
{
|
{
|
||||||
WARN_LOG_FMT(IOS_FS, "GET STATS - returning static values for now");
|
const auto root_stats = GetDirectoryStats("/");
|
||||||
|
if (!root_stats)
|
||||||
|
return root_stats.Error(); // TODO: is this right? can this fail on hardware?
|
||||||
|
|
||||||
// TODO: scrape the real amounts from somewhere...
|
|
||||||
NandStats stats{};
|
NandStats stats{};
|
||||||
stats.cluster_size = 0x4000;
|
stats.cluster_size = CLUSTER_SIZE;
|
||||||
stats.free_clusters = 0x5DEC;
|
stats.free_clusters = USABLE_CLUSTERS - root_stats->used_clusters;
|
||||||
stats.used_clusters = 0x1DD4;
|
stats.used_clusters = root_stats->used_clusters;
|
||||||
stats.bad_clusters = 0x10;
|
stats.bad_clusters = 0;
|
||||||
stats.reserved_clusters = 0x02F0;
|
stats.reserved_clusters = RESERVED_CLUSTERS;
|
||||||
stats.free_inodes = 0x146B;
|
stats.free_inodes = TOTAL_INODES - root_stats->used_inodes;
|
||||||
stats.used_inodes = 0x0394;
|
stats.used_inodes = root_stats->used_inodes;
|
||||||
|
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
@ -771,19 +821,25 @@ Result<DirectoryStats> HostFileSystem::GetDirectoryStats(const std::string& wii_
|
||||||
|
|
||||||
DirectoryStats stats{};
|
DirectoryStats stats{};
|
||||||
std::string path(BuildFilename(wii_path).host_path);
|
std::string path(BuildFilename(wii_path).host_path);
|
||||||
if (File::IsDirectory(path))
|
File::FileInfo info(path);
|
||||||
|
if (!info.Exists())
|
||||||
|
{
|
||||||
|
return ResultCode::NotFound;
|
||||||
|
}
|
||||||
|
if (info.IsDirectory())
|
||||||
{
|
{
|
||||||
File::FSTEntry parent_dir = File::ScanDirectoryTree(path, true);
|
File::FSTEntry parent_dir = File::ScanDirectoryTree(path, true);
|
||||||
|
FixupDirectoryEntries(&parent_dir, wii_path == "/");
|
||||||
|
|
||||||
// add one for the folder itself
|
// add one for the folder itself
|
||||||
stats.used_inodes = 1 + (u32)parent_dir.size;
|
stats.used_inodes = static_cast<u32>(std::min<u64>(1 + parent_dir.size, TOTAL_INODES));
|
||||||
|
|
||||||
u64 total_size = ComputeTotalFileSize(parent_dir); // "Real" size to convert to nand blocks
|
const u64 clusters = ComputeUsedClusters(parent_dir);
|
||||||
|
stats.used_clusters = static_cast<u32>(std::min<u64>(clusters, USABLE_CLUSTERS));
|
||||||
stats.used_clusters = (u32)(total_size / (16 * 1024)); // one block is 16kb
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
WARN_LOG_FMT(IOS_FS, "fsBlock failed, cannot find directory: {}", path);
|
return ResultCode::Invalid;
|
||||||
}
|
}
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue