@@ -19,13 +19,13 @@
#include "Common/Assert.h"
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Common/File.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/NandPaths.h"
#include "Common/StringUtil.h"
#include "Common/Swap.h"
#include "Core/CommonTitles.h"
#include "Core/IOS/Device.h"
#include "Core/IOS/FS/FileSystem.h"
#include "Core/IOS/IOS.h"
#include "Core/IOS/IOSC.h"

@@ -489,15 +489,15 @@ struct SharedContentMap::Entry
std::array<u8, 20> sha1;
};

SharedContentMap::SharedContentMap(Common::FromWhichRoot root) : m_root(root)
static const std::string CONTENT_MAP_PATH = "/shared1/content.map";
SharedContentMap::SharedContentMap(std::shared_ptr<HLE::FS::FileSystem> fs) : m_fs{fs}
{
static_assert(sizeof(Entry) == 28, "SharedContentMap::Entry has the wrong size");

m_file_path = Common::RootUserPath(root) + "/shared1/content.map";

File::IOFile file(m_file_path, "rb");
Entry entry;
while (file.ReadArray(&entry, 1))
const auto file =
fs->OpenFile(HLE::PID_KERNEL, HLE::PID_KERNEL, CONTENT_MAP_PATH, HLE::FS::Mode::Read);
while (file && file->Read(&entry, 1))
{
m_entries.push_back(entry);
m_last_id++;
@@ -515,7 +515,7 @@ SharedContentMap::GetFilenameFromSHA1(const std::array<u8, 20>& sha1) const
return {};

const std::string id_string(it->id.begin(), it->id.end());
return Common::RootUserPath(m_root) + StringFromFormat("/shared1/%s.app", id_string.c_str());
return StringFromFormat("/shared1/%s.app", id_string.c_str());
}

std::vector<std::array<u8, 20>> SharedContentMap::GetHashes() const
@@ -541,7 +541,7 @@ std::string SharedContentMap::AddSharedContent(const std::array<u8, 20>& sha1)
m_entries.push_back(entry);

WriteEntries();
filename = Common::RootUserPath(m_root) + StringFromFormat("/shared1/%s.app", id.c_str());
filename = StringFromFormat("/shared1/%s.app", id.c_str());
m_last_id++;
return *filename;
}
@@ -556,45 +556,49 @@ bool SharedContentMap::DeleteSharedContent(const std::array<u8, 20>& sha1)

bool SharedContentMap::WriteEntries() const
{
// Temporary files in ES are only 12 characters long (excluding /tmp/).
const std::string temp_path = Common::RootUserPath(m_root) + "/tmp/shared1/cont";
File::CreateFullPath(temp_path);
// Temporary files are only 12 characters long and must match the final file name
const std::string temp_path = "/tmp/content.map";
m_fs->CreateFile(HLE::PID_KERNEL, HLE::PID_KERNEL, temp_path, 0, HLE::FS::Mode::ReadWrite,
HLE::FS::Mode::ReadWrite, HLE::FS::Mode::None);

// Atomically write the new content map.
{
File::IOFile file(temp_path, "w+b");
if (!file.WriteArray(m_entries.data(), m_entries.size()))
const auto file =
m_fs->OpenFile(HLE::PID_KERNEL, HLE::PID_KERNEL, temp_path, HLE::FS::Mode::Write);
if (!file || !file->Write(m_entries.data(), m_entries.size()))
return false;
File::CreateFullPath(m_file_path);
}
return File::RenameSync(temp_path, m_file_path);
return m_fs->Rename(HLE::PID_KERNEL, HLE::PID_KERNEL, temp_path, CONTENT_MAP_PATH) ==
HLE::FS::ResultCode::Success;
}

static std::pair<u32, u64> ReadUidSysEntry(File::IOFile& file)
static std::pair<u32, u64> ReadUidSysEntry(const HLE::FS::FileHandle& file)
{
u64 title_id = 0;
if (!file.ReadBytes(&title_id, sizeof(title_id)))
if (!file.Read(&title_id, 1))
return {};

u32 uid = 0;
if (!file.ReadBytes(&uid, sizeof(uid)))
if (!file.Read(&uid, 1))
return {};

return {Common::swap32(uid), Common::swap64(title_id)};
}

UIDSys::UIDSys(Common::FromWhichRoot root)
static const std::string UID_MAP_PATH = "/sys/uid.sys";
UIDSys::UIDSys(std::shared_ptr<HLE::FS::FileSystem> fs) : m_fs{fs}
{
m_file_path = Common::RootUserPath(root) + "/sys/uid.sys";

File::IOFile file(m_file_path, "rb");
while (true)
if (const auto file =
fs->OpenFile(HLE::PID_KERNEL, HLE::PID_KERNEL, UID_MAP_PATH, HLE::FS::Mode::Read))
{
const std::pair<u32, u64> entry = ReadUidSysEntry(file);
if (!entry.first && !entry.second)
break;
while (true)
{
const std::pair<u32, u64> entry = ReadUidSysEntry(*file);
if (!entry.first && !entry.second)
break;

m_entries.insert(std::move(entry));
m_entries.insert(std::move(entry));
}
}

if (m_entries.empty())
@@ -613,7 +617,7 @@ u32 UIDSys::GetUIDFromTitle(u64 title_id) const
u32 UIDSys::GetNextUID() const
{
if (m_entries.empty())
return 0x00001000;
return FIRST_PPC_UID;
return m_entries.rbegin()->first + 1;
}

@@ -633,11 +637,10 @@ u32 UIDSys::GetOrInsertUIDForTitle(const u64 title_id)
const u64 swapped_title_id = Common::swap64(title_id);
const u32 swapped_uid = Common::swap32(uid);

File::CreateFullPath(m_file_path);
File::IOFile file(m_file_path, "ab");

if (!file.WriteBytes(&swapped_title_id, sizeof(title_id)) ||
!file.WriteBytes(&swapped_uid, sizeof(uid)))
const auto file =
m_fs->OpenFile(HLE::PID_KERNEL, HLE::PID_KERNEL, UID_MAP_PATH, HLE::FS::Mode::ReadWrite);
if (!file || !file->Seek(0, HLE::FS::SeekMode::End) || !file->Write(&swapped_title_id, 1) ||
!file->Write(&swapped_uid, 1))
{
ERROR_LOG(IOS_ES, "Failed to write to /sys/uid.sys");
return 0;
@@ -10,12 +10,12 @@
#include <array>
#include <cstddef>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "Common/CommonTypes.h"
#include "Common/NandPaths.h"
#include "Core/IOS/Device.h"
#include "Core/IOS/IOSC.h"
#include "DiscIO/Enums.h"
@@ -24,6 +24,11 @@ class PointerWrap;

namespace IOS
{
namespace HLE::FS
{
class FileSystem;
}

namespace ES
{
enum class TitleType : u32
@@ -249,7 +254,7 @@ class TicketReader final : public SignedBlobReader
class SharedContentMap final
{
public:
explicit SharedContentMap(Common::FromWhichRoot root);
explicit SharedContentMap(std::shared_ptr<HLE::FS::FileSystem> fs);
~SharedContentMap();

std::optional<std::string> GetFilenameFromSHA1(const std::array<u8, 20>& sha1) const;
@@ -261,23 +266,24 @@ class SharedContentMap final
bool WriteEntries() const;

struct Entry;
Common::FromWhichRoot m_root;
u32 m_last_id = 0;
std::string m_file_path;
std::vector<Entry> m_entries;
std::shared_ptr<HLE::FS::FileSystem> m_fs;
};

constexpr u32 FIRST_PPC_UID = 0x1000;

class UIDSys final
{
public:
explicit UIDSys(Common::FromWhichRoot root);
explicit UIDSys(std::shared_ptr<HLE::FS::FileSystem> fs);

u32 GetUIDFromTitle(u64 title_id) const;
u32 GetOrInsertUIDForTitle(u64 title_id);
u32 GetNextUID() const;

private:
std::string m_file_path;
std::shared_ptr<HLE::FS::FileSystem> m_fs;
std::map<u32, u64> m_entries;
};

Large diffs are not rendered by default.

@@ -33,11 +33,13 @@ s32 ES::OpenContent(const IOS::ES::TMDReader& tmd, u16 content_index, u32 uid)
if (entry.m_opened)
continue;

if (!entry.m_file.Open(GetContentPath(title_id, content), "rb"))
return FS_ENOENT;
auto file = m_ios.GetFS()->OpenFile(PID_KERNEL, PID_KERNEL, GetContentPath(title_id, content),
FS::Mode::Read);
if (!file)
return FS::ConvertResult(file.Error());

entry.m_opened = true;
entry.m_position = 0;
entry.m_fd = file->Release();
entry.m_content = content;
entry.m_title_id = title_id;
entry.m_uid = uid;
@@ -78,7 +80,7 @@ IPCCommandResult ES::OpenActiveTitleContent(u32 caller_uid, const IOCtlVRequest&
if (!m_title_context.active)
return GetDefaultReply(ES_EINVAL);

IOS::ES::UIDSys uid_map{Common::FROM_SESSION_ROOT};
IOS::ES::UIDSys uid_map{m_ios.GetFS()};
const u32 uid = uid_map.GetOrInsertUIDForTitle(m_title_context.tmd.GetTitleId());
if (caller_uid != 0 && caller_uid != uid)
return GetDefaultReply(ES_EACCES);
@@ -97,21 +99,8 @@ s32 ES::ReadContent(u32 cfd, u8* buffer, u32 size, u32 uid)
if (!entry.m_opened)
return IPC_EINVAL;

// XXX: make this reuse the FS code... ES just does a simple "IOS_Read" call here
// instead of all this duplicated filesystem logic.

if (entry.m_position + size > entry.m_file.GetSize())
size = static_cast<u32>(entry.m_file.GetSize()) - entry.m_position;

entry.m_file.Seek(entry.m_position, SEEK_SET);
if (!entry.m_file.ReadBytes(buffer, size))
{
ERROR_LOG(IOS_ES, "ES: failed to read %u bytes from %u!", size, entry.m_position);
return ES_SHORT_READ;
}

entry.m_position += size;
return size;
const auto result = m_ios.GetFS()->ReadBytesFromFile(entry.m_fd, buffer, size);
return result.Succeeded() ? *result : FS::ConvertResult(result.Error());
}

IPCCommandResult ES::ReadContent(u32 uid, const IOCtlVRequest& request)
@@ -137,6 +126,7 @@ ReturnCode ES::CloseContent(u32 cfd, u32 uid)
if (!entry.m_opened)
return IPC_EINVAL;

m_ios.GetFS()->Close(entry.m_fd);
entry = {};
INFO_LOG(IOS_ES, "CloseContent: CFD %u", cfd);
return IPC_SUCCESS;
@@ -162,26 +152,8 @@ s32 ES::SeekContent(u32 cfd, u32 offset, SeekMode mode, u32 uid)
if (!entry.m_opened)
return IPC_EINVAL;

// XXX: This should be a simple IOS_Seek.
switch (mode)
{
case SeekMode::IOS_SEEK_SET:
entry.m_position = offset;
break;

case SeekMode::IOS_SEEK_CUR:
entry.m_position += offset;
break;

case SeekMode::IOS_SEEK_END:
entry.m_position = static_cast<u32>(entry.m_content.size) + offset;
break;

default:
return FS_EINVAL;
}

return entry.m_position;
const auto result = m_ios.GetFS()->SeekFile(entry.m_fd, offset, static_cast<FS::SeekMode>(mode));
return result.Succeeded() ? *result : FS::ConvertResult(result.Error());
}

IPCCommandResult ES::SeekContent(u32 uid, const IOCtlVRequest& request)
@@ -13,34 +13,36 @@
#include <mbedtls/sha1.h>

#include "Common/Align.h"
#include "Common/File.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/NandPaths.h"
#include "Common/StringUtil.h"
#include "Core/CommonTitles.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/IOS/FS/FileSystem.h"

namespace IOS
{
namespace HLE
{
namespace Device
{
static ReturnCode WriteTicket(const IOS::ES::TicketReader& ticket)
static ReturnCode WriteTicket(FS::FileSystem* fs, const IOS::ES::TicketReader& ticket)
{
const u64 title_id = ticket.GetTitleId();

const std::string ticket_path = Common::GetTicketFileName(title_id, Common::FROM_SESSION_ROOT);
File::CreateFullPath(ticket_path);
const std::string path = Common::GetTicketFileName(title_id);
fs->CreateFullPath(PID_KERNEL, PID_KERNEL, path, 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite,
FS::Mode::None);
fs->CreateFile(PID_KERNEL, PID_KERNEL, path, 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite,
FS::Mode::None);

File::IOFile ticket_file(ticket_path, "wb");
if (!ticket_file)
return ES_EIO;
const auto file = fs->OpenFile(PID_KERNEL, PID_KERNEL, path, FS::Mode::Write);
if (!file)
return FS::ConvertResult(file.Error());

const std::vector<u8>& raw_ticket = ticket.GetBytes();
return ticket_file.WriteBytes(raw_ticket.data(), raw_ticket.size()) ? IPC_SUCCESS : ES_EIO;
return file->Write(raw_ticket.data(), raw_ticket.size()) ? IPC_SUCCESS : ES_EIO;
}

void ES::TitleImportExportContext::DoState(PointerWrap& p)
@@ -84,7 +86,7 @@ ReturnCode ES::ImportTicket(const std::vector<u8>& ticket_bytes, const std::vect
if (verify_ret != IPC_SUCCESS)
return verify_ret;

const ReturnCode write_ret = WriteTicket(ticket);
const ReturnCode write_ret = WriteTicket(m_ios.GetFS().get(), ticket);
if (write_ret != IPC_SUCCESS)
return write_ret;

@@ -155,7 +157,7 @@ ReturnCode ES::ImportTmd(Context& context, const std::vector<u8>& tmd_bytes)
if (ret != IPC_SUCCESS)
return ret;

if (!InitImport(context.title_import_export.tmd.GetTitleId()))
if (!InitImport(context.title_import_export.tmd))
return ES_EIO;

ret =
@@ -237,7 +239,7 @@ ReturnCode ES::ImportTitleInit(Context& context, const std::vector<u8>& tmd_byte
if (ret != IPC_SUCCESS)
return ret;

if (!InitImport(context.title_import_export.tmd.GetTitleId()))
if (!InitImport(context.title_import_export.tmd))
return ES_EIO;

context.title_import_export.valid = true;
@@ -342,8 +344,7 @@ static bool CheckIfContentHashMatches(const std::vector<u8>& content, const IOS:

static std::string GetImportContentPath(u64 title_id, u32 content_id)
{
return Common::GetImportTitlePath(title_id, Common::FROM_SESSION_ROOT) +
StringFromFormat("/content/%08x.app", content_id);
return Common::GetImportTitlePath(title_id) + StringFromFormat("/content/%08x.app", content_id);
}

ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd)
@@ -370,37 +371,39 @@ ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd)
return ES_HASH_MISMATCH;
}

const auto fs = m_ios.GetFS();
std::string content_path;
if (content_info.IsShared())
{
IOS::ES::SharedContentMap shared_content{Common::FROM_SESSION_ROOT};
IOS::ES::SharedContentMap shared_content{fs};
content_path = shared_content.AddSharedContent(content_info.sha1);
}
else
{
content_path = GetImportContentPath(context.title_import_export.tmd.GetTitleId(),
context.title_import_export.content.id);
}
File::CreateFullPath(content_path);

const std::string temp_path =
Common::RootUserPath(Common::FROM_SESSION_ROOT) +
StringFromFormat("/tmp/%08x.app", context.title_import_export.content.id);
File::CreateFullPath(temp_path);
"/tmp/" + content_path.substr(content_path.find_last_of('/') + 1, std::string::npos);

fs->CreateFile(PID_KERNEL, PID_KERNEL, temp_path, 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite,
FS::Mode::None);
{
File::IOFile file(temp_path, "wb");
if (!file.WriteBytes(decrypted_data.data(), content_info.size))
const auto file = fs->OpenFile(PID_KERNEL, PID_KERNEL, temp_path, FS::Mode::Write);
if (!file || !file->Write(decrypted_data.data(), content_info.size))
{
ERROR_LOG(IOS_ES, "ImportContentEnd: Failed to write to %s", temp_path.c_str());
return ES_EIO;
}
}

if (!File::Rename(temp_path, content_path))
const FS::ResultCode rename_result = fs->Rename(PID_KERNEL, PID_KERNEL, temp_path, content_path);
if (rename_result != FS::ResultCode::Success)
{
fs->Delete(PID_KERNEL, PID_KERNEL, temp_path);
ERROR_LOG(IOS_ES, "ImportContentEnd: Failed to move content to %s", content_path.c_str());
return ES_EIO;
return FS::ConvertResult(rename_result);
}

context.title_import_export.content = {};
@@ -424,7 +427,7 @@ ReturnCode ES::ImportTitleDone(Context& context)
// Make sure all listed, non-optional contents have been imported.
const u64 title_id = context.title_import_export.tmd.GetTitleId();
const std::vector<IOS::ES::Content> contents = context.title_import_export.tmd.GetContents();
const IOS::ES::SharedContentMap shared_content_map{Common::FROM_SESSION_ROOT};
const IOS::ES::SharedContentMap shared_content_map{m_ios.GetFS()};
const bool has_all_required_contents =
std::all_of(contents.cbegin(), contents.cend(), [&](const IOS::ES::Content& content) {
if (content.IsOptional())
@@ -435,8 +438,9 @@ ReturnCode ES::ImportTitleDone(Context& context)

// Note: the import hasn't been finalised yet, so the whole title directory
// is still in /import, not /title.
return File::Exists(Common::GetImportTitlePath(title_id, Common::FROM_SESSION_ROOT) +
StringFromFormat("/content/%08x.app", content.id));
const std::string path = Common::GetImportTitlePath(title_id) +
StringFromFormat("/content/%08x.app", content.id);
return m_ios.GetFS()->GetMetadata(PID_KERNEL, PID_KERNEL, path).Succeeded();
});
if (!has_all_required_contents)
return ES_EINVAL;
@@ -497,17 +501,8 @@ ReturnCode ES::DeleteTitle(u64 title_id)
if (!CanDeleteTitle(title_id))
return ES_EINVAL;

const std::string title_dir = Common::GetTitlePath(title_id, Common::FROM_SESSION_ROOT);
if (!File::IsDirectory(title_dir))
return FS_ENOENT;

if (!File::DeleteDirRecursively(title_dir))
{
ERROR_LOG(IOS_ES, "DeleteTitle: Failed to delete title directory: %s", title_dir.c_str());
return FS_EACCESS;
}

return IPC_SUCCESS;
const std::string title_dir = Common::GetTitlePath(title_id);
return FS::ConvertResult(m_ios.GetFS()->Delete(PID_KERNEL, PID_KERNEL, title_dir));
}

IPCCommandResult ES::DeleteTitle(const IOCtlVRequest& request)
@@ -521,6 +516,7 @@ IPCCommandResult ES::DeleteTitle(const IOCtlVRequest& request)

ReturnCode ES::DeleteTicket(const u8* ticket_view)
{
const auto fs = m_ios.GetFS();
const u64 title_id = Common::swap64(ticket_view + offsetof(IOS::ES::TicketView, title_id));

if (!CanDeleteTitle(title_id))
@@ -534,24 +530,26 @@ ReturnCode ES::DeleteTicket(const u8* ticket_view)
ticket.DeleteTicket(ticket_id);

const std::vector<u8>& new_ticket = ticket.GetBytes();
const std::string ticket_path = Common::GetTicketFileName(title_id, Common::FROM_SESSION_ROOT);
const std::string ticket_path = Common::GetTicketFileName(title_id);
if (!new_ticket.empty())
{
File::IOFile ticket_file(ticket_path, "wb");
if (!ticket_file || !ticket_file.WriteBytes(new_ticket.data(), new_ticket.size()))
const auto file = fs->OpenFile(PID_KERNEL, PID_KERNEL, ticket_path, FS::Mode::ReadWrite);
if (!file || !file->Write(new_ticket.data(), new_ticket.size()))
return ES_EIO;
}

// Delete the ticket file if it is now empty.
if (new_ticket.empty())
File::Delete(ticket_path);
else
{
// Delete the ticket file if it is now empty.
fs->Delete(PID_KERNEL, PID_KERNEL, ticket_path);
}

// Delete the ticket directory if it is now empty.
const std::string ticket_parent_dir =
Common::RootUserPath(Common::FROM_CONFIGURED_ROOT) +
StringFromFormat("/ticket/%08x", static_cast<u32>(title_id >> 32));
const auto ticket_parent_dir_entries = File::ScanDirectoryTree(ticket_parent_dir, false);
if (ticket_parent_dir_entries.children.empty())
File::DeleteDir(ticket_parent_dir);
const auto ticket_parent_dir_entries =
fs->ReadDirectory(PID_KERNEL, PID_KERNEL, ticket_parent_dir);
if (ticket_parent_dir_entries && ticket_parent_dir_entries->empty())
fs->Delete(PID_KERNEL, PID_KERNEL, ticket_parent_dir);

return IPC_SUCCESS;
}
@@ -571,14 +569,15 @@ ReturnCode ES::DeleteTitleContent(u64 title_id) const
if (!CanDeleteTitle(title_id))
return ES_EINVAL;

const std::string content_dir = Common::GetTitleContentPath(title_id, Common::FROM_SESSION_ROOT);
if (!File::IsDirectory(content_dir))
return FS_ENOENT;
const std::string content_dir = Common::GetTitleContentPath(title_id);
const auto files = m_ios.GetFS()->ReadDirectory(PID_KERNEL, PID_KERNEL, content_dir);
if (!files)
return FS::ConvertResult(files.Error());

for (const auto& file : File::ScanDirectoryTree(content_dir, false).children)
for (const std::string& file_name : *files)
{
if (file.virtualName.size() == 12 && file.virtualName.compare(8, 4, ".app") == 0)
File::Delete(file.physicalName);
if (file_name.size() == 12 && file_name.compare(8, 4, ".app") == 0)
m_ios.GetFS()->Delete(PID_KERNEL, PID_KERNEL, content_dir + '/' + file_name);
}

return IPC_SUCCESS;
@@ -604,13 +603,9 @@ ReturnCode ES::DeleteContent(u64 title_id, u32 content_id) const
if (!tmd.FindContentById(content_id, &content))
return ES_EINVAL;

if (!File::Delete(Common::GetTitleContentPath(title_id, Common::FROM_SESSION_ROOT) +
StringFromFormat("/%08x.app", content_id)))
{
return FS_ENOENT;
}

return IPC_SUCCESS;
return FS::ConvertResult(m_ios.GetFS()->Delete(PID_KERNEL, PID_KERNEL,
Common::GetTitleContentPath(title_id) +
StringFromFormat("/%08x.app", content_id)));
}

IPCCommandResult ES::DeleteContent(const IOCtlVRequest& request)
@@ -785,7 +780,7 @@ IPCCommandResult ES::ExportTitleDone(Context& context, const IOCtlVRequest& requ

ReturnCode ES::DeleteSharedContent(const std::array<u8, 20>& sha1) const
{
IOS::ES::SharedContentMap map{Common::FromWhichRoot::FROM_SESSION_ROOT};
IOS::ES::SharedContentMap map{m_ios.GetFS()};
const auto content_path = map.GetFilenameFromSHA1(sha1);
if (!content_path)
return ES_EINVAL;
@@ -810,8 +805,9 @@ ReturnCode ES::DeleteSharedContent(const std::array<u8, 20>& sha1) const
return ES_EINVAL;

// Delete the shared content and update the content map.
if (!File::Delete(*content_path))
return FS_ENOENT;
const auto delete_result = m_ios.GetFS()->Delete(PID_KERNEL, PID_KERNEL, *content_path);
if (delete_result != FS::ResultCode::Success)
return FS::ConvertResult(delete_result);

if (!map.DeleteSharedContent(sha1))
return ES_EIO;
@@ -6,6 +6,7 @@

#include "Common/Assert.h"
#include "Common/FileUtil.h"
#include "Core/IOS/Device.h"
#include "Core/IOS/FS/HostBackend/FS.h"

namespace IOS::HLE::FS
@@ -17,6 +18,15 @@ std::unique_ptr<FileSystem> MakeFileSystem(Location location)
return std::make_unique<HostFileSystem>(nand_root);
}

IOS::HLE::ReturnCode ConvertResult(ResultCode code)
{
if (code == ResultCode::Success)
return IPC_SUCCESS;
// FS error codes start at -100. Since result codes in the enum are listed in the same way
// as the IOS codes, we just need to return -100-code.
return static_cast<ReturnCode>(-(static_cast<s32>(code) + 100));
}

FileHandle::FileHandle(FileSystem* fs, Fd fd) : m_fs{fs}, m_fd{fd}
{
}
@@ -14,7 +14,11 @@

class PointerWrap;

namespace IOS::HLE::FS
namespace IOS::HLE
{
enum ReturnCode : s32;

namespace FS
{
enum class ResultCode
{
@@ -218,4 +222,8 @@ enum class Location

std::unique_ptr<FileSystem> MakeFileSystem(Location location = Location::Session);

} // namespace IOS::HLE::FS
/// Convert a FS result code to an IOS error code.
IOS::HLE::ReturnCode ConvertResult(ResultCode code);

} // namespace FS
} // namespace IOS::HLE
@@ -23,13 +23,6 @@ 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);
}

static IPCCommandResult GetFSReply(s32 return_value, u64 extra_tb_ticks = 0)
{
// According to hardware tests, FS takes at least 2700 TB ticks to reply to commands.
@@ -310,10 +310,11 @@ Result<Metadata> HostFileSystem::GetMetadata(Uid, Gid, const std::string& path)

// Hack: if the path that is being accessed is within an installed title directory, get the
// UID/GID from the installed title TMD.
Kernel* ios = GetIOS();
u64 title_id;
if (IsTitlePath(file_name, Common::FROM_SESSION_ROOT, &title_id))
if (ios && IsTitlePath(file_name, Common::FROM_SESSION_ROOT, &title_id))
{
IOS::ES::TMDReader tmd = GetIOS()->GetES()->FindInstalledTMD(title_id);
IOS::ES::TMDReader tmd = ios->GetES()->FindInstalledTMD(title_id);
if (tmd.IsValid())
metadata.gid = tmd.GetGroupId();
}
@@ -266,11 +266,27 @@ u16 Kernel::GetGidForPPC() const
return m_ppc_gid;
}

static std::vector<u8> ReadBootContent(FS::FileSystem* fs, const std::string& path, size_t max_size)
{
const auto file = fs->OpenFile(0, 0, path, FS::Mode::Read);
if (!file)
return {};

const size_t file_size = file->GetStatus()->size;
if (max_size != 0 && file_size > max_size)
return {};

std::vector<u8> buffer(file_size);
if (!file->Read(buffer.data(), buffer.size()))
return {};
return buffer;
}

// This corresponds to syscall 0x41, which loads a binary from the NAND and bootstraps the PPC.
// Unlike 0x42, IOS will set up some constants in memory before booting the PPC.
bool Kernel::BootstrapPPC(const std::string& boot_content_path)
{
const DolReader dol{boot_content_path};
const DolReader dol{ReadBootContent(m_fs.get(), boot_content_path, 0)};

if (!dol.IsValid())
return false;
@@ -329,16 +345,7 @@ bool Kernel::BootIOS(const u64 ios_title_id, const std::string& boot_content_pat
// Load the ARM binary to memory (if possible).
// Because we do not actually emulate the Starlet, only load the sections that are in MEM1.

File::IOFile file{boot_content_path, "rb"};
// TODO: should return IPC_ERROR_MAX.
if (file.GetSize() > 0xB00000)
return false;

std::vector<u8> data(file.GetSize());
if (!file.ReadBytes(data.data(), data.size()))
return false;

ARMBinary binary{std::move(data)};
ARMBinary binary{ReadBootContent(m_fs.get(), boot_content_path, 0xB00000)};
if (!binary.IsValid())
return false;

@@ -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 = 96; // Last changed in PR 6565
static const u32 STATE_VERSION = 97; // Last changed in PR 6772

// Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list,