diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index d29d6a6d53..4619516c76 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -14,8 +14,6 @@ #include #include "Common/ChunkFile.h" -#include "Common/File.h" -#include "Common/FileUtil.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/NandPaths.h" @@ -25,6 +23,7 @@ #include "Core/ConfigManager.h" #include "Core/HW/Memmap.h" #include "Core/IOS/ES/Formats.h" +#include "Core/IOS/FS/FileSystem.h" #include "Core/IOS/IOSC.h" #include "Core/IOS/VersionInfo.h" @@ -40,41 +39,42 @@ static u64 s_title_to_launch; struct DirectoryToCreate { const char* path; - u32 attributes; - OpenMode owner_perm; - OpenMode group_perm; - OpenMode other_perm; + FS::FileAttribute attribute; + FS::Mode owner_mode; + FS::Mode group_mode; + FS::Mode other_mode; + FS::Uid uid = PID_KERNEL; + FS::Gid gid = PID_KERNEL; }; constexpr std::array s_directories_to_create = {{ - {"/sys", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE}, - {"/ticket", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE}, - {"/title", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_READ}, - {"/shared1", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE}, - {"/shared2", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW}, - {"/tmp", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW}, - {"/import", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE}, - {"/meta", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW}, - {"/wfs", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE, OpenMode::IOS_OPEN_NONE}, + {"/sys", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::None}, + {"/ticket", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::None}, + {"/title", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::Read}, + {"/shared1", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::None}, + {"/shared2", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite}, + {"/tmp", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite}, + {"/import", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::None}, + {"/meta", 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite, + 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) { 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. - if (File::IsDirectory(path)) - continue; - - 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. + // Now update the UID/GID and other attributes. + m_ios.GetFS()->SetMetadata(0, directory.path, directory.uid, directory.gid, directory.attribute, + directory.owner_mode, directory.group_mode, directory.other_mode); } FinishAllStaleImports(); @@ -161,7 +161,7 @@ IPCCommandResult ES::GetTitleId(const IOCtlVRequest& request) 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 u32 uid = uid_sys.GetOrInsertUIDForTitle(title_id); if (!uid) @@ -174,9 +174,9 @@ static bool UpdateUIDAndGID(Kernel& kernel, const IOS::ES::TMDReader& tmd) 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); if (!system_menu_uid) 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 s32 ret = CheckIsAllowedToSetUID(uid); + const s32 ret = CheckIsAllowedToSetUID(m_ios, uid); if (ret < 0) { 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_title_id); p.Do(entry.m_content); - p.Do(entry.m_position); + p.Do(entry.m_fd); 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); @@ -623,6 +619,32 @@ IPCCommandResult ES::DIVerify(const IOCtlVRequest& request) 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) { 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); 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); + // XXX: We are supposed to verify the TMD and ticket here, but cannot because + // this may cause issues with custom/patched games. - File::CreateFullPath(tmd_path); - File::CreateFullPath(Common::GetTitleDataPath(tmd.GetTitleId(), Common::FROM_SESSION_ROOT)); - - if (!File::Exists(tmd_path)) + const auto fs = m_ios.GetFS(); + if (!FindInstalledTMD(tmd.GetTitleId()).IsValid()) { - // XXX: We are supposed to verify the TMD and ticket here, but cannot because - // this may cause issues with custom/patched games. - - File::IOFile tmd_file(tmd_path, "wb"); - const std::vector& tmd_bytes = tmd.GetBytes(); - 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)) @@ -658,7 +677,12 @@ s32 ES::DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& tic 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; @@ -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* buffer) const { if (!SConfig::GetInstance().m_enable_signature_checks) return IPC_SUCCESS; - const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys"; - File::IOFile store_file{store_path, "rb"}; + const auto store_file = + m_ios.GetFS()->OpenFile(PID_KERNEL, PID_KERNEL, CERT_STORE_PATH, FS::Mode::Read); if (!store_file) - return FS_ENOENT; + return FS::ConvertResult(store_file.Error()); - buffer->resize(store_file.GetSize()); - if (!store_file.ReadBytes(buffer->data(), buffer->size())) + buffer->resize(store_file->GetStatus()->size); + if (!store_file->Read(buffer->data(), buffer->size())) return ES_SHORT_READ; 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. - const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys"; - File::IOFile store_file{store_path, "ab"}; - if (!store_file || !store_file.WriteBytes(cert.GetBytes().data(), cert.GetBytes().size())) + const auto store_file = + m_ios.GetFS()->OpenFile(PID_KERNEL, PID_KERNEL, CERT_STORE_PATH, FS::Mode::ReadWrite); + if (!store_file || !store_file->Seek(0, FS::SeekMode::End) || + !store_file->Write(cert.GetBytes().data(), cert.GetBytes().size())) + { return ES_EIO; + } return IPC_SUCCESS; } diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index ac5998e805..38492d39da 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -10,9 +10,9 @@ #include #include "Common/CommonTypes.h" -#include "Common/File.h" #include "Core/IOS/Device.h" #include "Core/IOS/ES/Formats.h" +#include "Core/IOS/FS/FileSystem.h" #include "Core/IOS/IOS.h" #include "Core/IOS/IOSC.h" @@ -326,7 +326,7 @@ private: const std::vector& cert_chain, u32 iosc_handle = 0); // 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. bool FinishImport(const IOS::ES::TMDReader& tmd); // Write a TMD for a title in /import atomically. @@ -336,17 +336,15 @@ private: void FinishAllStaleImports(); std::string GetContentPath(u64 title_id, const IOS::ES::Content& content, - const IOS::ES::SharedContentMap& map = IOS::ES::SharedContentMap{ - Common::FROM_SESSION_ROOT}) const; + const IOS::ES::SharedContentMap& map) const; + std::string GetContentPath(u64 title_id, const IOS::ES::Content& content) const; - // TODO: reuse the FS code. struct OpenedContent { bool m_opened = false; - File::IOFile m_file; + FS::Fd m_fd; u64 m_title_id = 0; IOS::ES::Content m_content; - u32 m_position = 0; u32 m_uid = 0; }; diff --git a/Source/Core/Core/IOS/ES/Formats.cpp b/Source/Core/Core/IOS/ES/Formats.cpp index d5f5ee17f0..6a50d95047 100644 --- a/Source/Core/Core/IOS/ES/Formats.cpp +++ b/Source/Core/Core/IOS/ES/Formats.cpp @@ -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 sha1; }; -SharedContentMap::SharedContentMap(Common::FromWhichRoot root) : m_root(root) +static const std::string CONTENT_MAP_PATH = "/shared1/content.map"; +SharedContentMap::SharedContentMap(std::shared_ptr 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& 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> SharedContentMap::GetHashes() const @@ -541,7 +541,7 @@ std::string SharedContentMap::AddSharedContent(const std::array& 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& 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 ReadUidSysEntry(File::IOFile& file) +static std::pair 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 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 entry = ReadUidSysEntry(file); - if (!entry.first && !entry.second) - break; + while (true) + { + const std::pair 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; diff --git a/Source/Core/Core/IOS/ES/Formats.h b/Source/Core/Core/IOS/ES/Formats.h index bb8ccd4eae..8dae59d1c6 100644 --- a/Source/Core/Core/IOS/ES/Formats.h +++ b/Source/Core/Core/IOS/ES/Formats.h @@ -10,12 +10,12 @@ #include #include #include +#include #include #include #include #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 @@ public: class SharedContentMap final { public: - explicit SharedContentMap(Common::FromWhichRoot root); + explicit SharedContentMap(std::shared_ptr fs); ~SharedContentMap(); std::optional GetFilenameFromSHA1(const std::array& sha1) const; @@ -261,23 +266,24 @@ private: bool WriteEntries() const; struct Entry; - Common::FromWhichRoot m_root; u32 m_last_id = 0; - std::string m_file_path; std::vector m_entries; + std::shared_ptr m_fs; }; +constexpr u32 FIRST_PPC_UID = 0x1000; + class UIDSys final { public: - explicit UIDSys(Common::FromWhichRoot root); + explicit UIDSys(std::shared_ptr fs); u32 GetUIDFromTitle(u64 title_id) const; u32 GetOrInsertUIDForTitle(u64 title_id); u32 GetNextUID() const; private: - std::string m_file_path; + std::shared_ptr m_fs; std::map m_entries; }; diff --git a/Source/Core/Core/IOS/ES/NandUtils.cpp b/Source/Core/Core/IOS/ES/NandUtils.cpp index 1b332bf97b..738ddeaafd 100644 --- a/Source/Core/Core/IOS/ES/NandUtils.cpp +++ b/Source/Core/Core/IOS/ES/NandUtils.cpp @@ -13,8 +13,6 @@ #include #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" @@ -27,14 +25,14 @@ namespace HLE { 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) return {}; - std::vector tmd_bytes(file.GetSize()); - if (!file.ReadBytes(tmd_bytes.data(), tmd_bytes.size())) + std::vector tmd_bytes(file->GetStatus()->size); + if (!file->Read(tmd_bytes.data(), tmd_bytes.size())) return {}; 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 { - return FindTMD(title_id, Common::GetImportTitlePath(title_id, Common::FROM_SESSION_ROOT) + - "/content/title.tmd"); + return FindTMD(m_ios.GetFS().get(), title_id, + Common::GetImportTitlePath(title_id) + "/content/title.tmd"); } 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 { - const std::string path = Common::GetTicketFileName(title_id, Common::FROM_SESSION_ROOT); - File::IOFile ticket_file(path, "rb"); + const std::string path = Common::GetTicketFileName(title_id); + const auto ticket_file = m_ios.GetFS()->OpenFile(PID_KERNEL, PID_KERNEL, path, FS::Mode::Read); if (!ticket_file) return {}; - std::vector signed_ticket(ticket_file.GetSize()); - if (!ticket_file.ReadBytes(signed_ticket.data(), signed_ticket.size())) + std::vector signed_ticket(ticket_file->GetStatus()->size); + if (!ticket_file->Read(signed_ticket.data(), signed_ticket.size())) return {}; 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; }); } -static std::vector GetTitlesInTitleOrImport(const std::string& titles_dir) +static std::vector 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()); return {}; @@ -85,22 +84,26 @@ static std::vector GetTitlesInTitleOrImport(const std::string& titles_dir) // 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). - const auto entries = File::ScanDirectoryTree(titles_dir, true); - for (const File::FSTEntry& title_type : entries.children) + for (const std::string& title_type : *entries) { - if (!title_type.isDirectory || !IsValidPartOfTitleID(title_type.virtualName)) + if (!IsValidPartOfTitleID(title_type)) continue; - if (title_type.children.empty()) + const auto title_entries = + fs->ReadDirectory(PID_KERNEL, PID_KERNEL, titles_dir + '/' + title_type); + if (!title_entries) 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; - const u32 type = std::stoul(title_type.virtualName, nullptr, 16); - const u32 identifier = std::stoul(title_identifier.virtualName, nullptr, 16); + const u32 type = std::stoul(title_type, nullptr, 16); + const u32 identifier = std::stoul(title_identifier, nullptr, 16); title_ids.push_back(static_cast(type) << 32 | identifier); } } @@ -116,18 +119,19 @@ static std::vector GetTitlesInTitleOrImport(const std::string& titles_dir) std::vector ES::GetInstalledTitles() const { - return GetTitlesInTitleOrImport(Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/title"); + return GetTitlesInTitleOrImport(m_ios.GetFS().get(), "/title"); } std::vector ES::GetTitleImports() const { - return GetTitlesInTitleOrImport(Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/import"); + return GetTitlesInTitleOrImport(m_ios.GetFS().get(), "/import"); } std::vector ES::GetTitlesWithTickets() const { - const std::string tickets_dir = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/ticket"; - if (!File::IsDirectory(tickets_dir)) + const auto fs = m_ios.GetFS(); + const auto entries = fs->ReadDirectory(PID_KERNEL, PID_KERNEL, "/ticket"); + if (!entries) { ERROR_LOG(IOS_ES, "/ticket is not a directory"); return {}; @@ -137,25 +141,25 @@ std::vector ES::GetTitlesWithTickets() const // 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). - const auto entries = File::ScanDirectoryTree(tickets_dir, true); - for (const File::FSTEntry& title_type : entries.children) + for (const std::string& title_type : *entries) { - if (!title_type.isDirectory || !IsValidPartOfTitleID(title_type.virtualName)) + if (!IsValidPartOfTitleID(title_type)) continue; - if (title_type.children.empty()) + const auto sub_entries = fs->ReadDirectory(PID_KERNEL, PID_KERNEL, "/ticket/" + title_type); + if (!sub_entries) 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); - if (ticket.isDirectory || !IsValidPartOfTitleID(name_without_ext) || - name_without_ext + ".tik" != ticket.virtualName) + const std::string name_without_ext = file_name.substr(0, 8); + if (fs->ReadDirectory(PID_KERNEL, PID_KERNEL, "/ticket/" + title_type + '/' + file_name) || + !IsValidPartOfTitleID(name_without_ext) || name_without_ext + ".tik" != file_name) { 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); title_ids.push_back(static_cast(type) << 32 | identifier); } @@ -169,7 +173,7 @@ std::vector ES::GetStoredContentsFromTMD(const IOS::ES::TMDRea if (!tmd.IsValid()) return {}; - const IOS::ES::SharedContentMap map{Common::FROM_SESSION_ROOT}; + const IOS::ES::SharedContentMap map{m_ios.GetFS()}; const std::vector contents = tmd.GetContents(); std::vector stored_contents; @@ -177,7 +181,8 @@ std::vector ES::GetStoredContentsFromTMD(const IOS::ES::TMDRea std::copy_if(contents.begin(), contents.end(), std::back_inserter(stored_contents), [this, &tmd, &map](const IOS::ES::Content& content) { 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; @@ -185,80 +190,116 @@ std::vector ES::GetStoredContentsFromTMD(const IOS::ES::TMDRea u32 ES::GetSharedContentsCount() const { - const std::string shared1_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/shared1"; - const auto entries = File::ScanDirectoryTree(shared1_path, false); + const auto entries = m_ios.GetFS()->ReadDirectory(PID_KERNEL, PID_KERNEL, "/shared1"); return static_cast( - std::count_if(entries.children.begin(), entries.children.end(), [](const auto& entry) { - return !entry.isDirectory && entry.virtualName.size() == 12 && - entry.virtualName.compare(8, 4, ".app") == 0; + std::count_if(entries->begin(), entries->end(), [this](const std::string& entry) { + return !m_ios.GetFS()->ReadDirectory(PID_KERNEL, PID_KERNEL, "/shared1/" + entry) && + entry.size() == 12 && entry.compare(8, 4, ".app") == 0; })); } std::vector> ES::GetSharedContents() const { - const IOS::ES::SharedContentMap map{Common::FROM_SESSION_ROOT}; + const IOS::ES::SharedContentMap map{m_ios.GetFS()}; 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); - const std::string data_dir = Common::GetTitleDataPath(title_id, Common::FROM_SESSION_ROOT); - for (const auto& dir : {content_dir, data_dir}) + std::string::size_type position = std::string::npos; + do { - 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); - return false; + if (fs->Delete(PID_KERNEL, PID_KERNEL, path.substr(0, position)) != FS::ResultCode::Success) + 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}; - uid_sys.GetOrInsertUIDForTitle(title_id); + const std::string data_dir = Common::GetTitleDataPath(tmd.GetTitleId()); + 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. - if (File::Exists(Common::GetTMDFileName(title_id, Common::FROM_SESSION_ROOT))) - { - const std::string import_content_dir = - 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; - } - } + 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) + { + ERROR_LOG(IOS_ES, "InitImport: Failed to move content dir for %016" PRIx64, tmd.GetTitleId()); + return false; + } + DeleteDirectoriesIfEmpty(m_ios.GetFS().get(), import_content_dir); return true; } bool ES::FinishImport(const IOS::ES::TMDReader& tmd) { + const auto fs = m_ios.GetFS(); const u64 title_id = tmd.GetTitleId(); - const std::string import_content_dir = - Common::GetImportTitlePath(title_id, Common::FROM_SESSION_ROOT) + "/content"; + const std::string import_content_dir = Common::GetImportTitlePath(title_id) + "/content"; // Remove everything not listed in the TMD. std::unordered_set expected_entries = {"title.tmd"}; for (const auto& content_info : tmd.GetContents()) expected_entries.insert(StringFromFormat("%08x.app", content_info.id)); - const auto entries = File::ScanDirectoryTree(import_content_dir, false); - for (const File::FSTEntry& entry : entries.children) + const auto entries = fs->ReadDirectory(PID_KERNEL, PID_KERNEL, import_content_dir); + 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. - if (entry.isDirectory) - File::DeleteDirRecursively(entry.physicalName); - else if (expected_entries.find(entry.virtualName) == expected_entries.end()) - File::Delete(entry.physicalName); + if (fs->ReadDirectory(PID_KERNEL, PID_KERNEL, absolute_path)) + fs->Delete(PID_KERNEL, PID_KERNEL, absolute_path); + else if (expected_entries.find(name) == expected_entries.end()) + fs->Delete(PID_KERNEL, PID_KERNEL, absolute_path); } - const std::string content_dir = Common::GetTitleContentPath(title_id, Common::FROM_SESSION_ROOT); - if (File::IsDirectory(content_dir)) - { - 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)) + const std::string content_dir = Common::GetTitleContentPath(title_id); + if (fs->Rename(PID_KERNEL, PID_KERNEL, import_content_dir, content_dir) != + FS::ResultCode::Success) { ERROR_LOG(IOS_ES, "FinishImport: Failed to rename import directory to %s", content_dir.c_str()); return false; @@ -268,28 +309,34 @@ bool ES::FinishImport(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"; - File::CreateFullPath(tmd_path); - + const auto fs = m_ios.GetFS(); + const std::string tmd_path = "/tmp/title.tmd"; { - File::IOFile file(tmd_path, "wb"); - if (!file.WriteBytes(tmd.GetBytes().data(), tmd.GetBytes().size())) + fs->CreateFile(PID_KERNEL, PID_KERNEL, tmd_path, 0, FS::Mode::ReadWrite, FS::Mode::ReadWrite, + 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; } - const std::string dest = Common::GetImportTitlePath(tmd.GetTitleId(), Common::FROM_SESSION_ROOT) + - "/content/title.tmd"; - return File::Rename(tmd_path, dest); + const std::string dest = Common::GetImportTitlePath(tmd.GetTitleId()) + "/content/title.tmd"; + return fs->Rename(PID_KERNEL, PID_KERNEL, tmd_path, dest) == FS::ResultCode::Success; } void ES::FinishStaleImport(u64 title_id) { + const auto fs = m_ios.GetFS(); const auto import_tmd = FindImportTMD(title_id); 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 + { FinishImport(import_tmd); + } } void ES::FinishAllStaleImports() @@ -297,10 +344,6 @@ void ES::FinishAllStaleImports() const std::vector titles = GetTitleImports(); for (const u64& title_id : titles) 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, @@ -308,9 +351,13 @@ std::string ES::GetContentPath(const u64 title_id, const IOS::ES::Content& conte { if (content.IsShared()) 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) + - StringFromFormat("/%08x.app", content.id); +std::string ES::GetContentPath(const u64 title_id, const IOS::ES::Content& content) const +{ + IOS::ES::SharedContentMap map{m_ios.GetFS()}; + return GetContentPath(title_id, content, map); } } // namespace Device } // namespace HLE diff --git a/Source/Core/Core/IOS/ES/TitleContents.cpp b/Source/Core/Core/IOS/ES/TitleContents.cpp index 8e7cbb616e..0570ccb167 100644 --- a/Source/Core/Core/IOS/ES/TitleContents.cpp +++ b/Source/Core/Core/IOS/ES/TitleContents.cpp @@ -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(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(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(mode)); + return result.Succeeded() ? *result : FS::ConvertResult(result.Error()); } IPCCommandResult ES::SeekContent(u32 uid, const IOCtlVRequest& request) diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index 1bb9a1a752..30d15d23e0 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -13,14 +13,13 @@ #include #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 { @@ -28,19 +27,22 @@ 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& 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& 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& 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& 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& 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,10 +371,11 @@ 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 @@ -381,26 +383,27 @@ ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd) 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 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& 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(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& 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& 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; diff --git a/Source/Core/Core/IOS/FS/FileSystem.cpp b/Source/Core/Core/IOS/FS/FileSystem.cpp index c6db165002..e62c6bcc6b 100644 --- a/Source/Core/Core/IOS/FS/FileSystem.cpp +++ b/Source/Core/Core/IOS/FS/FileSystem.cpp @@ -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 MakeFileSystem(Location location) return std::make_unique(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(-(static_cast(code) + 100)); +} + FileHandle::FileHandle(FileSystem* fs, Fd fd) : m_fs{fs}, m_fd{fd} { } diff --git a/Source/Core/Core/IOS/FS/FileSystem.h b/Source/Core/Core/IOS/FS/FileSystem.h index 42edb14cdf..a53cd36ac9 100644 --- a/Source/Core/Core/IOS/FS/FileSystem.h +++ b/Source/Core/Core/IOS/FS/FileSystem.h @@ -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 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 diff --git a/Source/Core/Core/IOS/FS/FileSystemProxy.cpp b/Source/Core/Core/IOS/FS/FileSystemProxy.cpp index 587577d497..2fbcee6794 100644 --- a/Source/Core/Core/IOS/FS/FileSystemProxy.cpp +++ b/Source/Core/Core/IOS/FS/FileSystemProxy.cpp @@ -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(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. diff --git a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp index 1e512a623a..112eee0e66 100644 --- a/Source/Core/Core/IOS/FS/HostBackend/FS.cpp +++ b/Source/Core/Core/IOS/FS/HostBackend/FS.cpp @@ -310,10 +310,11 @@ Result 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(); } diff --git a/Source/Core/Core/IOS/IOS.cpp b/Source/Core/Core/IOS/IOS.cpp index fe62d8373c..325e6bb5ff 100644 --- a/Source/Core/Core/IOS/IOS.cpp +++ b/Source/Core/Core/IOS/IOS.cpp @@ -266,11 +266,27 @@ u16 Kernel::GetGidForPPC() const return m_ppc_gid; } +static std::vector 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 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 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; diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 925039f5da..6f1a8817fc 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent; static std::thread g_save_thread; // Don't forget to increase this after doing changes on the savestate system -static const u32 STATE_VERSION = 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,