| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| // Copyright 2018 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include "Core/IOS/FS/FileSystem.h" | ||
|
|
||
| #include "Common/Assert.h" | ||
| #include "Common/FileUtil.h" | ||
| #include "Core/IOS/FS/HostBackend/FS.h" | ||
|
|
||
| namespace IOS::HLE::FS | ||
| { | ||
| std::unique_ptr<FileSystem> MakeFileSystem(Location location) | ||
| { | ||
| const std::string nand_root = | ||
| File::GetUserPath(location == Location::Session ? D_SESSION_WIIROOT_IDX : D_WIIROOT_IDX); | ||
| return std::make_unique<HostFileSystem>(nand_root); | ||
| } | ||
|
|
||
| FileHandle::FileHandle(FileSystem* fs, Fd fd) : m_fs{fs}, m_fd{fd} | ||
| { | ||
| } | ||
|
|
||
| FileHandle::FileHandle(FileHandle&& other) : m_fs{other.m_fs}, m_fd{other.m_fd} | ||
| { | ||
| other.m_fd.reset(); | ||
| } | ||
|
|
||
| FileHandle& FileHandle::operator=(FileHandle&& other) | ||
| { | ||
| if (*this != other) | ||
| *this = std::move(other); | ||
| return *this; | ||
| } | ||
|
|
||
| FileHandle::~FileHandle() | ||
| { | ||
| if (m_fd && m_fs) | ||
| ASSERT(m_fs->Close(*m_fd) == FS::ResultCode::Success); | ||
| } | ||
|
|
||
| Fd FileHandle::Release() | ||
| { | ||
| const Fd fd = m_fd.value(); | ||
| m_fd.reset(); | ||
| return fd; | ||
| } | ||
|
|
||
| void FileSystem::Init() | ||
| { | ||
| if (Delete(0, 0, "/tmp") == ResultCode::Success) | ||
| CreateDirectory(0, 0, "/tmp", 0, Mode::ReadWrite, Mode::ReadWrite, Mode::ReadWrite); | ||
| } | ||
| } // namespace IOS::HLE::FS |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| // Copyright 2018 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <memory> | ||
| #include <optional> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include "Common/CommonTypes.h" | ||
| #include "Common/Result.h" | ||
|
|
||
| class PointerWrap; | ||
|
|
||
| namespace IOS::HLE::FS | ||
| { | ||
| enum class ResultCode | ||
| { | ||
| Success, | ||
| Invalid, | ||
| AccessDenied, | ||
| SuperblockWriteFailed, | ||
| SuperblockInitFailed, | ||
| AlreadyExists, | ||
| NotFound, | ||
| FstFull, | ||
| NoFreeSpace, | ||
| NoFreeHandle, | ||
| TooManyPathComponents, | ||
| InUse, | ||
| BadBlock, | ||
| EccError, | ||
| CriticalEccError, | ||
| FileNotEmpty, | ||
| CheckFailed, | ||
| UnknownError, | ||
| ShortRead, | ||
| }; | ||
|
|
||
| template <typename T> | ||
| using Result = Common::Result<ResultCode, T>; | ||
|
|
||
| using Uid = u32; | ||
| using Gid = u16; | ||
| using Fd = u32; | ||
|
|
||
| enum class Mode : u8 | ||
| { | ||
| None = 0, | ||
| Read = 1, | ||
| Write = 2, | ||
| ReadWrite = 3, | ||
| }; | ||
|
|
||
| enum class SeekMode : u32 | ||
| { | ||
| Set = 0, | ||
| Current = 1, | ||
| End = 2, | ||
| }; | ||
|
|
||
| using FileAttribute = u8; | ||
|
|
||
| struct Metadata | ||
| { | ||
| Uid uid; | ||
| Gid gid; | ||
| FileAttribute attribute; | ||
| Mode owner_mode, group_mode, other_mode; | ||
| bool is_file; | ||
| u32 size; | ||
| u16 fst_index; | ||
| }; | ||
|
|
||
| struct NandStats | ||
| { | ||
| u32 cluster_size; | ||
| u32 free_clusters; | ||
| u32 used_clusters; | ||
| u32 bad_clusters; | ||
| u32 reserved_clusters; | ||
| u32 free_inodes; | ||
| u32 used_inodes; | ||
| }; | ||
|
|
||
| struct DirectoryStats | ||
| { | ||
| u32 used_clusters; | ||
| u32 used_inodes; | ||
| }; | ||
|
|
||
| struct FileStatus | ||
| { | ||
| u32 offset; | ||
| u32 size; | ||
| }; | ||
|
|
||
| class FileSystem; | ||
| class FileHandle final | ||
| { | ||
| public: | ||
| FileHandle(FileSystem* fs, Fd fd); | ||
| FileHandle(FileHandle&&); | ||
| ~FileHandle(); | ||
| FileHandle(const FileHandle&) = delete; | ||
| FileHandle& operator=(const FileHandle&) = delete; | ||
| FileHandle& operator=(FileHandle&&); | ||
|
|
||
| operator Fd() const { return m_fd.value(); } | ||
| /// Release the FD so that it is not automatically closed. | ||
| Fd Release(); | ||
|
|
||
| private: | ||
| FileSystem* m_fs; | ||
| std::optional<Fd> m_fd; | ||
| }; | ||
|
|
||
| class FileSystem | ||
| { | ||
| public: | ||
| virtual ~FileSystem() = default; | ||
|
|
||
| virtual void DoState(PointerWrap& p) = 0; | ||
|
|
||
| /// Format the file system. | ||
| virtual ResultCode Format(Uid uid) = 0; | ||
|
|
||
| /// Get a file descriptor for accessing a file. The FD will be automatically closed after use. | ||
| virtual Result<FileHandle> OpenFile(Uid uid, Gid gid, const std::string& path, Mode mode) = 0; | ||
| /// Close a file descriptor. | ||
| virtual ResultCode Close(Fd fd) = 0; | ||
| /// Read `size` bytes from the file descriptor. Returns the number of bytes read. | ||
| virtual Result<u32> ReadBytesFromFile(Fd fd, u8* ptr, u32 size) = 0; | ||
| /// Write `size` bytes to the file descriptor. Returns the number of bytes written. | ||
| virtual Result<u32> WriteBytesToFile(Fd fd, const u8* ptr, u32 size) = 0; | ||
| /// Reposition the file offset for a file descriptor. | ||
| virtual Result<u32> SeekFile(Fd fd, u32 offset, SeekMode mode) = 0; | ||
| /// Get status for a file descriptor. | ||
| virtual Result<FileStatus> GetFileStatus(Fd fd) = 0; | ||
|
|
||
| template <typename T> | ||
| Result<u32> ReadFile(Fd fd, T* ptr, u32 count) | ||
| { | ||
| const Result<u32> bytes = ReadBytesFromFile(fd, reinterpret_cast<u8*>(ptr), sizeof(T) * count); | ||
| if (!bytes) | ||
| return bytes.Error(); | ||
| if (*bytes != sizeof(T) * count) | ||
| return ResultCode::ShortRead; | ||
| return count; | ||
| } | ||
|
|
||
| template <typename T> | ||
| Result<u32> WriteFile(Fd fd, const T* ptr, u32 count) | ||
| { | ||
| const auto result = WriteBytesToFile(fd, reinterpret_cast<const u8*>(ptr), sizeof(T) * count); | ||
| if (!result) | ||
| return result.Error(); | ||
| return count; | ||
| } | ||
|
|
||
| /// Create a file with the specified path and metadata. | ||
| virtual ResultCode CreateFile(Uid caller_uid, Gid caller_gid, const std::string& path, | ||
| FileAttribute attribute, Mode owner_mode, Mode group_mode, | ||
| Mode other_mode) = 0; | ||
| /// Create a directory with the specified path and metadata. | ||
| virtual ResultCode CreateDirectory(Uid caller_uid, Gid caller_gid, const std::string& path, | ||
| FileAttribute attribute, Mode owner_mode, Mode group_mode, | ||
| Mode other_mode) = 0; | ||
|
|
||
| /// Delete a file or directory with the specified path. | ||
| virtual ResultCode Delete(Uid caller_uid, Gid caller_gid, const std::string& path) = 0; | ||
| /// Rename a file or directory with the specified path. | ||
| virtual ResultCode Rename(Uid caller_uid, Gid caller_gid, const std::string& old_path, | ||
| const std::string& new_path) = 0; | ||
|
|
||
| /// List the children of a directory (non-recursively). | ||
| virtual Result<std::vector<std::string>> ReadDirectory(Uid caller_uid, Gid caller_gid, | ||
| const std::string& path) = 0; | ||
|
|
||
| /// Get metadata about a file. | ||
| virtual Result<Metadata> GetMetadata(Uid caller_uid, Gid caller_gid, const std::string& path) = 0; | ||
| /// Set metadata for a file. | ||
| virtual ResultCode SetMetadata(Uid caller_uid, const std::string& path, Uid uid, Gid gid, | ||
| FileAttribute attribute, Mode owner_mode, Mode group_mode, | ||
| Mode other_mode) = 0; | ||
|
|
||
| /// Get usage information about the NAND (block size, cluster and inode counts). | ||
| virtual Result<NandStats> GetNandStats() = 0; | ||
| /// Get usage information about a directory (used cluster and inode counts). | ||
| virtual Result<DirectoryStats> GetDirectoryStats(const std::string& path) = 0; | ||
|
|
||
| protected: | ||
| void Init(); | ||
| }; | ||
|
|
||
| enum class Location | ||
| { | ||
| Configured, | ||
| Session, | ||
| }; | ||
|
|
||
| std::unique_ptr<FileSystem> MakeFileSystem(Location location = Location::Session); | ||
|
|
||
| } // namespace IOS::HLE::FS |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| // Copyright 2018 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <array> | ||
| #include <map> | ||
| #include <string> | ||
|
|
||
| #include "Common/CommonTypes.h" | ||
| #include "Core/IOS/Device.h" | ||
| #include "Core/IOS/FS/FileSystem.h" | ||
| #include "Core/IOS/IOS.h" | ||
|
|
||
| class PointerWrap; | ||
|
|
||
| namespace IOS | ||
| { | ||
| namespace HLE | ||
| { | ||
| namespace Device | ||
| { | ||
| constexpr IOS::HLE::FS::Fd INVALID_FD = 0xffffffff; | ||
|
|
||
| class FS : public Device | ||
| { | ||
| public: | ||
| FS(Kernel& ios, const std::string& device_name); | ||
|
|
||
| void DoState(PointerWrap& p) override; | ||
|
|
||
| IPCCommandResult Open(const OpenRequest& request) override; | ||
| IPCCommandResult Close(u32 fd) override; | ||
| IPCCommandResult Read(const ReadWriteRequest& request) override; | ||
| IPCCommandResult Write(const ReadWriteRequest& request) override; | ||
| IPCCommandResult Seek(const SeekRequest& request) override; | ||
| IPCCommandResult IOCtl(const IOCtlRequest& request) override; | ||
| IPCCommandResult IOCtlV(const IOCtlVRequest& request) override; | ||
|
|
||
| private: | ||
| struct Handle | ||
| { | ||
| u16 gid = 0; | ||
| u32 uid = 0; | ||
| IOS::HLE::FS::Fd fs_fd = INVALID_FD; | ||
| // We use a std::array to keep this savestate friendly. | ||
| std::array<char, 64> name{}; | ||
| }; | ||
|
|
||
| enum | ||
| { | ||
| ISFS_IOCTL_FORMAT = 1, | ||
| ISFS_IOCTL_GETSTATS = 2, | ||
| ISFS_IOCTL_CREATEDIR = 3, | ||
| ISFS_IOCTLV_READDIR = 4, | ||
| ISFS_IOCTL_SETATTR = 5, | ||
| ISFS_IOCTL_GETATTR = 6, | ||
| ISFS_IOCTL_DELETE = 7, | ||
| ISFS_IOCTL_RENAME = 8, | ||
| ISFS_IOCTL_CREATEFILE = 9, | ||
| ISFS_IOCTL_SETFILEVERCTRL = 10, | ||
| ISFS_IOCTL_GETFILESTATS = 11, | ||
| ISFS_IOCTLV_GETUSAGE = 12, | ||
| ISFS_IOCTL_SHUTDOWN = 13, | ||
| }; | ||
|
|
||
| IPCCommandResult Format(const Handle& handle, const IOCtlRequest& request); | ||
| IPCCommandResult GetStats(const Handle& handle, const IOCtlRequest& request); | ||
| IPCCommandResult CreateDirectory(const Handle& handle, const IOCtlRequest& request); | ||
| IPCCommandResult ReadDirectory(const Handle& handle, const IOCtlVRequest& request); | ||
| IPCCommandResult SetAttribute(const Handle& handle, const IOCtlRequest& request); | ||
| IPCCommandResult GetAttribute(const Handle& handle, const IOCtlRequest& request); | ||
| IPCCommandResult DeleteFile(const Handle& handle, const IOCtlRequest& request); | ||
| IPCCommandResult RenameFile(const Handle& handle, const IOCtlRequest& request); | ||
| IPCCommandResult CreateFile(const Handle& handle, const IOCtlRequest& request); | ||
| IPCCommandResult SetFileVersionControl(const Handle& handle, const IOCtlRequest& request); | ||
| IPCCommandResult GetFileStats(const Handle& handle, const IOCtlRequest& request); | ||
| IPCCommandResult GetUsage(const Handle& handle, const IOCtlVRequest& request); | ||
| IPCCommandResult Shutdown(const Handle& handle, const IOCtlRequest& request); | ||
|
|
||
| std::map<u32, Handle> m_fd_map; | ||
| }; | ||
| } // namespace Device | ||
| } // namespace HLE | ||
| } // namespace IOS |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,379 @@ | ||
| // Copyright 2008 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include <algorithm> | ||
|
|
||
| #include "Common/Assert.h" | ||
| #include "Common/ChunkFile.h" | ||
| #include "Common/FileUtil.h" | ||
| #include "Common/Logging/Log.h" | ||
| #include "Common/NandPaths.h" | ||
| #include "Core/IOS/ES/ES.h" | ||
| #include "Core/IOS/FS/HostBackend/FS.h" | ||
| #include "Core/IOS/IOS.h" | ||
|
|
||
| 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) | ||
| return m_root_path + Common::EscapePath(wii_path); | ||
|
|
||
| ASSERT(false); | ||
| return m_root_path; | ||
| } | ||
|
|
||
| // 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; | ||
| } | ||
|
|
||
| HostFileSystem::HostFileSystem(const std::string& root_path) : m_root_path{root_path} | ||
| { | ||
| Init(); | ||
| } | ||
|
|
||
| HostFileSystem::~HostFileSystem() = default; | ||
|
|
||
| void HostFileSystem::DoState(PointerWrap& p) | ||
| { | ||
| p.Do(m_root_path); | ||
|
|
||
| // Temporarily close the file, to prevent any issues with the savestating of /tmp | ||
| for (Handle& handle : m_handles) | ||
| handle.host_file.reset(); | ||
|
|
||
| // handle /tmp | ||
| std::string Path = BuildFilename("/tmp"); | ||
| if (p.GetMode() == PointerWrap::MODE_READ) | ||
| { | ||
| File::DeleteDirRecursively(Path); | ||
| File::CreateDir(Path); | ||
|
|
||
| // now restore from the stream | ||
| while (1) | ||
| { | ||
| char type = 0; | ||
| p.Do(type); | ||
| if (!type) | ||
| break; | ||
| std::string file_name; | ||
| p.Do(file_name); | ||
| std::string name = Path + "/" + file_name; | ||
| switch (type) | ||
| { | ||
| case 'd': | ||
| { | ||
| File::CreateDir(name); | ||
| break; | ||
| } | ||
| case 'f': | ||
| { | ||
| u32 size = 0; | ||
| p.Do(size); | ||
|
|
||
| File::IOFile handle(name, "wb"); | ||
| char buf[65536]; | ||
| u32 count = size; | ||
| while (count > 65536) | ||
| { | ||
| p.DoArray(buf); | ||
| handle.WriteArray(&buf[0], 65536); | ||
| count -= 65536; | ||
| } | ||
| p.DoArray(&buf[0], count); | ||
| handle.WriteArray(&buf[0], count); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| else | ||
| { | ||
| // recurse through tmp and save dirs and files | ||
|
|
||
| File::FSTEntry parent_entry = File::ScanDirectoryTree(Path, true); | ||
| std::deque<File::FSTEntry> todo; | ||
| todo.insert(todo.end(), parent_entry.children.begin(), parent_entry.children.end()); | ||
|
|
||
| while (!todo.empty()) | ||
| { | ||
| File::FSTEntry& entry = todo.front(); | ||
| std::string name = entry.physicalName; | ||
| name.erase(0, Path.length() + 1); | ||
| char type = entry.isDirectory ? 'd' : 'f'; | ||
| p.Do(type); | ||
| p.Do(name); | ||
| if (entry.isDirectory) | ||
| { | ||
| todo.insert(todo.end(), entry.children.begin(), entry.children.end()); | ||
| } | ||
| else | ||
| { | ||
| u32 size = (u32)entry.size; | ||
| p.Do(size); | ||
|
|
||
| File::IOFile handle(entry.physicalName, "rb"); | ||
| char buf[65536]; | ||
| u32 count = size; | ||
| while (count > 65536) | ||
| { | ||
| handle.ReadArray(&buf[0], 65536); | ||
| p.DoArray(buf); | ||
| count -= 65536; | ||
| } | ||
| handle.ReadArray(&buf[0], count); | ||
| p.DoArray(&buf[0], count); | ||
| } | ||
| todo.pop_front(); | ||
| } | ||
|
|
||
| char type = 0; | ||
| p.Do(type); | ||
| } | ||
|
|
||
| for (Handle& handle : m_handles) | ||
| { | ||
| p.Do(handle.opened); | ||
| p.Do(handle.mode); | ||
| p.Do(handle.wii_path); | ||
| p.Do(handle.file_offset); | ||
| if (handle.opened) | ||
| handle.host_file = OpenHostFile(BuildFilename(handle.wii_path)); | ||
| } | ||
| } | ||
|
|
||
| ResultCode HostFileSystem::Format(Uid uid) | ||
| { | ||
| const std::string root = BuildFilename("/"); | ||
| if (!File::DeleteDirRecursively(root) || !File::CreateDir(root)) | ||
| return ResultCode::UnknownError; | ||
| return ResultCode::Success; | ||
| } | ||
|
|
||
| ResultCode HostFileSystem::CreateFile(Uid, Gid, const std::string& path, FileAttribute attribute, | ||
| Mode owner_mode, Mode group_mode, Mode other_mode) | ||
| { | ||
| std::string file_name(BuildFilename(path)); | ||
| // check if the file already exist | ||
| if (File::Exists(file_name)) | ||
| return ResultCode::AlreadyExists; | ||
|
|
||
| // create the file | ||
| File::CreateFullPath(file_name); // just to be sure | ||
| if (!File::CreateEmptyFile(file_name)) | ||
| { | ||
| ERROR_LOG(IOS_FS, "couldn't create new file"); | ||
| return ResultCode::Invalid; | ||
| } | ||
|
|
||
| return ResultCode::Success; | ||
| } | ||
|
|
||
| ResultCode HostFileSystem::CreateDirectory(Uid, Gid, const std::string& path, | ||
| FileAttribute attribute, Mode owner_mode, | ||
| Mode group_mode, Mode other_mode) | ||
| { | ||
| if (!IsValidWiiPath(path)) | ||
| return ResultCode::Invalid; | ||
|
|
||
| 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::Delete(Uid, Gid, const std::string& path) | ||
| { | ||
| if (!IsValidWiiPath(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()); | ||
| else | ||
| WARN_LOG(IOS_FS, "DeleteFile %s - failed!!!", file_name.c_str()); | ||
|
|
||
| return ResultCode::Success; | ||
| } | ||
|
|
||
| ResultCode HostFileSystem::Rename(Uid, Gid, const std::string& old_path, | ||
| const std::string& new_path) | ||
| { | ||
| if (!IsValidWiiPath(old_path)) | ||
| return ResultCode::Invalid; | ||
| const std::string old_name = BuildFilename(old_path); | ||
|
|
||
| if (!IsValidWiiPath(new_path)) | ||
| return ResultCode::Invalid; | ||
| const std::string new_name = BuildFilename(new_path); | ||
|
|
||
| // try to make the basis directory | ||
| File::CreateFullPath(new_name); | ||
|
|
||
| // if there is already a file, delete it | ||
| if (File::Exists(old_name) && File::Exists(new_name)) | ||
| { | ||
| File::Delete(new_name); | ||
| } | ||
|
|
||
| // finally try to rename the file | ||
| if (!File::Rename(old_name, new_name)) | ||
| { | ||
| ERROR_LOG(IOS_FS, "Rename %s to %s - failed", old_name.c_str(), new_name.c_str()); | ||
| return ResultCode::NotFound; | ||
| } | ||
|
|
||
| return ResultCode::Success; | ||
| } | ||
|
|
||
| Result<std::vector<std::string>> HostFileSystem::ReadDirectory(Uid, Gid, const std::string& path) | ||
| { | ||
| if (!IsValidWiiPath(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); | ||
|
|
||
| if (!file_info.Exists()) | ||
| { | ||
| WARN_LOG(IOS_FS, "Search not found: %s", dir_name.c_str()); | ||
| return ResultCode::NotFound; | ||
| } | ||
|
|
||
| if (!file_info.IsDirectory()) | ||
| { | ||
| // It's not a directory, so error. | ||
| return ResultCode::Invalid; | ||
| } | ||
|
|
||
| File::FSTEntry entry = File::ScanDirectoryTree(dir_name, false); | ||
|
|
||
| for (File::FSTEntry& child : 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; | ||
| }); | ||
|
|
||
| std::vector<std::string> output; | ||
| for (File::FSTEntry& child : entry.children) | ||
| output.emplace_back(child.virtualName); | ||
| return output; | ||
| } | ||
|
|
||
| Result<Metadata> HostFileSystem::GetMetadata(Uid, 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 (!IsValidWiiPath(path)) | ||
| return ResultCode::Invalid; | ||
|
|
||
| std::string file_name = BuildFilename(path); | ||
| metadata.owner_mode = Mode::ReadWrite; | ||
| metadata.group_mode = Mode::ReadWrite; | ||
| metadata.other_mode = 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. | ||
| u64 title_id; | ||
| if (IsTitlePath(file_name, Common::FROM_SESSION_ROOT, &title_id)) | ||
| { | ||
| IOS::ES::TMDReader tmd = GetIOS()->GetES()->FindInstalledTMD(title_id); | ||
| if (tmd.IsValid()) | ||
| metadata.gid = tmd.GetGroupId(); | ||
| } | ||
|
|
||
| const File::FileInfo info{file_name}; | ||
| metadata.is_file = info.IsFile(); | ||
| metadata.size = info.GetSize(); | ||
| if (!info.Exists()) | ||
| return ResultCode::NotFound; | ||
| return metadata; | ||
| } | ||
|
|
||
| ResultCode HostFileSystem::SetMetadata(Uid caller_uid, const std::string& path, Uid uid, Gid gid, | ||
| FileAttribute attribute, Mode owner_mode, Mode group_mode, | ||
| Mode other_mode) | ||
| { | ||
| if (!IsValidWiiPath(path)) | ||
| return ResultCode::Invalid; | ||
| return ResultCode::Success; | ||
| } | ||
|
|
||
| Result<NandStats> HostFileSystem::GetNandStats() | ||
| { | ||
| WARN_LOG(IOS_FS, "GET STATS - returning static values for now"); | ||
|
|
||
| // 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; | ||
|
|
||
| return stats; | ||
| } | ||
|
|
||
| Result<DirectoryStats> HostFileSystem::GetDirectoryStats(const std::string& wii_path) | ||
| { | ||
| if (!IsValidWiiPath(wii_path)) | ||
| return ResultCode::Invalid; | ||
|
|
||
| DirectoryStats stats{}; | ||
| std::string path(BuildFilename(wii_path)); | ||
| if (File::IsDirectory(path)) | ||
| { | ||
| File::FSTEntry parent_dir = File::ScanDirectoryTree(path, true); | ||
| // add one for the folder itself | ||
| stats.used_inodes = 1 + (u32)parent_dir.size; | ||
|
|
||
| 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 | ||
| } | ||
| else | ||
| { | ||
| WARN_LOG(IOS_FS, "fsBlock failed, cannot find directory: %s", path.c_str()); | ||
| } | ||
| return stats; | ||
| } | ||
|
|
||
| } // namespace IOS::HLE::FS |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| // Copyright 2018 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <array> | ||
| #include <map> | ||
| #include <memory> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include "Common/CommonTypes.h" | ||
| #include "Common/File.h" | ||
| #include "Core/IOS/FS/FileSystem.h" | ||
|
|
||
| namespace IOS::HLE::FS | ||
| { | ||
| /// Backend that uses the host file system as backend. | ||
| /// | ||
| /// Ignores metadata like permissions, attributes and various checks and also | ||
| /// sometimes returns wrong information because metadata is not available. | ||
| class HostFileSystem final : public FileSystem | ||
| { | ||
| public: | ||
| HostFileSystem(const std::string& root_path); | ||
| ~HostFileSystem(); | ||
|
|
||
| void DoState(PointerWrap& p) override; | ||
|
|
||
| ResultCode Format(Uid uid) override; | ||
|
|
||
| Result<FileHandle> OpenFile(Uid uid, Gid gid, const std::string& path, Mode mode) override; | ||
| ResultCode Close(Fd fd) override; | ||
| Result<u32> ReadBytesFromFile(Fd fd, u8* ptr, u32 size) override; | ||
| Result<u32> WriteBytesToFile(Fd fd, const u8* ptr, u32 size) override; | ||
| Result<u32> SeekFile(Fd fd, u32 offset, SeekMode mode) override; | ||
| Result<FileStatus> GetFileStatus(Fd fd) override; | ||
|
|
||
| ResultCode CreateFile(Uid caller_uid, Gid caller_gid, const std::string& path, | ||
| FileAttribute attribute, Mode owner_mode, Mode group_mode, | ||
| Mode other_mode) override; | ||
|
|
||
| ResultCode CreateDirectory(Uid caller_uid, Gid caller_gid, const std::string& path, | ||
| FileAttribute attribute, Mode owner_mode, Mode group_mode, | ||
| Mode other_mode) override; | ||
|
|
||
| ResultCode Delete(Uid caller_uid, Gid caller_gid, const std::string& path) override; | ||
| ResultCode Rename(Uid caller_uid, Gid caller_gid, const std::string& old_path, | ||
| const std::string& new_path) override; | ||
|
|
||
| Result<std::vector<std::string>> ReadDirectory(Uid caller_uid, Gid caller_gid, | ||
| const std::string& path) override; | ||
|
|
||
| Result<Metadata> GetMetadata(Uid caller_uid, Gid caller_gid, const std::string& path) override; | ||
| ResultCode SetMetadata(Uid caller_uid, const std::string& path, Uid uid, Gid gid, | ||
| FileAttribute attribute, Mode owner_mode, Mode group_mode, | ||
| Mode other_mode) override; | ||
|
|
||
| Result<NandStats> GetNandStats() override; | ||
| Result<DirectoryStats> GetDirectoryStats(const std::string& path) override; | ||
|
|
||
| private: | ||
| struct Handle | ||
| { | ||
| bool opened = false; | ||
| Mode mode = Mode::None; | ||
| std::string wii_path; | ||
| std::shared_ptr<File::IOFile> host_file; | ||
| u32 file_offset = 0; | ||
| }; | ||
| Handle* AssignFreeHandle(); | ||
| Handle* GetHandleFromFd(Fd fd); | ||
| Fd ConvertHandleToFd(const Handle* handle) const; | ||
|
|
||
| std::string BuildFilename(const std::string& wii_path) const; | ||
| std::shared_ptr<File::IOFile> OpenHostFile(const std::string& host_path); | ||
|
|
||
| std::string m_root_path; | ||
| std::array<Handle, 16> m_handles{}; | ||
| std::map<std::string, std::weak_ptr<File::IOFile>> m_open_files; | ||
| }; | ||
|
|
||
| } // namespace IOS::HLE::FS |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
| // Copyright 2018 Dolphin Emulator Project | ||
| // Licensed under GPLv2+ | ||
| // Refer to the license.txt file included. | ||
|
|
||
| #include <algorithm> | ||
| #include <memory> | ||
|
|
||
| #include "Common/File.h" | ||
| #include "Common/FileUtil.h" | ||
| #include "Common/Logging/Log.h" | ||
| #include "Core/IOS/FS/HostBackend/FS.h" | ||
|
|
||
| namespace IOS::HLE::FS | ||
| { | ||
| // This isn't theadsafe, but it's only called from the CPU thread. | ||
| std::shared_ptr<File::IOFile> HostFileSystem::OpenHostFile(const std::string& host_path) | ||
| { | ||
| // On the wii, all file operations are strongly ordered. | ||
| // If a game opens the same file twice (or 8 times, looking at you PokePark Wii) | ||
| // and writes to one file handle, it will be able to immediately read the written | ||
| // data from the other handle. | ||
| // On 'real' operating systems, there are various buffers and caches meaning | ||
| // applications doing such naughty things will not get expected results. | ||
|
|
||
| // So we fix this by catching any attempts to open the same file twice and | ||
| // only opening one file. Accesses to a single file handle are ordered. | ||
| // | ||
| // Hall of Shame: | ||
| // - PokePark Wii (gets stuck on the loading screen of Pikachu falling) | ||
| // - PokePark 2 (Also gets stuck while loading) | ||
| // - Wii System Menu (Can't access the system settings, gets stuck on blank screen) | ||
| // - The Beatles: Rock Band (saving doesn't work) | ||
|
|
||
| // Check if the file has already been opened. | ||
| std::shared_ptr<File::IOFile> file; | ||
| auto search = m_open_files.find(host_path); | ||
| if (search != m_open_files.end()) | ||
| { | ||
| file = search->second.lock(); // Lock a shared pointer to use. | ||
| } | ||
| else | ||
| { | ||
| // This code will be called when all references to the shared pointer below have been removed. | ||
| auto deleter = [this, host_path](File::IOFile* ptr) { | ||
| delete ptr; // IOFile's deconstructor closes the file. | ||
| m_open_files.erase(host_path); // erase the weak pointer from the list of open files. | ||
| }; | ||
|
|
||
| // All files are opened read/write. Actual access rights will be controlled per handle by the | ||
| // read/write functions below | ||
| file = std::shared_ptr<File::IOFile>(new File::IOFile(host_path, "r+b"), | ||
| deleter); // Use the custom deleter from above. | ||
|
|
||
| // Store a weak pointer to our newly opened file in the cache. | ||
| m_open_files[host_path] = std::weak_ptr<File::IOFile>(file); | ||
| } | ||
| return file; | ||
| } | ||
|
|
||
| Result<FileHandle> HostFileSystem::OpenFile(Uid, Gid, const std::string& path, Mode mode) | ||
| { | ||
| Handle* handle = AssignFreeHandle(); | ||
| if (!handle) | ||
| return ResultCode::NoFreeHandle; | ||
|
|
||
| const std::string host_path = BuildFilename(path); | ||
| if (!File::IsFile(host_path)) | ||
| { | ||
| *handle = Handle{}; | ||
| return ResultCode::NotFound; | ||
| } | ||
|
|
||
| handle->host_file = OpenHostFile(host_path); | ||
| handle->wii_path = path; | ||
| handle->mode = mode; | ||
| handle->file_offset = 0; | ||
| return FileHandle{this, ConvertHandleToFd(handle)}; | ||
| } | ||
|
|
||
| ResultCode HostFileSystem::Close(Fd fd) | ||
| { | ||
| Handle* handle = GetHandleFromFd(fd); | ||
| if (!handle) | ||
| return ResultCode::Invalid; | ||
|
|
||
| // Let go of our pointer to the file, it will automatically close if we are the last handle | ||
| // accessing it. | ||
| *handle = Handle{}; | ||
| return ResultCode::Success; | ||
| } | ||
|
|
||
| Result<u32> HostFileSystem::ReadBytesFromFile(Fd fd, u8* ptr, u32 count) | ||
| { | ||
| Handle* handle = GetHandleFromFd(fd); | ||
| if (!handle || !handle->host_file->IsOpen()) | ||
| return ResultCode::Invalid; | ||
|
|
||
| if ((u8(handle->mode) & u8(Mode::Read)) == 0) | ||
| return ResultCode::AccessDenied; | ||
|
|
||
| const u32 file_size = static_cast<u32>(handle->host_file->GetSize()); | ||
| // IOS has this check in the read request handler. | ||
| if (count + handle->file_offset > file_size) | ||
| count = file_size - handle->file_offset; | ||
|
|
||
| // File might be opened twice, need to seek before we read | ||
| handle->host_file->Seek(handle->file_offset, SEEK_SET); | ||
| const u32 actually_read = static_cast<u32>(fread(ptr, 1, count, handle->host_file->GetHandle())); | ||
|
|
||
| if (actually_read != count && ferror(handle->host_file->GetHandle())) | ||
| return ResultCode::AccessDenied; | ||
|
|
||
| // IOS returns the number of bytes read and adds that value to the seek position, | ||
| // instead of adding the *requested* read length. | ||
| handle->file_offset += actually_read; | ||
| return actually_read; | ||
| } | ||
|
|
||
| Result<u32> HostFileSystem::WriteBytesToFile(Fd fd, const u8* ptr, u32 count) | ||
| { | ||
| Handle* handle = GetHandleFromFd(fd); | ||
| if (!handle || !handle->host_file->IsOpen()) | ||
| return ResultCode::Invalid; | ||
|
|
||
| if ((u8(handle->mode) & u8(Mode::Write)) == 0) | ||
| return ResultCode::AccessDenied; | ||
|
|
||
| // File might be opened twice, need to seek before we read | ||
| handle->host_file->Seek(handle->file_offset, SEEK_SET); | ||
| if (!handle->host_file->WriteBytes(ptr, count)) | ||
| return ResultCode::AccessDenied; | ||
|
|
||
| handle->file_offset += count; | ||
| return count; | ||
| } | ||
|
|
||
| Result<u32> HostFileSystem::SeekFile(Fd fd, std::uint32_t offset, SeekMode mode) | ||
| { | ||
| Handle* handle = GetHandleFromFd(fd); | ||
| if (!handle || !handle->host_file->IsOpen()) | ||
| return ResultCode::Invalid; | ||
|
|
||
| u32 new_position = 0; | ||
| switch (mode) | ||
| { | ||
| case SeekMode::Set: | ||
| new_position = offset; | ||
| break; | ||
| case SeekMode::Current: | ||
| new_position = handle->file_offset + offset; | ||
| break; | ||
| case SeekMode::End: | ||
| new_position = handle->host_file->GetSize() + offset; | ||
| break; | ||
| default: | ||
| return ResultCode::Invalid; | ||
| } | ||
|
|
||
| // This differs from POSIX behaviour which allows seeking past the end of the file. | ||
| if (handle->host_file->GetSize() < new_position) | ||
| return ResultCode::Invalid; | ||
|
|
||
| handle->file_offset = new_position; | ||
| return handle->file_offset; | ||
| } | ||
|
|
||
| Result<FileStatus> HostFileSystem::GetFileStatus(Fd fd) | ||
| { | ||
| const Handle* handle = GetHandleFromFd(fd); | ||
| if (!handle || !handle->host_file->IsOpen()) | ||
| return ResultCode::Invalid; | ||
|
|
||
| FileStatus status; | ||
| status.size = handle->host_file->GetSize(); | ||
| status.offset = handle->file_offset; | ||
| return status; | ||
| } | ||
|
|
||
| HostFileSystem::Handle* HostFileSystem::AssignFreeHandle() | ||
| { | ||
| const auto it = std::find_if(m_handles.begin(), m_handles.end(), | ||
| [](const Handle& handle) { return !handle.opened; }); | ||
| if (it == m_handles.end()) | ||
| return nullptr; | ||
|
|
||
| *it = Handle{}; | ||
| it->opened = true; | ||
| return &*it; | ||
| } | ||
|
|
||
| HostFileSystem::Handle* HostFileSystem::GetHandleFromFd(Fd fd) | ||
| { | ||
| if (fd >= m_handles.size() || !m_handles[fd].opened) | ||
| return nullptr; | ||
| return &m_handles[fd]; | ||
| } | ||
|
|
||
| Fd HostFileSystem::ConvertHandleToFd(const Handle* handle) const | ||
| { | ||
| return handle - m_handles.data(); | ||
| } | ||
|
|
||
| } // namespace IOS::HLE::FS |