IOS: Extract existing FS code into a host backend
Extract the existing FS code into a HostBackend implementing the filesystem interface. Compared to the original code, this uses less static state. The open host files map is now a member variable as it should have been. Filesystem handles are now also easier to savestate. Some variable names and log messages were cleaned up. Nothing else has been changed.
This commit is contained in:
parent
f1dbb8952c
commit
1eec459e30
|
@ -165,6 +165,8 @@ add_library(core
|
|||
IOS/ES/TitleInformation.cpp
|
||||
IOS/ES/TitleManagement.cpp
|
||||
IOS/ES/Views.cpp
|
||||
IOS/FS/HostBackend/File.cpp
|
||||
IOS/FS/HostBackend/FS.cpp
|
||||
IOS/FS/FileIO.cpp
|
||||
IOS/FS/FS.cpp
|
||||
IOS/Network/ICMPLin.cpp
|
||||
|
|
|
@ -197,6 +197,8 @@
|
|||
<ClCompile Include="IOS\ES\Views.cpp" />
|
||||
<ClCompile Include="IOS\FS\FileIO.cpp" />
|
||||
<ClCompile Include="IOS\FS\FS.cpp" />
|
||||
<ClCompile Include="IOS\FS\HostBackend\FS.cpp" />
|
||||
<ClCompile Include="IOS\FS\HostBackend\File.cpp" />
|
||||
<ClCompile Include="IOS\Network\ICMPLin.cpp" />
|
||||
<ClCompile Include="IOS\Network\MACUtils.cpp" />
|
||||
<ClCompile Include="IOS\Network\Socket.cpp" />
|
||||
|
@ -448,6 +450,8 @@
|
|||
<ClInclude Include="IOS\FS\FileIO.h" />
|
||||
<ClInclude Include="IOS\FS\FileSystem.h" />
|
||||
<ClInclude Include="IOS\FS\FS.h" />
|
||||
<ClInclude Include="IOS\FS\HostBackend\File.h" />
|
||||
<ClInclude Include="IOS\FS\HostBackend\FS.h" />
|
||||
<ClInclude Include="IOS\Network\ICMPLin.h" />
|
||||
<ClInclude Include="IOS\Network\ICMP.h" />
|
||||
<ClInclude Include="IOS\Network\MACUtils.h" />
|
||||
|
|
|
@ -735,6 +735,12 @@
|
|||
<ClCompile Include="IOS\FS\FileIO.cpp">
|
||||
<Filter>IOS\FS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="IOS\FS\HostBackend\FS.cpp">
|
||||
<Filter>IOS\FS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="IOS\FS\HostBackend\File.cpp">
|
||||
<Filter>IOS\FS</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="IOS\ES\ES.cpp">
|
||||
<Filter>IOS\ES</Filter>
|
||||
</ClCompile>
|
||||
|
@ -1407,6 +1413,12 @@
|
|||
<ClInclude Include="IOS\FS\FileSystem.h">
|
||||
<Filter>IOS\FS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="IOS\FS\HostBackend\File.h">
|
||||
<Filter>IOS\FS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="IOS\FS\HostBackend\FS.h">
|
||||
<Filter>IOS\FS</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="IOS\ES\Formats.h">
|
||||
<Filter>IOS\ES</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
@ -0,0 +1,433 @@
|
|||
// 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)
|
||||
{
|
||||
std::string nand_path = File::GetUserPath(D_SESSION_WIIROOT_IDX);
|
||||
if (wii_path.compare(0, 1, "/") == 0)
|
||||
return nand_path + Common::EscapePath(wii_path);
|
||||
|
||||
ASSERT(false);
|
||||
return nand_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() = default;
|
||||
|
||||
HostFileSystem::~HostFileSystem() = default;
|
||||
|
||||
void HostFileSystem::DoState(PointerWrap& p)
|
||||
{
|
||||
// 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))
|
||||
{
|
||||
INFO_LOG(IOS_FILEIO, "\tresult = FS_EEXIST");
|
||||
return ResultCode::AlreadyExists;
|
||||
}
|
||||
|
||||
// create the file
|
||||
File::CreateFullPath(file_name); // just to be sure
|
||||
if (!File::CreateEmptyFile(file_name))
|
||||
{
|
||||
ERROR_LOG(IOS_FILEIO, "couldn't create new file");
|
||||
return ResultCode::Invalid;
|
||||
}
|
||||
|
||||
INFO_LOG(IOS_FILEIO, "\tresult = IPC_SUCCESS");
|
||||
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))
|
||||
{
|
||||
WARN_LOG(IOS_FILEIO, "Not a valid path: %s", path.c_str());
|
||||
return ResultCode::Invalid;
|
||||
}
|
||||
|
||||
std::string name(BuildFilename(path));
|
||||
|
||||
name += "/";
|
||||
File::CreateFullPath(name);
|
||||
DEBUG_ASSERT_MSG(IOS_FILEIO, 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))
|
||||
{
|
||||
WARN_LOG(IOS_FILEIO, "Not a valid path: %s", path.c_str());
|
||||
return ResultCode::Invalid;
|
||||
}
|
||||
|
||||
const std::string file_name = BuildFilename(path);
|
||||
if (File::Delete(file_name))
|
||||
INFO_LOG(IOS_FILEIO, "DeleteFile %s", file_name.c_str());
|
||||
else if (File::DeleteDirRecursively(file_name))
|
||||
INFO_LOG(IOS_FILEIO, "DeleteDir %s", file_name.c_str());
|
||||
else
|
||||
WARN_LOG(IOS_FILEIO, "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))
|
||||
{
|
||||
WARN_LOG(IOS_FILEIO, "Not a valid path: %s", old_path.c_str());
|
||||
return ResultCode::Invalid;
|
||||
}
|
||||
std::string old_name = BuildFilename(old_path);
|
||||
|
||||
if (!IsValidWiiPath(new_path))
|
||||
{
|
||||
WARN_LOG(IOS_FILEIO, "Not a valid path: %s", new_path.c_str());
|
||||
return ResultCode::Invalid;
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
INFO_LOG(IOS_FILEIO, "Rename %s to %s", old_name.c_str(), new_name.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG(IOS_FILEIO, "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))
|
||||
{
|
||||
WARN_LOG(IOS_FILEIO, "Not a valid path: %s", path.c_str());
|
||||
return ResultCode::Invalid;
|
||||
}
|
||||
|
||||
// the Wii uses this function to define the type (dir or file)
|
||||
const std::string dir_name(BuildFilename(path));
|
||||
|
||||
INFO_LOG(IOS_FILEIO, "IOCTL_READ_DIR %s", dir_name.c_str());
|
||||
|
||||
const File::FileInfo file_info(dir_name);
|
||||
|
||||
if (!file_info.Exists())
|
||||
{
|
||||
WARN_LOG(IOS_FILEIO, "Search not found: %s", dir_name.c_str());
|
||||
return ResultCode::NotFound;
|
||||
}
|
||||
|
||||
if (!file_info.IsDirectory())
|
||||
{
|
||||
// It's not a directory, so error.
|
||||
// Games don't usually seem to care WHICH error they get, as long as it's <
|
||||
// Well the system menu CARES!
|
||||
WARN_LOG(IOS_FILEIO, "\tNot a directory - return FS_EINVAL");
|
||||
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);
|
||||
INFO_LOG(IOS_FILEIO, "\tFound: %s", child.virtualName.c_str());
|
||||
}
|
||||
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))
|
||||
{
|
||||
WARN_LOG(IOS_FILEIO, "Not a valid path: %s", path.c_str());
|
||||
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.IsDirectory())
|
||||
{
|
||||
INFO_LOG(IOS_FILEIO, "GET_ATTR Directory %s - all permission flags are set", file_name.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (info.Exists())
|
||||
{
|
||||
INFO_LOG(IOS_FILEIO, "GET_ATTR %s - all permission flags are set", file_name.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
INFO_LOG(IOS_FILEIO, "GET_ATTR unknown %s", file_name.c_str());
|
||||
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))
|
||||
{
|
||||
WARN_LOG(IOS_FILEIO, "Not a valid path: %s", path.c_str());
|
||||
return ResultCode::Invalid;
|
||||
}
|
||||
return ResultCode::Success;
|
||||
}
|
||||
|
||||
Result<NandStats> HostFileSystem::GetNandStats()
|
||||
{
|
||||
WARN_LOG(IOS_FILEIO, "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))
|
||||
{
|
||||
WARN_LOG(IOS_FILEIO, "Not a valid path: %s", wii_path.c_str());
|
||||
return ResultCode::Invalid;
|
||||
}
|
||||
|
||||
DirectoryStats stats{};
|
||||
std::string path(BuildFilename(wii_path));
|
||||
INFO_LOG(IOS_FILEIO, "IOCTL_GETUSAGE %s", path.c_str());
|
||||
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
|
||||
|
||||
INFO_LOG(IOS_FILEIO, "fsBlock: %i, inodes: %i", stats.used_clusters, stats.used_inodes);
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG(IOS_FILEIO, "fsBlock failed, cannot find directory: %s", path.c_str());
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
} // namespace IOS::HLE::FS
|
|
@ -0,0 +1,83 @@
|
|||
// 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();
|
||||
~HostFileSystem();
|
||||
|
||||
void DoState(PointerWrap& p) override;
|
||||
|
||||
ResultCode Format(Uid uid) override;
|
||||
|
||||
Result<Fd> 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);
|
||||
std::shared_ptr<File::IOFile> OpenHostFile(const std::string& host_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<Fd> HostFileSystem::OpenFile(Uid uid, Gid 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 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
|
Loading…
Reference in New Issue