From d185bc6f0927b7b08a8f28a96066f8d7a72617e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sat, 28 Dec 2019 17:44:47 +0100 Subject: [PATCH 01/19] IOS/FS: Move path validity check functions They will be used in more places than just HostBackend/FS.cpp. Also fix the check and make it accurate while we're at it. --- Source/Core/Core/IOS/FS/FileSystem.cpp | 11 +++++++++++ Source/Core/Core/IOS/FS/FileSystem.h | 8 ++++++++ Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 21 ++++++++------------- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/Source/Core/Core/IOS/FS/FileSystem.cpp b/Source/Core/Core/IOS/FS/FileSystem.cpp index 3fa652fb22..11806f313c 100644 --- a/Source/Core/Core/IOS/FS/FileSystem.cpp +++ b/Source/Core/Core/IOS/FS/FileSystem.cpp @@ -11,6 +11,17 @@ namespace IOS::HLE::FS { +bool IsValidPath(std::string_view path) +{ + return path == "/" || IsValidNonRootPath(path); +} + +bool IsValidNonRootPath(std::string_view path) +{ + return path.length() > 1 && path.length() <= MaxPathLength && path[0] == '/' && + path.back() != '/'; +} + std::unique_ptr MakeFileSystem(Location location) { const std::string nand_root = diff --git a/Source/Core/Core/IOS/FS/FileSystem.h b/Source/Core/Core/IOS/FS/FileSystem.h index 6266ba6979..5bed4349d0 100644 --- a/Source/Core/Core/IOS/FS/FileSystem.h +++ b/Source/Core/Core/IOS/FS/FileSystem.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #ifdef _WIN32 @@ -111,6 +112,13 @@ struct FileStatus u32 size; }; +/// The maximum number of characters a path can have. +constexpr size_t MaxPathLength = 64; + +/// Returns whether a Wii path is valid. +bool IsValidPath(std::string_view path); +bool IsValidNonRootPath(std::string_view path); + class FileSystem; class FileHandle final { diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index 345800f5a5..65f42e228e 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -15,11 +15,6 @@ namespace IOS::HLE::FS { -static bool IsValidWiiPath(const std::string& path) -{ - return path.compare(0, 1, "/") == 0; -} - std::string HostFileSystem::BuildFilename(const std::string& wii_path) const { if (wii_path.compare(0, 1, "/") == 0) @@ -185,7 +180,7 @@ ResultCode HostFileSystem::CreateFile(Uid, Gid, const std::string& path, FileAtt ResultCode HostFileSystem::CreateDirectory(Uid, Gid, const std::string& path, FileAttribute, Modes) { - if (!IsValidWiiPath(path)) + if (!IsValidPath(path)) return ResultCode::Invalid; std::string name(BuildFilename(path)); @@ -199,7 +194,7 @@ ResultCode HostFileSystem::CreateDirectory(Uid, Gid, const std::string& path, Fi ResultCode HostFileSystem::Delete(Uid, Gid, const std::string& path) { - if (!IsValidWiiPath(path)) + if (!IsValidPath(path)) return ResultCode::Invalid; const std::string file_name = BuildFilename(path); @@ -216,11 +211,11 @@ ResultCode HostFileSystem::Delete(Uid, Gid, const std::string& path) ResultCode HostFileSystem::Rename(Uid, Gid, const std::string& old_path, const std::string& new_path) { - if (!IsValidWiiPath(old_path)) + if (!IsValidPath(old_path)) return ResultCode::Invalid; const std::string old_name = BuildFilename(old_path); - if (!IsValidWiiPath(new_path)) + if (!IsValidPath(new_path)) return ResultCode::Invalid; const std::string new_name = BuildFilename(new_path); @@ -252,7 +247,7 @@ ResultCode HostFileSystem::Rename(Uid, Gid, const std::string& old_path, Result> HostFileSystem::ReadDirectory(Uid, Gid, const std::string& path) { - if (!IsValidWiiPath(path)) + if (!IsValidPath(path)) return ResultCode::Invalid; // the Wii uses this function to define the type (dir or file) @@ -301,7 +296,7 @@ Result HostFileSystem::GetMetadata(Uid, Gid, const std::string& path) metadata.gid = 0x3031; // this is also known as makercd, 01 (0x3031) for nintendo and 08 // (0x3038) for MH3 etc - if (!IsValidWiiPath(path)) + if (!IsValidPath(path)) return ResultCode::Invalid; std::string file_name = BuildFilename(path); @@ -330,7 +325,7 @@ Result HostFileSystem::GetMetadata(Uid, Gid, const std::string& path) ResultCode HostFileSystem::SetMetadata(Uid caller_uid, const std::string& path, Uid uid, Gid gid, FileAttribute, Modes) { - if (!IsValidWiiPath(path)) + if (!IsValidPath(path)) return ResultCode::Invalid; return ResultCode::Success; } @@ -354,7 +349,7 @@ Result HostFileSystem::GetNandStats() Result HostFileSystem::GetDirectoryStats(const std::string& wii_path) { - if (!IsValidWiiPath(wii_path)) + if (!IsValidPath(wii_path)) return ResultCode::Invalid; DirectoryStats stats{}; From f743f100b157fbca28dda00010f6e8a8b57ea582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 27 Dec 2019 22:30:29 +0100 Subject: [PATCH 02/19] IOS/FS: Add base FST functions Some official titles rely on implementation details of Nintendo's FS sysmodule and will not work properly if those are changed. Notably, some games and older versions of the System Menu appear to be relying on the order of files returned by FS::ReadDirectory and will either fail to find their save data (for Bolt) or outright crash (for the System Menu). Some titles also actually expect filesystem metadata to be correct. One title that has been confirmed to do this is DQX, which generates paths based on the GID of files within its own title directory. While it is easy to make workarounds for these issues -- and in fact we already do have some for the sysmenu and DQX, having hacks is obviously nonideal and adding yet another hack would be required to fix Bolt -- one that would be even uglier. Furthermore, while it is currently unknown whether any official title cares about permissions, the lack of FS metadata means that we are unable to implement them if that turns out to be desirable or necessary. By adding a FST, we can implement things correctly and solve all those problems without hacks. Apart from DQX, the sysmenu and Bolt, this changeset also fixes the Photo Channel complaining about corrupted system files on the initial launch. This first commit adds the basic structures and functions that are necessary to load, save, query and update our version of the FST. For simplicity, a binary format that is inspired from Nintendo's FST structure was chosen for serialization. It is not expected to ever receive an update. PS: an update on the NAND image backend: A long time ago I had planned to add another FS backend which would be using a NAND image/blob as the storage. While I have already written an implementation that has been tested, solves all the aforementioned issues and more, produces images that are fully compatible with IOS's FS driver, I feel like NAND images raise too many issues: savestate sizes, code complexity and maintenance cost. Since many fixes and additions that are part of that implementation (e.g. FS timings, utility structures, FST) have already been merged or will be submitted as part of this changeset, I will likely not submit the branch. --- Source/Core/Core/IOS/FS/FileSystem.cpp | 7 + Source/Core/Core/IOS/FS/FileSystem.h | 12 ++ Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 181 ++++++++++++++++++++- Source/Core/Core/IOS/FS/HostBackend/FS.h | 32 ++++ 4 files changed, 231 insertions(+), 1 deletion(-) diff --git a/Source/Core/Core/IOS/FS/FileSystem.cpp b/Source/Core/Core/IOS/FS/FileSystem.cpp index 11806f313c..e1651ad99b 100644 --- a/Source/Core/Core/IOS/FS/FileSystem.cpp +++ b/Source/Core/Core/IOS/FS/FileSystem.cpp @@ -22,6 +22,13 @@ bool IsValidNonRootPath(std::string_view path) path.back() != '/'; } +SplitPathResult SplitPathAndBasename(std::string_view path) +{ + const auto last_separator = path.rfind('/'); + return {std::string(path.substr(0, std::max(1, last_separator))), + std::string(path.substr(last_separator + 1))}; +} + std::unique_ptr MakeFileSystem(Location location) { const std::string nand_root = diff --git a/Source/Core/Core/IOS/FS/FileSystem.h b/Source/Core/Core/IOS/FS/FileSystem.h index 5bed4349d0..3b6d8333f9 100644 --- a/Source/Core/Core/IOS/FS/FileSystem.h +++ b/Source/Core/Core/IOS/FS/FileSystem.h @@ -112,6 +112,8 @@ struct FileStatus u32 size; }; +/// The maximum number of components a path can have. +constexpr size_t MaxPathDepth = 8; /// The maximum number of characters a path can have. constexpr size_t MaxPathLength = 64; @@ -119,6 +121,16 @@ constexpr size_t MaxPathLength = 64; bool IsValidPath(std::string_view path); bool IsValidNonRootPath(std::string_view path); +struct SplitPathResult +{ + std::string parent; + std::string file_name; +}; +/// Split a path into a parent path and the file name. Takes a *valid non-root* path. +/// +/// Example: /shared2/sys/SYSCONF => {/shared2/sys, SYSCONF} +SplitPathResult SplitPathAndBasename(std::string_view path); + class FileSystem; class FileHandle final { diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index 65f42e228e..e5861cd60f 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -3,12 +3,19 @@ // Refer to the license.txt file included. #include +#include +#include +#include + +#include #include "Common/Assert.h" #include "Common/ChunkFile.h" #include "Common/FileUtil.h" #include "Common/Logging/Log.h" #include "Common/NandPaths.h" +#include "Common/StringUtil.h" +#include "Common/Swap.h" #include "Core/IOS/ES/ES.h" #include "Core/IOS/FS/HostBackend/FS.h" #include "Core/IOS/IOS.h" @@ -39,13 +46,186 @@ static u64 ComputeTotalFileSize(const File::FSTEntry& parent_entry) return sizeOfFiles; } +namespace +{ +struct SerializedFstEntry +{ + std::string_view GetName() const { return {name.data(), strnlen(name.data(), name.size())}; } + void SetName(std::string_view new_name) + { + std::memcpy(name.data(), new_name.data(), std::min(name.size(), new_name.length())); + } + + /// File name + std::array name{}; + /// File owner user ID + Common::BigEndianValue uid{}; + /// File owner group ID + Common::BigEndianValue gid{}; + /// Is this a file or a directory? + bool is_file = false; + /// File access modes + Modes modes{}; + /// File attribute + FileAttribute attribute{}; + /// Unknown property + Common::BigEndianValue x3{}; + /// Number of children + Common::BigEndianValue num_children{}; +}; +static_assert(std::is_standard_layout()); +static_assert(sizeof(SerializedFstEntry) == 0x20); + +template +auto GetMetadataFields(T& obj) +{ + return std::tie(obj.uid, obj.gid, obj.is_file, obj.modes, obj.attribute); +} + +auto GetNamePredicate(const std::string& name) +{ + return [&name](const auto& entry) { return entry.name == name; }; +} +} // namespace + +bool HostFileSystem::FstEntry::CheckPermission(Uid caller_uid, Gid caller_gid, + Mode requested_mode) const +{ + if (caller_uid == 0) + return true; + Mode file_mode = data.modes.other; + if (data.uid == caller_uid) + file_mode = data.modes.owner; + else if (data.gid == caller_gid) + file_mode = data.modes.group; + return (u8(requested_mode) & u8(file_mode)) == u8(requested_mode); +} + HostFileSystem::HostFileSystem(const std::string& root_path) : m_root_path{root_path} { Init(); + ResetFst(); + LoadFst(); } HostFileSystem::~HostFileSystem() = default; +std::string HostFileSystem::GetFstFilePath() const +{ + return fmt::format("{}/fst.bin", m_root_path); +} + +void HostFileSystem::ResetFst() +{ + m_root_entry = {}; + m_root_entry.name = "/"; + // Mode 0x16 (Directory | Owner_None | Group_Read | Other_Read) in the FS sysmodule + m_root_entry.data.modes = {Mode::None, Mode::Read, Mode::Read}; +} + +void HostFileSystem::LoadFst() +{ + File::IOFile file{GetFstFilePath(), "rb"}; + // Existing filesystems will not have a FST. This is not a problem, + // as the rest of HostFileSystem will use sane defaults. + if (!file) + return; + + const auto parse_entry = [&file](const auto& parse, size_t depth) -> std::optional { + if (depth > MaxPathDepth) + return std::nullopt; + + SerializedFstEntry entry; + if (!file.ReadArray(&entry, 1)) + return std::nullopt; + + FstEntry result; + result.name = entry.GetName(); + GetMetadataFields(result.data) = GetMetadataFields(entry); + for (size_t i = 0; i < entry.num_children; ++i) + { + const auto maybe_child = parse(parse, depth + 1); + if (!maybe_child.has_value()) + return std::nullopt; + result.children.push_back(*maybe_child); + } + return result; + }; + + const auto root_entry = parse_entry(parse_entry, 0); + if (!root_entry.has_value()) + { + ERROR_LOG(IOS_FS, "Failed to parse FST: at least one of the entries was invalid"); + return; + } + m_root_entry = *root_entry; +} + +void HostFileSystem::SaveFst() +{ + std::vector to_write; + auto collect_entries = [&to_write](const auto& collect, const FstEntry& entry) -> void { + SerializedFstEntry& serialized = to_write.emplace_back(); + serialized.SetName(entry.name); + GetMetadataFields(serialized) = GetMetadataFields(entry.data); + serialized.num_children = u32(entry.children.size()); + for (const FstEntry& child : entry.children) + collect(collect, child); + }; + collect_entries(collect_entries, m_root_entry); + + const std::string dest_path = GetFstFilePath(); + const std::string temp_path = File::GetTempFilenameForAtomicWrite(dest_path); + File::IOFile file{temp_path, "wb"}; + if (!file.WriteArray(to_write.data(), to_write.size()) || !File::Rename(temp_path, dest_path)) + ERROR_LOG(IOS_FS, "Failed to write new FST"); +} + +HostFileSystem::FstEntry* HostFileSystem::GetFstEntryForPath(const std::string& path) +{ + if (path == "/") + return &m_root_entry; + + if (!IsValidNonRootPath(path)) + return nullptr; + + const File::FileInfo host_file_info{BuildFilename(path)}; + if (!host_file_info.Exists()) + return nullptr; + + FstEntry* entry = &m_root_entry; + std::string complete_path = ""; + for (const std::string& component : SplitString(std::string(path.substr(1)), '/')) + { + complete_path += '/' + component; + const auto next = + std::find_if(entry->children.begin(), entry->children.end(), GetNamePredicate(component)); + if (next != entry->children.end()) + { + entry = &*next; + } + else + { + // Fall back to dummy data to avoid breaking existing filesystems. + // This code path is also reached when creating a new file or directory; + // proper metadata is filled in later. + INFO_LOG(IOS_FS, "Creating a default entry for %s", complete_path.c_str()); + entry = &entry->children.emplace_back(); + entry->name = component; + entry->data.modes = {Mode::ReadWrite, Mode::ReadWrite, Mode::ReadWrite}; + } + } + + entry->data.is_file = host_file_info.IsFile(); + if (entry->data.is_file && !entry->children.empty()) + { + WARN_LOG(IOS_FS, "%s is a file but also has children; clearing children", path.c_str()); + entry->children.clear(); + } + + return entry; +} + void HostFileSystem::DoState(PointerWrap& p) { // Temporarily close the file, to prevent any issues with the savestating of /tmp @@ -250,7 +430,6 @@ Result> HostFileSystem::ReadDirectory(Uid, Gid, const s if (!IsValidPath(path)) return ResultCode::Invalid; - // the Wii uses this function to define the type (dir or file) const std::string dir_name(BuildFilename(path)); const File::FileInfo file_info(dir_name); diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.h b/Source/Core/Core/IOS/FS/HostBackend/FS.h index 8e3a683887..a147194c1e 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.h +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.h @@ -58,6 +58,20 @@ public: Result GetDirectoryStats(const std::string& path) override; private: + struct FstEntry + { + bool CheckPermission(Uid uid, Gid gid, Mode requested_mode) const; + + std::string name; + Metadata data{}; + /// Children of this FST entry. Only valid for directories. + /// + /// We use a vector rather than a list here because iterating over children + /// happens a lot more often than removals. + /// Newly created entries are added at the end. + std::vector children; + }; + struct Handle { bool opened = false; @@ -73,6 +87,24 @@ private: std::string BuildFilename(const std::string& wii_path) const; std::shared_ptr OpenHostFile(const std::string& host_path); + std::string GetFstFilePath() const; + void ResetFst(); + void LoadFst(); + void SaveFst(); + /// Get the FST entry for a file (or directory). + /// Automatically creates fallback entries for parents if they do not exist. + /// Returns nullptr if the path is invalid or the file does not exist. + FstEntry* GetFstEntryForPath(const std::string& path); + + /// FST entry for the filesystem root. + /// + /// Note that unlike a real Wii's FST, ours is the single source of truth only for + /// filesystem metadata and ordering. File existence must be checked by querying + /// the host filesystem. + /// The reasons for this design are twofold: existing users do not have a FST + /// and we do not want FS to break if the user adds or removes files in their + /// filesystem root manually. + FstEntry m_root_entry{}; std::string m_root_path; std::map> m_open_files; std::array m_handles{}; From 0543598574b48f591bc70d3b9ede39b29459b2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Wed, 14 Mar 2018 17:11:21 +0100 Subject: [PATCH 03/19] IOS/FS: Move /tmp clearing back to the IPC interface Prevents /tmp from being cleared unnecessarily; clearing /tmp is normally only done once every time IOS is reloaded. --- Source/Core/Core/IOS/FS/FileSystem.cpp | 6 ------ Source/Core/Core/IOS/FS/FileSystem.h | 3 --- Source/Core/Core/IOS/FS/FileSystemProxy.cpp | 6 ++++++ Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 1 - 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Source/Core/Core/IOS/FS/FileSystem.cpp b/Source/Core/Core/IOS/FS/FileSystem.cpp index e1651ad99b..66bd7659ae 100644 --- a/Source/Core/Core/IOS/FS/FileSystem.cpp +++ b/Source/Core/Core/IOS/FS/FileSystem.cpp @@ -84,12 +84,6 @@ Result FileHandle::GetStatus() const return m_fs->GetFileStatus(*m_fd); } -void FileSystem::Init() -{ - if (Delete(0, 0, "/tmp") == ResultCode::Success) - CreateDirectory(0, 0, "/tmp", 0, {Mode::ReadWrite, Mode::ReadWrite, Mode::ReadWrite}); -} - Result FileSystem::CreateAndOpenFile(Uid uid, Gid gid, const std::string& path, Modes modes) { diff --git a/Source/Core/Core/IOS/FS/FileSystem.h b/Source/Core/Core/IOS/FS/FileSystem.h index 3b6d8333f9..8b4b59a858 100644 --- a/Source/Core/Core/IOS/FS/FileSystem.h +++ b/Source/Core/Core/IOS/FS/FileSystem.h @@ -216,9 +216,6 @@ public: virtual Result GetNandStats() = 0; /// Get usage information about a directory (used cluster and inode counts). virtual Result GetDirectoryStats(const std::string& path) = 0; - -protected: - void Init(); }; template diff --git a/Source/Core/Core/IOS/FS/FileSystemProxy.cpp b/Source/Core/Core/IOS/FS/FileSystemProxy.cpp index 6a4bcd929d..181a739748 100644 --- a/Source/Core/Core/IOS/FS/FileSystemProxy.cpp +++ b/Source/Core/Core/IOS/FS/FileSystemProxy.cpp @@ -16,6 +16,7 @@ #include "Core/HW/Memmap.h" #include "Core/HW/SystemTimers.h" #include "Core/IOS/FS/FileSystem.h" +#include "Core/IOS/Uids.h" namespace IOS::HLE::Device { @@ -37,6 +38,11 @@ constexpr size_t CLUSTER_DATA_SIZE = 0x4000; FS::FS(Kernel& ios, const std::string& device_name) : Device(ios, device_name) { + if (ios.GetFS()->Delete(PID_KERNEL, PID_KERNEL, "/tmp") == ResultCode::Success) + { + ios.GetFS()->CreateDirectory(PID_KERNEL, PID_KERNEL, "/tmp", 0, + {Mode::ReadWrite, Mode::ReadWrite, Mode::ReadWrite}); + } } void FS::DoState(PointerWrap& p) diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index e5861cd60f..9ee4dc9fd1 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -103,7 +103,6 @@ bool HostFileSystem::FstEntry::CheckPermission(Uid caller_uid, Gid caller_gid, HostFileSystem::HostFileSystem(const std::string& root_path) : m_root_path{root_path} { - Init(); ResetFst(); LoadFst(); } From a83d9e56000fef7212d666d08a0f5a6e7e7b39c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 29 Dec 2019 17:29:53 +0100 Subject: [PATCH 04/19] IOS/FS: Make sure FS root directory exists Previously, the FS root directory would get created as a side effect of calling CreateDirectory during boot (since the implementation was sloppy and used File::CreateFullDir). Since CreateDirectory no longer does that, it is necessary to ensure that the FS root directory does exist by creating it explicitly. --- Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index 9ee4dc9fd1..4cff7bfb72 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -103,6 +103,7 @@ bool HostFileSystem::FstEntry::CheckPermission(Uid caller_uid, Gid caller_gid, HostFileSystem::HostFileSystem(const std::string& root_path) : m_root_path{root_path} { + File::CreateFullPath(m_root_path + "/"); ResetFst(); LoadFst(); } From 36676d2628a5fbcbf73badb3995c942283b838bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sat, 28 Dec 2019 20:31:21 +0100 Subject: [PATCH 05/19] IOS/FS: Implement Format properly --- Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index 4cff7bfb72..4f67a30a03 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -334,9 +334,17 @@ void HostFileSystem::DoState(PointerWrap& p) ResultCode HostFileSystem::Format(Uid uid) { + if (uid != 0) + return ResultCode::AccessDenied; + if (m_root_path.empty()) + return ResultCode::AccessDenied; const std::string root = BuildFilename("/"); if (!File::DeleteDirRecursively(root) || !File::CreateDir(root)) return ResultCode::UnknownError; + ResetFst(); + SaveFst(); + // Reset and close all handles. + m_handles = {}; return ResultCode::Success; } From 8f74d02659fe633f991b95422c731b784d6146a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sat, 4 Jan 2020 01:55:40 +0100 Subject: [PATCH 06/19] Core: Fix a few misuses of FS::CreateDirectory CreateDirectory does not create missing parent directories. If that behaviour is desired, CreateFullPath should be used instead. (These small misuses went unnoticed since the previous implementation of CreateDirectory automatically created parent directories.) --- Source/Core/Core/NetPlayClient.cpp | 8 ++++---- Source/Core/Core/WiiRoot.cpp | 20 +++++++------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index c64a2769a4..e769fc599b 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -904,8 +904,8 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) { auto buffer = DecompressPacketIntoBuffer(packet); - temp_fs->CreateDirectory(IOS::PID_KERNEL, IOS::PID_KERNEL, "/shared2/menu/FaceLib", 0, - fs_modes); + temp_fs->CreateFullPath(IOS::PID_KERNEL, IOS::PID_KERNEL, "/shared2/menu/FaceLib/", 0, + fs_modes); auto file = temp_fs->CreateAndOpenFile(IOS::PID_KERNEL, IOS::PID_KERNEL, Common::GetMiiDatabasePath(), fs_modes); @@ -924,8 +924,8 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) { u64 title_id = Common::PacketReadU64(packet); titles.push_back(title_id); - temp_fs->CreateDirectory(IOS::PID_KERNEL, IOS::PID_KERNEL, - Common::GetTitleDataPath(title_id), 0, fs_modes); + temp_fs->CreateFullPath(IOS::PID_KERNEL, IOS::PID_KERNEL, + Common::GetTitleDataPath(title_id) + '/', 0, fs_modes); auto save = WiiSave::MakeNandStorage(temp_fs.get(), title_id); bool exists; diff --git a/Source/Core/Core/WiiRoot.cpp b/Source/Core/Core/WiiRoot.cpp index 06be93900a..538cc394fb 100644 --- a/Source/Core/Core/WiiRoot.cpp +++ b/Source/Core/Core/WiiRoot.cpp @@ -34,9 +34,8 @@ static std::string s_temp_wii_root; static void CopySave(FS::FileSystem* source, FS::FileSystem* dest, const u64 title_id) { - dest->CreateDirectory(IOS::PID_KERNEL, IOS::PID_KERNEL, Common::GetTitleDataPath(title_id), 0, - {IOS::HLE::FS::Mode::ReadWrite, IOS::HLE::FS::Mode::ReadWrite, - IOS::HLE::FS::Mode::ReadWrite}); + dest->CreateFullPath(IOS::PID_KERNEL, IOS::PID_KERNEL, Common::GetTitleDataPath(title_id) + '/', + 0, {FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite}); const auto source_save = WiiSave::MakeNandStorage(source, title_id); const auto dest_save = WiiSave::MakeNandStorage(dest, title_id); WiiSave::Copy(source_save.get(), dest_save.get()); @@ -49,9 +48,8 @@ static bool CopyNandFile(FS::FileSystem* source_fs, const std::string& source_fi if (last_slash != std::string::npos && last_slash > 0) { const std::string dir = dest_file.substr(0, last_slash); - dest_fs->CreateDirectory(IOS::PID_KERNEL, IOS::PID_KERNEL, dir, 0, - {IOS::HLE::FS::Mode::ReadWrite, IOS::HLE::FS::Mode::ReadWrite, - IOS::HLE::FS::Mode::ReadWrite}); + dest_fs->CreateFullPath(IOS::PID_KERNEL, IOS::PID_KERNEL, dir + '/', 0, + {FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite}); } auto source_handle = @@ -190,7 +188,7 @@ static bool CopySysmenuFilesToFS(FS::FileSystem* fs, const std::string& host_sou if (entry.isDirectory) { - fs->CreateDirectory(IOS::SYSMENU_UID, IOS::SYSMENU_GID, nand_path, 0, public_modes); + fs->CreateFullPath(IOS::SYSMENU_UID, IOS::SYSMENU_GID, nand_path + '/', 0, public_modes); if (!CopySysmenuFilesToFS(fs, host_path, nand_path)) return false; } @@ -259,12 +257,8 @@ void CleanUpWiiFileSystemContents() // FS won't write the save if the directory doesn't exist const std::string title_path = Common::GetTitleDataPath(title_id); - if (!configured_fs->GetMetadata(IOS::PID_KERNEL, IOS::PID_KERNEL, title_path)) - { - configured_fs->CreateDirectory(IOS::PID_KERNEL, IOS::PID_KERNEL, title_path, 0, - {IOS::HLE::FS::Mode::ReadWrite, IOS::HLE::FS::Mode::ReadWrite, - IOS::HLE::FS::Mode::ReadWrite}); - } + configured_fs->CreateFullPath(IOS::PID_KERNEL, IOS::PID_KERNEL, title_path + '/', 0, + {FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite}); const auto user_save = WiiSave::MakeNandStorage(configured_fs.get(), title_id); From 8517528f8c094f51a3c4dab5922e894232225820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sat, 28 Dec 2019 20:44:16 +0100 Subject: [PATCH 07/19] IOS/FS: Implement CreateFile and CreateDir properly --- Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 59 +++++++++++++++------- Source/Core/Core/IOS/FS/HostBackend/FS.h | 3 ++ 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index 4f67a30a03..8cb9127a91 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -348,36 +348,57 @@ ResultCode HostFileSystem::Format(Uid uid) return ResultCode::Success; } -ResultCode HostFileSystem::CreateFile(Uid, Gid, const std::string& path, FileAttribute, Modes) +ResultCode HostFileSystem::CreateFileOrDirectory(Uid uid, Gid gid, const std::string& path, + FileAttribute attr, Modes modes, bool is_file) { - std::string file_name(BuildFilename(path)); - // check if the file already exist - if (File::Exists(file_name)) + if (!IsValidNonRootPath(path) || !std::all_of(path.begin(), path.end(), IsPrintableCharacter)) + return ResultCode::Invalid; + + if (!is_file && std::count(path.begin(), path.end(), '/') > int(MaxPathDepth)) + return ResultCode::TooManyPathComponents; + + const auto split_path = SplitPathAndBasename(path); + const std::string host_path = BuildFilename(path); + + FstEntry* parent = GetFstEntryForPath(split_path.parent); + if (!parent) + return ResultCode::NotFound; + + if (!parent->CheckPermission(uid, gid, Mode::Write)) + return ResultCode::AccessDenied; + + if (File::Exists(host_path)) return ResultCode::AlreadyExists; - // create the file - File::CreateFullPath(file_name); // just to be sure - if (!File::CreateEmptyFile(file_name)) + const bool ok = is_file ? File::CreateEmptyFile(host_path) : File::CreateDir(host_path); + if (!ok) { - ERROR_LOG(IOS_FS, "couldn't create new file"); - return ResultCode::Invalid; + ERROR_LOG(IOS_FS, "Failed to create file or directory: %s", host_path.c_str()); + return ResultCode::UnknownError; } + FstEntry* child = GetFstEntryForPath(path); + *child = {}; + child->name = split_path.file_name; + child->data.is_file = is_file; + child->data.modes = modes; + child->data.uid = uid; + child->data.gid = gid; + child->data.attribute = attr; + SaveFst(); return ResultCode::Success; } -ResultCode HostFileSystem::CreateDirectory(Uid, Gid, const std::string& path, FileAttribute, Modes) +ResultCode HostFileSystem::CreateFile(Uid uid, Gid gid, const std::string& path, FileAttribute attr, + Modes modes) { - if (!IsValidPath(path)) - return ResultCode::Invalid; + return CreateFileOrDirectory(uid, gid, path, attr, modes, true); +} - std::string name(BuildFilename(path)); - - name += "/"; - File::CreateFullPath(name); - DEBUG_ASSERT_MSG(IOS_FS, File::IsDirectory(name), "CREATE_DIR %s failed", name.c_str()); - - return ResultCode::Success; +ResultCode HostFileSystem::CreateDirectory(Uid uid, Gid gid, const std::string& path, + FileAttribute attr, Modes modes) +{ + return CreateFileOrDirectory(uid, gid, path, attr, modes, false); } ResultCode HostFileSystem::Delete(Uid, Gid, const std::string& path) diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.h b/Source/Core/Core/IOS/FS/HostBackend/FS.h index a147194c1e..fa4269d952 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.h +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.h @@ -87,6 +87,9 @@ private: std::string BuildFilename(const std::string& wii_path) const; std::shared_ptr OpenHostFile(const std::string& host_path); + ResultCode CreateFileOrDirectory(Uid uid, Gid gid, const std::string& path, + FileAttribute attribute, Modes modes, bool is_file); + std::string GetFstFilePath() const; void ResetFst(); void LoadFst(); From 53ceb6c6935e4913316fbe6df277ff5bc083515d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sat, 28 Dec 2019 21:01:43 +0100 Subject: [PATCH 08/19] IOS/FS: Implement Delete properly --- Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 48 ++++++++++++++++++---- Source/Core/Core/IOS/FS/HostBackend/FS.h | 2 + 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index 8cb9127a91..581d092390 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -401,18 +401,50 @@ ResultCode HostFileSystem::CreateDirectory(Uid uid, Gid gid, const std::string& return CreateFileOrDirectory(uid, gid, path, attr, modes, false); } -ResultCode HostFileSystem::Delete(Uid, Gid, const std::string& path) +bool HostFileSystem::IsFileOpened(const std::string& path) const { - if (!IsValidPath(path)) + return std::any_of(m_handles.begin(), m_handles.end(), [&path](const Handle& handle) { + return handle.opened && handle.wii_path == path; + }); +} + +bool HostFileSystem::IsDirectoryInUse(const std::string& path) const +{ + return std::any_of(m_handles.begin(), m_handles.end(), [&path](const Handle& handle) { + return handle.opened && StringBeginsWith(handle.wii_path, path); + }); +} + +ResultCode HostFileSystem::Delete(Uid uid, Gid gid, const std::string& path) +{ + if (!IsValidNonRootPath(path)) return ResultCode::Invalid; - const std::string file_name = BuildFilename(path); - if (File::Delete(file_name)) - INFO_LOG(IOS_FS, "DeleteFile %s", file_name.c_str()); - else if (File::DeleteDirRecursively(file_name)) - INFO_LOG(IOS_FS, "DeleteDir %s", file_name.c_str()); + const std::string host_path = BuildFilename(path); + const auto split_path = SplitPathAndBasename(path); + + FstEntry* parent = GetFstEntryForPath(split_path.parent); + if (!parent) + return ResultCode::NotFound; + + if (!parent->CheckPermission(uid, gid, Mode::Write)) + return ResultCode::AccessDenied; + + if (!File::Exists(host_path)) + return ResultCode::NotFound; + + if (File::IsFile(host_path) && !IsFileOpened(path)) + File::Delete(host_path); + else if (File::IsDirectory(host_path) && !IsDirectoryInUse(path)) + File::DeleteDirRecursively(host_path); else - WARN_LOG(IOS_FS, "DeleteFile %s - failed!!!", file_name.c_str()); + return ResultCode::InUse; + + const auto it = std::find_if(parent->children.begin(), parent->children.end(), + GetNamePredicate(split_path.file_name)); + if (it != parent->children.end()) + parent->children.erase(it); + SaveFst(); return ResultCode::Success; } diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.h b/Source/Core/Core/IOS/FS/HostBackend/FS.h index fa4269d952..beb4d56148 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.h +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.h @@ -89,6 +89,8 @@ private: ResultCode CreateFileOrDirectory(Uid uid, Gid gid, const std::string& path, FileAttribute attribute, Modes modes, bool is_file); + bool IsFileOpened(const std::string& path) const; + bool IsDirectoryInUse(const std::string& path) const; std::string GetFstFilePath() const; void ResetFst(); From a40f297d1d46255ac8e4655148738be82de672d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sat, 28 Dec 2019 21:32:35 +0100 Subject: [PATCH 09/19] IOS/FS: Implement Rename properly --- Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 64 +++++++++++++++++----- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index 581d092390..92ce3aeff0 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -449,40 +449,74 @@ ResultCode HostFileSystem::Delete(Uid uid, Gid gid, const std::string& path) return ResultCode::Success; } -ResultCode HostFileSystem::Rename(Uid, Gid, const std::string& old_path, +ResultCode HostFileSystem::Rename(Uid uid, Gid gid, const std::string& old_path, const std::string& new_path) { - if (!IsValidPath(old_path)) + if (!IsValidNonRootPath(old_path) || !IsValidNonRootPath(new_path)) return ResultCode::Invalid; - const std::string old_name = BuildFilename(old_path); - if (!IsValidPath(new_path)) + const auto split_old_path = SplitPathAndBasename(old_path); + const auto split_new_path = SplitPathAndBasename(new_path); + + FstEntry* old_parent = GetFstEntryForPath(split_old_path.parent); + FstEntry* new_parent = GetFstEntryForPath(split_new_path.parent); + if (!old_parent || !new_parent) + return ResultCode::NotFound; + + if (!old_parent->CheckPermission(uid, gid, Mode::Write) || + !new_parent->CheckPermission(uid, gid, Mode::Write)) + { + return ResultCode::AccessDenied; + } + + FstEntry* entry = GetFstEntryForPath(old_path); + if (!entry) + return ResultCode::NotFound; + + // For files, the file name is not allowed to change. + if (entry->data.is_file && split_old_path.file_name != split_new_path.file_name) return ResultCode::Invalid; - const std::string new_name = BuildFilename(new_path); - // try to make the basis directory - File::CreateFullPath(new_name); + if ((!entry->data.is_file && IsDirectoryInUse(old_path)) || + (entry->data.is_file && IsFileOpened(old_path))) + { + return ResultCode::InUse; + } + + const std::string host_old_path = BuildFilename(old_path); + const std::string host_new_path = BuildFilename(new_path); // If there is already something of the same type at the new path, delete it. - if (File::Exists(new_name)) + if (File::Exists(host_new_path)) { - const bool old_is_file = File::IsFile(old_name); - const bool new_is_file = File::IsFile(new_name); + const bool old_is_file = File::IsFile(host_old_path); + const bool new_is_file = File::IsFile(host_new_path); if (old_is_file && new_is_file) - File::Delete(new_name); + File::Delete(host_new_path); else if (!old_is_file && !new_is_file) - File::DeleteDirRecursively(new_name); + File::DeleteDirRecursively(host_new_path); else return ResultCode::Invalid; } - // finally try to rename the file - if (!File::Rename(old_name, new_name)) + if (!File::Rename(host_old_path, host_new_path)) { - ERROR_LOG(IOS_FS, "Rename %s to %s - failed", old_name.c_str(), new_name.c_str()); + ERROR_LOG(IOS_FS, "Rename %s to %s - failed", host_old_path.c_str(), host_new_path.c_str()); return ResultCode::NotFound; } + // Finally, remove the child from the old parent and move it to the new parent. + const auto it = std::find_if(old_parent->children.begin(), old_parent->children.end(), + GetNamePredicate(split_old_path.file_name)); + FstEntry* new_entry = GetFstEntryForPath(new_path); + if (it != old_parent->children.end()) + { + *new_entry = *it; + old_parent->children.erase(it); + } + new_entry->name = split_new_path.file_name; + SaveFst(); + return ResultCode::Success; } From 396429d58219015918d02a5ff5b16b3757a41fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 29 Dec 2019 12:26:40 +0100 Subject: [PATCH 10/19] IOS/FS: Implement ReadDirectory properly and remove sorting hack With the CreateFile/CreateDirectory fix and this commit, we can finally return correct results in ReadDirectory and the sorting hack -- whose purpose was to prevent certain versions of the System Menu from crashing -- can be removed too. --- Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 61 ++++++++++++++-------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index 92ce3aeff0..766eea90f8 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -520,45 +521,61 @@ ResultCode HostFileSystem::Rename(Uid uid, Gid gid, const std::string& old_path, return ResultCode::Success; } -Result> HostFileSystem::ReadDirectory(Uid, Gid, const std::string& path) +Result> HostFileSystem::ReadDirectory(Uid uid, Gid gid, + const std::string& path) { if (!IsValidPath(path)) return ResultCode::Invalid; - const std::string dir_name(BuildFilename(path)); - - const File::FileInfo file_info(dir_name); - - if (!file_info.Exists()) - { - WARN_LOG(IOS_FS, "Search not found: %s", dir_name.c_str()); + const FstEntry* entry = GetFstEntryForPath(path); + if (!entry) return ResultCode::NotFound; - } - if (!file_info.IsDirectory()) - { - // It's not a directory, so error. + if (!entry->CheckPermission(uid, gid, Mode::Read)) + return ResultCode::AccessDenied; + + if (entry->data.is_file) return ResultCode::Invalid; - } - File::FSTEntry entry = File::ScanDirectoryTree(dir_name, false); - - for (File::FSTEntry& child : entry.children) + const std::string host_path = BuildFilename(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); } - // NOTE(leoetlino): this is absolutely wrong, but there is no way to fix this properly - // if we use the host filesystem. - std::sort(entry.children.begin(), entry.children.end(), - [](const File::FSTEntry& one, const File::FSTEntry& two) { - return one.virtualName < two.virtualName; + // Sort files according to their order in the FST tree (issue 10234). + // The result should look like this: + // [FilesNotInFst, ..., OldestFileInFst, ..., NewestFileInFst] + std::unordered_map sort_keys; + sort_keys.reserve(entry->children.size()); + for (size_t i = 0; i < entry->children.size(); ++i) + sort_keys.emplace(entry->children[i].name, int(i)); + + const auto get_key = [&sort_keys](std::string_view key) { + const auto it = sort_keys.find(key); + // As a fallback, files that are not in the FST are put at the beginning. + return it != sort_keys.end() ? it->second : -1; + }; + + // Now sort in reverse order because Nintendo traverses a linked list + // in which new elements are inserted at the front. + std::sort(host_entry.children.begin(), host_entry.children.end(), + [&get_key](const File::FSTEntry& one, const File::FSTEntry& two) { + const int key1 = get_key(one.virtualName); + const int key2 = get_key(two.virtualName); + if (key1 != key2) + return key1 > key2; + + // For files that are not in the FST, sort lexicographically to ensure that + // results are consistent no matter what the underlying filesystem is. + return one.virtualName > two.virtualName; }); std::vector output; - for (File::FSTEntry& child : entry.children) + for (const File::FSTEntry& child : host_entry.children) output.emplace_back(child.virtualName); return output; } From e4dd582d1d69285da40a5524702e8cd231fbc24a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 29 Dec 2019 13:56:48 +0100 Subject: [PATCH 11/19] IOS/FS: Implement GetMetadata properly and remove GID hack Now that all FS functions that create new inodes are properly implemented, we can make GetMetadata actually return correct file metadata rather than giving fixed information. The hack for the DQX installer can also be removed now since our ES and FS keep track of caller UID/GIDs now. --- Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 46 ++++++++++------------ 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index 766eea90f8..1a364436af 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -580,36 +580,32 @@ Result> HostFileSystem::ReadDirectory(Uid uid, Gid gid, return output; } -Result HostFileSystem::GetMetadata(Uid, Gid, const std::string& path) +Result HostFileSystem::GetMetadata(Uid uid, Gid gid, const std::string& path) { - Metadata metadata; - metadata.uid = 0; - metadata.gid = 0x3031; // this is also known as makercd, 01 (0x3031) for nintendo and 08 - // (0x3038) for MH3 etc - - if (!IsValidPath(path)) - return ResultCode::Invalid; - - std::string file_name = BuildFilename(path); - metadata.modes = {Mode::ReadWrite, Mode::ReadWrite, Mode::ReadWrite}; - metadata.attribute = 0x00; // no attributes - - // Hack: if the path that is being accessed is within an installed title directory, get the - // UID/GID from the installed title TMD. - Kernel* ios = GetIOS(); - u64 title_id; - if (ios && IsTitlePath(file_name, Common::FROM_SESSION_ROOT, &title_id)) + const FstEntry* entry = nullptr; + if (path == "/") { - IOS::ES::TMDReader tmd = ios->GetES()->FindInstalledTMD(title_id); - if (tmd.IsValid()) - metadata.gid = tmd.GetGroupId(); + entry = &m_root_entry; + } + else + { + if (!IsValidNonRootPath(path)) + return ResultCode::Invalid; + + const auto split_path = SplitPathAndBasename(path); + const FstEntry* parent = GetFstEntryForPath(split_path.parent); + if (!parent) + return ResultCode::NotFound; + if (!parent->CheckPermission(uid, gid, Mode::Read)) + return ResultCode::AccessDenied; + entry = GetFstEntryForPath(path); } - const File::FileInfo info{file_name}; - metadata.is_file = info.IsFile(); - metadata.size = info.GetSize(); - if (!info.Exists()) + if (!entry) return ResultCode::NotFound; + + Metadata metadata = entry->data; + metadata.size = File::GetSize(BuildFilename(path)); return metadata; } From 142b7e048b5243c42cf8b24f244187cb848ea7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 29 Dec 2019 14:01:39 +0100 Subject: [PATCH 12/19] IOS/FS: Actually implement SetMetadata --- Source/Core/Core/IOS/FS/HostBackend/FS.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index 1a364436af..fea5d16261 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -610,10 +610,30 @@ Result HostFileSystem::GetMetadata(Uid uid, Gid gid, const std::string } ResultCode HostFileSystem::SetMetadata(Uid caller_uid, const std::string& path, Uid uid, Gid gid, - FileAttribute, Modes) + FileAttribute attr, Modes modes) { if (!IsValidPath(path)) return ResultCode::Invalid; + + FstEntry* entry = GetFstEntryForPath(path); + if (!entry) + return ResultCode::NotFound; + + if (caller_uid != 0 && caller_uid != entry->data.uid) + return ResultCode::AccessDenied; + if (caller_uid != 0 && uid != entry->data.uid) + return ResultCode::AccessDenied; + + const bool is_empty = File::GetSize(BuildFilename(path)) == 0; + if (entry->data.uid != uid && entry->data.is_file && !is_empty) + return ResultCode::FileNotEmpty; + + entry->data.gid = gid; + entry->data.uid = uid; + entry->data.attribute = attr; + entry->data.modes = modes; + SaveFst(); + return ResultCode::Success; } From 484cfb93288ee593d860e824ee466dc244f72ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 29 Dec 2019 15:42:16 +0100 Subject: [PATCH 13/19] UnitTests/FS: Add metadata tests --- Source/Core/Core/IOS/FS/FileSystem.h | 10 ++++++++ .../UnitTests/Core/IOS/FS/FileSystemTest.cpp | 25 ++++++++++++------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Source/Core/Core/IOS/FS/FileSystem.h b/Source/Core/Core/IOS/FS/FileSystem.h index 8b4b59a858..3b3c8e9ede 100644 --- a/Source/Core/Core/IOS/FS/FileSystem.h +++ b/Source/Core/Core/IOS/FS/FileSystem.h @@ -77,6 +77,16 @@ struct Modes { Mode owner, group, other; }; +inline bool operator==(const Modes& lhs, const Modes& rhs) +{ + const auto fields = [](const Modes& obj) { return std::tie(obj.owner, obj.group, obj.other); }; + return fields(lhs) == fields(rhs); +} + +inline bool operator!=(const Modes& lhs, const Modes& rhs) +{ + return !(lhs == rhs); +} struct Metadata { diff --git a/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp b/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp index 3a4f443139..c84caf6f6a 100644 --- a/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp +++ b/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp @@ -52,14 +52,18 @@ TEST_F(FileSystemTest, CreateFile) { const std::string PATH = "/tmp/f"; - ASSERT_EQ(m_fs->CreateFile(Uid{0}, Gid{0}, PATH, 0, modes), ResultCode::Success); + constexpr u8 ArbitraryAttribute = 0xE1; + + ASSERT_EQ(m_fs->CreateFile(Uid{0}, Gid{0}, PATH, ArbitraryAttribute, modes), ResultCode::Success); const Result stats = m_fs->GetMetadata(Uid{0}, Gid{0}, PATH); ASSERT_TRUE(stats.Succeeded()); EXPECT_TRUE(stats->is_file); EXPECT_EQ(stats->size, 0u); - // TODO: After we start saving metadata correctly, check the UID, GID, permissions - // as well (issue 10234). + EXPECT_EQ(stats->uid, 0); + EXPECT_EQ(stats->gid, 0); + EXPECT_EQ(stats->modes, modes); + EXPECT_EQ(stats->attribute, ArbitraryAttribute); ASSERT_EQ(m_fs->CreateFile(Uid{0}, Gid{0}, PATH, 0, modes), ResultCode::AlreadyExists); @@ -72,21 +76,24 @@ TEST_F(FileSystemTest, CreateDirectory) { const std::string PATH = "/tmp/d"; - ASSERT_EQ(m_fs->CreateDirectory(Uid{0}, Gid{0}, PATH, 0, modes), ResultCode::Success); + constexpr u8 ArbitraryAttribute = 0x20; + + ASSERT_EQ(m_fs->CreateDirectory(Uid{0}, Gid{0}, PATH, ArbitraryAttribute, modes), + ResultCode::Success); const Result stats = m_fs->GetMetadata(Uid{0}, Gid{0}, PATH); ASSERT_TRUE(stats.Succeeded()); EXPECT_FALSE(stats->is_file); - // TODO: After we start saving metadata correctly, check the UID, GID, permissions - // as well (issue 10234). + EXPECT_EQ(stats->uid, 0); + EXPECT_EQ(stats->gid, 0); + EXPECT_EQ(stats->modes, modes); + EXPECT_EQ(stats->attribute, ArbitraryAttribute); const Result> children = m_fs->ReadDirectory(Uid{0}, Gid{0}, PATH); ASSERT_TRUE(children.Succeeded()); EXPECT_TRUE(children->empty()); - // TODO: uncomment this after the FS code is fixed to return AlreadyExists. - // EXPECT_EQ(m_fs->CreateDirectory(Uid{0}, Gid{0}, PATH, 0, Mode::Read, Mode::None, Mode::None), - // ResultCode::AlreadyExists); + EXPECT_EQ(m_fs->CreateDirectory(Uid{0}, Gid{0}, PATH, 0, modes), ResultCode::AlreadyExists); } TEST_F(FileSystemTest, Delete) From d4ba0acb3a6422da70951c9ea0b50895d8b2666f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 29 Dec 2019 16:13:26 +0100 Subject: [PATCH 14/19] UnitTests/FS: Add path validity and splitting tests --- Source/Core/Core/IOS/FS/FileSystem.h | 13 ++++++ .../UnitTests/Core/IOS/FS/FileSystemTest.cpp | 46 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/Source/Core/Core/IOS/FS/FileSystem.h b/Source/Core/Core/IOS/FS/FileSystem.h index 3b3c8e9ede..7d53b6ca0f 100644 --- a/Source/Core/Core/IOS/FS/FileSystem.h +++ b/Source/Core/Core/IOS/FS/FileSystem.h @@ -136,6 +136,19 @@ struct SplitPathResult std::string parent; std::string file_name; }; +inline bool operator==(const SplitPathResult& lhs, const SplitPathResult& rhs) +{ + const auto fields = [](const SplitPathResult& obj) { + return std::tie(obj.parent, obj.file_name); + }; + return fields(lhs) == fields(rhs); +} + +inline bool operator!=(const SplitPathResult& lhs, const SplitPathResult& rhs) +{ + return !(lhs == rhs); +} + /// Split a path into a parent path and the file name. Takes a *valid non-root* path. /// /// Example: /shared2/sys/SYSCONF => {/shared2/sys, SYSCONF} diff --git a/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp b/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp index c84caf6f6a..0f65bcd0ce 100644 --- a/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp +++ b/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp @@ -39,6 +39,41 @@ private: std::string m_profile_path; }; +TEST(FileSystem, BasicPathValidity) +{ + EXPECT_TRUE(IsValidPath("/")); + EXPECT_FALSE(IsValidNonRootPath("/")); + + EXPECT_TRUE(IsValidNonRootPath("/shared2/sys/SYSCONF")); + EXPECT_TRUE(IsValidNonRootPath("/shared2/sys")); + EXPECT_TRUE(IsValidNonRootPath("/shared2")); + + // Paths must start with /. + EXPECT_FALSE(IsValidNonRootPath("\\test")); + // Paths must not end with /. + EXPECT_FALSE(IsValidNonRootPath("/shared2/sys/")); + // Paths must not be longer than 64 characters. + EXPECT_FALSE(IsValidPath( + "/abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz")); +} + +TEST(FileSystem, PathSplitting) +{ + SplitPathResult result; + + result = {"/shared1", "00000042.app"}; + EXPECT_EQ(SplitPathAndBasename("/shared1/00000042.app"), result); + + result = {"/shared2/sys", "SYSCONF"}; + EXPECT_EQ(SplitPathAndBasename("/shared2/sys/SYSCONF"), result); + + result = {"/shared2", "sys"}; + EXPECT_EQ(SplitPathAndBasename("/shared2/sys"), result); + + result = {"/", "shared2"}; + EXPECT_EQ(SplitPathAndBasename("/shared2"), result); +} + TEST_F(FileSystemTest, EssentialDirectories) { for (const std::string& path : @@ -70,6 +105,13 @@ TEST_F(FileSystemTest, CreateFile) const Result> tmp_files = m_fs->ReadDirectory(Uid{0}, Gid{0}, "/tmp"); ASSERT_TRUE(tmp_files.Succeeded()); EXPECT_EQ(std::count(tmp_files->begin(), tmp_files->end(), "f"), 1u); + + // Test invalid paths + // Unprintable characters + EXPECT_EQ(m_fs->CreateFile(Uid{0}, Gid{0}, "/tmp/tes\1t", 0, modes), ResultCode::Invalid); + EXPECT_EQ(m_fs->CreateFile(Uid{0}, Gid{0}, "/tmp/te\x7fst", 0, modes), ResultCode::Invalid); + // Paths with too many components are not rejected for files. + EXPECT_EQ(m_fs->CreateFile(Uid{0}, Gid{0}, "/1/2/3/4/5/6/7/8/9", 0, modes), ResultCode::NotFound); } TEST_F(FileSystemTest, CreateDirectory) @@ -94,6 +136,10 @@ TEST_F(FileSystemTest, CreateDirectory) EXPECT_TRUE(children->empty()); EXPECT_EQ(m_fs->CreateDirectory(Uid{0}, Gid{0}, PATH, 0, modes), ResultCode::AlreadyExists); + + // Paths with too many components should be rejected. + EXPECT_EQ(m_fs->CreateDirectory(Uid{0}, Gid{0}, "/1/2/3/4/5/6/7/8/9", 0, modes), + ResultCode::TooManyPathComponents); } TEST_F(FileSystemTest, Delete) From 8789a6ddb38df1559201582c2fe946479643e14b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 29 Dec 2019 18:14:51 +0100 Subject: [PATCH 15/19] UnitTests/FS: Fix file rename tests Files cannot be given a different file name, only moved across directories. Add a test for that behaviour and fix the existing RenameWithExistingTargetFile test. --- .../UnitTests/Core/IOS/FS/FileSystemTest.cpp | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp b/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp index 0f65bcd0ce..39936678b8 100644 --- a/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp +++ b/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp @@ -157,6 +157,14 @@ TEST_F(FileSystemTest, Rename) EXPECT_EQ(m_fs->ReadDirectory(Uid{0}, Gid{0}, "/tmp").Error(), ResultCode::NotFound); EXPECT_TRUE(m_fs->ReadDirectory(Uid{0}, Gid{0}, "/test").Succeeded()); + + // Rename /test back to /tmp. + EXPECT_EQ(m_fs->Rename(Uid{0}, Gid{0}, "/test", "/tmp"), ResultCode::Success); + + // Create a file called /tmp/f1, and rename it to /tmp/f2. + // This should not work; file name changes are not allowed for files. + ASSERT_EQ(m_fs->CreateFile(Uid{0}, Gid{0}, "/tmp/f1", 0, modes), ResultCode::Success); + EXPECT_EQ(m_fs->Rename(Uid{0}, Gid{0}, "/tmp/f1", "/tmp/f2"), ResultCode::Invalid); } TEST_F(FileSystemTest, RenameWithExistingTargetDirectory) @@ -177,26 +185,29 @@ TEST_F(FileSystemTest, RenameWithExistingTargetDirectory) TEST_F(FileSystemTest, RenameWithExistingTargetFile) { + const std::string source_path = "/sys/f2"; + const std::string dest_path = "/tmp/f2"; + // Create the test source file and write some data (so that we can check its size later on). - ASSERT_EQ(m_fs->CreateFile(Uid{0}, Gid{0}, "/tmp/f1", 0, modes), ResultCode::Success); + ASSERT_EQ(m_fs->CreateFile(Uid{0}, Gid{0}, source_path, 0, modes), ResultCode::Success); const std::vector TEST_DATA{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}; std::vector read_buffer(TEST_DATA.size()); { - const Result file = m_fs->OpenFile(Uid{0}, Gid{0}, "/tmp/f1", Mode::ReadWrite); + const Result file = m_fs->OpenFile(Uid{0}, Gid{0}, source_path, Mode::ReadWrite); ASSERT_TRUE(file.Succeeded()); ASSERT_TRUE(file->Write(TEST_DATA.data(), TEST_DATA.size()).Succeeded()); } // Create the test target file and leave it empty. - ASSERT_EQ(m_fs->CreateFile(Uid{0}, Gid{0}, "/tmp/f2", 0, modes), ResultCode::Success); + ASSERT_EQ(m_fs->CreateFile(Uid{0}, Gid{0}, dest_path, 0, modes), ResultCode::Success); - // Rename f1 to f2 and check that f1 replaced f2. - EXPECT_EQ(m_fs->Rename(Uid{0}, Gid{0}, "/tmp/f1", "/tmp/f2"), ResultCode::Success); + // Rename /sys/f2 to /tmp/f2 and check that f1 replaced f2. + EXPECT_EQ(m_fs->Rename(Uid{0}, Gid{0}, source_path, dest_path), ResultCode::Success); - ASSERT_FALSE(m_fs->GetMetadata(Uid{0}, Gid{0}, "/tmp/f1").Succeeded()); - EXPECT_EQ(m_fs->GetMetadata(Uid{0}, Gid{0}, "/tmp/f1").Error(), ResultCode::NotFound); + ASSERT_FALSE(m_fs->GetMetadata(Uid{0}, Gid{0}, source_path).Succeeded()); + EXPECT_EQ(m_fs->GetMetadata(Uid{0}, Gid{0}, source_path).Error(), ResultCode::NotFound); - const Result metadata = m_fs->GetMetadata(Uid{0}, Gid{0}, "/tmp/f2"); + const Result metadata = m_fs->GetMetadata(Uid{0}, Gid{0}, dest_path); ASSERT_TRUE(metadata.Succeeded()); EXPECT_TRUE(metadata->is_file); EXPECT_EQ(metadata->size, TEST_DATA.size()); From af416c60b054277603fa4386a253517801a55111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 29 Dec 2019 21:19:11 +0100 Subject: [PATCH 16/19] UnitTests/FS: Add ReadDirectory ordering test (issue 10234) --- .../UnitTests/Core/IOS/FS/FileSystemTest.cpp | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp b/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp index 39936678b8..b39e4a48cb 100644 --- a/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp +++ b/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include #include #include #include @@ -389,3 +390,27 @@ TEST_F(FileSystemTest, ReadDirectoryOnFile) ASSERT_FALSE(result.Succeeded()); EXPECT_EQ(result.Error(), ResultCode::Invalid); } + +TEST_F(FileSystemTest, ReadDirectoryOrdering) +{ + ASSERT_EQ(m_fs->CreateDirectory(Uid{0}, Gid{0}, "/tmp/o", 0, modes), ResultCode::Success); + + // Randomly generated file names in no particular order. + const std::array file_names{{ + "Rkj62lGwHp", + "XGDQTDJMea", + "1z5M43WeFw", + "YAY39VuMRd", + "hxJ86nkoBX", + }}; + // Create the files. + for (const auto& name : file_names) + ASSERT_EQ(m_fs->CreateFile(Uid{0}, Gid{0}, "/tmp/o/" + name, 0, modes), ResultCode::Success); + + // Verify that ReadDirectory returns a file list that is ordered by descending creation date + // (issue 10234). + const Result> result = m_fs->ReadDirectory(Uid{0}, Gid{0}, "/tmp/o"); + ASSERT_TRUE(result.Succeeded()); + ASSERT_EQ(result->size(), file_names.size()); + EXPECT_TRUE(std::equal(result->begin(), result->end(), file_names.rbegin())); +} From 150c832532956ea8494e02b06a61f2a15135d71a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 29 Dec 2019 21:39:32 +0100 Subject: [PATCH 17/19] Tools: Add a small Python tool to print FSTs --- Tools/print-fs-fst.py | 58 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Tools/print-fs-fst.py diff --git a/Tools/print-fs-fst.py b/Tools/print-fs-fst.py new file mode 100644 index 0000000000..8b88155e4a --- /dev/null +++ b/Tools/print-fs-fst.py @@ -0,0 +1,58 @@ +import argparse +import struct + +def read_entry(f) -> dict: + name = struct.unpack_from("12s", f.read(12))[0] + uid = struct.unpack_from(">I", f.read(4))[0] + gid = struct.unpack_from(">H", f.read(2))[0] + is_file = struct.unpack_from("?", f.read(1))[0] + modes = struct.unpack_from("BBB", f.read(3)) + attr = struct.unpack_from("B", f.read(2))[0] + x3 = struct.unpack_from(">I", f.read(4))[0] + num_children = struct.unpack_from(">I", f.read(4))[0] + + children = [] + for i in range(num_children): + children.append(read_entry(f)) + + return { + "name": name, + "uid": uid, + "gid": gid, + "is_file": is_file, + "modes": modes, + "attr": attr, + "x3": x3, + "children": children, + } + +COLOR_RESET = "\x1b[0;00m" +BOLD = "\x1b[0;37m" +COLOR_BLUE = "\x1b[1;34m" +COLOR_GREEN = "\x1b[0;32m" + +def print_entry(entry, indent) -> None: + mode_str = {0: "--", 1: "r-", 2: "-w", 3: "rw"} + + sp = ' ' * indent + color = BOLD if entry["is_file"] else COLOR_BLUE + + owner = f"{COLOR_GREEN}{entry['uid']:04x}{COLOR_RESET}:{entry['gid']:04x}" + attrs = f"{''.join(mode_str[mode] for mode in entry['modes'])}" + other_attrs = f"{entry['attr']} {entry['x3']}" + + print(f"{sp}{color}{entry['name'].decode()}{COLOR_RESET} [{owner} {attrs} {other_attrs}]") + for child in entry["children"]: + print_entry(child, indent + 2) + +def main() -> None: + parser = argparse.ArgumentParser(description="Prints a FST in a tree-like format.") + parser.add_argument("file") + args = parser.parse_args() + + with open(args.file, "rb") as f: + root = read_entry(f) + + print_entry(root, 0) + +main() From 031c63eb8a931eb007ec285840f848332a1cf526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 29 Dec 2019 22:27:28 +0100 Subject: [PATCH 18/19] UnitTests/FS: Improve deletion test * Test recursive directory deletion * Test "in use" check for both files and directories --- .../UnitTests/Core/IOS/FS/FileSystemTest.cpp | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp b/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp index b39e4a48cb..79f853f441 100644 --- a/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp +++ b/Source/UnitTests/Core/IOS/FS/FileSystemTest.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -148,6 +149,25 @@ TEST_F(FileSystemTest, Delete) EXPECT_TRUE(m_fs->ReadDirectory(Uid{0}, Gid{0}, "/tmp").Succeeded()); EXPECT_EQ(m_fs->Delete(Uid{0}, Gid{0}, "/tmp"), ResultCode::Success); EXPECT_EQ(m_fs->ReadDirectory(Uid{0}, Gid{0}, "/tmp").Error(), ResultCode::NotFound); + + // Test recursive directory deletion. + ASSERT_EQ(m_fs->CreateDirectory(Uid{0}, Gid{0}, "/sys/1", 0, modes), ResultCode::Success); + ASSERT_EQ(m_fs->CreateDirectory(Uid{0}, Gid{0}, "/sys/1/2", 0, modes), ResultCode::Success); + ASSERT_EQ(m_fs->CreateFile(Uid{0}, Gid{0}, "/sys/1/2/3", 0, modes), ResultCode::Success); + ASSERT_EQ(m_fs->CreateFile(Uid{0}, Gid{0}, "/sys/1/2/4", 0, modes), ResultCode::Success); + + // Leave a file open. Deletion should fail while the file is in use. + auto handle = std::make_optional(m_fs->OpenFile(Uid{0}, Gid{0}, "/sys/1/2/3", Mode::Read)); + ASSERT_TRUE(handle->Succeeded()); + EXPECT_EQ(m_fs->Delete(Uid{0}, Gid{0}, "/sys/1/2/3"), ResultCode::InUse); + // A directory that contains a file that is in use is considered to be in use, + // so this should fail too. + EXPECT_EQ(m_fs->Delete(Uid{0}, Gid{0}, "/sys/1"), ResultCode::InUse); + + // With the handle closed, both of these should work: + handle.reset(); + EXPECT_EQ(m_fs->Delete(Uid{0}, Gid{0}, "/sys/1/2/3"), ResultCode::Success); + EXPECT_EQ(m_fs->Delete(Uid{0}, Gid{0}, "/sys/1"), ResultCode::Success); } TEST_F(FileSystemTest, Rename) From c02e7de55aac49bc1a02a8b5993ebb3ccbce1927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Mon, 30 Dec 2019 12:13:00 +0100 Subject: [PATCH 19/19] IOS/ES: Remove now unnecessary title sorting hack ES now uses FS to access the filesystem and FS's ReadDirectory now returns file lists that are correctly ordered. --- Source/Core/Core/IOS/ES/NandUtils.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Source/Core/Core/IOS/ES/NandUtils.cpp b/Source/Core/Core/IOS/ES/NandUtils.cpp index 5e98ed618b..ab25f06c76 100644 --- a/Source/Core/Core/IOS/ES/NandUtils.cpp +++ b/Source/Core/Core/IOS/ES/NandUtils.cpp @@ -109,12 +109,6 @@ static std::vector GetTitlesInTitleOrImport(FS::FileSystem* fs, const std:: } } - // On a real Wii, the title list is not in any particular order. However, because of how - // the flash filesystem works, titles such as 1-2 are *never* in the first position. - // We must keep this behaviour, or some versions of the System Menu may break. - - std::sort(title_ids.begin(), title_ids.end(), std::greater<>()); - return title_ids; }