summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorAdmiral H. Curtiss <pikachu025@gmail.com>2023-04-10 20:01:51 +0200
committerGitHub <noreply@github.com>2023-04-10 20:01:51 +0200
commitfdc1ff1dad4a2bef88c059d90005b2bffc4e4703 (patch)
tree73336a98786540346b9efb4e3139caac3777f0be /Source/Core
parent4c80c992eb9fdfceee6ff3e6b15a17ac89bc5914 (diff)
parent5373fcc1e7351b80b64072b7edf625f5bae00c44 (diff)
Merge pull request #11711 from AdmiralCurtiss/ios-fs-stats
IOS/FS: More accurate emulation of GetDirectoryStats() and GetNandStats().
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/Core/IOS/FS/FileSystem.h3
-rw-r--r--Source/Core/Core/IOS/FS/FileSystemCommon.cpp8
-rw-r--r--Source/Core/Core/IOS/FS/HostBackend/FS.cpp128
3 files changed, 103 insertions, 36 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 <algorithm>
+
#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 2b2cf16be1..991f1353d2 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 <algorithm>
+#include <cmath>
#include <optional>
#include <string_view>
#include <type_traits>
@@ -11,6 +12,7 @@
#include <fmt/format.h>
+#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
@@ -102,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,
@@ -645,12 +686,7 @@ Result<std::vector<std::string>> 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:
@@ -747,19 +783,33 @@ 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<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{};
- 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;
}
@@ -771,19 +821,25 @@ Result<DirectoryStats> 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
- stats.used_inodes = 1 + (u32)parent_dir.size;
+ FixupDirectoryEntries(&parent_dir, wii_path == "/");
- u64 total_size = ComputeTotalFileSize(parent_dir); // "Real" size to convert to nand blocks
+ // add one for the folder itself
+ stats.used_inodes = static_cast<u32>(std::min<u64>(1 + parent_dir.size, TOTAL_INODES));
- stats.used_clusters = (u32)(total_size / (16 * 1024)); // one block is 16kb
+ const u64 clusters = ComputeUsedClusters(parent_dir);
+ stats.used_clusters = static_cast<u32>(std::min<u64>(clusters, USABLE_CLUSTERS));
}
else
{
- WARN_LOG_FMT(IOS_FS, "fsBlock failed, cannot find directory: {}", path);
+ return ResultCode::Invalid;
}
return stats;
}