Merge pull request #6772 from leoetlino/fs-es
IOS/ES: Migrate to new filesystem interface
This commit is contained in:
commit
90f869e940
|
@ -14,8 +14,6 @@
|
||||||
#include <mbedtls/sha1.h>
|
#include <mbedtls/sha1.h>
|
||||||
|
|
||||||
#include "Common/ChunkFile.h"
|
#include "Common/ChunkFile.h"
|
||||||
#include "Common/File.h"
|
|
||||||
#include "Common/FileUtil.h"
|
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/MsgHandler.h"
|
#include "Common/MsgHandler.h"
|
||||||
#include "Common/NandPaths.h"
|
#include "Common/NandPaths.h"
|
||||||
|
@ -25,6 +23,7 @@
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
#include "Core/HW/Memmap.h"
|
#include "Core/HW/Memmap.h"
|
||||||
#include "Core/IOS/ES/Formats.h"
|
#include "Core/IOS/ES/Formats.h"
|
||||||
|
#include "Core/IOS/FS/FileSystem.h"
|
||||||
#include "Core/IOS/IOSC.h"
|
#include "Core/IOS/IOSC.h"
|
||||||
#include "Core/IOS/VersionInfo.h"
|
#include "Core/IOS/VersionInfo.h"
|
||||||
|
|
||||||
|
@ -40,41 +39,42 @@ static u64 s_title_to_launch;
|
||||||
struct DirectoryToCreate
|
struct DirectoryToCreate
|
||||||
{
|
{
|
||||||
const char* path;
|
const char* path;
|
||||||
u32 attributes;
|
FS::FileAttribute attribute;
|
||||||
OpenMode owner_perm;
|
FS::Mode owner_mode;
|
||||||
OpenMode group_perm;
|
FS::Mode group_mode;
|
||||||
OpenMode other_perm;
|
FS::Mode other_mode;
|
||||||
|
FS::Uid uid = PID_KERNEL;
|
||||||
|
FS::Gid gid = PID_KERNEL;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr std::array<DirectoryToCreate, 9> s_directories_to_create = {{
|
constexpr std::array<DirectoryToCreate, 9> s_directories_to_create = {{
|
||||||
{"/sys", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE},
|
{"/sys", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::None},
|
||||||
{"/ticket", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE},
|
{"/ticket", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::None},
|
||||||
{"/title", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_READ},
|
{"/title", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::Read},
|
||||||
{"/shared1", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE},
|
{"/shared1", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::None},
|
||||||
{"/shared2", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW},
|
{"/shared2", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite},
|
||||||
{"/tmp", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW},
|
{"/tmp", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite},
|
||||||
{"/import", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE},
|
{"/import", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::None},
|
||||||
{"/meta", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW},
|
{"/meta", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite,
|
||||||
{"/wfs", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE, OpenMode::IOS_OPEN_NONE},
|
IOS::ES::FIRST_PPC_UID, 0x1},
|
||||||
|
{"/wfs", 0, FS::Mode::ReadWrite, FS::Mode::None, FS::Mode::None, PID_UNKNOWN, PID_UNKNOWN},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
ES::ES(Kernel& ios, const std::string& device_name) : Device(ios, device_name)
|
ES::ES(Kernel& ios, const std::string& device_name) : Device(ios, device_name)
|
||||||
{
|
{
|
||||||
for (const auto& directory : s_directories_to_create)
|
for (const auto& directory : s_directories_to_create)
|
||||||
{
|
{
|
||||||
const std::string path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + directory.path;
|
// Note: ES sets its own UID and GID to 0/0 at boot, so all filesystem accesses in ES are done
|
||||||
|
// as UID 0 even though its PID is 1.
|
||||||
|
const auto result = m_ios.GetFS()->CreateDirectory(PID_KERNEL, PID_KERNEL, directory.path,
|
||||||
|
directory.attribute, directory.owner_mode,
|
||||||
|
directory.group_mode, directory.other_mode);
|
||||||
|
if (result != FS::ResultCode::Success && result != FS::ResultCode::AlreadyExists)
|
||||||
|
ERROR_LOG(IOS_ES, "Failed to create %s: error %d", directory.path, FS::ConvertResult(result));
|
||||||
|
|
||||||
// Create the directory if it does not exist.
|
// Now update the UID/GID and other attributes.
|
||||||
if (File::IsDirectory(path))
|
m_ios.GetFS()->SetMetadata(0, directory.path, directory.uid, directory.gid, directory.attribute,
|
||||||
continue;
|
directory.owner_mode, directory.group_mode, directory.other_mode);
|
||||||
|
|
||||||
File::CreateFullPath(path);
|
|
||||||
if (File::CreateDir(path))
|
|
||||||
INFO_LOG(IOS_ES, "Created %s (at %s)", directory.path, path.c_str());
|
|
||||||
else
|
|
||||||
ERROR_LOG(IOS_ES, "Failed to create %s (at %s)", directory.path, path.c_str());
|
|
||||||
|
|
||||||
// TODO: Set permissions.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FinishAllStaleImports();
|
FinishAllStaleImports();
|
||||||
|
@ -161,7 +161,7 @@ IPCCommandResult ES::GetTitleId(const IOCtlVRequest& request)
|
||||||
|
|
||||||
static bool UpdateUIDAndGID(Kernel& kernel, const IOS::ES::TMDReader& tmd)
|
static bool UpdateUIDAndGID(Kernel& kernel, const IOS::ES::TMDReader& tmd)
|
||||||
{
|
{
|
||||||
IOS::ES::UIDSys uid_sys{Common::FromWhichRoot::FROM_SESSION_ROOT};
|
IOS::ES::UIDSys uid_sys{kernel.GetFS()};
|
||||||
const u64 title_id = tmd.GetTitleId();
|
const u64 title_id = tmd.GetTitleId();
|
||||||
const u32 uid = uid_sys.GetOrInsertUIDForTitle(title_id);
|
const u32 uid = uid_sys.GetOrInsertUIDForTitle(title_id);
|
||||||
if (!uid)
|
if (!uid)
|
||||||
|
@ -174,9 +174,9 @@ static bool UpdateUIDAndGID(Kernel& kernel, const IOS::ES::TMDReader& tmd)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ReturnCode CheckIsAllowedToSetUID(const u32 caller_uid)
|
static ReturnCode CheckIsAllowedToSetUID(Kernel& kernel, const u32 caller_uid)
|
||||||
{
|
{
|
||||||
IOS::ES::UIDSys uid_map{Common::FromWhichRoot::FROM_SESSION_ROOT};
|
IOS::ES::UIDSys uid_map{kernel.GetFS()};
|
||||||
const u32 system_menu_uid = uid_map.GetOrInsertUIDForTitle(Titles::SYSTEM_MENU);
|
const u32 system_menu_uid = uid_map.GetOrInsertUIDForTitle(Titles::SYSTEM_MENU);
|
||||||
if (!system_menu_uid)
|
if (!system_menu_uid)
|
||||||
return ES_SHORT_READ;
|
return ES_SHORT_READ;
|
||||||
|
@ -190,7 +190,7 @@ IPCCommandResult ES::SetUID(u32 uid, const IOCtlVRequest& request)
|
||||||
|
|
||||||
const u64 title_id = Memory::Read_U64(request.in_vectors[0].address);
|
const u64 title_id = Memory::Read_U64(request.in_vectors[0].address);
|
||||||
|
|
||||||
const s32 ret = CheckIsAllowedToSetUID(uid);
|
const s32 ret = CheckIsAllowedToSetUID(m_ios, uid);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
ERROR_LOG(IOS_ES, "SetUID: Permission check failed with error %d", ret);
|
ERROR_LOG(IOS_ES, "SetUID: Permission check failed with error %d", ret);
|
||||||
|
@ -343,12 +343,8 @@ void ES::DoState(PointerWrap& p)
|
||||||
p.Do(entry.m_opened);
|
p.Do(entry.m_opened);
|
||||||
p.Do(entry.m_title_id);
|
p.Do(entry.m_title_id);
|
||||||
p.Do(entry.m_content);
|
p.Do(entry.m_content);
|
||||||
p.Do(entry.m_position);
|
p.Do(entry.m_fd);
|
||||||
p.Do(entry.m_uid);
|
p.Do(entry.m_uid);
|
||||||
if (entry.m_opened)
|
|
||||||
entry.m_opened = entry.m_file.Open(GetContentPath(entry.m_title_id, entry.m_content), "rb");
|
|
||||||
else
|
|
||||||
entry.m_file.Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_title_context.DoState(p);
|
m_title_context.DoState(p);
|
||||||
|
@ -623,6 +619,32 @@ IPCCommandResult ES::DIVerify(const IOCtlVRequest& request)
|
||||||
return GetDefaultReply(ES_EINVAL);
|
return GetDefaultReply(ES_EINVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static s32 WriteTmdForDiVerify(FS::FileSystem* fs, const IOS::ES::TMDReader& tmd)
|
||||||
|
{
|
||||||
|
const std::string temp_path = "/tmp/title.tmd";
|
||||||
|
fs->Delete(PID_KERNEL, PID_KERNEL, temp_path);
|
||||||
|
fs->CreateFile(PID_KERNEL, PID_KERNEL, temp_path, 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite,
|
||||||
|
FS::Mode::None);
|
||||||
|
{
|
||||||
|
const auto file = fs->OpenFile(PID_KERNEL, PID_KERNEL, temp_path, FS::Mode::Write);
|
||||||
|
if (!file)
|
||||||
|
return FS::ConvertResult(file.Error());
|
||||||
|
if (!file->Write(tmd.GetBytes().data(), tmd.GetBytes().size()))
|
||||||
|
return ES_EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string tmd_dir = Common::GetTitleContentPath(tmd.GetTitleId());
|
||||||
|
const std::string tmd_path = Common::GetTMDFileName(tmd.GetTitleId());
|
||||||
|
const auto result = fs->CreateFullPath(PID_KERNEL, PID_KERNEL, tmd_path, 0, FS::Mode::ReadWrite,
|
||||||
|
FS::Mode::ReadWrite, FS::Mode::Read);
|
||||||
|
if (result != FS::ResultCode::Success)
|
||||||
|
return FS::ConvertResult(result);
|
||||||
|
|
||||||
|
fs->SetMetadata(PID_KERNEL, tmd_dir, PID_KERNEL, PID_KERNEL, 0, FS::Mode::ReadWrite,
|
||||||
|
FS::Mode::ReadWrite, FS::Mode::None);
|
||||||
|
return FS::ConvertResult(fs->Rename(PID_KERNEL, PID_KERNEL, temp_path, tmd_path));
|
||||||
|
}
|
||||||
|
|
||||||
s32 ES::DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& ticket)
|
s32 ES::DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& ticket)
|
||||||
{
|
{
|
||||||
m_title_context.Clear();
|
m_title_context.Clear();
|
||||||
|
@ -637,20 +659,17 @@ s32 ES::DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& tic
|
||||||
m_title_context.Update(tmd, ticket);
|
m_title_context.Update(tmd, ticket);
|
||||||
INFO_LOG(IOS_ES, "ES_DIVerify: Title context changed: %016" PRIx64, tmd.GetTitleId());
|
INFO_LOG(IOS_ES, "ES_DIVerify: Title context changed: %016" PRIx64, tmd.GetTitleId());
|
||||||
|
|
||||||
std::string tmd_path = Common::GetTMDFileName(tmd.GetTitleId(), Common::FROM_SESSION_ROOT);
|
|
||||||
|
|
||||||
File::CreateFullPath(tmd_path);
|
|
||||||
File::CreateFullPath(Common::GetTitleDataPath(tmd.GetTitleId(), Common::FROM_SESSION_ROOT));
|
|
||||||
|
|
||||||
if (!File::Exists(tmd_path))
|
|
||||||
{
|
|
||||||
// XXX: We are supposed to verify the TMD and ticket here, but cannot because
|
// XXX: We are supposed to verify the TMD and ticket here, but cannot because
|
||||||
// this may cause issues with custom/patched games.
|
// this may cause issues with custom/patched games.
|
||||||
|
|
||||||
File::IOFile tmd_file(tmd_path, "wb");
|
const auto fs = m_ios.GetFS();
|
||||||
const std::vector<u8>& tmd_bytes = tmd.GetBytes();
|
if (!FindInstalledTMD(tmd.GetTitleId()).IsValid())
|
||||||
if (!tmd_file.WriteBytes(tmd_bytes.data(), tmd_bytes.size()))
|
{
|
||||||
ERROR_LOG(IOS_ES, "DIVerify failed to write disc TMD to NAND.");
|
if (const s32 ret = WriteTmdForDiVerify(fs.get(), tmd))
|
||||||
|
{
|
||||||
|
ERROR_LOG(IOS_ES, "DiVerify failed to write disc TMD to NAND.");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!UpdateUIDAndGID(*GetIOS(), m_title_context.tmd))
|
if (!UpdateUIDAndGID(*GetIOS(), m_title_context.tmd))
|
||||||
|
@ -658,7 +677,12 @@ s32 ES::DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& tic
|
||||||
return ES_SHORT_READ;
|
return ES_SHORT_READ;
|
||||||
}
|
}
|
||||||
|
|
||||||
return IPC_SUCCESS;
|
const std::string data_dir = Common::GetTitleDataPath(tmd.GetTitleId());
|
||||||
|
// Might already exist, so we only need to check whether the second operation succeeded.
|
||||||
|
fs->CreateDirectory(PID_KERNEL, PID_KERNEL, data_dir, 0, FS::Mode::ReadWrite, FS::Mode::None,
|
||||||
|
FS::Mode::None);
|
||||||
|
return FS::ConvertResult(fs->SetMetadata(0, data_dir, m_ios.GetUidForPPC(), m_ios.GetGidForPPC(),
|
||||||
|
0, FS::Mode::ReadWrite, FS::Mode::None, FS::Mode::None));
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr u32 FIRST_PPC_UID = 0x1000;
|
constexpr u32 FIRST_PPC_UID = 0x1000;
|
||||||
|
@ -823,18 +847,20 @@ bool ES::IsIssuerCorrect(VerifyContainerType type, const IOS::ES::CertReader& is
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const std::string CERT_STORE_PATH = "/sys/cert.sys";
|
||||||
|
|
||||||
ReturnCode ES::ReadCertStore(std::vector<u8>* buffer) const
|
ReturnCode ES::ReadCertStore(std::vector<u8>* buffer) const
|
||||||
{
|
{
|
||||||
if (!SConfig::GetInstance().m_enable_signature_checks)
|
if (!SConfig::GetInstance().m_enable_signature_checks)
|
||||||
return IPC_SUCCESS;
|
return IPC_SUCCESS;
|
||||||
|
|
||||||
const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys";
|
const auto store_file =
|
||||||
File::IOFile store_file{store_path, "rb"};
|
m_ios.GetFS()->OpenFile(PID_KERNEL, PID_KERNEL, CERT_STORE_PATH, FS::Mode::Read);
|
||||||
if (!store_file)
|
if (!store_file)
|
||||||
return FS_ENOENT;
|
return FS::ConvertResult(store_file.Error());
|
||||||
|
|
||||||
buffer->resize(store_file.GetSize());
|
buffer->resize(store_file->GetStatus()->size);
|
||||||
if (!store_file.ReadBytes(buffer->data(), buffer->size()))
|
if (!store_file->Read(buffer->data(), buffer->size()))
|
||||||
return ES_SHORT_READ;
|
return ES_SHORT_READ;
|
||||||
return IPC_SUCCESS;
|
return IPC_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -853,10 +879,13 @@ ReturnCode ES::WriteNewCertToStore(const IOS::ES::CertReader& cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, write the new cert at the end of the store.
|
// Otherwise, write the new cert at the end of the store.
|
||||||
const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys";
|
const auto store_file =
|
||||||
File::IOFile store_file{store_path, "ab"};
|
m_ios.GetFS()->OpenFile(PID_KERNEL, PID_KERNEL, CERT_STORE_PATH, FS::Mode::ReadWrite);
|
||||||
if (!store_file || !store_file.WriteBytes(cert.GetBytes().data(), cert.GetBytes().size()))
|
if (!store_file || !store_file->Seek(0, FS::SeekMode::End) ||
|
||||||
|
!store_file->Write(cert.GetBytes().data(), cert.GetBytes().size()))
|
||||||
|
{
|
||||||
return ES_EIO;
|
return ES_EIO;
|
||||||
|
}
|
||||||
return IPC_SUCCESS;
|
return IPC_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/File.h"
|
|
||||||
#include "Core/IOS/Device.h"
|
#include "Core/IOS/Device.h"
|
||||||
#include "Core/IOS/ES/Formats.h"
|
#include "Core/IOS/ES/Formats.h"
|
||||||
|
#include "Core/IOS/FS/FileSystem.h"
|
||||||
#include "Core/IOS/IOS.h"
|
#include "Core/IOS/IOS.h"
|
||||||
#include "Core/IOS/IOSC.h"
|
#include "Core/IOS/IOSC.h"
|
||||||
|
|
||||||
|
@ -326,7 +326,7 @@ private:
|
||||||
const std::vector<u8>& cert_chain, u32 iosc_handle = 0);
|
const std::vector<u8>& cert_chain, u32 iosc_handle = 0);
|
||||||
|
|
||||||
// Start a title import.
|
// Start a title import.
|
||||||
bool InitImport(u64 title_id);
|
bool InitImport(const IOS::ES::TMDReader& tmd);
|
||||||
// Clean up the import content directory and move it back to /title.
|
// Clean up the import content directory and move it back to /title.
|
||||||
bool FinishImport(const IOS::ES::TMDReader& tmd);
|
bool FinishImport(const IOS::ES::TMDReader& tmd);
|
||||||
// Write a TMD for a title in /import atomically.
|
// Write a TMD for a title in /import atomically.
|
||||||
|
@ -336,17 +336,15 @@ private:
|
||||||
void FinishAllStaleImports();
|
void FinishAllStaleImports();
|
||||||
|
|
||||||
std::string GetContentPath(u64 title_id, const IOS::ES::Content& content,
|
std::string GetContentPath(u64 title_id, const IOS::ES::Content& content,
|
||||||
const IOS::ES::SharedContentMap& map = IOS::ES::SharedContentMap{
|
const IOS::ES::SharedContentMap& map) const;
|
||||||
Common::FROM_SESSION_ROOT}) const;
|
std::string GetContentPath(u64 title_id, const IOS::ES::Content& content) const;
|
||||||
|
|
||||||
// TODO: reuse the FS code.
|
|
||||||
struct OpenedContent
|
struct OpenedContent
|
||||||
{
|
{
|
||||||
bool m_opened = false;
|
bool m_opened = false;
|
||||||
File::IOFile m_file;
|
FS::Fd m_fd;
|
||||||
u64 m_title_id = 0;
|
u64 m_title_id = 0;
|
||||||
IOS::ES::Content m_content;
|
IOS::ES::Content m_content;
|
||||||
u32 m_position = 0;
|
|
||||||
u32 m_uid = 0;
|
u32 m_uid = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,13 +19,13 @@
|
||||||
#include "Common/Assert.h"
|
#include "Common/Assert.h"
|
||||||
#include "Common/ChunkFile.h"
|
#include "Common/ChunkFile.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/File.h"
|
|
||||||
#include "Common/FileUtil.h"
|
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
|
#include "Common/NandPaths.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
#include "Common/Swap.h"
|
#include "Common/Swap.h"
|
||||||
#include "Core/CommonTitles.h"
|
#include "Core/CommonTitles.h"
|
||||||
#include "Core/IOS/Device.h"
|
#include "Core/IOS/Device.h"
|
||||||
|
#include "Core/IOS/FS/FileSystem.h"
|
||||||
#include "Core/IOS/IOS.h"
|
#include "Core/IOS/IOS.h"
|
||||||
#include "Core/IOS/IOSC.h"
|
#include "Core/IOS/IOSC.h"
|
||||||
|
|
||||||
|
@ -489,15 +489,15 @@ struct SharedContentMap::Entry
|
||||||
std::array<u8, 20> sha1;
|
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");
|
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;
|
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_entries.push_back(entry);
|
||||||
m_last_id++;
|
m_last_id++;
|
||||||
|
@ -515,7 +515,7 @@ SharedContentMap::GetFilenameFromSHA1(const std::array<u8, 20>& sha1) const
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
const std::string id_string(it->id.begin(), it->id.end());
|
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
|
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);
|
m_entries.push_back(entry);
|
||||||
|
|
||||||
WriteEntries();
|
WriteEntries();
|
||||||
filename = Common::RootUserPath(m_root) + StringFromFormat("/shared1/%s.app", id.c_str());
|
filename = StringFromFormat("/shared1/%s.app", id.c_str());
|
||||||
m_last_id++;
|
m_last_id++;
|
||||||
return *filename;
|
return *filename;
|
||||||
}
|
}
|
||||||
|
@ -556,46 +556,50 @@ bool SharedContentMap::DeleteSharedContent(const std::array<u8, 20>& sha1)
|
||||||
|
|
||||||
bool SharedContentMap::WriteEntries() const
|
bool SharedContentMap::WriteEntries() const
|
||||||
{
|
{
|
||||||
// Temporary files in ES are only 12 characters long (excluding /tmp/).
|
// Temporary files are only 12 characters long and must match the final file name
|
||||||
const std::string temp_path = Common::RootUserPath(m_root) + "/tmp/shared1/cont";
|
const std::string temp_path = "/tmp/content.map";
|
||||||
File::CreateFullPath(temp_path);
|
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.
|
// Atomically write the new content map.
|
||||||
{
|
{
|
||||||
File::IOFile file(temp_path, "w+b");
|
const auto file =
|
||||||
if (!file.WriteArray(m_entries.data(), m_entries.size()))
|
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;
|
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;
|
u64 title_id = 0;
|
||||||
if (!file.ReadBytes(&title_id, sizeof(title_id)))
|
if (!file.Read(&title_id, 1))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
u32 uid = 0;
|
u32 uid = 0;
|
||||||
if (!file.ReadBytes(&uid, sizeof(uid)))
|
if (!file.Read(&uid, 1))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return {Common::swap32(uid), Common::swap64(title_id)};
|
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";
|
if (const auto file =
|
||||||
|
fs->OpenFile(HLE::PID_KERNEL, HLE::PID_KERNEL, UID_MAP_PATH, HLE::FS::Mode::Read))
|
||||||
File::IOFile file(m_file_path, "rb");
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
const std::pair<u32, u64> entry = ReadUidSysEntry(file);
|
const std::pair<u32, u64> entry = ReadUidSysEntry(*file);
|
||||||
if (!entry.first && !entry.second)
|
if (!entry.first && !entry.second)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
m_entries.insert(std::move(entry));
|
m_entries.insert(std::move(entry));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (m_entries.empty())
|
if (m_entries.empty())
|
||||||
{
|
{
|
||||||
|
@ -613,7 +617,7 @@ u32 UIDSys::GetUIDFromTitle(u64 title_id) const
|
||||||
u32 UIDSys::GetNextUID() const
|
u32 UIDSys::GetNextUID() const
|
||||||
{
|
{
|
||||||
if (m_entries.empty())
|
if (m_entries.empty())
|
||||||
return 0x00001000;
|
return FIRST_PPC_UID;
|
||||||
return m_entries.rbegin()->first + 1;
|
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 u64 swapped_title_id = Common::swap64(title_id);
|
||||||
const u32 swapped_uid = Common::swap32(uid);
|
const u32 swapped_uid = Common::swap32(uid);
|
||||||
|
|
||||||
File::CreateFullPath(m_file_path);
|
const auto file =
|
||||||
File::IOFile file(m_file_path, "ab");
|
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) ||
|
||||||
if (!file.WriteBytes(&swapped_title_id, sizeof(title_id)) ||
|
!file->Write(&swapped_uid, 1))
|
||||||
!file.WriteBytes(&swapped_uid, sizeof(uid)))
|
|
||||||
{
|
{
|
||||||
ERROR_LOG(IOS_ES, "Failed to write to /sys/uid.sys");
|
ERROR_LOG(IOS_ES, "Failed to write to /sys/uid.sys");
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -10,12 +10,12 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/NandPaths.h"
|
|
||||||
#include "Core/IOS/Device.h"
|
#include "Core/IOS/Device.h"
|
||||||
#include "Core/IOS/IOSC.h"
|
#include "Core/IOS/IOSC.h"
|
||||||
#include "DiscIO/Enums.h"
|
#include "DiscIO/Enums.h"
|
||||||
|
@ -24,6 +24,11 @@ class PointerWrap;
|
||||||
|
|
||||||
namespace IOS
|
namespace IOS
|
||||||
{
|
{
|
||||||
|
namespace HLE::FS
|
||||||
|
{
|
||||||
|
class FileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
namespace ES
|
namespace ES
|
||||||
{
|
{
|
||||||
enum class TitleType : u32
|
enum class TitleType : u32
|
||||||
|
@ -249,7 +254,7 @@ public:
|
||||||
class SharedContentMap final
|
class SharedContentMap final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit SharedContentMap(Common::FromWhichRoot root);
|
explicit SharedContentMap(std::shared_ptr<HLE::FS::FileSystem> fs);
|
||||||
~SharedContentMap();
|
~SharedContentMap();
|
||||||
|
|
||||||
std::optional<std::string> GetFilenameFromSHA1(const std::array<u8, 20>& sha1) const;
|
std::optional<std::string> GetFilenameFromSHA1(const std::array<u8, 20>& sha1) const;
|
||||||
|
@ -261,23 +266,24 @@ private:
|
||||||
bool WriteEntries() const;
|
bool WriteEntries() const;
|
||||||
|
|
||||||
struct Entry;
|
struct Entry;
|
||||||
Common::FromWhichRoot m_root;
|
|
||||||
u32 m_last_id = 0;
|
u32 m_last_id = 0;
|
||||||
std::string m_file_path;
|
|
||||||
std::vector<Entry> m_entries;
|
std::vector<Entry> m_entries;
|
||||||
|
std::shared_ptr<HLE::FS::FileSystem> m_fs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr u32 FIRST_PPC_UID = 0x1000;
|
||||||
|
|
||||||
class UIDSys final
|
class UIDSys final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit UIDSys(Common::FromWhichRoot root);
|
explicit UIDSys(std::shared_ptr<HLE::FS::FileSystem> fs);
|
||||||
|
|
||||||
u32 GetUIDFromTitle(u64 title_id) const;
|
u32 GetUIDFromTitle(u64 title_id) const;
|
||||||
u32 GetOrInsertUIDForTitle(u64 title_id);
|
u32 GetOrInsertUIDForTitle(u64 title_id);
|
||||||
u32 GetNextUID() const;
|
u32 GetNextUID() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string m_file_path;
|
std::shared_ptr<HLE::FS::FileSystem> m_fs;
|
||||||
std::map<u32, u64> m_entries;
|
std::map<u32, u64> m_entries;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,6 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/File.h"
|
|
||||||
#include "Common/FileUtil.h"
|
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/NandPaths.h"
|
#include "Common/NandPaths.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
|
@ -27,14 +25,14 @@ namespace HLE
|
||||||
{
|
{
|
||||||
namespace Device
|
namespace Device
|
||||||
{
|
{
|
||||||
static IOS::ES::TMDReader FindTMD(u64 title_id, const std::string& tmd_path)
|
static IOS::ES::TMDReader FindTMD(FS::FileSystem* fs, u64 title_id, const std::string& tmd_path)
|
||||||
{
|
{
|
||||||
File::IOFile file(tmd_path, "rb");
|
const auto file = fs->OpenFile(PID_KERNEL, PID_KERNEL, tmd_path, FS::Mode::Read);
|
||||||
if (!file)
|
if (!file)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
std::vector<u8> tmd_bytes(file.GetSize());
|
std::vector<u8> tmd_bytes(file->GetStatus()->size);
|
||||||
if (!file.ReadBytes(tmd_bytes.data(), tmd_bytes.size()))
|
if (!file->Read(tmd_bytes.data(), tmd_bytes.size()))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return IOS::ES::TMDReader{std::move(tmd_bytes)};
|
return IOS::ES::TMDReader{std::move(tmd_bytes)};
|
||||||
|
@ -42,24 +40,24 @@ static IOS::ES::TMDReader FindTMD(u64 title_id, const std::string& tmd_path)
|
||||||
|
|
||||||
IOS::ES::TMDReader ES::FindImportTMD(u64 title_id) const
|
IOS::ES::TMDReader ES::FindImportTMD(u64 title_id) const
|
||||||
{
|
{
|
||||||
return FindTMD(title_id, Common::GetImportTitlePath(title_id, Common::FROM_SESSION_ROOT) +
|
return FindTMD(m_ios.GetFS().get(), title_id,
|
||||||
"/content/title.tmd");
|
Common::GetImportTitlePath(title_id) + "/content/title.tmd");
|
||||||
}
|
}
|
||||||
|
|
||||||
IOS::ES::TMDReader ES::FindInstalledTMD(u64 title_id) const
|
IOS::ES::TMDReader ES::FindInstalledTMD(u64 title_id) const
|
||||||
{
|
{
|
||||||
return FindTMD(title_id, Common::GetTMDFileName(title_id, Common::FROM_SESSION_ROOT));
|
return FindTMD(m_ios.GetFS().get(), title_id, Common::GetTMDFileName(title_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
IOS::ES::TicketReader ES::FindSignedTicket(u64 title_id) const
|
IOS::ES::TicketReader ES::FindSignedTicket(u64 title_id) const
|
||||||
{
|
{
|
||||||
const std::string path = Common::GetTicketFileName(title_id, Common::FROM_SESSION_ROOT);
|
const std::string path = Common::GetTicketFileName(title_id);
|
||||||
File::IOFile ticket_file(path, "rb");
|
const auto ticket_file = m_ios.GetFS()->OpenFile(PID_KERNEL, PID_KERNEL, path, FS::Mode::Read);
|
||||||
if (!ticket_file)
|
if (!ticket_file)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
std::vector<u8> signed_ticket(ticket_file.GetSize());
|
std::vector<u8> signed_ticket(ticket_file->GetStatus()->size);
|
||||||
if (!ticket_file.ReadBytes(signed_ticket.data(), signed_ticket.size()))
|
if (!ticket_file->Read(signed_ticket.data(), signed_ticket.size()))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
return IOS::ES::TicketReader{std::move(signed_ticket)};
|
return IOS::ES::TicketReader{std::move(signed_ticket)};
|
||||||
|
@ -73,9 +71,10 @@ static bool IsValidPartOfTitleID(const std::string& string)
|
||||||
[](const auto character) { return std::isxdigit(character) != 0; });
|
[](const auto character) { return std::isxdigit(character) != 0; });
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<u64> GetTitlesInTitleOrImport(const std::string& titles_dir)
|
static std::vector<u64> GetTitlesInTitleOrImport(FS::FileSystem* fs, const std::string& titles_dir)
|
||||||
{
|
{
|
||||||
if (!File::IsDirectory(titles_dir))
|
const auto entries = fs->ReadDirectory(PID_KERNEL, PID_KERNEL, titles_dir);
|
||||||
|
if (!entries)
|
||||||
{
|
{
|
||||||
ERROR_LOG(IOS_ES, "%s is not a directory", titles_dir.c_str());
|
ERROR_LOG(IOS_ES, "%s is not a directory", titles_dir.c_str());
|
||||||
return {};
|
return {};
|
||||||
|
@ -85,22 +84,26 @@ static std::vector<u64> GetTitlesInTitleOrImport(const std::string& titles_dir)
|
||||||
|
|
||||||
// The /title and /import directories contain one directory per title type, and each of them has
|
// The /title and /import directories contain one directory per title type, and each of them has
|
||||||
// a directory per title (where the name is the low 32 bits of the title ID in %08x format).
|
// a directory per title (where the name is the low 32 bits of the title ID in %08x format).
|
||||||
const auto entries = File::ScanDirectoryTree(titles_dir, true);
|
for (const std::string& title_type : *entries)
|
||||||
for (const File::FSTEntry& title_type : entries.children)
|
|
||||||
{
|
{
|
||||||
if (!title_type.isDirectory || !IsValidPartOfTitleID(title_type.virtualName))
|
if (!IsValidPartOfTitleID(title_type))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (title_type.children.empty())
|
const auto title_entries =
|
||||||
|
fs->ReadDirectory(PID_KERNEL, PID_KERNEL, titles_dir + '/' + title_type);
|
||||||
|
if (!title_entries)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (const File::FSTEntry& title_identifier : title_type.children)
|
for (const std::string& title_identifier : *title_entries)
|
||||||
{
|
{
|
||||||
if (!title_identifier.isDirectory || !IsValidPartOfTitleID(title_identifier.virtualName))
|
if (!IsValidPartOfTitleID(title_identifier))
|
||||||
|
continue;
|
||||||
|
if (!fs->ReadDirectory(PID_KERNEL, PID_KERNEL,
|
||||||
|
titles_dir + '/' + title_type + '/' + title_identifier))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const u32 type = std::stoul(title_type.virtualName, nullptr, 16);
|
const u32 type = std::stoul(title_type, nullptr, 16);
|
||||||
const u32 identifier = std::stoul(title_identifier.virtualName, nullptr, 16);
|
const u32 identifier = std::stoul(title_identifier, nullptr, 16);
|
||||||
title_ids.push_back(static_cast<u64>(type) << 32 | identifier);
|
title_ids.push_back(static_cast<u64>(type) << 32 | identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,18 +119,19 @@ static std::vector<u64> GetTitlesInTitleOrImport(const std::string& titles_dir)
|
||||||
|
|
||||||
std::vector<u64> ES::GetInstalledTitles() const
|
std::vector<u64> ES::GetInstalledTitles() const
|
||||||
{
|
{
|
||||||
return GetTitlesInTitleOrImport(Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/title");
|
return GetTitlesInTitleOrImport(m_ios.GetFS().get(), "/title");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u64> ES::GetTitleImports() const
|
std::vector<u64> ES::GetTitleImports() const
|
||||||
{
|
{
|
||||||
return GetTitlesInTitleOrImport(Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/import");
|
return GetTitlesInTitleOrImport(m_ios.GetFS().get(), "/import");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u64> ES::GetTitlesWithTickets() const
|
std::vector<u64> ES::GetTitlesWithTickets() const
|
||||||
{
|
{
|
||||||
const std::string tickets_dir = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/ticket";
|
const auto fs = m_ios.GetFS();
|
||||||
if (!File::IsDirectory(tickets_dir))
|
const auto entries = fs->ReadDirectory(PID_KERNEL, PID_KERNEL, "/ticket");
|
||||||
|
if (!entries)
|
||||||
{
|
{
|
||||||
ERROR_LOG(IOS_ES, "/ticket is not a directory");
|
ERROR_LOG(IOS_ES, "/ticket is not a directory");
|
||||||
return {};
|
return {};
|
||||||
|
@ -137,25 +141,25 @@ std::vector<u64> ES::GetTitlesWithTickets() const
|
||||||
|
|
||||||
// The /ticket directory contains one directory per title type, and each of them contains
|
// The /ticket directory contains one directory per title type, and each of them contains
|
||||||
// one ticket per title (where the name is the low 32 bits of the title ID in %08x format).
|
// one ticket per title (where the name is the low 32 bits of the title ID in %08x format).
|
||||||
const auto entries = File::ScanDirectoryTree(tickets_dir, true);
|
for (const std::string& title_type : *entries)
|
||||||
for (const File::FSTEntry& title_type : entries.children)
|
|
||||||
{
|
{
|
||||||
if (!title_type.isDirectory || !IsValidPartOfTitleID(title_type.virtualName))
|
if (!IsValidPartOfTitleID(title_type))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (title_type.children.empty())
|
const auto sub_entries = fs->ReadDirectory(PID_KERNEL, PID_KERNEL, "/ticket/" + title_type);
|
||||||
|
if (!sub_entries)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (const File::FSTEntry& ticket : title_type.children)
|
for (const std::string& file_name : *sub_entries)
|
||||||
{
|
{
|
||||||
const std::string name_without_ext = ticket.virtualName.substr(0, 8);
|
const std::string name_without_ext = file_name.substr(0, 8);
|
||||||
if (ticket.isDirectory || !IsValidPartOfTitleID(name_without_ext) ||
|
if (fs->ReadDirectory(PID_KERNEL, PID_KERNEL, "/ticket/" + title_type + '/' + file_name) ||
|
||||||
name_without_ext + ".tik" != ticket.virtualName)
|
!IsValidPartOfTitleID(name_without_ext) || name_without_ext + ".tik" != file_name)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const u32 type = std::stoul(title_type.virtualName, nullptr, 16);
|
const u32 type = std::stoul(title_type, nullptr, 16);
|
||||||
const u32 identifier = std::stoul(name_without_ext, nullptr, 16);
|
const u32 identifier = std::stoul(name_without_ext, nullptr, 16);
|
||||||
title_ids.push_back(static_cast<u64>(type) << 32 | identifier);
|
title_ids.push_back(static_cast<u64>(type) << 32 | identifier);
|
||||||
}
|
}
|
||||||
|
@ -169,7 +173,7 @@ std::vector<IOS::ES::Content> ES::GetStoredContentsFromTMD(const IOS::ES::TMDRea
|
||||||
if (!tmd.IsValid())
|
if (!tmd.IsValid())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
const IOS::ES::SharedContentMap map{Common::FROM_SESSION_ROOT};
|
const IOS::ES::SharedContentMap map{m_ios.GetFS()};
|
||||||
const std::vector<IOS::ES::Content> contents = tmd.GetContents();
|
const std::vector<IOS::ES::Content> contents = tmd.GetContents();
|
||||||
|
|
||||||
std::vector<IOS::ES::Content> stored_contents;
|
std::vector<IOS::ES::Content> stored_contents;
|
||||||
|
@ -177,7 +181,8 @@ std::vector<IOS::ES::Content> ES::GetStoredContentsFromTMD(const IOS::ES::TMDRea
|
||||||
std::copy_if(contents.begin(), contents.end(), std::back_inserter(stored_contents),
|
std::copy_if(contents.begin(), contents.end(), std::back_inserter(stored_contents),
|
||||||
[this, &tmd, &map](const IOS::ES::Content& content) {
|
[this, &tmd, &map](const IOS::ES::Content& content) {
|
||||||
const std::string path = GetContentPath(tmd.GetTitleId(), content, map);
|
const std::string path = GetContentPath(tmd.GetTitleId(), content, map);
|
||||||
return !path.empty() && File::Exists(path);
|
return !path.empty() &&
|
||||||
|
m_ios.GetFS()->GetMetadata(PID_KERNEL, PID_KERNEL, path).Succeeded();
|
||||||
});
|
});
|
||||||
|
|
||||||
return stored_contents;
|
return stored_contents;
|
||||||
|
@ -185,80 +190,116 @@ std::vector<IOS::ES::Content> ES::GetStoredContentsFromTMD(const IOS::ES::TMDRea
|
||||||
|
|
||||||
u32 ES::GetSharedContentsCount() const
|
u32 ES::GetSharedContentsCount() const
|
||||||
{
|
{
|
||||||
const std::string shared1_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/shared1";
|
const auto entries = m_ios.GetFS()->ReadDirectory(PID_KERNEL, PID_KERNEL, "/shared1");
|
||||||
const auto entries = File::ScanDirectoryTree(shared1_path, false);
|
|
||||||
return static_cast<u32>(
|
return static_cast<u32>(
|
||||||
std::count_if(entries.children.begin(), entries.children.end(), [](const auto& entry) {
|
std::count_if(entries->begin(), entries->end(), [this](const std::string& entry) {
|
||||||
return !entry.isDirectory && entry.virtualName.size() == 12 &&
|
return !m_ios.GetFS()->ReadDirectory(PID_KERNEL, PID_KERNEL, "/shared1/" + entry) &&
|
||||||
entry.virtualName.compare(8, 4, ".app") == 0;
|
entry.size() == 12 && entry.compare(8, 4, ".app") == 0;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::array<u8, 20>> ES::GetSharedContents() const
|
std::vector<std::array<u8, 20>> ES::GetSharedContents() const
|
||||||
{
|
{
|
||||||
const IOS::ES::SharedContentMap map{Common::FROM_SESSION_ROOT};
|
const IOS::ES::SharedContentMap map{m_ios.GetFS()};
|
||||||
return map.GetHashes();
|
return map.GetHashes();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ES::InitImport(u64 title_id)
|
static bool DeleteDirectoriesIfEmpty(FS::FileSystem* fs, const std::string& path)
|
||||||
{
|
{
|
||||||
const std::string content_dir = Common::GetTitleContentPath(title_id, Common::FROM_SESSION_ROOT);
|
std::string::size_type position = std::string::npos;
|
||||||
const std::string data_dir = Common::GetTitleDataPath(title_id, Common::FROM_SESSION_ROOT);
|
do
|
||||||
for (const auto& dir : {content_dir, data_dir})
|
|
||||||
{
|
{
|
||||||
if (!File::IsDirectory(dir) && !File::CreateFullPath(dir) && !File::CreateDir(dir))
|
const auto directory = fs->ReadDirectory(PID_KERNEL, PID_KERNEL, path.substr(0, position));
|
||||||
|
if ((directory && directory->empty()) ||
|
||||||
|
(!directory && directory.Error() != FS::ResultCode::NotFound))
|
||||||
{
|
{
|
||||||
ERROR_LOG(IOS_ES, "InitImport: Failed to create title dirs for %016" PRIx64, title_id);
|
if (fs->Delete(PID_KERNEL, PID_KERNEL, path.substr(0, position)) != FS::ResultCode::Success)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
position = path.find_last_of('/', position - 1);
|
||||||
|
} while (position != 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ES::InitImport(const IOS::ES::TMDReader& tmd)
|
||||||
|
{
|
||||||
|
const auto fs = m_ios.GetFS();
|
||||||
|
const std::string content_dir = Common::GetTitleContentPath(tmd.GetTitleId());
|
||||||
|
const std::string import_content_dir = Common::GetImportTitlePath(tmd.GetTitleId()) + "/content";
|
||||||
|
|
||||||
|
const auto result1 = fs->CreateFullPath(PID_KERNEL, PID_KERNEL, content_dir + '/', 0,
|
||||||
|
FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::Read);
|
||||||
|
const auto result2 = fs->SetMetadata(PID_KERNEL, content_dir, PID_KERNEL, PID_KERNEL, 0,
|
||||||
|
FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::None);
|
||||||
|
const auto result3 = fs->CreateFullPath(PID_KERNEL, PID_KERNEL, import_content_dir + '/', 0,
|
||||||
|
FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::None);
|
||||||
|
if (result1 != FS::ResultCode::Success || result2 != FS::ResultCode::Success ||
|
||||||
|
result3 != FS::ResultCode::Success)
|
||||||
|
{
|
||||||
|
ERROR_LOG(IOS_ES, "InitImport: Failed to create content dir for %016" PRIx64, tmd.GetTitleId());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
IOS::ES::UIDSys uid_sys{Common::FROM_CONFIGURED_ROOT};
|
const std::string data_dir = Common::GetTitleDataPath(tmd.GetTitleId());
|
||||||
uid_sys.GetOrInsertUIDForTitle(title_id);
|
const auto data_dir_contents = fs->ReadDirectory(PID_KERNEL, PID_KERNEL, data_dir);
|
||||||
|
if (!data_dir_contents &&
|
||||||
|
(data_dir_contents.Error() != FS::ResultCode::NotFound ||
|
||||||
|
fs->CreateDirectory(PID_KERNEL, PID_KERNEL, data_dir, 0, FS::Mode::ReadWrite, FS::Mode::None,
|
||||||
|
FS::Mode::None) != FS::ResultCode::Success))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IOS::ES::UIDSys uid_sys{fs};
|
||||||
|
const u32 uid = uid_sys.GetOrInsertUIDForTitle(tmd.GetTitleId());
|
||||||
|
if (fs->SetMetadata(0, data_dir, uid, tmd.GetGroupId(), 0, FS::Mode::ReadWrite, FS::Mode::None,
|
||||||
|
FS::Mode::None) != FS::ResultCode::Success)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// IOS moves the title content directory to /import if the TMD exists during an import.
|
// IOS moves the title content directory to /import if the TMD exists during an import.
|
||||||
if (File::Exists(Common::GetTMDFileName(title_id, Common::FROM_SESSION_ROOT)))
|
const auto file_info =
|
||||||
|
fs->GetMetadata(PID_KERNEL, PID_KERNEL, Common::GetTMDFileName(tmd.GetTitleId()));
|
||||||
|
if (!file_info || !file_info->is_file)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const auto result = fs->Rename(PID_KERNEL, PID_KERNEL, content_dir, import_content_dir);
|
||||||
|
if (result != FS::ResultCode::Success)
|
||||||
{
|
{
|
||||||
const std::string import_content_dir =
|
ERROR_LOG(IOS_ES, "InitImport: Failed to move content dir for %016" PRIx64, tmd.GetTitleId());
|
||||||
Common::GetImportTitlePath(title_id, Common::FROM_SESSION_ROOT) + "/content";
|
|
||||||
File::CreateFullPath(import_content_dir);
|
|
||||||
if (!File::Rename(content_dir, import_content_dir))
|
|
||||||
{
|
|
||||||
ERROR_LOG(IOS_ES, "InitImport: Failed to move content dir for %016" PRIx64, title_id);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
DeleteDirectoriesIfEmpty(m_ios.GetFS().get(), import_content_dir);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ES::FinishImport(const IOS::ES::TMDReader& tmd)
|
bool ES::FinishImport(const IOS::ES::TMDReader& tmd)
|
||||||
{
|
{
|
||||||
|
const auto fs = m_ios.GetFS();
|
||||||
const u64 title_id = tmd.GetTitleId();
|
const u64 title_id = tmd.GetTitleId();
|
||||||
const std::string import_content_dir =
|
const std::string import_content_dir = Common::GetImportTitlePath(title_id) + "/content";
|
||||||
Common::GetImportTitlePath(title_id, Common::FROM_SESSION_ROOT) + "/content";
|
|
||||||
|
|
||||||
// Remove everything not listed in the TMD.
|
// Remove everything not listed in the TMD.
|
||||||
std::unordered_set<std::string> expected_entries = {"title.tmd"};
|
std::unordered_set<std::string> expected_entries = {"title.tmd"};
|
||||||
for (const auto& content_info : tmd.GetContents())
|
for (const auto& content_info : tmd.GetContents())
|
||||||
expected_entries.insert(StringFromFormat("%08x.app", content_info.id));
|
expected_entries.insert(StringFromFormat("%08x.app", content_info.id));
|
||||||
const auto entries = File::ScanDirectoryTree(import_content_dir, false);
|
const auto entries = fs->ReadDirectory(PID_KERNEL, PID_KERNEL, import_content_dir);
|
||||||
for (const File::FSTEntry& entry : entries.children)
|
if (!entries)
|
||||||
|
return false;
|
||||||
|
for (const std::string& name : *entries)
|
||||||
{
|
{
|
||||||
|
const std::string absolute_path = import_content_dir + '/' + name;
|
||||||
// There should not be any directory in there. Remove it.
|
// There should not be any directory in there. Remove it.
|
||||||
if (entry.isDirectory)
|
if (fs->ReadDirectory(PID_KERNEL, PID_KERNEL, absolute_path))
|
||||||
File::DeleteDirRecursively(entry.physicalName);
|
fs->Delete(PID_KERNEL, PID_KERNEL, absolute_path);
|
||||||
else if (expected_entries.find(entry.virtualName) == expected_entries.end())
|
else if (expected_entries.find(name) == expected_entries.end())
|
||||||
File::Delete(entry.physicalName);
|
fs->Delete(PID_KERNEL, PID_KERNEL, absolute_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string content_dir = Common::GetTitleContentPath(title_id, Common::FROM_SESSION_ROOT);
|
const std::string content_dir = Common::GetTitleContentPath(title_id);
|
||||||
if (File::IsDirectory(content_dir))
|
if (fs->Rename(PID_KERNEL, PID_KERNEL, import_content_dir, content_dir) !=
|
||||||
{
|
FS::ResultCode::Success)
|
||||||
WARN_LOG(IOS_ES, "FinishImport: %s already exists -- removing", content_dir.c_str());
|
|
||||||
File::DeleteDirRecursively(content_dir);
|
|
||||||
}
|
|
||||||
if (!File::Rename(import_content_dir, content_dir))
|
|
||||||
{
|
{
|
||||||
ERROR_LOG(IOS_ES, "FinishImport: Failed to rename import directory to %s", content_dir.c_str());
|
ERROR_LOG(IOS_ES, "FinishImport: Failed to rename import directory to %s", content_dir.c_str());
|
||||||
return false;
|
return false;
|
||||||
|
@ -268,28 +309,34 @@ bool ES::FinishImport(const IOS::ES::TMDReader& tmd)
|
||||||
|
|
||||||
bool ES::WriteImportTMD(const IOS::ES::TMDReader& tmd)
|
bool ES::WriteImportTMD(const IOS::ES::TMDReader& tmd)
|
||||||
{
|
{
|
||||||
const std::string tmd_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/tmp/title.tmd";
|
const auto fs = m_ios.GetFS();
|
||||||
File::CreateFullPath(tmd_path);
|
const std::string tmd_path = "/tmp/title.tmd";
|
||||||
|
|
||||||
{
|
{
|
||||||
File::IOFile file(tmd_path, "wb");
|
fs->CreateFile(PID_KERNEL, PID_KERNEL, tmd_path, 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite,
|
||||||
if (!file.WriteBytes(tmd.GetBytes().data(), tmd.GetBytes().size()))
|
FS::Mode::None);
|
||||||
|
const auto file = fs->OpenFile(PID_KERNEL, PID_KERNEL, tmd_path, FS::Mode::Write);
|
||||||
|
if (!file || !file->Write(tmd.GetBytes().data(), tmd.GetBytes().size()))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string dest = Common::GetImportTitlePath(tmd.GetTitleId(), Common::FROM_SESSION_ROOT) +
|
const std::string dest = Common::GetImportTitlePath(tmd.GetTitleId()) + "/content/title.tmd";
|
||||||
"/content/title.tmd";
|
return fs->Rename(PID_KERNEL, PID_KERNEL, tmd_path, dest) == FS::ResultCode::Success;
|
||||||
return File::Rename(tmd_path, dest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ES::FinishStaleImport(u64 title_id)
|
void ES::FinishStaleImport(u64 title_id)
|
||||||
{
|
{
|
||||||
|
const auto fs = m_ios.GetFS();
|
||||||
const auto import_tmd = FindImportTMD(title_id);
|
const auto import_tmd = FindImportTMD(title_id);
|
||||||
if (!import_tmd.IsValid())
|
if (!import_tmd.IsValid())
|
||||||
File::DeleteDirRecursively(Common::GetImportTitlePath(title_id, Common::FROM_SESSION_ROOT) +
|
{
|
||||||
"/content");
|
fs->Delete(PID_KERNEL, PID_KERNEL, Common::GetImportTitlePath(title_id) + "/content");
|
||||||
|
DeleteDirectoriesIfEmpty(fs.get(), Common::GetImportTitlePath(title_id));
|
||||||
|
DeleteDirectoriesIfEmpty(fs.get(), Common::GetTitlePath(title_id));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
FinishImport(import_tmd);
|
FinishImport(import_tmd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ES::FinishAllStaleImports()
|
void ES::FinishAllStaleImports()
|
||||||
|
@ -297,10 +344,6 @@ void ES::FinishAllStaleImports()
|
||||||
const std::vector<u64> titles = GetTitleImports();
|
const std::vector<u64> titles = GetTitleImports();
|
||||||
for (const u64& title_id : titles)
|
for (const u64& title_id : titles)
|
||||||
FinishStaleImport(title_id);
|
FinishStaleImport(title_id);
|
||||||
|
|
||||||
const std::string import_dir = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/import";
|
|
||||||
File::DeleteDirRecursively(import_dir);
|
|
||||||
File::CreateDir(import_dir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ES::GetContentPath(const u64 title_id, const IOS::ES::Content& content,
|
std::string ES::GetContentPath(const u64 title_id, const IOS::ES::Content& content,
|
||||||
|
@ -308,9 +351,13 @@ std::string ES::GetContentPath(const u64 title_id, const IOS::ES::Content& conte
|
||||||
{
|
{
|
||||||
if (content.IsShared())
|
if (content.IsShared())
|
||||||
return content_map.GetFilenameFromSHA1(content.sha1).value_or("");
|
return content_map.GetFilenameFromSHA1(content.sha1).value_or("");
|
||||||
|
return Common::GetTitleContentPath(title_id) + StringFromFormat("/%08x.app", content.id);
|
||||||
|
}
|
||||||
|
|
||||||
return Common::GetTitleContentPath(title_id, Common::FROM_SESSION_ROOT) +
|
std::string ES::GetContentPath(const u64 title_id, const IOS::ES::Content& content) const
|
||||||
StringFromFormat("/%08x.app", content.id);
|
{
|
||||||
|
IOS::ES::SharedContentMap map{m_ios.GetFS()};
|
||||||
|
return GetContentPath(title_id, content, map);
|
||||||
}
|
}
|
||||||
} // namespace Device
|
} // namespace Device
|
||||||
} // namespace HLE
|
} // namespace HLE
|
||||||
|
|
|
@ -33,11 +33,13 @@ s32 ES::OpenContent(const IOS::ES::TMDReader& tmd, u16 content_index, u32 uid)
|
||||||
if (entry.m_opened)
|
if (entry.m_opened)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!entry.m_file.Open(GetContentPath(title_id, content), "rb"))
|
auto file = m_ios.GetFS()->OpenFile(PID_KERNEL, PID_KERNEL, GetContentPath(title_id, content),
|
||||||
return FS_ENOENT;
|
FS::Mode::Read);
|
||||||
|
if (!file)
|
||||||
|
return FS::ConvertResult(file.Error());
|
||||||
|
|
||||||
entry.m_opened = true;
|
entry.m_opened = true;
|
||||||
entry.m_position = 0;
|
entry.m_fd = file->Release();
|
||||||
entry.m_content = content;
|
entry.m_content = content;
|
||||||
entry.m_title_id = title_id;
|
entry.m_title_id = title_id;
|
||||||
entry.m_uid = uid;
|
entry.m_uid = uid;
|
||||||
|
@ -78,7 +80,7 @@ IPCCommandResult ES::OpenActiveTitleContent(u32 caller_uid, const IOCtlVRequest&
|
||||||
if (!m_title_context.active)
|
if (!m_title_context.active)
|
||||||
return GetDefaultReply(ES_EINVAL);
|
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());
|
const u32 uid = uid_map.GetOrInsertUIDForTitle(m_title_context.tmd.GetTitleId());
|
||||||
if (caller_uid != 0 && caller_uid != uid)
|
if (caller_uid != 0 && caller_uid != uid)
|
||||||
return GetDefaultReply(ES_EACCES);
|
return GetDefaultReply(ES_EACCES);
|
||||||
|
@ -97,21 +99,8 @@ s32 ES::ReadContent(u32 cfd, u8* buffer, u32 size, u32 uid)
|
||||||
if (!entry.m_opened)
|
if (!entry.m_opened)
|
||||||
return IPC_EINVAL;
|
return IPC_EINVAL;
|
||||||
|
|
||||||
// XXX: make this reuse the FS code... ES just does a simple "IOS_Read" call here
|
const auto result = m_ios.GetFS()->ReadBytesFromFile(entry.m_fd, buffer, size);
|
||||||
// instead of all this duplicated filesystem logic.
|
return result.Succeeded() ? *result : FS::ConvertResult(result.Error());
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IPCCommandResult ES::ReadContent(u32 uid, const IOCtlVRequest& request)
|
IPCCommandResult ES::ReadContent(u32 uid, const IOCtlVRequest& request)
|
||||||
|
@ -137,6 +126,7 @@ ReturnCode ES::CloseContent(u32 cfd, u32 uid)
|
||||||
if (!entry.m_opened)
|
if (!entry.m_opened)
|
||||||
return IPC_EINVAL;
|
return IPC_EINVAL;
|
||||||
|
|
||||||
|
m_ios.GetFS()->Close(entry.m_fd);
|
||||||
entry = {};
|
entry = {};
|
||||||
INFO_LOG(IOS_ES, "CloseContent: CFD %u", cfd);
|
INFO_LOG(IOS_ES, "CloseContent: CFD %u", cfd);
|
||||||
return IPC_SUCCESS;
|
return IPC_SUCCESS;
|
||||||
|
@ -162,26 +152,8 @@ s32 ES::SeekContent(u32 cfd, u32 offset, SeekMode mode, u32 uid)
|
||||||
if (!entry.m_opened)
|
if (!entry.m_opened)
|
||||||
return IPC_EINVAL;
|
return IPC_EINVAL;
|
||||||
|
|
||||||
// XXX: This should be a simple IOS_Seek.
|
const auto result = m_ios.GetFS()->SeekFile(entry.m_fd, offset, static_cast<FS::SeekMode>(mode));
|
||||||
switch (mode)
|
return result.Succeeded() ? *result : FS::ConvertResult(result.Error());
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IPCCommandResult ES::SeekContent(u32 uid, const IOCtlVRequest& request)
|
IPCCommandResult ES::SeekContent(u32 uid, const IOCtlVRequest& request)
|
||||||
|
|
|
@ -13,14 +13,13 @@
|
||||||
#include <mbedtls/sha1.h>
|
#include <mbedtls/sha1.h>
|
||||||
|
|
||||||
#include "Common/Align.h"
|
#include "Common/Align.h"
|
||||||
#include "Common/File.h"
|
|
||||||
#include "Common/FileUtil.h"
|
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/NandPaths.h"
|
#include "Common/NandPaths.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
#include "Core/CommonTitles.h"
|
#include "Core/CommonTitles.h"
|
||||||
#include "Core/HW/Memmap.h"
|
#include "Core/HW/Memmap.h"
|
||||||
#include "Core/IOS/ES/Formats.h"
|
#include "Core/IOS/ES/Formats.h"
|
||||||
|
#include "Core/IOS/FS/FileSystem.h"
|
||||||
|
|
||||||
namespace IOS
|
namespace IOS
|
||||||
{
|
{
|
||||||
|
@ -28,19 +27,22 @@ namespace HLE
|
||||||
{
|
{
|
||||||
namespace Device
|
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 u64 title_id = ticket.GetTitleId();
|
||||||
|
|
||||||
const std::string ticket_path = Common::GetTicketFileName(title_id, Common::FROM_SESSION_ROOT);
|
const std::string path = Common::GetTicketFileName(title_id);
|
||||||
File::CreateFullPath(ticket_path);
|
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");
|
const auto file = fs->OpenFile(PID_KERNEL, PID_KERNEL, path, FS::Mode::Write);
|
||||||
if (!ticket_file)
|
if (!file)
|
||||||
return ES_EIO;
|
return FS::ConvertResult(file.Error());
|
||||||
|
|
||||||
const std::vector<u8>& raw_ticket = ticket.GetBytes();
|
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)
|
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)
|
if (verify_ret != IPC_SUCCESS)
|
||||||
return verify_ret;
|
return verify_ret;
|
||||||
|
|
||||||
const ReturnCode write_ret = WriteTicket(ticket);
|
const ReturnCode write_ret = WriteTicket(m_ios.GetFS().get(), ticket);
|
||||||
if (write_ret != IPC_SUCCESS)
|
if (write_ret != IPC_SUCCESS)
|
||||||
return write_ret;
|
return write_ret;
|
||||||
|
|
||||||
|
@ -155,7 +157,7 @@ ReturnCode ES::ImportTmd(Context& context, const std::vector<u8>& tmd_bytes)
|
||||||
if (ret != IPC_SUCCESS)
|
if (ret != IPC_SUCCESS)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
if (!InitImport(context.title_import_export.tmd.GetTitleId()))
|
if (!InitImport(context.title_import_export.tmd))
|
||||||
return ES_EIO;
|
return ES_EIO;
|
||||||
|
|
||||||
ret =
|
ret =
|
||||||
|
@ -237,7 +239,7 @@ ReturnCode ES::ImportTitleInit(Context& context, const std::vector<u8>& tmd_byte
|
||||||
if (ret != IPC_SUCCESS)
|
if (ret != IPC_SUCCESS)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
if (!InitImport(context.title_import_export.tmd.GetTitleId()))
|
if (!InitImport(context.title_import_export.tmd))
|
||||||
return ES_EIO;
|
return ES_EIO;
|
||||||
|
|
||||||
context.title_import_export.valid = true;
|
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)
|
static std::string GetImportContentPath(u64 title_id, u32 content_id)
|
||||||
{
|
{
|
||||||
return Common::GetImportTitlePath(title_id, Common::FROM_SESSION_ROOT) +
|
return Common::GetImportTitlePath(title_id) + StringFromFormat("/content/%08x.app", content_id);
|
||||||
StringFromFormat("/content/%08x.app", content_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd)
|
ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd)
|
||||||
|
@ -370,10 +371,11 @@ ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd)
|
||||||
return ES_HASH_MISMATCH;
|
return ES_HASH_MISMATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto fs = m_ios.GetFS();
|
||||||
std::string content_path;
|
std::string content_path;
|
||||||
if (content_info.IsShared())
|
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);
|
content_path = shared_content.AddSharedContent(content_info.sha1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -381,26 +383,27 @@ ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd)
|
||||||
content_path = GetImportContentPath(context.title_import_export.tmd.GetTitleId(),
|
content_path = GetImportContentPath(context.title_import_export.tmd.GetTitleId(),
|
||||||
context.title_import_export.content.id);
|
context.title_import_export.content.id);
|
||||||
}
|
}
|
||||||
File::CreateFullPath(content_path);
|
|
||||||
|
|
||||||
const std::string temp_path =
|
const std::string temp_path =
|
||||||
Common::RootUserPath(Common::FROM_SESSION_ROOT) +
|
"/tmp/" + content_path.substr(content_path.find_last_of('/') + 1, std::string::npos);
|
||||||
StringFromFormat("/tmp/%08x.app", context.title_import_export.content.id);
|
|
||||||
File::CreateFullPath(temp_path);
|
|
||||||
|
|
||||||
|
fs->CreateFile(PID_KERNEL, PID_KERNEL, temp_path, 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite,
|
||||||
|
FS::Mode::None);
|
||||||
{
|
{
|
||||||
File::IOFile file(temp_path, "wb");
|
const auto file = fs->OpenFile(PID_KERNEL, PID_KERNEL, temp_path, FS::Mode::Write);
|
||||||
if (!file.WriteBytes(decrypted_data.data(), content_info.size))
|
if (!file || !file->Write(decrypted_data.data(), content_info.size))
|
||||||
{
|
{
|
||||||
ERROR_LOG(IOS_ES, "ImportContentEnd: Failed to write to %s", temp_path.c_str());
|
ERROR_LOG(IOS_ES, "ImportContentEnd: Failed to write to %s", temp_path.c_str());
|
||||||
return ES_EIO;
|
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());
|
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 = {};
|
context.title_import_export.content = {};
|
||||||
|
@ -424,7 +427,7 @@ ReturnCode ES::ImportTitleDone(Context& context)
|
||||||
// Make sure all listed, non-optional contents have been imported.
|
// Make sure all listed, non-optional contents have been imported.
|
||||||
const u64 title_id = context.title_import_export.tmd.GetTitleId();
|
const u64 title_id = context.title_import_export.tmd.GetTitleId();
|
||||||
const std::vector<IOS::ES::Content> contents = context.title_import_export.tmd.GetContents();
|
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 =
|
const bool has_all_required_contents =
|
||||||
std::all_of(contents.cbegin(), contents.cend(), [&](const IOS::ES::Content& content) {
|
std::all_of(contents.cbegin(), contents.cend(), [&](const IOS::ES::Content& content) {
|
||||||
if (content.IsOptional())
|
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
|
// Note: the import hasn't been finalised yet, so the whole title directory
|
||||||
// is still in /import, not /title.
|
// is still in /import, not /title.
|
||||||
return File::Exists(Common::GetImportTitlePath(title_id, Common::FROM_SESSION_ROOT) +
|
const std::string path = Common::GetImportTitlePath(title_id) +
|
||||||
StringFromFormat("/content/%08x.app", content.id));
|
StringFromFormat("/content/%08x.app", content.id);
|
||||||
|
return m_ios.GetFS()->GetMetadata(PID_KERNEL, PID_KERNEL, path).Succeeded();
|
||||||
});
|
});
|
||||||
if (!has_all_required_contents)
|
if (!has_all_required_contents)
|
||||||
return ES_EINVAL;
|
return ES_EINVAL;
|
||||||
|
@ -497,17 +501,8 @@ ReturnCode ES::DeleteTitle(u64 title_id)
|
||||||
if (!CanDeleteTitle(title_id))
|
if (!CanDeleteTitle(title_id))
|
||||||
return ES_EINVAL;
|
return ES_EINVAL;
|
||||||
|
|
||||||
const std::string title_dir = Common::GetTitlePath(title_id, Common::FROM_SESSION_ROOT);
|
const std::string title_dir = Common::GetTitlePath(title_id);
|
||||||
if (!File::IsDirectory(title_dir))
|
return FS::ConvertResult(m_ios.GetFS()->Delete(PID_KERNEL, PID_KERNEL, 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IPCCommandResult ES::DeleteTitle(const IOCtlVRequest& request)
|
IPCCommandResult ES::DeleteTitle(const IOCtlVRequest& request)
|
||||||
|
@ -521,6 +516,7 @@ IPCCommandResult ES::DeleteTitle(const IOCtlVRequest& request)
|
||||||
|
|
||||||
ReturnCode ES::DeleteTicket(const u8* ticket_view)
|
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));
|
const u64 title_id = Common::swap64(ticket_view + offsetof(IOS::ES::TicketView, title_id));
|
||||||
|
|
||||||
if (!CanDeleteTitle(title_id))
|
if (!CanDeleteTitle(title_id))
|
||||||
|
@ -534,24 +530,26 @@ ReturnCode ES::DeleteTicket(const u8* ticket_view)
|
||||||
ticket.DeleteTicket(ticket_id);
|
ticket.DeleteTicket(ticket_id);
|
||||||
|
|
||||||
const std::vector<u8>& new_ticket = ticket.GetBytes();
|
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");
|
const auto file = fs->OpenFile(PID_KERNEL, PID_KERNEL, ticket_path, FS::Mode::ReadWrite);
|
||||||
if (!ticket_file || !ticket_file.WriteBytes(new_ticket.data(), new_ticket.size()))
|
if (!file || !file->Write(new_ticket.data(), new_ticket.size()))
|
||||||
return ES_EIO;
|
return ES_EIO;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// Delete the ticket file if it is now empty.
|
// Delete the ticket file if it is now empty.
|
||||||
if (new_ticket.empty())
|
fs->Delete(PID_KERNEL, PID_KERNEL, ticket_path);
|
||||||
File::Delete(ticket_path);
|
}
|
||||||
|
|
||||||
// Delete the ticket directory if it is now empty.
|
// Delete the ticket directory if it is now empty.
|
||||||
const std::string ticket_parent_dir =
|
const std::string ticket_parent_dir =
|
||||||
Common::RootUserPath(Common::FROM_CONFIGURED_ROOT) +
|
|
||||||
StringFromFormat("/ticket/%08x", static_cast<u32>(title_id >> 32));
|
StringFromFormat("/ticket/%08x", static_cast<u32>(title_id >> 32));
|
||||||
const auto ticket_parent_dir_entries = File::ScanDirectoryTree(ticket_parent_dir, false);
|
const auto ticket_parent_dir_entries =
|
||||||
if (ticket_parent_dir_entries.children.empty())
|
fs->ReadDirectory(PID_KERNEL, PID_KERNEL, ticket_parent_dir);
|
||||||
File::DeleteDir(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;
|
return IPC_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -571,14 +569,15 @@ ReturnCode ES::DeleteTitleContent(u64 title_id) const
|
||||||
if (!CanDeleteTitle(title_id))
|
if (!CanDeleteTitle(title_id))
|
||||||
return ES_EINVAL;
|
return ES_EINVAL;
|
||||||
|
|
||||||
const std::string content_dir = Common::GetTitleContentPath(title_id, Common::FROM_SESSION_ROOT);
|
const std::string content_dir = Common::GetTitleContentPath(title_id);
|
||||||
if (!File::IsDirectory(content_dir))
|
const auto files = m_ios.GetFS()->ReadDirectory(PID_KERNEL, PID_KERNEL, content_dir);
|
||||||
return FS_ENOENT;
|
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)
|
if (file_name.size() == 12 && file_name.compare(8, 4, ".app") == 0)
|
||||||
File::Delete(file.physicalName);
|
m_ios.GetFS()->Delete(PID_KERNEL, PID_KERNEL, content_dir + '/' + file_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return IPC_SUCCESS;
|
return IPC_SUCCESS;
|
||||||
|
@ -604,13 +603,9 @@ ReturnCode ES::DeleteContent(u64 title_id, u32 content_id) const
|
||||||
if (!tmd.FindContentById(content_id, &content))
|
if (!tmd.FindContentById(content_id, &content))
|
||||||
return ES_EINVAL;
|
return ES_EINVAL;
|
||||||
|
|
||||||
if (!File::Delete(Common::GetTitleContentPath(title_id, Common::FROM_SESSION_ROOT) +
|
return FS::ConvertResult(m_ios.GetFS()->Delete(PID_KERNEL, PID_KERNEL,
|
||||||
StringFromFormat("/%08x.app", content_id)))
|
Common::GetTitleContentPath(title_id) +
|
||||||
{
|
StringFromFormat("/%08x.app", content_id)));
|
||||||
return FS_ENOENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
return IPC_SUCCESS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IPCCommandResult ES::DeleteContent(const IOCtlVRequest& request)
|
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
|
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);
|
const auto content_path = map.GetFilenameFromSHA1(sha1);
|
||||||
if (!content_path)
|
if (!content_path)
|
||||||
return ES_EINVAL;
|
return ES_EINVAL;
|
||||||
|
@ -810,8 +805,9 @@ ReturnCode ES::DeleteSharedContent(const std::array<u8, 20>& sha1) const
|
||||||
return ES_EINVAL;
|
return ES_EINVAL;
|
||||||
|
|
||||||
// Delete the shared content and update the content map.
|
// Delete the shared content and update the content map.
|
||||||
if (!File::Delete(*content_path))
|
const auto delete_result = m_ios.GetFS()->Delete(PID_KERNEL, PID_KERNEL, *content_path);
|
||||||
return FS_ENOENT;
|
if (delete_result != FS::ResultCode::Success)
|
||||||
|
return FS::ConvertResult(delete_result);
|
||||||
|
|
||||||
if (!map.DeleteSharedContent(sha1))
|
if (!map.DeleteSharedContent(sha1))
|
||||||
return ES_EIO;
|
return ES_EIO;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "Common/Assert.h"
|
#include "Common/Assert.h"
|
||||||
#include "Common/FileUtil.h"
|
#include "Common/FileUtil.h"
|
||||||
|
#include "Core/IOS/Device.h"
|
||||||
#include "Core/IOS/FS/HostBackend/FS.h"
|
#include "Core/IOS/FS/HostBackend/FS.h"
|
||||||
|
|
||||||
namespace IOS::HLE::FS
|
namespace IOS::HLE::FS
|
||||||
|
@ -17,6 +18,15 @@ std::unique_ptr<FileSystem> MakeFileSystem(Location location)
|
||||||
return std::make_unique<HostFileSystem>(nand_root);
|
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}
|
FileHandle::FileHandle(FileSystem* fs, Fd fd) : m_fs{fs}, m_fd{fd}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,11 @@
|
||||||
|
|
||||||
class PointerWrap;
|
class PointerWrap;
|
||||||
|
|
||||||
namespace IOS::HLE::FS
|
namespace IOS::HLE
|
||||||
|
{
|
||||||
|
enum ReturnCode : s32;
|
||||||
|
|
||||||
|
namespace FS
|
||||||
{
|
{
|
||||||
enum class ResultCode
|
enum class ResultCode
|
||||||
{
|
{
|
||||||
|
@ -218,4 +222,8 @@ enum class Location
|
||||||
|
|
||||||
std::unique_ptr<FileSystem> MakeFileSystem(Location location = Location::Session);
|
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;
|
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)
|
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.
|
// 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
|
// Hack: if the path that is being accessed is within an installed title directory, get the
|
||||||
// UID/GID from the installed title TMD.
|
// UID/GID from the installed title TMD.
|
||||||
|
Kernel* ios = GetIOS();
|
||||||
u64 title_id;
|
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())
|
if (tmd.IsValid())
|
||||||
metadata.gid = tmd.GetGroupId();
|
metadata.gid = tmd.GetGroupId();
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,11 +266,27 @@ u16 Kernel::GetGidForPPC() const
|
||||||
return m_ppc_gid;
|
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.
|
// 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.
|
// Unlike 0x42, IOS will set up some constants in memory before booting the PPC.
|
||||||
bool Kernel::BootstrapPPC(const std::string& boot_content_path)
|
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())
|
if (!dol.IsValid())
|
||||||
return false;
|
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).
|
// Load the ARM binary to memory (if possible).
|
||||||
// Because we do not actually emulate the Starlet, only load the sections that are in MEM1.
|
// Because we do not actually emulate the Starlet, only load the sections that are in MEM1.
|
||||||
|
|
||||||
File::IOFile file{boot_content_path, "rb"};
|
ARMBinary binary{ReadBootContent(m_fs.get(), boot_content_path, 0xB00000)};
|
||||||
// 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)};
|
|
||||||
if (!binary.IsValid())
|
if (!binary.IsValid())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
|
||||||
static std::thread g_save_thread;
|
static std::thread g_save_thread;
|
||||||
|
|
||||||
// Don't forget to increase this after doing changes on the savestate system
|
// 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.
|
// Maps savestate versions to Dolphin versions.
|
||||||
// Versions after 42 don't need to be added to this list,
|
// Versions after 42 don't need to be added to this list,
|
||||||
|
|
Loading…
Reference in New Issue