diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 1127654089..cbd92a4b40 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -471,6 +471,8 @@ IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request) return DeleteTicket(request); case IOCTL_ES_DELETETITLECONTENT: return DeleteTitleContent(request); + case IOCTL_ES_DELETESHAREDCONTENT: + return DeleteSharedContent(request); case IOCTL_ES_GETSTOREDTMDSIZE: return GetStoredTMDSize(request); case IOCTL_ES_GETSTOREDTMD: @@ -503,7 +505,6 @@ IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request) return GetBoot2Version(request); case IOCTL_ES_VERIFYSIGN: - case IOCTL_ES_DELETESHAREDCONTENT: case IOCTL_ES_UNKNOWN_3B: case IOCTL_ES_UNKNOWN_3C: case IOCTL_ES_UNKNOWN_3D: diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index dc71fd2f80..9bc9418090 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -114,6 +114,7 @@ public: ReturnCode DeleteTitle(u64 title_id); ReturnCode DeleteTitleContent(u64 title_id) const; ReturnCode DeleteTicket(const u8* ticket_view); + ReturnCode DeleteSharedContent(const std::array& sha1) const; private: enum @@ -209,6 +210,7 @@ private: IPCCommandResult DeleteTitle(const IOCtlVRequest& request); IPCCommandResult DeleteTitleContent(const IOCtlVRequest& request); IPCCommandResult DeleteTicket(const IOCtlVRequest& request); + IPCCommandResult DeleteSharedContent(const IOCtlVRequest& request); // Device identity and encryption IPCCommandResult GetConsoleID(const IOCtlVRequest& request); diff --git a/Source/Core/Core/IOS/ES/Formats.cpp b/Source/Core/Core/IOS/ES/Formats.cpp index dce527519f..1524208808 100644 --- a/Source/Core/Core/IOS/ES/Formats.cpp +++ b/Source/Core/Core/IOS/ES/Formats.cpp @@ -423,16 +423,34 @@ std::string SharedContentMap::AddSharedContent(const std::array& sha1) entry.sha1 = sha1; m_entries.push_back(entry); - File::CreateFullPath(m_file_path); - - File::IOFile file(m_file_path, "ab"); - file.WriteArray(&entry, 1); - + WriteEntries(); filename = Common::RootUserPath(m_root) + StringFromFormat("/shared1/%s.app", id.c_str()); m_last_id++; return filename; } +bool SharedContentMap::DeleteSharedContent(const std::array& sha1) +{ + m_entries.erase(std::remove_if(m_entries.begin(), m_entries.end(), + [&sha1](const auto& entry) { return entry.sha1 == sha1; }), + m_entries.end()); + return WriteEntries(); +} + +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); + + // Atomically write the new content map. + File::IOFile file(temp_path, "w+b"); + if (!file.WriteArray(m_entries.data(), m_entries.size())) + return false; + File::CreateFullPath(m_file_path); + return File::RenameSync(temp_path, m_file_path); +} + static std::pair ReadUidSysEntry(File::IOFile& file) { u64 title_id = 0; diff --git a/Source/Core/Core/IOS/ES/Formats.h b/Source/Core/Core/IOS/ES/Formats.h index 6337aeaf32..64100d2915 100644 --- a/Source/Core/Core/IOS/ES/Formats.h +++ b/Source/Core/Core/IOS/ES/Formats.h @@ -212,9 +212,12 @@ public: std::string GetFilenameFromSHA1(const std::array& sha1) const; std::string AddSharedContent(const std::array& sha1); + bool DeleteSharedContent(const std::array& sha1); std::vector> GetHashes() const; private: + bool WriteEntries() const; + struct Entry; Common::FromWhichRoot m_root; u32 m_last_id = 0; diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index 4a199ee306..a8e326d371 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -622,6 +622,51 @@ IPCCommandResult ES::ExportTitleDone(Context& context, const IOCtlVRequest& requ { return GetDefaultReply(ExportTitleDone(context)); } + +ReturnCode ES::DeleteSharedContent(const std::array& sha1) const +{ + IOS::ES::SharedContentMap map{Common::FromWhichRoot::FROM_SESSION_ROOT}; + const std::string content_path = map.GetFilenameFromSHA1(sha1); + if (content_path == "unk") + return ES_EINVAL; + + // Check whether the shared content is used by a system title. + const std::vector titles = IOS::ES::GetInstalledTitles(); + const bool is_used_by_system_title = std::any_of(titles.begin(), titles.end(), [&sha1](u64 id) { + if (!IOS::ES::IsTitleType(id, IOS::ES::TitleType::System)) + return false; + + const auto tmd = IOS::ES::FindInstalledTMD(id); + if (!tmd.IsValid()) + return true; + + const auto contents = tmd.GetContents(); + return std::any_of(contents.begin(), contents.end(), + [&sha1](const auto& content) { return content.sha1 == sha1; }); + }); + + // Any shared content used by a system title cannot be deleted. + if (is_used_by_system_title) + return ES_EINVAL; + + // Delete the shared content and update the content map. + if (!File::Delete(content_path)) + return FS_ENOENT; + + if (!map.DeleteSharedContent(sha1)) + return ES_EIO; + + return IPC_SUCCESS; +} + +IPCCommandResult ES::DeleteSharedContent(const IOCtlVRequest& request) +{ + std::array sha1; + if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != sha1.size()) + return GetDefaultReply(ES_EINVAL); + Memory::CopyFromEmu(sha1.data(), request.in_vectors[0].address, request.in_vectors[0].size); + return GetDefaultReply(DeleteSharedContent(sha1)); +} } // namespace Device } // namespace HLE } // namespace IOS