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 <optional> #include <string_view> #include <type_traits> +#include <unordered_map> #include <fmt/format.h> @@ -520,45 +521,61 @@ ResultCode HostFileSystem::Rename(Uid uid, Gid gid, const std::string& old_path, return ResultCode::Success; } -Result<std::vector<std::string>> HostFileSystem::ReadDirectory(Uid, Gid, const std::string& path) +Result<std::vector<std::string>> 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<std::string_view, int> 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<std::string> output; - for (File::FSTEntry& child : entry.children) + for (const File::FSTEntry& child : host_entry.children) output.emplace_back(child.virtualName); return output; }