summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorLéo Lam <leolino.lam@gmail.com>2018-04-06 12:17:09 +0200
committerGitHub <noreply@github.com>2018-04-06 12:17:09 +0200
commita957bd1ecc7af636eab2cb449b14801abb06b0bd (patch)
tree1c4ac1a8d0963ec75cfb8f5601b270f78f51369f /Source
parentc2fa6f5bee75a283d6195c4d53111145e1a98549 (diff)
parent4e547772da89bc541352ae7376c369eb8ceb7f44 (diff)
Merge pull request #6421 from leoetlino/fs-interface
IOS: Refactor the filesystem code
Diffstat (limited to 'Source')
-rw-r--r--Source/Core/Common/Common.vcxproj1
-rw-r--r--Source/Core/Common/Common.vcxproj.filters1
-rw-r--r--Source/Core/Common/Logging/Log.h2
-rw-r--r--Source/Core/Common/Logging/LogManager.cpp2
-rw-r--r--Source/Core/Common/Result.h30
-rw-r--r--Source/Core/Common/Swap.h12
-rw-r--r--Source/Core/Core/Boot/Boot_WiiWAD.cpp44
-rw-r--r--Source/Core/Core/CMakeLists.txt6
-rw-r--r--Source/Core/Core/Core.vcxproj12
-rw-r--r--Source/Core/Core/Core.vcxproj.filters28
-rw-r--r--Source/Core/Core/IOS/Device.h1
-rw-r--r--Source/Core/Core/IOS/FS/FS.cpp650
-rw-r--r--Source/Core/Core/IOS/FS/FS.h73
-rw-r--r--Source/Core/Core/IOS/FS/FileIO.cpp322
-rw-r--r--Source/Core/Core/IOS/FS/FileIO.h83
-rw-r--r--Source/Core/Core/IOS/FS/FileSystem.cpp54
-rw-r--r--Source/Core/Core/IOS/FS/FileSystem.h206
-rw-r--r--Source/Core/Core/IOS/FS/FileSystemProxy.cpp466
-rw-r--r--Source/Core/Core/IOS/FS/FileSystemProxy.h86
-rw-r--r--Source/Core/Core/IOS/FS/HostBackend/FS.cpp379
-rw-r--r--Source/Core/Core/IOS/FS/HostBackend/FS.h84
-rw-r--r--Source/Core/Core/IOS/FS/HostBackend/File.cpp203
-rw-r--r--Source/Core/Core/IOS/IOS.cpp18
-rw-r--r--Source/Core/Core/IOS/IOS.h9
-rw-r--r--Source/Core/Core/State.cpp2
-rw-r--r--Source/Core/Core/WiiRoot.cpp5
26 files changed, 1612 insertions, 1167 deletions
diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj
index a518b531f1..5b64898b7b 100644
--- a/Source/Core/Common/Common.vcxproj
+++ b/Source/Core/Common/Common.vcxproj
@@ -138,6 +138,7 @@
<ClInclude Include="PcapFile.h" />
<ClInclude Include="Profiler.h" />
<ClInclude Include="QoSSession.h" />
+ <ClInclude Include="Result.h" />
<ClInclude Include="ScopeGuard.h" />
<ClInclude Include="SDCardUtil.h" />
<ClInclude Include="Semaphore.h" />
diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters
index 50ea0f619d..58b75ff30f 100644
--- a/Source/Core/Common/Common.vcxproj.filters
+++ b/Source/Core/Common/Common.vcxproj.filters
@@ -59,6 +59,7 @@
<ClInclude Include="PcapFile.h" />
<ClInclude Include="Profiler.h" />
<ClInclude Include="QoSSession.h" />
+ <ClInclude Include="Result.h" />
<ClInclude Include="ScopeGuard.h" />
<ClInclude Include="SDCardUtil.h" />
<ClInclude Include="SettingsHandler.h" />
diff --git a/Source/Core/Common/Logging/Log.h b/Source/Core/Common/Logging/Log.h
index 6c50789b9d..c2dd70937e 100644
--- a/Source/Core/Common/Logging/Log.h
+++ b/Source/Core/Common/Logging/Log.h
@@ -31,7 +31,7 @@ enum LOG_TYPE
IOS,
IOS_DI,
IOS_ES,
- IOS_FILEIO,
+ IOS_FS,
IOS_NET,
IOS_SD,
IOS_SSL,
diff --git a/Source/Core/Common/Logging/LogManager.cpp b/Source/Core/Common/Logging/LogManager.cpp
index fcf606ad0b..7d910b3a8b 100644
--- a/Source/Core/Common/Logging/LogManager.cpp
+++ b/Source/Core/Common/Logging/LogManager.cpp
@@ -99,7 +99,7 @@ LogManager::LogManager()
m_log[LogTypes::IOS] = {"IOS", "IOS"};
m_log[LogTypes::IOS_DI] = {"IOS_DI", "IOS - Drive Interface"};
m_log[LogTypes::IOS_ES] = {"IOS_ES", "IOS - ETicket Services"};
- m_log[LogTypes::IOS_FILEIO] = {"IOS_FILEIO", "IOS - FileIO"};
+ m_log[LogTypes::IOS_FS] = {"IOS_FS", "IOS - Filesystem Services"};
m_log[LogTypes::IOS_SD] = {"IOS_SD", "IOS - SDIO"};
m_log[LogTypes::IOS_SSL] = {"IOS_SSL", "IOS - SSL"};
m_log[LogTypes::IOS_STM] = {"IOS_STM", "IOS - State Transition Manager"};
diff --git a/Source/Core/Common/Result.h b/Source/Core/Common/Result.h
new file mode 100644
index 0000000000..2c4f47b3d7
--- /dev/null
+++ b/Source/Core/Common/Result.h
@@ -0,0 +1,30 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <variant>
+
+namespace Common
+{
+template <typename ResultCode, typename T>
+class Result final
+{
+public:
+ Result(ResultCode code) : m_variant{code} {}
+ Result(const T& t) : m_variant{t} {}
+ Result(T&& t) : m_variant{std::move(t)} {}
+ explicit operator bool() const { return Succeeded(); }
+ bool Succeeded() const { return std::holds_alternative<T>(m_variant); }
+ // Must only be called when Succeeded() returns false.
+ ResultCode Error() const { return std::get<ResultCode>(m_variant); }
+ // Must only be called when Succeeded() returns true.
+ const T& operator*() const { return std::get<T>(m_variant); }
+ const T* operator->() const { return &std::get<T>(m_variant); }
+ T& operator*() { return std::get<T>(m_variant); }
+ T* operator->() { return &std::get<T>(m_variant); }
+private:
+ std::variant<ResultCode, T> m_variant;
+};
+} // namespace Common
diff --git a/Source/Core/Common/Swap.h b/Source/Core/Common/Swap.h
index c5b143dbcb..207faac93c 100644
--- a/Source/Core/Common/Swap.h
+++ b/Source/Core/Common/Swap.h
@@ -166,4 +166,16 @@ inline T FromBigEndian(T data)
swap<sizeof(data)>(reinterpret_cast<u8*>(&data));
return data;
}
+
+template <typename value_type>
+struct BigEndianValue
+{
+ static_assert(std::is_arithmetic<value_type>(), "value_type must be an arithmetic type");
+ BigEndianValue() = default;
+ explicit BigEndianValue(value_type val) { *this = val; }
+ operator value_type() const { return FromBigEndian(raw); }
+ void operator=(value_type v) { raw = FromBigEndian(v); }
+private:
+ value_type raw;
+};
} // Namespace Common
diff --git a/Source/Core/Core/Boot/Boot_WiiWAD.cpp b/Source/Core/Core/Boot/Boot_WiiWAD.cpp
index 156c2698a8..111f87de3b 100644
--- a/Source/Core/Core/Boot/Boot_WiiWAD.cpp
+++ b/Source/Core/Core/Boot/Boot_WiiWAD.cpp
@@ -2,37 +2,67 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
-#include <cstddef>
+#include <algorithm>
#include <memory>
#include <string>
-#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
-#include "Common/FileUtil.h"
#include "Common/MsgHandler.h"
-#include "Common/NandPaths.h"
#include "Core/Boot/Boot.h"
#include "Core/CommonTitles.h"
#include "Core/IOS/ES/ES.h"
#include "Core/IOS/ES/Formats.h"
-#include "Core/IOS/FS/FileIO.h"
+#include "Core/IOS/FS/FileSystem.h"
#include "Core/IOS/IOS.h"
#include "Core/WiiUtils.h"
#include "DiscIO/WiiWad.h"
+// cdb.vff is a virtual Fat filesystem created on first launch of sysmenu
+// we create it here as it is faster ~3 minutes for me when sysmenu does it
+// ~1 second created here
+static void CreateVirtualFATFilesystem(std::shared_ptr<IOS::HLE::FS::FileSystem> fs)
+{
+ constexpr u32 SYSMENU_UID = 0x1000;
+ constexpr u16 SYSMENU_GID = 0x1;
+
+ const std::string cdb_path = "/title/00000001/00000002/data/cdb.vff";
+ fs->CreateFile(SYSMENU_UID, SYSMENU_GID, cdb_path, 0, IOS::HLE::FS::Mode::ReadWrite,
+ IOS::HLE::FS::Mode::ReadWrite, IOS::HLE::FS::Mode::ReadWrite);
+
+ constexpr size_t CDB_SIZE = 0x01400000;
+ const auto metadata = fs->GetMetadata(SYSMENU_UID, SYSMENU_GID, cdb_path);
+ if (!metadata || metadata->size >= CDB_SIZE)
+ return;
+
+ const auto fd = fs->OpenFile(SYSMENU_UID, SYSMENU_GID, cdb_path, IOS::HLE::FS::Mode::Write);
+ if (!fd)
+ return;
+
+ constexpr u8 CDB_HEADER[0x20] = {'V', 'F', 'F', 0x20, 0xfe, 0xff, 1, 0, 1, 0x40, 0, 0, 0, 0x20};
+ constexpr u8 CDB_FAT[4] = {0xf0, 0xff, 0xff, 0xff};
+ std::vector<u8> data(CDB_SIZE);
+ std::copy_n(CDB_HEADER, sizeof(CDB_HEADER), data.begin());
+ std::copy_n(CDB_FAT, sizeof(CDB_FAT), data.begin() + sizeof(CDB_HEADER));
+ std::copy_n(CDB_FAT, sizeof(CDB_FAT), data.begin() + 0x14020);
+ // write the final 0 to 0 file from the second FAT to 20 MiB
+ data[CDB_SIZE - 1] = 0;
+ fs->WriteFile(*fd, data.data(), static_cast<u32>(data.size()));
+}
+
bool CBoot::BootNANDTitle(const u64 title_id)
{
UpdateStateFlags([](StateFlags* state) {
state->type = 0x04; // TYPE_NANDBOOT
});
+ auto* ios = IOS::HLE::GetIOS();
+
if (title_id == Titles::SYSTEM_MENU)
- IOS::HLE::CreateVirtualFATFilesystem();
+ CreateVirtualFATFilesystem(ios->GetFS());
SetupWiiMemory();
- auto* ios = IOS::HLE::GetIOS();
return ios->GetES()->LaunchTitle(title_id);
}
diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index 60d6d23769..aca7f15e81 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -165,8 +165,10 @@ add_library(core
IOS/ES/TitleInformation.cpp
IOS/ES/TitleManagement.cpp
IOS/ES/Views.cpp
- IOS/FS/FileIO.cpp
- IOS/FS/FS.cpp
+ IOS/FS/FileSystem.cpp
+ IOS/FS/FileSystemProxy.cpp
+ IOS/FS/HostBackend/File.cpp
+ IOS/FS/HostBackend/FS.cpp
IOS/Network/ICMPLin.cpp
IOS/Network/MACUtils.cpp
IOS/Network/Socket.cpp
diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj
index 99f2b19876..641aaeaaab 100644
--- a/Source/Core/Core/Core.vcxproj
+++ b/Source/Core/Core/Core.vcxproj
@@ -195,8 +195,10 @@
<ClCompile Include="IOS\ES\TitleInformation.cpp" />
<ClCompile Include="IOS\ES\TitleManagement.cpp" />
<ClCompile Include="IOS\ES\Views.cpp" />
- <ClCompile Include="IOS\FS\FileIO.cpp" />
- <ClCompile Include="IOS\FS\FS.cpp" />
+ <ClCompile Include="IOS\FS\FileSystem.cpp" />
+ <ClCompile Include="IOS\FS\FileSystemProxy.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" />
@@ -445,8 +447,10 @@
<ClInclude Include="IOS\DI\DI.h" />
<ClInclude Include="IOS\ES\ES.h" />
<ClInclude Include="IOS\ES\Formats.h" />
- <ClInclude Include="IOS\FS\FileIO.h" />
- <ClInclude Include="IOS\FS\FS.h" />
+ <ClInclude Include="IOS\FS\FileSystem.h" />
+ <ClInclude Include="IOS\FS\FileSystemProxy.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" />
diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters
index 189b7d2088..5456cd4be5 100644
--- a/Source/Core/Core/Core.vcxproj.filters
+++ b/Source/Core/Core/Core.vcxproj.filters
@@ -732,7 +732,16 @@
<ClCompile Include="IOS\DI\DI.cpp">
<Filter>IOS\DI</Filter>
</ClCompile>
- <ClCompile Include="IOS\FS\FileIO.cpp">
+ <ClCompile Include="IOS\FS\FileSystem.cpp">
+ <Filter>IOS\FS</Filter>
+ </ClCompile>
+ <ClCompile Include="IOS\FS\FileSystemProxy.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">
@@ -759,9 +768,6 @@
<ClCompile Include="IOS\ES\Views.cpp">
<Filter>IOS\ES</Filter>
</ClCompile>
- <ClCompile Include="IOS\FS\FS.cpp">
- <Filter>IOS\FS</Filter>
- </ClCompile>
<ClCompile Include="IOS\Network\ICMPLin.cpp">
<Filter>IOS\Network</Filter>
</ClCompile>
@@ -1401,15 +1407,21 @@
<ClInclude Include="IOS\ES\ES.h">
<Filter>IOS\ES</Filter>
</ClInclude>
- <ClInclude Include="IOS\FS\FileIO.h">
+ <ClInclude Include="IOS\FS\FileSystem.h">
<Filter>IOS\FS</Filter>
</ClInclude>
- <ClInclude Include="IOS\ES\Formats.h">
- <Filter>IOS\ES</Filter>
+ <ClInclude Include="IOS\FS\FileSystemProxy.h">
+ <Filter>IOS\FS</Filter>
+ </ClInclude>
+ <ClInclude Include="IOS\FS\HostBackend\File.h">
+ <Filter>IOS\FS</Filter>
</ClInclude>
- <ClInclude Include="IOS\FS\FS.h">
+ <ClInclude Include="IOS\FS\HostBackend\FS.h">
<Filter>IOS\FS</Filter>
</ClInclude>
+ <ClInclude Include="IOS\ES\Formats.h">
+ <Filter>IOS\ES</Filter>
+ </ClInclude>
<ClInclude Include="IOS\USB\Bluetooth\hci.h">
<Filter>IOS\USB\Bluetooth</Filter>
</ClInclude>
diff --git a/Source/Core/Core/IOS/Device.h b/Source/Core/Core/IOS/Device.h
index 46cb0d0479..32dc456037 100644
--- a/Source/Core/Core/IOS/Device.h
+++ b/Source/Core/Core/IOS/Device.h
@@ -173,7 +173,6 @@ public:
enum class DeviceType : u32
{
Static, // Devices which appear in s_device_map.
- FileIO, // FileIO devices which are created dynamically.
OH0, // OH0 child devices which are created dynamically.
};
diff --git a/Source/Core/Core/IOS/FS/FS.cpp b/Source/Core/Core/IOS/FS/FS.cpp
deleted file mode 100644
index b9c5889390..0000000000
--- a/Source/Core/Core/IOS/FS/FS.cpp
+++ /dev/null
@@ -1,650 +0,0 @@
-// Copyright 2008 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
-
-#include "Core/IOS/FS/FS.h"
-
-#include <algorithm>
-#include <cstring>
-#include <deque>
-#include <memory>
-#include <vector>
-
-#include "Common/Assert.h"
-#include "Common/ChunkFile.h"
-#include "Common/CommonPaths.h"
-#include "Common/CommonTypes.h"
-#include "Common/File.h"
-#include "Common/FileUtil.h"
-#include "Common/Logging/Log.h"
-#include "Common/MsgHandler.h"
-#include "Common/NandPaths.h"
-#include "Core/HW/Memmap.h"
-#include "Core/HW/SystemTimers.h"
-#include "Core/IOS/ES/ES.h"
-#include "Core/IOS/ES/Formats.h"
-#include "Core/IOS/FS/FileIO.h"
-
-namespace IOS
-{
-namespace HLE
-{
-static bool IsValidWiiPath(const std::string& path)
-{
- return path.compare(0, 1, "/") == 0;
-}
-
-namespace Device
-{
-FS::FS(Kernel& ios, const std::string& device_name) : Device(ios, device_name)
-{
- const std::string tmp_dir = BuildFilename("/tmp");
- File::DeleteDirRecursively(tmp_dir);
- File::CreateDir(tmp_dir);
-}
-
-void FS::DoState(PointerWrap& p)
-{
- DoStateShared(p);
-
- // handle /tmp
-
- std::string Path = File::GetUserPath(D_SESSION_WIIROOT_IDX) + "/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 filename;
- p.Do(filename);
- std::string name = Path + DIR_SEP + filename;
- 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 parentEntry = File::ScanDirectoryTree(Path, true);
- std::deque<File::FSTEntry> todo;
- todo.insert(todo.end(), parentEntry.children.begin(), parentEntry.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);
- }
-}
-
-// 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& parentEntry)
-{
- u64 sizeOfFiles = 0;
- for (const File::FSTEntry& entry : parentEntry.children)
- {
- if (entry.isDirectory)
- sizeOfFiles += ComputeTotalFileSize(entry);
- else
- sizeOfFiles += entry.size;
- }
- return sizeOfFiles;
-}
-
-IPCCommandResult FS::IOCtl(const IOCtlRequest& request)
-{
- Memory::Memset(request.buffer_out, 0, request.buffer_out_size);
-
- switch (request.request)
- {
- case IOCTL_GET_STATS:
- return GetStats(request);
- case IOCTL_CREATE_DIR:
- return CreateDirectory(request);
- case IOCTL_SET_ATTR:
- return SetAttribute(request);
- case IOCTL_GET_ATTR:
- return GetAttribute(request);
- case IOCTL_DELETE_FILE:
- return DeleteFile(request);
- case IOCTL_RENAME_FILE:
- return RenameFile(request);
- case IOCTL_CREATE_FILE:
- return CreateFile(request);
- case IOCTL_SHUTDOWN:
- return Shutdown(request);
- default:
- request.DumpUnknown(GetDeviceName(), LogTypes::IOS_FILEIO);
- break;
- }
-
- return GetFSReply(FS_EINVAL);
-}
-
-IPCCommandResult FS::IOCtlV(const IOCtlVRequest& request)
-{
- switch (request.request)
- {
- case IOCTLV_READ_DIR:
- return ReadDirectory(request);
- case IOCTLV_GETUSAGE:
- return GetUsage(request);
- default:
- request.DumpUnknown(GetDeviceName(), LogTypes::IOS_FILEIO);
- break;
- }
-
- return GetFSReply(IPC_SUCCESS);
-}
-
-// ~1/1000th of a second is too short and causes hangs in Wii Party
-// Play it safe at 1/500th
-IPCCommandResult FS::GetFSReply(const s32 return_value) const
-{
- return {return_value, true, SystemTimers::GetTicksPerSecond() / 500};
-}
-
-IPCCommandResult FS::GetStats(const IOCtlRequest& request)
-{
- if (request.buffer_out_size < 0x1c)
- return GetFSReply(-1017);
-
- WARN_LOG(IOS_FILEIO, "FS: GET STATS - returning static values for now");
-
- // TODO: scrape the real amounts from somewhere...
- NANDStat fs;
- fs.BlockSize = 0x4000;
- fs.FreeUserBlocks = 0x5DEC;
- fs.UsedUserBlocks = 0x1DD4;
- fs.FreeSysBlocks = 0x10;
- fs.UsedSysBlocks = 0x02F0;
- fs.Free_INodes = 0x146B;
- fs.Used_Inodes = 0x0394;
-
- std::memcpy(Memory::GetPointer(request.buffer_out), &fs, sizeof(NANDStat));
-
- return GetFSReply(IPC_SUCCESS);
-}
-
-IPCCommandResult FS::CreateDirectory(const IOCtlRequest& request)
-{
- DEBUG_ASSERT(request.buffer_out_size == 0);
- u32 Addr = request.buffer_in;
-
- u32 OwnerID = Memory::Read_U32(Addr);
- Addr += 4;
- u16 GroupID = Memory::Read_U16(Addr);
- Addr += 2;
-
- const std::string wii_path = Memory::GetString(Addr, 64);
- if (!IsValidWiiPath(wii_path))
- {
- WARN_LOG(IOS_FILEIO, "Not a valid path: %s", wii_path.c_str());
- return GetFSReply(FS_EINVAL);
- }
-
- std::string DirName(BuildFilename(wii_path));
- Addr += 64;
- Addr += 9; // owner attribs, permission
- u8 Attribs = Memory::Read_U8(Addr);
-
- INFO_LOG(IOS_FILEIO, "FS: CREATE_DIR %s, OwnerID %#x, GroupID %#x, Attributes %#x",
- DirName.c_str(), OwnerID, GroupID, Attribs);
-
- DirName += DIR_SEP;
- File::CreateFullPath(DirName);
- DEBUG_ASSERT_MSG(IOS_FILEIO, File::IsDirectory(DirName), "FS: CREATE_DIR %s failed",
- DirName.c_str());
-
- return GetFSReply(IPC_SUCCESS);
-}
-
-IPCCommandResult FS::SetAttribute(const IOCtlRequest& request)
-{
- u32 Addr = request.buffer_in;
-
- u32 OwnerID = Memory::Read_U32(Addr);
- Addr += 4;
- u16 GroupID = Memory::Read_U16(Addr);
- Addr += 2;
-
- const std::string wii_path = Memory::GetString(Addr, 64);
- if (!IsValidWiiPath(wii_path))
- {
- WARN_LOG(IOS_FILEIO, "Not a valid path: %s", wii_path.c_str());
- return GetFSReply(FS_EINVAL);
- }
-
- std::string Filename = BuildFilename(wii_path);
- Addr += 64;
- u8 OwnerPerm = Memory::Read_U8(Addr);
- Addr += 1;
- u8 GroupPerm = Memory::Read_U8(Addr);
- Addr += 1;
- u8 OtherPerm = Memory::Read_U8(Addr);
- Addr += 1;
- u8 Attributes = Memory::Read_U8(Addr);
- Addr += 1;
-
- INFO_LOG(IOS_FILEIO, "FS: SetAttrib %s", Filename.c_str());
- DEBUG_LOG(IOS_FILEIO, " OwnerID: 0x%08x", OwnerID);
- DEBUG_LOG(IOS_FILEIO, " GroupID: 0x%04x", GroupID);
- DEBUG_LOG(IOS_FILEIO, " OwnerPerm: 0x%02x", OwnerPerm);
- DEBUG_LOG(IOS_FILEIO, " GroupPerm: 0x%02x", GroupPerm);
- DEBUG_LOG(IOS_FILEIO, " OtherPerm: 0x%02x", OtherPerm);
- DEBUG_LOG(IOS_FILEIO, " Attributes: 0x%02x", Attributes);
-
- return GetFSReply(IPC_SUCCESS);
-}
-
-IPCCommandResult FS::GetAttribute(const IOCtlRequest& request)
-{
- DEBUG_ASSERT_MSG(IOS_FILEIO, request.buffer_out_size == 76,
- " GET_ATTR needs an 76 bytes large output buffer but it is %i bytes large",
- request.buffer_out_size);
-
- u32 OwnerID = 0;
- u16 GroupID = 0x3031; // this is also known as makercd, 01 (0x3031) for nintendo and 08
- // (0x3038) for MH3 etc
-
- const std::string wii_path = Memory::GetString(request.buffer_in, 64);
- if (!IsValidWiiPath(wii_path))
- {
- WARN_LOG(IOS_FILEIO, "Not a valid path: %s", wii_path.c_str());
- return GetFSReply(FS_EINVAL);
- }
-
- std::string Filename = BuildFilename(wii_path);
- u8 OwnerPerm = 0x3; // read/write
- u8 GroupPerm = 0x3; // read/write
- u8 OtherPerm = 0x3; // read/write
- u8 Attributes = 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(Filename, Common::FROM_SESSION_ROOT, &title_id))
- {
- IOS::ES::TMDReader tmd = GetIOS()->GetES()->FindInstalledTMD(title_id);
- if (tmd.IsValid())
- {
- GroupID = tmd.GetGroupId();
- }
- }
-
- if (File::IsDirectory(Filename))
- {
- INFO_LOG(IOS_FILEIO, "FS: GET_ATTR Directory %s - all permission flags are set",
- Filename.c_str());
- }
- else
- {
- if (File::Exists(Filename))
- {
- INFO_LOG(IOS_FILEIO, "FS: GET_ATTR %s - all permission flags are set", Filename.c_str());
- }
- else
- {
- INFO_LOG(IOS_FILEIO, "FS: GET_ATTR unknown %s", Filename.c_str());
- return GetFSReply(FS_ENOENT);
- }
- }
-
- // write answer to buffer
- if (request.buffer_out_size == 76)
- {
- u32 Addr = request.buffer_out;
- Memory::Write_U32(OwnerID, Addr);
- Addr += 4;
- Memory::Write_U16(GroupID, Addr);
- Addr += 2;
- memcpy(Memory::GetPointer(Addr), Memory::GetPointer(request.buffer_in), 64);
- Addr += 64;
- Memory::Write_U8(OwnerPerm, Addr);
- Addr += 1;
- Memory::Write_U8(GroupPerm, Addr);
- Addr += 1;
- Memory::Write_U8(OtherPerm, Addr);
- Addr += 1;
- Memory::Write_U8(Attributes, Addr);
- Addr += 1;
- }
-
- return GetFSReply(IPC_SUCCESS);
-}
-
-IPCCommandResult FS::DeleteFile(const IOCtlRequest& request)
-{
- DEBUG_ASSERT(request.buffer_out_size == 0);
- int Offset = 0;
-
- const std::string wii_path = Memory::GetString(request.buffer_in + Offset, 64);
- if (!IsValidWiiPath(wii_path))
- {
- WARN_LOG(IOS_FILEIO, "Not a valid path: %s", wii_path.c_str());
- return GetFSReply(FS_EINVAL);
- }
-
- std::string Filename = BuildFilename(wii_path);
- Offset += 64;
- if (File::Delete(Filename))
- {
- INFO_LOG(IOS_FILEIO, "FS: DeleteFile %s", Filename.c_str());
- }
- else if (File::DeleteDir(Filename))
- {
- INFO_LOG(IOS_FILEIO, "FS: DeleteDir %s", Filename.c_str());
- }
- else
- {
- WARN_LOG(IOS_FILEIO, "FS: DeleteFile %s - failed!!!", Filename.c_str());
- }
-
- return GetFSReply(IPC_SUCCESS);
-}
-
-IPCCommandResult FS::RenameFile(const IOCtlRequest& request)
-{
- DEBUG_ASSERT(request.buffer_out_size == 0);
- int Offset = 0;
-
- const std::string wii_path = Memory::GetString(request.buffer_in + Offset, 64);
- if (!IsValidWiiPath(wii_path))
- {
- WARN_LOG(IOS_FILEIO, "Not a valid path: %s", wii_path.c_str());
- return GetFSReply(FS_EINVAL);
- }
- std::string Filename = BuildFilename(wii_path);
- Offset += 64;
-
- const std::string wii_path_rename = Memory::GetString(request.buffer_in + Offset, 64);
- if (!IsValidWiiPath(wii_path_rename))
- {
- WARN_LOG(IOS_FILEIO, "Not a valid path: %s", wii_path_rename.c_str());
- return GetFSReply(FS_EINVAL);
- }
-
- std::string FilenameRename = BuildFilename(wii_path_rename);
- Offset += 64;
-
- // try to make the basis directory
- File::CreateFullPath(FilenameRename);
-
- // if there is already a file, delete it
- if (File::Exists(Filename) && File::Exists(FilenameRename))
- {
- File::Delete(FilenameRename);
- }
-
- // finally try to rename the file
- if (File::Rename(Filename, FilenameRename))
- {
- INFO_LOG(IOS_FILEIO, "FS: Rename %s to %s", Filename.c_str(), FilenameRename.c_str());
- }
- else
- {
- ERROR_LOG(IOS_FILEIO, "FS: Rename %s to %s - failed", Filename.c_str(), FilenameRename.c_str());
- return GetFSReply(FS_ENOENT);
- }
-
- return GetFSReply(IPC_SUCCESS);
-}
-
-IPCCommandResult FS::CreateFile(const IOCtlRequest& request)
-{
- DEBUG_ASSERT(request.buffer_out_size == 0);
-
- u32 Addr = request.buffer_in;
- u32 OwnerID = Memory::Read_U32(Addr);
- Addr += 4;
- u16 GroupID = Memory::Read_U16(Addr);
- Addr += 2;
-
- const std::string wii_path = Memory::GetString(Addr, 64);
- if (!IsValidWiiPath(wii_path))
- {
- WARN_LOG(IOS_FILEIO, "Not a valid path: %s", wii_path.c_str());
- return GetFSReply(FS_EINVAL);
- }
-
- std::string Filename(BuildFilename(wii_path));
- Addr += 64;
- u8 OwnerPerm = Memory::Read_U8(Addr);
- Addr++;
- u8 GroupPerm = Memory::Read_U8(Addr);
- Addr++;
- u8 OtherPerm = Memory::Read_U8(Addr);
- Addr++;
- u8 Attributes = Memory::Read_U8(Addr);
- Addr++;
-
- INFO_LOG(IOS_FILEIO, "FS: CreateFile %s", Filename.c_str());
- DEBUG_LOG(IOS_FILEIO, " OwnerID: 0x%08x", OwnerID);
- DEBUG_LOG(IOS_FILEIO, " GroupID: 0x%04x", GroupID);
- DEBUG_LOG(IOS_FILEIO, " OwnerPerm: 0x%02x", OwnerPerm);
- DEBUG_LOG(IOS_FILEIO, " GroupPerm: 0x%02x", GroupPerm);
- DEBUG_LOG(IOS_FILEIO, " OtherPerm: 0x%02x", OtherPerm);
- DEBUG_LOG(IOS_FILEIO, " Attributes: 0x%02x", Attributes);
-
- // check if the file already exist
- if (File::Exists(Filename))
- {
- INFO_LOG(IOS_FILEIO, "\tresult = FS_EEXIST");
- return GetFSReply(FS_EEXIST);
- }
-
- // create the file
- File::CreateFullPath(Filename); // just to be sure
- bool Result = File::CreateEmptyFile(Filename);
- if (!Result)
- {
- ERROR_LOG(IOS_FILEIO, "FS: couldn't create new file");
- PanicAlert("FS: couldn't create new file");
- return GetFSReply(FS_EINVAL);
- }
-
- INFO_LOG(IOS_FILEIO, "\tresult = IPC_SUCCESS");
- return GetFSReply(IPC_SUCCESS);
-}
-
-IPCCommandResult FS::Shutdown(const IOCtlRequest& request)
-{
- // TODO: stop emulation
- INFO_LOG(IOS_FILEIO, "Wii called Shutdown()");
- return GetFSReply(IPC_SUCCESS);
-}
-
-IPCCommandResult FS::ReadDirectory(const IOCtlVRequest& request)
-{
- const std::string relative_path =
- Memory::GetString(request.in_vectors[0].address, request.in_vectors[0].size);
-
- if (!IsValidWiiPath(relative_path))
- {
- WARN_LOG(IOS_FILEIO, "Not a valid path: %s", relative_path.c_str());
- return GetFSReply(FS_EINVAL);
- }
-
- // the Wii uses this function to define the type (dir or file)
- std::string DirName(BuildFilename(relative_path));
-
- INFO_LOG(IOS_FILEIO, "FS: IOCTL_READ_DIR %s", DirName.c_str());
-
- const File::FileInfo file_info(DirName);
-
- if (!file_info.Exists())
- {
- WARN_LOG(IOS_FILEIO, "FS: Search not found: %s", DirName.c_str());
- return GetFSReply(FS_ENOENT);
- }
-
- 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 GetFSReply(FS_EINVAL);
- }
-
- File::FSTEntry entry = File::ScanDirectoryTree(DirName, false);
-
- // it is one
- if ((request.in_vectors.size() == 1) && (request.io_vectors.size() == 1))
- {
- size_t numFile = entry.children.size();
- INFO_LOG(IOS_FILEIO, "\t%zu files found", numFile);
-
- Memory::Write_U32((u32)numFile, request.io_vectors[0].address);
- }
- else
- {
- 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);
- }
-
- std::sort(entry.children.begin(), entry.children.end(),
- [](const File::FSTEntry& one, const File::FSTEntry& two) {
- return one.virtualName < two.virtualName;
- });
-
- u32 MaxEntries = Memory::Read_U32(request.in_vectors[0].address);
-
- memset(Memory::GetPointer(request.io_vectors[0].address), 0, request.io_vectors[0].size);
-
- size_t numFiles = 0;
- char* pFilename = (char*)Memory::GetPointer((u32)(request.io_vectors[0].address));
-
- for (size_t i = 0; i < entry.children.size() && i < MaxEntries; i++)
- {
- const std::string& FileName = entry.children[i].virtualName;
-
- strcpy(pFilename, FileName.c_str());
- pFilename += FileName.length();
- *pFilename++ = 0x00; // termination
- numFiles++;
-
- INFO_LOG(IOS_FILEIO, "\tFound: %s", FileName.c_str());
- }
-
- Memory::Write_U32((u32)numFiles, request.io_vectors[1].address);
- }
-
- return GetFSReply(IPC_SUCCESS);
-}
-
-IPCCommandResult FS::GetUsage(const IOCtlVRequest& request)
-{
- DEBUG_ASSERT(request.io_vectors.size() == 2);
- DEBUG_ASSERT(request.io_vectors[0].size == 4);
- DEBUG_ASSERT(request.io_vectors[1].size == 4);
-
- // this command sucks because it asks of the number of used
- // fsBlocks and inodes
- // It should be correct, but don't count on it...
- std::string relativepath =
- Memory::GetString(request.in_vectors[0].address, request.in_vectors[0].size);
-
- if (!IsValidWiiPath(relativepath))
- {
- WARN_LOG(IOS_FILEIO, "Not a valid path: %s", relativepath.c_str());
- return GetFSReply(FS_EINVAL);
- }
-
- std::string path(BuildFilename(relativepath));
- u32 fsBlocks = 0;
- u32 iNodes = 0;
-
- INFO_LOG(IOS_FILEIO, "IOCTL_GETUSAGE %s", path.c_str());
- if (File::IsDirectory(path))
- {
- File::FSTEntry parentDir = File::ScanDirectoryTree(path, true);
- // add one for the folder itself
- iNodes = 1 + (u32)parentDir.size;
-
- u64 totalSize = ComputeTotalFileSize(parentDir); // "Real" size, to be converted to nand blocks
-
- fsBlocks = (u32)(totalSize / (16 * 1024)); // one bock is 16kb
-
- INFO_LOG(IOS_FILEIO, "FS: fsBlock: %i, iNodes: %i", fsBlocks, iNodes);
- }
- else
- {
- fsBlocks = 0;
- iNodes = 0;
- WARN_LOG(IOS_FILEIO, "FS: fsBlock failed, cannot find directory: %s", path.c_str());
- }
-
- Memory::Write_U32(fsBlocks, request.io_vectors[0].address);
- Memory::Write_U32(iNodes, request.io_vectors[1].address);
-
- return GetFSReply(IPC_SUCCESS);
-}
-} // namespace Device
-} // namespace HLE
-} // namespace IOS
diff --git a/Source/Core/Core/IOS/FS/FS.h b/Source/Core/Core/IOS/FS/FS.h
deleted file mode 100644
index 0859e874cb..0000000000
--- a/Source/Core/Core/IOS/FS/FS.h
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2008 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <string>
-
-#include "Common/CommonTypes.h"
-#include "Core/IOS/Device.h"
-#include "Core/IOS/IOS.h"
-
-class PointerWrap;
-
-namespace IOS
-{
-namespace HLE
-{
-struct NANDStat
-{
- u32 BlockSize;
- u32 FreeUserBlocks;
- u32 UsedUserBlocks;
- u32 FreeSysBlocks;
- u32 UsedSysBlocks;
- u32 Free_INodes;
- u32 Used_Inodes;
-};
-
-namespace Device
-{
-class FS : public Device
-{
-public:
- FS(Kernel& ios, const std::string& device_name);
-
- void DoState(PointerWrap& p) override;
-
- IPCCommandResult IOCtl(const IOCtlRequest& request) override;
- IPCCommandResult IOCtlV(const IOCtlVRequest& request) override;
-
-private:
- enum
- {
- IOCTL_GET_STATS = 0x02,
- IOCTL_CREATE_DIR = 0x03,
- IOCTLV_READ_DIR = 0x04,
- IOCTL_SET_ATTR = 0x05,
- IOCTL_GET_ATTR = 0x06,
- IOCTL_DELETE_FILE = 0x07,
- IOCTL_RENAME_FILE = 0x08,
- IOCTL_CREATE_FILE = 0x09,
- IOCTLV_GETUSAGE = 0x0C,
- IOCTL_SHUTDOWN = 0x0D
- };
-
- IPCCommandResult GetFSReply(s32 return_value) const;
-
- IPCCommandResult GetStats(const IOCtlRequest& request);
- IPCCommandResult CreateDirectory(const IOCtlRequest& request);
- IPCCommandResult SetAttribute(const IOCtlRequest& request);
- IPCCommandResult GetAttribute(const IOCtlRequest& request);
- IPCCommandResult DeleteFile(const IOCtlRequest& request);
- IPCCommandResult RenameFile(const IOCtlRequest& request);
- IPCCommandResult CreateFile(const IOCtlRequest& request);
- IPCCommandResult Shutdown(const IOCtlRequest& request);
-
- IPCCommandResult ReadDirectory(const IOCtlVRequest& request);
- IPCCommandResult GetUsage(const IOCtlVRequest& request);
-};
-} // namespace Device
-} // namespace HLE
-} // namespace IOS
diff --git a/Source/Core/Core/IOS/FS/FileIO.cpp b/Source/Core/Core/IOS/FS/FileIO.cpp
deleted file mode 100644
index d7e3cb3f44..0000000000
--- a/Source/Core/Core/IOS/FS/FileIO.cpp
+++ /dev/null
@@ -1,322 +0,0 @@
-// Copyright 2008 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
-
-#include "Core/IOS/FS/FileIO.h"
-
-#include <cinttypes>
-#include <cstdio>
-#include <map>
-#include <memory>
-#include <utility>
-
-#include "Common/Assert.h"
-#include "Common/ChunkFile.h"
-#include "Common/CommonTypes.h"
-#include "Common/File.h"
-#include "Common/FileUtil.h"
-#include "Common/NandPaths.h"
-#include "Core/CommonTitles.h"
-#include "Core/HW/Memmap.h"
-#include "Core/IOS/IOS.h"
-
-namespace IOS
-{
-namespace HLE
-{
-static std::map<std::string, std::weak_ptr<File::IOFile>> openFiles;
-
-// This is used by several of the FileIO and /dev/fs functions
-std::string 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;
-}
-
-void CreateVirtualFATFilesystem()
-{
- const int cdbSize = 0x01400000;
- const std::string cdbPath =
- Common::GetTitleDataPath(Titles::SYSTEM_MENU, Common::FROM_SESSION_ROOT) + "cdb.vff";
- if ((int)File::GetSize(cdbPath) < cdbSize)
- {
- // cdb.vff is a virtual Fat filesystem created on first launch of sysmenu
- // we create it here as it is faster ~3 minutes for me when sysmenu does it ~1 second created
- // here
- const u8 cdbHDR[0x20] = {'V', 'F', 'F', 0x20, 0xfe, 0xff, 1, 0, 1, 0x40, 0, 0, 0, 0x20};
- const u8 cdbFAT[4] = {0xf0, 0xff, 0xff, 0xff};
-
- File::IOFile cdbFile(cdbPath, "wb");
- if (cdbFile)
- {
- cdbFile.WriteBytes(cdbHDR, 0x20);
- cdbFile.WriteBytes(cdbFAT, 0x4);
- cdbFile.Seek(0x14020, SEEK_SET);
- cdbFile.WriteBytes(cdbFAT, 0x4);
- // 20 MiB file
- cdbFile.Seek(cdbSize - 1, SEEK_SET);
- // write the final 0 to 0 file from the second FAT to 20 MiB
- cdbFile.WriteBytes(cdbHDR + 14, 1);
- if (!cdbFile.IsGood())
- {
- cdbFile.Close();
- File::Delete(cdbPath);
- }
- cdbFile.Flush();
- cdbFile.Close();
- }
- }
-}
-
-namespace Device
-{
-FileIO::FileIO(Kernel& ios, const std::string& device_name)
- : Device(ios, device_name, DeviceType::FileIO)
-{
-}
-
-IPCCommandResult FileIO::Close(u32 fd)
-{
- INFO_LOG(IOS_FILEIO, "FileIO: Close %s", m_name.c_str());
- m_Mode = 0;
-
- // Let go of our pointer to the file, it will automatically close if we are the last handle
- // accessing it.
- m_file.reset();
-
- m_is_active = false;
- return GetDefaultReply(IPC_SUCCESS);
-}
-
-IPCCommandResult FileIO::Open(const OpenRequest& request)
-{
- m_Mode = request.flags;
-
- static const char* const Modes[] = {"Unk Mode", "Read only", "Write only", "Read and Write"};
-
- m_filepath = BuildFilename(m_name);
-
- // The file must exist before we can open it
- // It should be created by ISFS_CreateFile, not here
- if (!File::IsFile(m_filepath))
- {
- WARN_LOG(IOS_FILEIO, "FileIO: Open (%s) failed - File doesn't exist %s", Modes[m_Mode],
- m_filepath.c_str());
- return GetDefaultReply(FS_ENOENT);
- }
-
- INFO_LOG(IOS_FILEIO, "FileIO: Open %s (%s == %08X)", m_name.c_str(), Modes[m_Mode], m_Mode);
- OpenFile();
-
- m_is_active = true;
- return GetDefaultReply(IPC_SUCCESS);
-}
-
-// This isn't theadsafe, but it's only called from the CPU thread.
-void FileIO::OpenFile()
-{
- // 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.
- auto search = openFiles.find(m_name);
- if (search != openFiles.end())
- {
- m_file = search->second.lock(); // Lock a shared pointer to use.
- }
- else
- {
- std::string path = m_name;
- // This code will be called when all references to the shared pointer below have been removed.
- auto deleter = [path](File::IOFile* ptr) {
- delete ptr; // IOFile's deconstructor closes the file.
- openFiles.erase(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
- m_file = std::shared_ptr<File::IOFile>(new File::IOFile(m_filepath, "r+b"),
- deleter); // Use the custom deleter from above.
-
- // Store a weak pointer to our newly opened file in the cache.
- openFiles[path] = std::weak_ptr<File::IOFile>(m_file);
- }
-}
-
-IPCCommandResult FileIO::Seek(const SeekRequest& request)
-{
- if (!m_file->IsOpen())
- return GetDefaultReply(FS_ENOENT);
-
- const u32 file_size = static_cast<u32>(m_file->GetSize());
- DEBUG_LOG(IOS_FILEIO, "FileIO: Seek Pos: 0x%08x, Mode: %i (%s, Length=0x%08x)", request.offset,
- request.mode, m_name.c_str(), file_size);
-
- u32 new_position = 0;
- switch (request.mode)
- {
- case IOS_SEEK_SET:
- new_position = request.offset;
- break;
-
- case IOS_SEEK_CUR:
- new_position = m_SeekPos + request.offset;
- break;
-
- case IOS_SEEK_END:
- new_position = file_size + request.offset;
- break;
-
- default:
- return GetDefaultReply(FS_EINVAL);
- }
-
- if (new_position > file_size)
- return GetDefaultReply(FS_EINVAL);
-
- m_SeekPos = new_position;
- return GetDefaultReply(new_position);
-}
-
-IPCCommandResult FileIO::Read(const ReadWriteRequest& request)
-{
- if (!m_file->IsOpen())
- {
- ERROR_LOG(IOS_FILEIO, "Failed to read from %s (Addr=0x%08x Size=0x%x) - file could "
- "not be opened or does not exist",
- m_name.c_str(), request.buffer, request.size);
- return GetDefaultReply(FS_ENOENT);
- }
-
- if (m_Mode == IOS_OPEN_WRITE)
- {
- WARN_LOG(IOS_FILEIO, "Attempted to read 0x%x bytes to 0x%08x on a write-only file %s",
- request.size, request.buffer, m_name.c_str());
- return GetDefaultReply(FS_EACCESS);
- }
-
- u32 requested_read_length = request.size;
- const u32 file_size = static_cast<u32>(m_file->GetSize());
- // IOS has this check in the read request handler.
- if (requested_read_length + m_SeekPos > file_size)
- requested_read_length = file_size - m_SeekPos;
-
- DEBUG_LOG(IOS_FILEIO, "Read 0x%x bytes to 0x%08x from %s", request.size, request.buffer,
- m_name.c_str());
- m_file->Seek(m_SeekPos, SEEK_SET); // File might be opened twice, need to seek before we read
- const u32 number_of_bytes_read = static_cast<u32>(
- fread(Memory::GetPointer(request.buffer), 1, requested_read_length, m_file->GetHandle()));
-
- if (number_of_bytes_read != requested_read_length && ferror(m_file->GetHandle()))
- return GetDefaultReply(FS_EACCESS);
-
- // IOS returns the number of bytes read and adds that value to the seek position,
- // instead of adding the *requested* read length.
- m_SeekPos += number_of_bytes_read;
- return GetDefaultReply(number_of_bytes_read);
-}
-
-IPCCommandResult FileIO::Write(const ReadWriteRequest& request)
-{
- s32 return_value = FS_EACCESS;
- if (m_file->IsOpen())
- {
- if (m_Mode == IOS_OPEN_READ)
- {
- WARN_LOG(IOS_FILEIO,
- "FileIO: Attempted to write 0x%x bytes from 0x%08x to a read-only file %s",
- request.size, request.buffer, m_name.c_str());
- }
- else
- {
- DEBUG_LOG(IOS_FILEIO, "FileIO: Write 0x%04x bytes from 0x%08x to %s", request.size,
- request.buffer, m_name.c_str());
- m_file->Seek(m_SeekPos,
- SEEK_SET); // File might be opened twice, need to seek before we write
- if (m_file->WriteBytes(Memory::GetPointer(request.buffer), request.size))
- {
- return_value = request.size;
- m_SeekPos += request.size;
- }
- }
- }
- else
- {
- ERROR_LOG(IOS_FILEIO, "FileIO: Failed to read from %s (Addr=0x%08x Size=0x%x) - file could "
- "not be opened or does not exist",
- m_name.c_str(), request.buffer, request.size);
- return_value = FS_ENOENT;
- }
-
- return GetDefaultReply(return_value);
-}
-
-IPCCommandResult FileIO::IOCtl(const IOCtlRequest& request)
-{
- DEBUG_LOG(IOS_FILEIO, "FileIO: IOCtl (Device=%s)", m_name.c_str());
-
- switch (request.request)
- {
- case ISFS_IOCTL_GETFILESTATS:
- return GetFileStats(request);
-
- default:
- request.Log(GetDeviceName(), LogTypes::IOS_FILEIO, LogTypes::LERROR);
- break;
- }
-
- return GetDefaultReply(IPC_SUCCESS);
-}
-
-void FileIO::PrepareForState(PointerWrap::Mode mode)
-{
- // Temporally close the file, to prevent any issues with the savestating of /tmp
- // it can be opened again with another call to OpenFile()
- m_file.reset();
-}
-
-void FileIO::DoState(PointerWrap& p)
-{
- DoStateShared(p);
-
- p.Do(m_Mode);
- p.Do(m_SeekPos);
-
- m_filepath = BuildFilename(m_name);
-
- // The file was closed during state (and might now be pointing at another file)
- // Open it again
- OpenFile();
-}
-
-IPCCommandResult FileIO::GetFileStats(const IOCtlRequest& request)
-{
- if (!m_file->IsOpen())
- return GetDefaultReply(FS_ENOENT);
-
- DEBUG_LOG(IOS_FILEIO, "File: %s, Length: %" PRIu64 ", Pos: %u", m_name.c_str(), m_file->GetSize(),
- m_SeekPos);
- Memory::Write_U32(static_cast<u32>(m_file->GetSize()), request.buffer_out);
- Memory::Write_U32(m_SeekPos, request.buffer_out + 4);
- return GetDefaultReply(IPC_SUCCESS);
-}
-} // namespace Device
-} // namespace HLE
-} // namespace IOS
diff --git a/Source/Core/Core/IOS/FS/FileIO.h b/Source/Core/Core/IOS/FS/FileIO.h
deleted file mode 100644
index df98921f32..0000000000
--- a/Source/Core/Core/IOS/FS/FileIO.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2008 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <string>
-
-#include "Common/ChunkFile.h"
-#include "Common/CommonTypes.h"
-#include "Core/IOS/Device.h"
-#include "Core/IOS/IOS.h"
-
-class PointerWrap;
-
-namespace File
-{
-class IOFile;
-}
-
-namespace IOS
-{
-namespace HLE
-{
-std::string BuildFilename(const std::string& wii_path);
-void CreateVirtualFATFilesystem();
-
-namespace Device
-{
-class FileIO : public Device
-{
-public:
- FileIO(Kernel& ios, const std::string& device_name);
-
- IPCCommandResult Close(u32 fd) override;
- IPCCommandResult Open(const OpenRequest& request) override;
- IPCCommandResult Seek(const SeekRequest& request) override;
- IPCCommandResult Read(const ReadWriteRequest& request) override;
- IPCCommandResult Write(const ReadWriteRequest& request) override;
- IPCCommandResult IOCtl(const IOCtlRequest& request) override;
- void PrepareForState(PointerWrap::Mode mode) override;
- void DoState(PointerWrap& p) override;
-
- void OpenFile();
-
-private:
- enum
- {
- ISFS_FUNCNULL = 0,
- ISFS_FUNCGETSTAT = 1,
- ISFS_FUNCREADDIR = 2,
- ISFS_FUNCGETATTR = 3,
- ISFS_FUNCGETUSAGE = 4
- };
-
- enum
- {
- ISFS_IOCTL_FORMAT = 1,
- ISFS_IOCTL_GETSTATS = 2,
- ISFS_IOCTL_CREATEDIR = 3,
- ISFS_IOCTL_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_IOCTL_GETUSAGE = 12,
- ISFS_IOCTL_SHUTDOWN = 13
- };
-
- IPCCommandResult GetFileStats(const IOCtlRequest& request);
-
- u32 m_Mode = 0;
- u32 m_SeekPos = 0;
-
- std::string m_filepath;
- std::shared_ptr<File::IOFile> m_file;
-};
-} // namespace Device
-} // namespace HLE
-} // namespace IOS
diff --git a/Source/Core/Core/IOS/FS/FileSystem.cpp b/Source/Core/Core/IOS/FS/FileSystem.cpp
new file mode 100644
index 0000000000..552d68ec8b
--- /dev/null
+++ b/Source/Core/Core/IOS/FS/FileSystem.cpp
@@ -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
diff --git a/Source/Core/Core/IOS/FS/FileSystem.h b/Source/Core/Core/IOS/FS/FileSystem.h
new file mode 100644
index 0000000000..87890cdbad
--- /dev/null
+++ b/Source/Core/Core/IOS/FS/FileSystem.h
@@ -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
diff --git a/Source/Core/Core/IOS/FS/FileSystemProxy.cpp b/Source/Core/Core/IOS/FS/FileSystemProxy.cpp
new file mode 100644
index 0000000000..75d0c6c6b4
--- /dev/null
+++ b/Source/Core/Core/IOS/FS/FileSystemProxy.cpp
@@ -0,0 +1,466 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "Core/IOS/FS/FileSystemProxy.h"
+
+#include <cstring>
+
+#include "Common/Assert.h"
+#include "Common/ChunkFile.h"
+#include "Common/StringUtil.h"
+#include "Common/Swap.h"
+#include "Core/HW/Memmap.h"
+#include "Core/HW/SystemTimers.h"
+#include "Core/IOS/FS/FileSystem.h"
+
+namespace IOS
+{
+namespace HLE
+{
+namespace Device
+{
+using namespace IOS::HLE::FS;
+
+static s32 ConvertResult(ResultCode code)
+{
+ if (code == ResultCode::Success)
+ return IPC_SUCCESS;
+ return -(static_cast<s32>(code) + 100);
+}
+
+// XXX: timing is not the same for all commands and in all cases.
+static IPCCommandResult GetFSReply(s32 return_value)
+{
+ return {return_value, true, SystemTimers::GetTicksPerSecond() / 500};
+}
+
+FS::FS(Kernel& ios, const std::string& device_name) : Device(ios, device_name)
+{
+}
+
+void FS::DoState(PointerWrap& p)
+{
+ p.Do(m_fd_map);
+}
+
+static void LogResult(const std::string& command, ResultCode code)
+{
+ GENERIC_LOG(LogTypes::IOS_FS, (code == ResultCode::Success ? LogTypes::LINFO : LogTypes::LERROR),
+ "%s: result %d", command.c_str(), ConvertResult(code));
+}
+
+template <typename T>
+static void LogResult(const std::string& command, const Result<T>& result)
+{
+ LogResult(command, result.Succeeded() ? ResultCode::Success : result.Error());
+}
+
+IPCCommandResult FS::Open(const OpenRequest& request)
+{
+ if (m_fd_map.size() >= 16)
+ return GetDefaultReply(ConvertResult(ResultCode::NoFreeHandle));
+
+ if (request.path.size() >= 64)
+ return GetDefaultReply(ConvertResult(ResultCode::Invalid));
+
+ if (request.path == "/dev/fs")
+ {
+ m_fd_map[request.fd] = {request.gid, request.uid, INVALID_FD};
+ return GetDefaultReply(IPC_SUCCESS);
+ }
+
+ auto backend_fd = m_ios.GetFS()->OpenFile(request.uid, request.gid, request.path,
+ static_cast<Mode>(request.flags & 3));
+ LogResult(StringFromFormat("OpenFile(%s)", request.path.c_str()), backend_fd);
+ if (!backend_fd)
+ return GetFSReply(ConvertResult(backend_fd.Error()));
+
+ m_fd_map[request.fd] = {request.gid, request.uid, backend_fd->Release()};
+ std::strncpy(m_fd_map[request.fd].name.data(), request.path.c_str(), 64);
+ return GetFSReply(IPC_SUCCESS);
+}
+
+IPCCommandResult FS::Close(u32 fd)
+{
+ if (m_fd_map[fd].fs_fd != INVALID_FD)
+ {
+ const ResultCode result = m_ios.GetFS()->Close(m_fd_map[fd].fs_fd);
+ LogResult(StringFromFormat("Close(%s)", m_fd_map[fd].name.data()), result);
+ m_fd_map.erase(fd);
+ if (result != ResultCode::Success)
+ return GetFSReply(ConvertResult(result));
+ }
+ else
+ {
+ m_fd_map.erase(fd);
+ }
+ return GetFSReply(IPC_SUCCESS);
+}
+
+IPCCommandResult FS::Read(const ReadWriteRequest& request)
+{
+ const Handle& handle = m_fd_map[request.fd];
+ if (handle.fs_fd == INVALID_FD)
+ return GetDefaultReply(ConvertResult(ResultCode::Invalid));
+
+ const Result<u32> result = m_ios.GetFS()->ReadBytesFromFile(
+ handle.fs_fd, Memory::GetPointer(request.buffer), request.size);
+ LogResult(
+ StringFromFormat("Read(%s, 0x%08x, %u)", handle.name.data(), request.buffer, request.size),
+ result);
+ if (!result)
+ return GetDefaultReply(ConvertResult(result.Error()));
+ return GetDefaultReply(*result);
+}
+
+IPCCommandResult FS::Write(const ReadWriteRequest& request)
+{
+ const Handle& handle = m_fd_map[request.fd];
+ if (handle.fs_fd == INVALID_FD)
+ return GetDefaultReply(ConvertResult(ResultCode::Invalid));
+
+ const Result<u32> result = m_ios.GetFS()->WriteBytesToFile(
+ handle.fs_fd, Memory::GetPointer(request.buffer), request.size);
+ LogResult(
+ StringFromFormat("Write(%s, 0x%08x, %u)", handle.name.data(), request.buffer, request.size),
+ result);
+ if (!result)
+ return GetDefaultReply(ConvertResult(result.Error()));
+ return GetDefaultReply(*result);
+}
+
+IPCCommandResult FS::Seek(const SeekRequest& request)
+{
+ const Handle& handle = m_fd_map[request.fd];
+ if (handle.fs_fd == INVALID_FD)
+ return GetDefaultReply(ConvertResult(ResultCode::Invalid));
+
+ const Result<u32> result =
+ m_ios.GetFS()->SeekFile(handle.fs_fd, request.offset, IOS::HLE::FS::SeekMode(request.mode));
+ LogResult(
+ StringFromFormat("Seek(%s, 0x%08x, %u)", handle.name.data(), request.offset, request.mode),
+ result);
+ if (!result)
+ return GetDefaultReply(ConvertResult(result.Error()));
+ return GetDefaultReply(*result);
+}
+
+#pragma pack(push, 1)
+struct ISFSParams
+{
+ Common::BigEndianValue<Uid> uid;
+ Common::BigEndianValue<Gid> gid;
+ char path[64];
+ Mode owner_mode;
+ Mode group_mode;
+ Mode other_mode;
+ FileAttribute attribute;
+};
+
+struct ISFSNandStats
+{
+ Common::BigEndianValue<u32> cluster_size;
+ Common::BigEndianValue<u32> free_clusters;
+ Common::BigEndianValue<u32> used_clusters;
+ Common::BigEndianValue<u32> bad_clusters;
+ Common::BigEndianValue<u32> reserved_clusters;
+ Common::BigEndianValue<u32> free_inodes;
+ Common::BigEndianValue<u32> used_inodes;
+};
+
+struct ISFSFileStats
+{
+ Common::BigEndianValue<u32> size;
+ Common::BigEndianValue<u32> seek_position;
+};
+#pragma pack(pop)
+
+template <typename T>
+static Result<T> GetParams(const IOCtlRequest& request)
+{
+ if (request.buffer_in_size < sizeof(T))
+ return ResultCode::Invalid;
+
+ T params;
+ Memory::CopyFromEmu(&params, request.buffer_in, sizeof(params));
+ return params;
+}
+
+IPCCommandResult FS::IOCtl(const IOCtlRequest& request)
+{
+ const auto it = m_fd_map.find(request.fd);
+ if (it == m_fd_map.end())
+ return GetDefaultReply(ConvertResult(ResultCode::Invalid));
+
+ switch (request.request)
+ {
+ case ISFS_IOCTL_FORMAT:
+ return Format(it->second, request);
+ case ISFS_IOCTL_GETSTATS:
+ return GetStats(it->second, request);
+ case ISFS_IOCTL_CREATEDIR:
+ return CreateDirectory(it->second, request);
+ case ISFS_IOCTL_SETATTR:
+ return SetAttribute(it->second, request);
+ case ISFS_IOCTL_GETATTR:
+ return GetAttribute(it->second, request);
+ case ISFS_IOCTL_DELETE:
+ return DeleteFile(it->second, request);
+ case ISFS_IOCTL_RENAME:
+ return RenameFile(it->second, request);
+ case ISFS_IOCTL_CREATEFILE:
+ return CreateFile(it->second, request);
+ case ISFS_IOCTL_SETFILEVERCTRL:
+ return SetFileVersionControl(it->second, request);
+ case ISFS_IOCTL_GETFILESTATS:
+ return GetFileStats(it->second, request);
+ case ISFS_IOCTL_SHUTDOWN:
+ return Shutdown(it->second, request);
+ default:
+ return GetFSReply(ConvertResult(ResultCode::Invalid));
+ }
+}
+
+IPCCommandResult FS::IOCtlV(const IOCtlVRequest& request)
+{
+ const auto it = m_fd_map.find(request.fd);
+ if (it == m_fd_map.end())
+ return GetDefaultReply(ConvertResult(ResultCode::Invalid));
+
+ switch (request.request)
+ {
+ case ISFS_IOCTLV_READDIR:
+ return ReadDirectory(it->second, request);
+ case ISFS_IOCTLV_GETUSAGE:
+ return GetUsage(it->second, request);
+ default:
+ return GetFSReply(ConvertResult(ResultCode::Invalid));
+ }
+}
+
+IPCCommandResult FS::Format(const Handle& handle, const IOCtlRequest& request)
+{
+ if (handle.uid != 0)
+ return GetFSReply(ConvertResult(ResultCode::AccessDenied));
+
+ const ResultCode result = m_ios.GetFS()->Format(handle.uid);
+ return GetFSReply(ConvertResult(result));
+}
+
+IPCCommandResult FS::GetStats(const Handle& handle, const IOCtlRequest& request)
+{
+ if (request.buffer_out_size < sizeof(ISFSNandStats))
+ return GetFSReply(ConvertResult(ResultCode::Invalid));
+
+ const Result<NandStats> stats = m_ios.GetFS()->GetNandStats();
+ LogResult("GetNandStats", stats);
+ if (!stats)
+ return GetDefaultReply(ConvertResult(stats.Error()));
+
+ ISFSNandStats out;
+ out.cluster_size = stats->cluster_size;
+ out.free_clusters = stats->free_clusters;
+ out.used_clusters = stats->used_clusters;
+ out.bad_clusters = stats->bad_clusters;
+ out.reserved_clusters = stats->reserved_clusters;
+ out.free_inodes = stats->free_inodes;
+ out.used_inodes = stats->used_inodes;
+ Memory::CopyToEmu(request.buffer_out, &out, sizeof(out));
+ return GetDefaultReply(IPC_SUCCESS);
+}
+
+IPCCommandResult FS::CreateDirectory(const Handle& handle, const IOCtlRequest& request)
+{
+ const auto params = GetParams<ISFSParams>(request);
+ if (!params)
+ return GetFSReply(ConvertResult(params.Error()));
+
+ const ResultCode result =
+ m_ios.GetFS()->CreateDirectory(handle.uid, handle.gid, params->path, params->attribute,
+ params->owner_mode, params->group_mode, params->other_mode);
+ LogResult(StringFromFormat("CreateDirectory(%s)", params->path), result);
+ return GetFSReply(ConvertResult(result));
+}
+
+IPCCommandResult FS::ReadDirectory(const Handle& handle, const IOCtlVRequest& request)
+{
+ if (request.in_vectors.empty() || request.in_vectors.size() != request.io_vectors.size() ||
+ request.in_vectors.size() > 2 || request.in_vectors[0].size != 64)
+ {
+ return GetFSReply(ConvertResult(ResultCode::Invalid));
+ }
+
+ u32 file_list_address, file_count_address, max_count;
+ if (request.in_vectors.size() == 2)
+ {
+ if (request.in_vectors[1].size != 4 || request.io_vectors[1].size != 4)
+ return GetFSReply(ConvertResult(ResultCode::Invalid));
+ max_count = Memory::Read_U32(request.in_vectors[1].address);
+ file_count_address = request.io_vectors[1].address;
+ file_list_address = request.io_vectors[0].address;
+ if (request.io_vectors[0].size != 13 * max_count)
+ return GetFSReply(ConvertResult(ResultCode::Invalid));
+ Memory::Write_U32(max_count, file_count_address);
+ }
+ else
+ {
+ if (request.io_vectors[0].size != 4)
+ return GetFSReply(ConvertResult(ResultCode::Invalid));
+ max_count = Memory::Read_U32(request.io_vectors[0].address);
+ file_count_address = request.io_vectors[0].address;
+ file_list_address = 0;
+ }
+
+ const std::string directory = Memory::GetString(request.in_vectors[0].address, 64);
+ const Result<std::vector<std::string>> list =
+ m_ios.GetFS()->ReadDirectory(handle.uid, handle.gid, directory);
+ LogResult(StringFromFormat("ReadDirectory(%s)", directory.c_str()), list);
+ if (!list)
+ return GetFSReply(ConvertResult(list.Error()));
+
+ if (!file_list_address)
+ {
+ Memory::Write_U32(static_cast<u32>(list->size()), file_count_address);
+ return GetFSReply(IPC_SUCCESS);
+ }
+
+ for (size_t i = 0; i < list->size() && i < max_count; ++i)
+ {
+ Memory::Memset(file_list_address, 0, 13);
+ Memory::CopyToEmu(file_list_address, (*list)[i].data(), (*list)[i].size());
+ Memory::Write_U8(0, file_list_address + 12);
+ file_list_address += 13;
+ }
+ return GetFSReply(IPC_SUCCESS);
+}
+
+IPCCommandResult FS::SetAttribute(const Handle& handle, const IOCtlRequest& request)
+{
+ const auto params = GetParams<ISFSParams>(request);
+ if (!params)
+ return GetFSReply(ConvertResult(params.Error()));
+
+ const ResultCode result = m_ios.GetFS()->SetMetadata(
+ handle.uid, params->path, params->uid, params->gid, params->attribute, params->owner_mode,
+ params->group_mode, params->other_mode);
+ LogResult(StringFromFormat("SetMetadata(%s)", params->path), result);
+ return GetFSReply(ConvertResult(result));
+}
+
+IPCCommandResult FS::GetAttribute(const Handle& handle, const IOCtlRequest& request)
+{
+ if (request.buffer_in_size < 64 || request.buffer_out_size < sizeof(ISFSParams))
+ return GetFSReply(ConvertResult(ResultCode::Invalid));
+
+ const std::string path = Memory::GetString(request.buffer_in, 64);
+ const Result<Metadata> metadata = m_ios.GetFS()->GetMetadata(handle.uid, handle.gid, path);
+ LogResult(StringFromFormat("GetMetadata(%s)", path.c_str()), metadata);
+ if (!metadata)
+ return GetFSReply(ConvertResult(metadata.Error()));
+
+ // Yes, the other members aren't copied at all. Actually, IOS does not even memset
+ // the struct at all, which means uninitialised bytes from the stack are returned.
+ // For the sake of determinism, let's just zero initialise the struct.
+ ISFSParams out{};
+ out.uid = metadata->uid;
+ out.gid = metadata->gid;
+ out.attribute = metadata->attribute;
+ out.owner_mode = metadata->owner_mode;
+ out.group_mode = metadata->group_mode;
+ out.other_mode = metadata->other_mode;
+ Memory::CopyToEmu(request.buffer_out, &out, sizeof(out));
+ return GetFSReply(IPC_SUCCESS);
+}
+
+IPCCommandResult FS::DeleteFile(const Handle& handle, const IOCtlRequest& request)
+{
+ if (request.buffer_in_size < 64)
+ return GetFSReply(ConvertResult(ResultCode::Invalid));
+
+ const std::string path = Memory::GetString(request.buffer_in, 64);
+ const ResultCode result = m_ios.GetFS()->Delete(handle.uid, handle.gid, path);
+ LogResult(StringFromFormat("Delete(%s)", path.c_str()), result);
+ return GetFSReply(ConvertResult(result));
+}
+
+IPCCommandResult FS::RenameFile(const Handle& handle, const IOCtlRequest& request)
+{
+ if (request.buffer_in_size < 64 * 2)
+ return GetFSReply(ConvertResult(ResultCode::Invalid));
+
+ const std::string old_path = Memory::GetString(request.buffer_in, 64);
+ const std::string new_path = Memory::GetString(request.buffer_in + 64, 64);
+ const ResultCode result = m_ios.GetFS()->Rename(handle.uid, handle.gid, old_path, new_path);
+ LogResult(StringFromFormat("Rename(%s, %s)", old_path.c_str(), new_path.c_str()), result);
+ return GetFSReply(ConvertResult(result));
+}
+
+IPCCommandResult FS::CreateFile(const Handle& handle, const IOCtlRequest& request)
+{
+ const auto params = GetParams<ISFSParams>(request);
+ if (!params)
+ return GetFSReply(ConvertResult(params.Error()));
+
+ const ResultCode result =
+ m_ios.GetFS()->CreateFile(handle.uid, handle.gid, params->path, params->attribute,
+ params->owner_mode, params->group_mode, params->other_mode);
+ LogResult(StringFromFormat("CreateFile(%s)", params->path), result);
+ return GetFSReply(ConvertResult(result));
+}
+
+IPCCommandResult FS::SetFileVersionControl(const Handle& handle, const IOCtlRequest& request)
+{
+ const auto params = GetParams<ISFSParams>(request);
+ if (!params)
+ return GetFSReply(ConvertResult(params.Error()));
+
+ // FS_SetFileVersionControl(ctx->uid, params->path, params->attribute)
+ ERROR_LOG(IOS_FS, "SetFileVersionControl(%s, 0x%x): Stubbed", params->path, params->attribute);
+ return GetFSReply(IPC_SUCCESS);
+}
+
+IPCCommandResult FS::GetFileStats(const Handle& handle, const IOCtlRequest& request)
+{
+ if (request.buffer_out_size < 8 || handle.fs_fd == INVALID_FD)
+ return GetFSReply(ConvertResult(ResultCode::Invalid));
+
+ const Result<FileStatus> status = m_ios.GetFS()->GetFileStatus(handle.fs_fd);
+ LogResult(StringFromFormat("GetFileStatus(%s)", handle.name.data()), status);
+ if (!status)
+ return GetDefaultReply(ConvertResult(status.Error()));
+
+ ISFSFileStats out;
+ out.size = status->size;
+ out.seek_position = status->offset;
+ Memory::CopyToEmu(request.buffer_out, &out, sizeof(out));
+ return GetDefaultReply(IPC_SUCCESS);
+}
+
+IPCCommandResult FS::GetUsage(const Handle& handle, const IOCtlVRequest& request)
+{
+ if (!request.HasNumberOfValidVectors(1, 2) || request.in_vectors[0].size != 64 ||
+ request.io_vectors[0].size != 4 || request.io_vectors[1].size != 4)
+ {
+ return GetFSReply(ConvertResult(ResultCode::Invalid));
+ }
+
+ const std::string directory = Memory::GetString(request.in_vectors[0].address, 64);
+ const Result<DirectoryStats> stats = m_ios.GetFS()->GetDirectoryStats(directory);
+ LogResult(StringFromFormat("GetDirectoryStats(%s)", directory.c_str()), stats);
+ if (!stats)
+ return GetFSReply(ConvertResult(stats.Error()));
+
+ Memory::Write_U32(stats->used_clusters, request.io_vectors[0].address);
+ Memory::Write_U32(stats->used_inodes, request.io_vectors[1].address);
+ return GetFSReply(IPC_SUCCESS);
+}
+
+IPCCommandResult FS::Shutdown(const Handle& handle, const IOCtlRequest& request)
+{
+ INFO_LOG(IOS_FS, "Shutdown");
+ return GetFSReply(IPC_SUCCESS);
+}
+} // namespace Device
+} // namespace HLE
+} // namespace IOS
diff --git a/Source/Core/Core/IOS/FS/FileSystemProxy.h b/Source/Core/Core/IOS/FS/FileSystemProxy.h
new file mode 100644
index 0000000000..c14f77f049
--- /dev/null
+++ b/Source/Core/Core/IOS/FS/FileSystemProxy.h
@@ -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
diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp
new file mode 100644
index 0000000000..1e512a623a
--- /dev/null
+++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp
@@ -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
diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.h b/Source/Core/Core/IOS/FS/HostBackend/FS.h
new file mode 100644
index 0000000000..b30e1742f6
--- /dev/null
+++ b/Source/Core/Core/IOS/FS/HostBackend/FS.h
@@ -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
diff --git a/Source/Core/Core/IOS/FS/HostBackend/File.cpp b/Source/Core/Core/IOS/FS/HostBackend/File.cpp
new file mode 100644
index 0000000000..661413300d
--- /dev/null
+++ b/Source/Core/Core/IOS/FS/HostBackend/File.cpp
@@ -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
diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp
index 4abd2312a5..862f40c8cc 100644
--- a/Source/Core/Core/IOS/IOS.cpp
+++ b/Source/Core/Core/IOS/IOS.cpp
@@ -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);
diff --git a/Source/Core/Core/IOS/IOS.h b/Source/Core/Core/IOS/IOS.h
index 0f58359683..1d3e94b50b 100644
--- a/Source/Core/Core/IOS/IOS.h
+++ b/Source/Core/Core/IOS/IOS.h
@@ -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 @@ public:
// 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 @@ protected:
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.
diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp
index 727ecddf59..8956763c0f 100644
--- a/Source/Core/Core/State.cpp
+++ b/Source/Core/Core/State.cpp
@@ -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,
diff --git a/Source/Core/Core/WiiRoot.cpp b/Source/Core/Core/WiiRoot.cpp
index 7a2f5104b3..f6d696a435 100644
--- a/Source/Core/Core/WiiRoot.cpp
+++ b/Source/Core/Core/WiiRoot.cpp
@@ -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};