From 69d6be09f7084444bc90d5fb6174d8532d5bd51c Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 31 Mar 2023 19:50:00 +0200 Subject: [PATCH 1/4] IOS/FS: GetDirectoryStats() should return an error if the given path is not a directory. --- Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index 2b2cf16be1..cf9a50396e 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -771,7 +771,12 @@ Result HostFileSystem::GetDirectoryStats(const std::string& wii_ DirectoryStats stats{}; 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); // add one for the folder itself @@ -783,7 +788,7 @@ Result HostFileSystem::GetDirectoryStats(const std::string& wii_ } else { - WARN_LOG_FMT(IOS_FS, "fsBlock failed, cannot find directory: {}", path); + return ResultCode::Invalid; } return stats; } From 8295d6d4154bef240f4b23d098c46afd9ea29752 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 31 Mar 2023 20:03:40 +0200 Subject: [PATCH 2/4] IOS/FS: More accurate cluster calculation in GetDirectoryStats(). --- Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 53 ++++++++++++++-------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index cf9a50396e..2f390e4d76 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -4,6 +4,7 @@ #include "Core/IOS/FS/HostBackend/FS.h" #include +#include #include #include #include @@ -11,6 +12,7 @@ #include +#include "Common/Align.h" #include "Common/Assert.h" #include "Common/ChunkFile.h" #include "Common/FileUtil.h" @@ -27,6 +29,21 @@ 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) @@ -47,21 +64,6 @@ HostFileSystem::HostFilename HostFileSystem::BuildFilename(const std::string& wi 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 { struct SerializedFstEntry @@ -747,6 +749,19 @@ ResultCode HostFileSystem::SetMetadata(Uid caller_uid, const std::string& path, 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 HostFileSystem::GetNandStats() { WARN_LOG_FMT(IOS_FS, "GET STATS - returning static values for now"); @@ -779,12 +794,12 @@ Result HostFileSystem::GetDirectoryStats(const std::string& wii_ if (info.IsDirectory()) { File::FSTEntry parent_dir = File::ScanDirectoryTree(path, true); + // add one for the folder itself - stats.used_inodes = 1 + (u32)parent_dir.size; + stats.used_inodes = static_cast(std::min(1 + parent_dir.size, TOTAL_INODES)); - u64 total_size = ComputeTotalFileSize(parent_dir); // "Real" size to convert to nand blocks - - stats.used_clusters = (u32)(total_size / (16 * 1024)); // one block is 16kb + const u64 clusters = ComputeUsedClusters(parent_dir); + stats.used_clusters = static_cast(std::min(clusters, USABLE_CLUSTERS)); } else { From 5c05155828431262c983bb3c8d3ec437bef6864a Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 31 Mar 2023 20:07:04 +0200 Subject: [PATCH 3/4] IOS/FS: Implement GetNandStats(). --- Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index 2f390e4d76..7a2d2135c2 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -764,17 +764,18 @@ static u64 ComputeUsedClusters(const File::FSTEntry& parent_entry) Result 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{}; - stats.cluster_size = 0x4000; - stats.free_clusters = 0x5DEC; - stats.used_clusters = 0x1DD4; - stats.bad_clusters = 0x10; - stats.reserved_clusters = 0x02F0; - stats.free_inodes = 0x146B; - stats.used_inodes = 0x0394; + stats.cluster_size = CLUSTER_SIZE; + stats.free_clusters = USABLE_CLUSTERS - root_stats->used_clusters; + stats.used_clusters = root_stats->used_clusters; + stats.bad_clusters = 0; + stats.reserved_clusters = RESERVED_CLUSTERS; + stats.free_inodes = TOTAL_INODES - root_stats->used_inodes; + stats.used_inodes = root_stats->used_inodes; return stats; } From 5373fcc1e7351b80b64072b7edf625f5bae00c44 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 31 Mar 2023 20:50:56 +0200 Subject: [PATCH 4/4] IOS/FS: Filter files exposed to the emulated system in ReadDirectory() and GetDirectoryStats(). --- Source/Core/Core/IOS/FS/FileSystem.h | 3 ++ Source/Core/Core/IOS/FS/FileSystemCommon.cpp | 8 ++++ Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 47 +++++++++++++++++--- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/Source/Core/Core/IOS/FS/FileSystem.h b/Source/Core/Core/IOS/FS/FileSystem.h index 6b1d7ddf78..581df6fbee 100644 --- a/Source/Core/Core/IOS/FS/FileSystem.h +++ b/Source/Core/Core/IOS/FS/FileSystem.h @@ -134,10 +134,13 @@ struct FileStatus constexpr size_t MaxPathDepth = 8; /// The maximum number of characters a path can have. 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. bool IsValidPath(std::string_view path); bool IsValidNonRootPath(std::string_view path); +bool IsValidFilename(std::string_view filename); struct SplitPathResult { diff --git a/Source/Core/Core/IOS/FS/FileSystemCommon.cpp b/Source/Core/Core/IOS/FS/FileSystemCommon.cpp index 8d246157bb..1ec5dbe068 100644 --- a/Source/Core/Core/IOS/FS/FileSystemCommon.cpp +++ b/Source/Core/Core/IOS/FS/FileSystemCommon.cpp @@ -3,6 +3,8 @@ #include "Core/IOS/FS/FileSystem.h" +#include + #include "Common/Assert.h" #include "Common/FileUtil.h" #include "Core/IOS/Device.h" @@ -21,6 +23,12 @@ bool IsValidNonRootPath(std::string_view path) 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) { const auto last_separator = path.rfind('/'); diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index 7a2d2135c2..991f1353d2 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -104,6 +104,45 @@ auto GetNamePredicate(const std::string& 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 bool HostFileSystem::FstEntry::CheckPermission(Uid caller_uid, Gid caller_gid, @@ -647,12 +686,7 @@ Result> HostFileSystem::ReadDirectory(Uid uid, Gid gid, const std::string host_path = BuildFilename(path).host_path; File::FSTEntry host_entry = File::ScanDirectoryTree(host_path, false); - for (File::FSTEntry& child : host_entry.children) - { - // 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); - } + FixupDirectoryEntries(&host_entry, path == "/"); // Sort files according to their order in the FST tree (issue 10234). // The result should look like this: @@ -795,6 +829,7 @@ Result HostFileSystem::GetDirectoryStats(const std::string& wii_ if (info.IsDirectory()) { File::FSTEntry parent_dir = File::ScanDirectoryTree(path, true); + FixupDirectoryEntries(&parent_dir, wii_path == "/"); // add one for the folder itself stats.used_inodes = static_cast(std::min(1 + parent_dir.size, TOTAL_INODES));