This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

@@ -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
@@ -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

Large diffs are not rendered by default.

@@ -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
@@ -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
@@ -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
@@ -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
@@ -31,8 +31,8 @@
#include "Core/IOS/Device.h"
#include "Core/IOS/DeviceStub.h"
#include "Core/IOS/ES/ES.h"
#include "Core/IOS/FS/FS.h"
#include "Core/IOS/FS/FileIO.h"
#include "Core/IOS/FS/FileSystem.h"
#include "Core/IOS/FS/FileSystemProxy.h"
#include "Core/IOS/MIOS.h"
#include "Core/IOS/Network/IP/Top.h"
#include "Core/IOS/Network/KD/NetKDRequest.h"
@@ -242,9 +242,9 @@ u32 Kernel::GetVersion() const
return static_cast<u32>(m_title_id);
}

std::shared_ptr<Device::FS> Kernel::GetFS()
std::shared_ptr<FS::FileSystem> Kernel::GetFS()
{
return std::static_pointer_cast<Device::FS>(m_device_map.at("/dev/fs"));
return m_fs;
}

std::shared_ptr<Device::ES> Kernel::GetES()
@@ -368,6 +368,9 @@ void Kernel::AddDevice(std::unique_ptr<Device::Device> device)

void Kernel::AddCoreDevices()
{
m_fs = FS::MakeFileSystem();
ASSERT(m_fs);

std::lock_guard<std::mutex> lock(m_device_map_mutex);
AddDevice(std::make_unique<Device::FS>(*this, "/dev/fs"));
AddDevice(std::make_unique<Device::ES>(*this, "/dev/es"));
@@ -491,7 +494,7 @@ IPCCommandResult Kernel::OpenDevice(OpenRequest& request)
}
else if (request.path.find('/') == 0)
{
device = std::make_shared<Device::FileIO>(*this, request.path);
device = GetDeviceByName("/dev/fs");
}

if (!device)
@@ -691,6 +694,7 @@ void Kernel::DoState(PointerWrap& p)
p.Do(m_ppc_gid);

m_iosc.DoState(p);
m_fs->DoState(p);

if (m_title_id == Titles::MIOS)
return;
@@ -725,10 +729,6 @@ void Kernel::DoState(PointerWrap& p)
m_fdmap[i] = GetDeviceByName(device_name);
break;
}
case Device::Device::DeviceType::FileIO:
m_fdmap[i] = std::make_shared<Device::FileIO>(*this, "");
m_fdmap[i]->DoState(p);
break;
case Device::Device::DeviceType::OH0:
m_fdmap[i] = std::make_shared<Device::OH0Device>(*this, "");
m_fdmap[i]->DoState(p);
@@ -23,11 +23,15 @@ namespace IOS
{
namespace HLE
{
namespace FS
{
class FileSystem;
}

namespace Device
{
class Device;
class ES;
class FS;
}

struct Request;
@@ -94,7 +98,7 @@ class Kernel

// These are *always* part of the IOS kernel and always available.
// They are also the only available resource managers even before loading any module.
std::shared_ptr<Device::FS> GetFS();
std::shared_ptr<FS::FileSystem> GetFS();
std::shared_ptr<Device::ES> GetES();

void SDIO_EventNotify();
@@ -146,6 +150,7 @@ class Kernel
u64 m_last_reply_time = 0;

IOSC m_iosc;
std::shared_ptr<FS::FileSystem> m_fs;
};

// HLE for an IOS tied to emulation: base kernel which may have additional modules loaded.
@@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;

// Don't forget to increase this after doing changes on the savestate system
static const u32 STATE_VERSION = 94; // Last changed in PR 6456
static const u32 STATE_VERSION = 95; // Last changed in PR 6421

// Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list,
@@ -57,12 +57,11 @@ void InitializeWiiRoot(bool use_temporary)
s_temp_wii_root = File::CreateTempDir();
if (s_temp_wii_root.empty())
{
ERROR_LOG(IOS_FILEIO, "Could not create temporary directory");
ERROR_LOG(IOS_FS, "Could not create temporary directory");
return;
}
File::CopyDir(File::GetSysDirectory() + WII_USER_DIR, s_temp_wii_root);
WARN_LOG(IOS_FILEIO, "Using temporary directory %s for minimal Wii FS",
s_temp_wii_root.c_str());
WARN_LOG(IOS_FS, "Using temporary directory %s for minimal Wii FS", s_temp_wii_root.c_str());
File::SetUserPath(D_SESSION_WIIROOT_IDX, s_temp_wii_root);
// Generate a SYSCONF with default settings for the temporary Wii NAND.
SysConf sysconf{Common::FromWhichRoot::FROM_SESSION_ROOT};