diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index da2a393c5b..1127654089 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -383,21 +383,21 @@ IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request) switch (request.request) { case IOCTL_ES_ADDTICKET: - return AddTicket(request); + return ImportTicket(request); case IOCTL_ES_ADDTMD: - return AddTMD(*context, request); + return ImportTmd(*context, request); case IOCTL_ES_ADDTITLESTART: - return AddTitleStart(*context, request); + return ImportTitleInit(*context, request); case IOCTL_ES_ADDCONTENTSTART: - return AddContentStart(*context, request); + return ImportContentBegin(*context, request); case IOCTL_ES_ADDCONTENTDATA: - return AddContentData(*context, request); + return ImportContentData(*context, request); case IOCTL_ES_ADDCONTENTFINISH: - return AddContentFinish(*context, request); + return ImportContentEnd(*context, request); case IOCTL_ES_ADDTITLEFINISH: - return AddTitleFinish(*context, request); + return ImportTitleDone(*context, request); case IOCTL_ES_ADDTITLECANCEL: - return AddTitleCancel(*context, request); + return ImportTitleCancel(*context, request); case IOCTL_ES_GETDEVICEID: return GetConsoleID(request); case IOCTL_ES_OPENTITLECONTENT: diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 7006b3badd..dc71fd2f80 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -55,7 +55,65 @@ public: ReturnCode Close(u32 fd) override; IPCCommandResult IOCtlV(const IOCtlVRequest& request) override; + struct OpenedContent + { + u64 m_title_id; + IOS::ES::Content m_content; + u32 m_position; + }; + + struct TitleImportContext + { + IOS::ES::TMDReader tmd; + u32 content_id = 0xFFFFFFFF; + std::vector content_buffer; + }; + + // TODO: merge this with TitleImportContext. Also reuse the global content table. + struct TitleExportContext + { + struct ExportContent + { + OpenedContent content; + std::array iv{}; + }; + + bool valid = false; + IOS::ES::TMDReader tmd; + std::vector title_key; + std::map contents; + }; + + struct Context + { + void DoState(PointerWrap& p); + + u16 gid = 0; + u32 uid = 0; + TitleImportContext title_import; + TitleExportContext title_export; + bool active = false; + // We use this to associate an IPC fd with an ES context. + u32 ipc_fd = -1; + }; + + // Title management + ReturnCode ImportTicket(const std::vector& ticket_bytes); + ReturnCode ImportTmd(Context& context, const std::vector& tmd_bytes); + ReturnCode ImportTitleInit(Context& context, const std::vector& tmd_bytes); + ReturnCode ImportContentBegin(Context& context, u64 title_id, u32 content_id); + ReturnCode ImportContentData(Context& context, u32 content_fd, const u8* data, u32 data_size); + ReturnCode ImportContentEnd(Context& context, u32 content_fd); + ReturnCode ImportTitleDone(Context& context); + ReturnCode ImportTitleCancel(Context& context); + ReturnCode ExportTitleInit(Context& context, u64 title_id, u8* tmd, u32 tmd_size); + ReturnCode ExportContentBegin(Context& context, u64 title_id, u32 content_id); + ReturnCode ExportContentData(Context& context, u32 content_fd, u8* data, u32 data_size); + ReturnCode ExportContentEnd(Context& context, u32 content_fd); + ReturnCode ExportTitleDone(Context& context); + ReturnCode DeleteTitle(u64 title_id); ReturnCode DeleteTitleContent(u64 title_id) const; + ReturnCode DeleteTicket(const u8* ticket_view); private: enum @@ -131,67 +189,26 @@ private: IOCTL_ES_CHECKKOREAREGION = 0x45, }; - struct OpenedContent - { - u64 m_title_id; - IOS::ES::Content m_content; - u32 m_position; - }; - - struct TitleImportContext - { - IOS::ES::TMDReader tmd; - u32 content_id = 0xFFFFFFFF; - std::vector content_buffer; - }; - - // TODO: merge this with TitleImportContext. Also reuse the global content table. - struct TitleExportContext - { - struct ExportContent - { - OpenedContent content; - std::array iv{}; - }; - - bool valid = false; - IOS::ES::TMDReader tmd; - std::vector title_key; - std::map contents; - }; - - struct Context - { - void DoState(PointerWrap& p); - - u16 gid = 0; - u32 uid = 0; - TitleImportContext title_import; - TitleExportContext title_export; - bool active = false; - // We use this to associate an IPC fd with an ES context. - u32 ipc_fd = -1; - }; // ES can only have 3 contexts at one time. using ContextArray = std::array; // Title management - IPCCommandResult AddTicket(const IOCtlVRequest& request); - IPCCommandResult AddTMD(Context& context, const IOCtlVRequest& request); - IPCCommandResult AddTitleStart(Context& context, const IOCtlVRequest& request); - IPCCommandResult AddContentStart(Context& context, const IOCtlVRequest& request); - IPCCommandResult AddContentData(Context& context, const IOCtlVRequest& request); - IPCCommandResult AddContentFinish(Context& context, const IOCtlVRequest& request); - IPCCommandResult AddTitleFinish(Context& context, const IOCtlVRequest& request); - IPCCommandResult AddTitleCancel(Context& context, const IOCtlVRequest& request); + IPCCommandResult ImportTicket(const IOCtlVRequest& request); + IPCCommandResult ImportTmd(Context& context, const IOCtlVRequest& request); + IPCCommandResult ImportTitleInit(Context& context, const IOCtlVRequest& request); + IPCCommandResult ImportContentBegin(Context& context, const IOCtlVRequest& request); + IPCCommandResult ImportContentData(Context& context, const IOCtlVRequest& request); + IPCCommandResult ImportContentEnd(Context& context, const IOCtlVRequest& request); + IPCCommandResult ImportTitleDone(Context& context, const IOCtlVRequest& request); + IPCCommandResult ImportTitleCancel(Context& context, const IOCtlVRequest& request); IPCCommandResult ExportTitleInit(Context& context, const IOCtlVRequest& request); IPCCommandResult ExportContentBegin(Context& context, const IOCtlVRequest& request); IPCCommandResult ExportContentData(Context& context, const IOCtlVRequest& request); IPCCommandResult ExportContentEnd(Context& context, const IOCtlVRequest& request); IPCCommandResult ExportTitleDone(Context& context, const IOCtlVRequest& request); IPCCommandResult DeleteTitle(const IOCtlVRequest& request); - IPCCommandResult DeleteTicket(const IOCtlVRequest& request); IPCCommandResult DeleteTitleContent(const IOCtlVRequest& request); + IPCCommandResult DeleteTicket(const IOCtlVRequest& request); // Device identity and encryption IPCCommandResult GetConsoleID(const IOCtlVRequest& request); diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index 254276994d..1a62720443 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -30,17 +30,11 @@ namespace HLE { namespace Device { -IPCCommandResult ES::AddTicket(const IOCtlVRequest& request) +ReturnCode ES::ImportTicket(const std::vector& ticket_bytes) { - if (!request.HasNumberOfValidVectors(3, 0)) - return GetDefaultReply(ES_EINVAL); - - std::vector bytes(request.in_vectors[0].size); - Memory::CopyFromEmu(bytes.data(), request.in_vectors[0].address, request.in_vectors[0].size); - - IOS::ES::TicketReader ticket{std::move(bytes)}; + IOS::ES::TicketReader ticket{ticket_bytes}; if (!ticket.IsValid()) - return GetDefaultReply(ES_EINVAL); + return ES_EINVAL; const u32 ticket_device_id = ticket.GetDeviceId(); const u32 device_id = EcWii::GetInstance().GetNGID(); @@ -49,59 +43,67 @@ IPCCommandResult ES::AddTicket(const IOCtlVRequest& request) if (device_id != ticket_device_id) { WARN_LOG(IOS_ES, "Device ID mismatch: ticket %08x, device %08x", ticket_device_id, device_id); - return GetDefaultReply(ES_DEVICE_ID_MISMATCH); + return ES_DEVICE_ID_MISMATCH; } - const s32 ret = ticket.Unpersonalise(); + const ReturnCode ret = static_cast(ticket.Unpersonalise()); if (ret < 0) { - ERROR_LOG(IOS_ES, "AddTicket: Failed to unpersonalise ticket for %016" PRIx64 " (ret = %d)", + ERROR_LOG(IOS_ES, "ImportTicket: Failed to unpersonalise ticket for %016" PRIx64 " (%d)", ticket.GetTitleId(), ret); - return GetDefaultReply(ret); + return ret; } } if (!DiscIO::AddTicket(ticket)) - return GetDefaultReply(ES_EIO); + return ES_EIO; - INFO_LOG(IOS_ES, "AddTicket: Imported ticket for title %016" PRIx64, ticket.GetTitleId()); - return GetDefaultReply(IPC_SUCCESS); + INFO_LOG(IOS_ES, "ImportTicket: Imported ticket for title %016" PRIx64, ticket.GetTitleId()); + return IPC_SUCCESS; } -IPCCommandResult ES::AddTMD(Context& context, const IOCtlVRequest& request) +IPCCommandResult ES::ImportTicket(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(3, 0)) + return GetDefaultReply(ES_EINVAL); + + std::vector bytes(request.in_vectors[0].size); + Memory::CopyFromEmu(bytes.data(), request.in_vectors[0].address, request.in_vectors[0].size); + return GetDefaultReply(ImportTicket(bytes)); +} + +ReturnCode ES::ImportTmd(Context& context, const std::vector& tmd_bytes) +{ + // Ioctlv 0x2b writes the TMD to /tmp/title.tmd (for imports) and doesn't seem to write it + // to either /import or /title. So here we simply have to set the import TMD. + context.title_import.tmd.SetBytes(tmd_bytes); + // TODO: validate TMDs and return the proper error code (-1027) if the signature type is invalid. + if (!context.title_import.tmd.IsValid()) + return ES_EINVAL; + + if (!IOS::ES::InitImport(context.title_import.tmd.GetTitleId())) + return ES_EIO; + + return IPC_SUCCESS; +} + +IPCCommandResult ES::ImportTmd(Context& context, const IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(1, 0)) return GetDefaultReply(ES_EINVAL); std::vector tmd(request.in_vectors[0].size); Memory::CopyFromEmu(tmd.data(), request.in_vectors[0].address, request.in_vectors[0].size); - - // Ioctlv 0x2b writes the TMD to /tmp/title.tmd (for imports) and doesn't seem to write it - // to either /import or /title. So here we simply have to set the import TMD. - context.title_import.tmd.SetBytes(std::move(tmd)); - // TODO: validate TMDs and return the proper error code (-1027) if the signature type is invalid. - if (!context.title_import.tmd.IsValid()) - return GetDefaultReply(ES_EINVAL); - - if (!IOS::ES::InitImport(context.title_import.tmd.GetTitleId())) - return GetDefaultReply(FS_EIO); - - return GetDefaultReply(IPC_SUCCESS); + return GetDefaultReply(ImportTmd(context, tmd)); } -IPCCommandResult ES::AddTitleStart(Context& context, const IOCtlVRequest& request) +ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_bytes) { - if (!request.HasNumberOfValidVectors(4, 0)) - return GetDefaultReply(ES_EINVAL); - - INFO_LOG(IOS_ES, "IOCTL_ES_ADDTITLESTART"); - std::vector tmd(request.in_vectors[0].size); - Memory::CopyFromEmu(tmd.data(), request.in_vectors[0].address, request.in_vectors[0].size); - - context.title_import.tmd.SetBytes(tmd); + INFO_LOG(IOS_ES, "ImportTitleInit"); + context.title_import.tmd.SetBytes(tmd_bytes); if (!context.title_import.tmd.IsValid()) { - ERROR_LOG(IOS_ES, "Invalid TMD while adding title (size = %zd)", tmd.size()); - return GetDefaultReply(ES_EINVAL); + ERROR_LOG(IOS_ES, "Invalid TMD while adding title (size = %zd)", tmd_bytes.size()); + return ES_EINVAL; } // Finish a previous import (if it exists). @@ -111,43 +113,47 @@ IPCCommandResult ES::AddTitleStart(Context& context, const IOCtlVRequest& reques FinishImport(previous_tmd); if (!IOS::ES::InitImport(context.title_import.tmd.GetTitleId())) - return GetDefaultReply(FS_EIO); + return ES_EIO; // TODO: check and use the other vectors. - return GetDefaultReply(IPC_SUCCESS); + return IPC_SUCCESS; } -IPCCommandResult ES::AddContentStart(Context& context, const IOCtlVRequest& request) +IPCCommandResult ES::ImportTitleInit(Context& context, const IOCtlVRequest& request) { - if (!request.HasNumberOfValidVectors(2, 0)) + if (!request.HasNumberOfValidVectors(4, 0)) return GetDefaultReply(ES_EINVAL); - u64 title_id = Memory::Read_U64(request.in_vectors[0].address); - u32 content_id = Memory::Read_U32(request.in_vectors[1].address); + std::vector tmd(request.in_vectors[0].size); + Memory::CopyFromEmu(tmd.data(), request.in_vectors[0].address, request.in_vectors[0].size); + return GetDefaultReply(ImportTitleInit(context, tmd)); +} +ReturnCode ES::ImportContentBegin(Context& context, u64 title_id, u32 content_id) +{ if (context.title_import.content_id != 0xFFFFFFFF) { ERROR_LOG(IOS_ES, "Trying to add content when we haven't finished adding " "another content. Unsupported."); - return GetDefaultReply(ES_EINVAL); + return ES_EINVAL; } context.title_import.content_id = content_id; context.title_import.content_buffer.clear(); - INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTSTART: title id %016" PRIx64 ", " - "content id %08x", - title_id, context.title_import.content_id); + INFO_LOG(IOS_ES, "ImportContentBegin: title %016" PRIx64 ", content ID %08x", title_id, + context.title_import.content_id); if (!context.title_import.tmd.IsValid()) - return GetDefaultReply(ES_EINVAL); + return ES_EINVAL; if (title_id != context.title_import.tmd.GetTitleId()) { - ERROR_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTSTART: title id %016" PRIx64 " != " + ERROR_LOG(IOS_ES, "ImportContentBegin: title id %016" PRIx64 " != " "TMD title id %016" PRIx64 ", ignoring", title_id, context.title_import.tmd.GetTitleId()); + return ES_EINVAL; } // We're supposed to return a "content file descriptor" here, which is @@ -155,24 +161,36 @@ IPCCommandResult ES::AddContentStart(Context& context, const IOCtlVRequest& requ // no known content installer which performs content addition concurrently. // Instead we just log an error (see above) if this condition is detected. s32 content_fd = 0; - return GetDefaultReply(content_fd); + return static_cast(content_fd); } -IPCCommandResult ES::AddContentData(Context& context, const IOCtlVRequest& request) +IPCCommandResult ES::ImportContentBegin(Context& context, const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(2, 0)) + return GetDefaultReply(ES_EINVAL); + + u64 title_id = Memory::Read_U64(request.in_vectors[0].address); + u32 content_id = Memory::Read_U32(request.in_vectors[1].address); + return GetDefaultReply(ImportContentBegin(context, title_id, content_id)); +} + +ReturnCode ES::ImportContentData(Context& context, u32 content_fd, const u8* data, u32 data_size) +{ + INFO_LOG(IOS_ES, "ImportContentData: content fd %08x, size %d", content_fd, data_size); + context.title_import.content_buffer.insert(context.title_import.content_buffer.end(), data, + data + data_size); + return IPC_SUCCESS; +} + +IPCCommandResult ES::ImportContentData(Context& context, const IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(2, 0)) return GetDefaultReply(ES_EINVAL); u32 content_fd = Memory::Read_U32(request.in_vectors[0].address); - INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTDATA: content fd %08x, " - "size %d", - content_fd, request.in_vectors[1].size); - u8* data_start = Memory::GetPointer(request.in_vectors[1].address); - u8* data_end = data_start + request.in_vectors[1].size; - context.title_import.content_buffer.insert(context.title_import.content_buffer.end(), data_start, - data_end); - return GetDefaultReply(IPC_SUCCESS); + return GetDefaultReply( + ImportContentData(context, content_fd, data_start, request.in_vectors[1].size)); } static bool CheckIfContentHashMatches(const std::vector& content, const IOS::ES::Content& info) @@ -187,34 +205,27 @@ static std::string GetImportContentPath(u64 title_id, u32 content_id) return Common::GetImportTitlePath(title_id) + StringFromFormat("/content/%08x.app", content_id); } -IPCCommandResult ES::AddContentFinish(Context& context, const IOCtlVRequest& request) +ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd) { - if (!request.HasNumberOfValidVectors(1, 0)) - return GetDefaultReply(ES_EINVAL); + INFO_LOG(IOS_ES, "ImportContentEnd: content fd %08x", content_fd); if (context.title_import.content_id == 0xFFFFFFFF) - return GetDefaultReply(ES_EINVAL); - - u32 content_fd = Memory::Read_U32(request.in_vectors[0].address); - INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTFINISH: content fd %08x", content_fd); + return ES_EINVAL; if (!context.title_import.tmd.IsValid()) - return GetDefaultReply(ES_EINVAL); + return ES_EINVAL; // Try to find the title key from a pre-installed ticket. IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(context.title_import.tmd.GetTitleId()); if (!ticket.IsValid()) - { - return GetDefaultReply(ES_NO_TICKET); - } + return ES_NO_TICKET; // The IV for title content decryption is the lower two bytes of the // content index, zero extended. IOS::ES::Content content_info; if (!context.title_import.tmd.FindContentById(context.title_import.content_id, &content_info)) - { - return GetDefaultReply(ES_EINVAL); - } + return ES_EINVAL; + u8 iv[16] = {0}; iv[0] = (content_info.index >> 8) & 0xFF; iv[1] = content_info.index & 0xFF; @@ -223,8 +234,8 @@ IPCCommandResult ES::AddContentFinish(Context& context, const IOCtlVRequest& req context.title_import.content_buffer.size()); if (!CheckIfContentHashMatches(decrypted_data, content_info)) { - ERROR_LOG(IOS_ES, "AddContentFinish: Hash for content %08x doesn't match", content_info.id); - return GetDefaultReply(ES_HASH_MISMATCH); + ERROR_LOG(IOS_ES, "ImportContentEnd: Hash for content %08x doesn't match", content_info.id); + return ES_HASH_MISMATCH; } std::string content_path; @@ -248,56 +259,82 @@ IPCCommandResult ES::AddContentFinish(Context& context, const IOCtlVRequest& req File::IOFile file(temp_path, "wb"); if (!file.WriteBytes(decrypted_data.data(), content_info.size)) { - ERROR_LOG(IOS_ES, "AddContentFinish: Failed to write to %s", temp_path.c_str()); - return GetDefaultReply(ES_EIO); + ERROR_LOG(IOS_ES, "ImportContentEnd: Failed to write to %s", temp_path.c_str()); + return ES_EIO; } } if (!File::Rename(temp_path, content_path)) { - ERROR_LOG(IOS_ES, "AddContentFinish: Failed to move content to %s", content_path.c_str()); - return GetDefaultReply(ES_EIO); + ERROR_LOG(IOS_ES, "ImportContentEnd: Failed to move content to %s", content_path.c_str()); + return ES_EIO; } context.title_import.content_id = 0xFFFFFFFF; - return GetDefaultReply(IPC_SUCCESS); + return IPC_SUCCESS; } -IPCCommandResult ES::AddTitleFinish(Context& context, const IOCtlVRequest& request) +IPCCommandResult ES::ImportContentEnd(Context& context, const IOCtlVRequest& request) { - if (!request.HasNumberOfValidVectors(0, 0) || !context.title_import.tmd.IsValid()) + if (!request.HasNumberOfValidVectors(1, 0)) return GetDefaultReply(ES_EINVAL); + u32 content_fd = Memory::Read_U32(request.in_vectors[0].address); + return GetDefaultReply(ImportContentEnd(context, content_fd)); +} + +ReturnCode ES::ImportTitleDone(Context& context) +{ + if (!context.title_import.tmd.IsValid()) + return ES_EINVAL; + if (!WriteImportTMD(context.title_import.tmd)) - return GetDefaultReply(ES_EIO); + return ES_EIO; if (!FinishImport(context.title_import.tmd)) - return GetDefaultReply(FS_EIO); + return ES_EIO; - INFO_LOG(IOS_ES, "IOCTL_ES_ADDTITLEFINISH"); + INFO_LOG(IOS_ES, "ImportTitleDone: title %016" PRIx64, context.title_import.tmd.GetTitleId()); context.title_import.tmd.SetBytes({}); - return GetDefaultReply(IPC_SUCCESS); + return IPC_SUCCESS; } -IPCCommandResult ES::AddTitleCancel(Context& context, const IOCtlVRequest& request) +IPCCommandResult ES::ImportTitleDone(Context& context, const IOCtlVRequest& request) { - if (!request.HasNumberOfValidVectors(0, 0) || !context.title_import.tmd.IsValid()) + if (!request.HasNumberOfValidVectors(0, 0)) return GetDefaultReply(ES_EINVAL); + return GetDefaultReply(ImportTitleDone(context)); +} + +ReturnCode ES::ImportTitleCancel(Context& context) +{ + if (!context.title_import.tmd.IsValid()) + return ES_EINVAL; + const IOS::ES::TMDReader original_tmd = IOS::ES::FindInstalledTMD(context.title_import.tmd.GetTitleId()); if (!original_tmd.IsValid()) { // This should never happen unless someone messed with the installed TMD directly. // Still, let's check for this case and return an error instead of potentially crashing. - return GetDefaultReply(FS_ENOENT); + return FS_ENOENT; } if (!FinishImport(original_tmd)) - return GetDefaultReply(FS_EIO); + return ES_EIO; + INFO_LOG(IOS_ES, "ImportTitleCancel: title %016" PRIx64, context.title_import.tmd.GetTitleId()); context.title_import.tmd.SetBytes({}); - return GetDefaultReply(IPC_SUCCESS); + return IPC_SUCCESS; +} + +IPCCommandResult ES::ImportTitleCancel(Context& context, const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(0, 0) || !context.title_import.tmd.IsValid()) + return GetDefaultReply(ES_EINVAL); + + return GetDefaultReply(ImportTitleCancel(context)); } static bool CanDeleteTitle(u64 title_id) @@ -306,51 +343,47 @@ static bool CanDeleteTitle(u64 title_id) return static_cast(title_id >> 32) != 0x00000001 || static_cast(title_id) > 0x101; } +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; + } + // XXX: ugly, but until we drop CNANDContentManager everywhere, this is going to be needed. + DiscIO::CNANDContentManager::Access().ClearCache(); + + return IPC_SUCCESS; +} + IPCCommandResult ES::DeleteTitle(const IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != 8) return GetDefaultReply(ES_EINVAL); const u64 title_id = Memory::Read_U64(request.in_vectors[0].address); - - if (!CanDeleteTitle(title_id)) - return GetDefaultReply(ES_EINVAL); - - const std::string title_dir = Common::GetTitlePath(title_id, Common::FROM_SESSION_ROOT); - if (!File::IsDirectory(title_dir)) - return GetDefaultReply(FS_ENOENT); - - if (!File::DeleteDirRecursively(title_dir)) - { - ERROR_LOG(IOS_ES, "DeleteTitle: Failed to delete title directory: %s", title_dir.c_str()); - return GetDefaultReply(FS_EACCESS); - } - // XXX: ugly, but until we drop CNANDContentManager everywhere, this is going to be needed. - DiscIO::CNANDContentManager::Access().ClearCache(); - - return GetDefaultReply(IPC_SUCCESS); + return GetDefaultReply(DeleteTitle(title_id)); } -IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request) +ReturnCode ES::DeleteTicket(const u8* ticket_view) { - if (!request.HasNumberOfValidVectors(1, 0) || - request.in_vectors[0].size != sizeof(IOS::ES::TicketView)) - { - return GetDefaultReply(ES_EINVAL); - } - - const u64 title_id = - Memory::Read_U64(request.in_vectors[0].address + offsetof(IOS::ES::TicketView, title_id)); + const u64 title_id = Common::swap64(ticket_view + offsetof(IOS::ES::TicketView, title_id)); if (!CanDeleteTitle(title_id)) - return GetDefaultReply(ES_EINVAL); + return ES_EINVAL; auto ticket = DiscIO::FindSignedTicket(title_id); if (!ticket.IsValid()) - return GetDefaultReply(FS_ENOENT); + return FS_ENOENT; - const u64 ticket_id = - Memory::Read_U64(request.in_vectors[0].address + offsetof(IOS::ES::TicketView, ticket_id)); + const u64 ticket_id = Common::swap64(ticket_view + offsetof(IOS::ES::TicketView, ticket_id)); ticket.DeleteTicket(ticket_id); const std::vector& new_ticket = ticket.GetRawTicket(); @@ -358,7 +391,7 @@ IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request) { File::IOFile ticket_file(ticket_path, "wb"); if (!ticket_file || !ticket_file.WriteBytes(new_ticket.data(), new_ticket.size())) - return GetDefaultReply(ES_EIO); + return ES_EIO; } // Delete the ticket file if it is now empty. @@ -373,7 +406,17 @@ IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request) if (ticket_parent_dir_entries.children.empty()) File::DeleteDir(ticket_parent_dir); - return GetDefaultReply(IPC_SUCCESS); + return IPC_SUCCESS; +} + +IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 0) || + request.in_vectors[0].size != sizeof(IOS::ES::TicketView)) + { + return GetDefaultReply(ES_EINVAL); + } + return GetDefaultReply(DeleteTicket(Memory::GetPointer(request.in_vectors[0].address))); } ReturnCode ES::DeleteTitleContent(u64 title_id) const @@ -401,37 +444,82 @@ IPCCommandResult ES::DeleteTitleContent(const IOCtlVRequest& request) return GetDefaultReply(DeleteTitleContent(Memory::Read_U64(request.in_vectors[0].address))); } -IPCCommandResult ES::ExportTitleInit(Context& context, const IOCtlVRequest& request) +ReturnCode ES::ExportTitleInit(Context& context, u64 title_id, u8* tmd_bytes, u32 tmd_size) { - if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != 8) - return GetDefaultReply(ES_EINVAL); - // No concurrent title import/export is allowed. if (context.title_export.valid) - return GetDefaultReply(ES_EINVAL); + return ES_EINVAL; - const auto tmd = IOS::ES::FindInstalledTMD(Memory::Read_U64(request.in_vectors[0].address)); + const auto tmd = IOS::ES::FindInstalledTMD(title_id); if (!tmd.IsValid()) - return GetDefaultReply(FS_ENOENT); + return FS_ENOENT; context.title_export.tmd = tmd; const auto ticket = DiscIO::FindSignedTicket(context.title_export.tmd.GetTitleId()); if (!ticket.IsValid()) - return GetDefaultReply(ES_NO_TICKET); + return ES_NO_TICKET; if (ticket.GetTitleId() != context.title_export.tmd.GetTitleId()) - return GetDefaultReply(ES_EINVAL); + return ES_EINVAL; context.title_export.title_key = ticket.GetTitleKey(); const auto& raw_tmd = context.title_export.tmd.GetRawTMD(); - if (request.io_vectors[0].size != raw_tmd.size()) - return GetDefaultReply(ES_EINVAL); + if (tmd_size != raw_tmd.size()) + return ES_EINVAL; - Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size()); + std::copy_n(raw_tmd.cbegin(), raw_tmd.size(), tmd_bytes); context.title_export.valid = true; - return GetDefaultReply(IPC_SUCCESS); + return IPC_SUCCESS; +} + +IPCCommandResult ES::ExportTitleInit(Context& context, const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != 8) + return GetDefaultReply(ES_EINVAL); + + const u64 title_id = Memory::Read_U64(request.in_vectors[0].address); + u8* tmd_bytes = Memory::GetPointer(request.io_vectors[0].address); + const u32 tmd_size = request.io_vectors[0].size; + + return GetDefaultReply(ExportTitleInit(context, title_id, tmd_bytes, tmd_size)); +} + +ReturnCode ES::ExportContentBegin(Context& context, u64 title_id, u32 content_id) +{ + if (!context.title_export.valid || context.title_export.tmd.GetTitleId() != title_id) + { + ERROR_LOG(IOS_ES, "Tried to use ExportContentBegin with an invalid title export context."); + return ES_EINVAL; + } + + const auto& content_loader = AccessContentDevice(title_id); + if (!content_loader.IsValid()) + return FS_ENOENT; + + const auto* content = content_loader.GetContentByID(content_id); + if (!content) + return ES_EINVAL; + + OpenedContent entry; + entry.m_position = 0; + entry.m_content = content->m_metadata; + entry.m_title_id = title_id; + content->m_Data->Open(); + + u32 cfd = 0; + while (context.title_export.contents.find(cfd) != context.title_export.contents.end()) + cfd++; + + TitleExportContext::ExportContent content_export; + content_export.content = std::move(entry); + content_export.iv[0] = (content->m_metadata.index >> 8) & 0xFF; + content_export.iv[1] = content->m_metadata.index & 0xFF; + + context.title_export.contents.emplace(cfd, content_export); + // IOS returns a content ID which is passed to further content calls. + return static_cast(cfd); } IPCCommandResult ES::ExportContentBegin(Context& context, const IOCtlVRequest& request) @@ -443,38 +531,44 @@ IPCCommandResult ES::ExportContentBegin(Context& context, const IOCtlVRequest& r const u64 title_id = Memory::Read_U64(request.in_vectors[0].address); const u32 content_id = Memory::Read_U32(request.in_vectors[1].address); - if (!context.title_export.valid || context.title_export.tmd.GetTitleId() != title_id) + return GetDefaultReply(ExportContentBegin(context, title_id, content_id)); +} + +ReturnCode ES::ExportContentData(Context& context, u32 content_fd, u8* data, u32 data_size) +{ + const auto iterator = context.title_export.contents.find(content_fd); + if (!context.title_export.valid || iterator == context.title_export.contents.end() || + iterator->second.content.m_position >= iterator->second.content.m_content.size) { - ERROR_LOG(IOS_ES, "Tried to use ExportContentBegin with an invalid title export context."); - return GetDefaultReply(ES_EINVAL); + return ES_EINVAL; } - const auto& content_loader = AccessContentDevice(title_id); - if (!content_loader.IsValid()) - return GetDefaultReply(FS_ENOENT); + auto& metadata = iterator->second.content; - const auto* content = content_loader.GetContentByID(content_id); - if (!content) - return GetDefaultReply(ES_EINVAL); - - OpenedContent entry; - entry.m_position = 0; - entry.m_content = content->m_metadata; - entry.m_title_id = title_id; + const auto& content_loader = AccessContentDevice(metadata.m_title_id); + const auto* content = content_loader.GetContentByID(metadata.m_content.id); content->m_Data->Open(); - u32 cid = 0; - while (context.title_export.contents.find(cid) != context.title_export.contents.end()) - cid++; + const u32 length = + std::min(static_cast(metadata.m_content.size - metadata.m_position), data_size); + std::vector buffer(length); - TitleExportContext::ExportContent content_export; - content_export.content = std::move(entry); - content_export.iv[0] = (content->m_metadata.index >> 8) & 0xFF; - content_export.iv[1] = content->m_metadata.index & 0xFF; + if (!content->m_Data->GetRange(metadata.m_position, length, buffer.data())) + { + ERROR_LOG(IOS_ES, "ExportContentData: ES_SHORT_READ"); + return ES_SHORT_READ; + } - context.title_export.contents.emplace(cid, content_export); - // IOS returns a content ID which is passed to further content calls. - return GetDefaultReply(cid); + // IOS aligns the buffer to 32 bytes. Since we also need to align it to 16 bytes, + // let's just follow IOS here. + buffer.resize(Common::AlignUp(buffer.size(), 32)); + + const std::vector output = + Common::AES::Encrypt(context.title_export.title_key.data(), iterator->second.iv.data(), + buffer.data(), buffer.size()); + std::copy_n(output.cbegin(), output.size(), data); + metadata.m_position += length; + return IPC_SUCCESS; } IPCCommandResult ES::ExportContentData(Context& context, const IOCtlVRequest& request) @@ -485,57 +579,20 @@ IPCCommandResult ES::ExportContentData(Context& context, const IOCtlVRequest& re return GetDefaultReply(ES_EINVAL); } - const u32 content_id = Memory::Read_U32(request.in_vectors[0].address); + const u32 content_fd = Memory::Read_U32(request.in_vectors[0].address); + u8* data = Memory::GetPointer(request.io_vectors[0].address); const u32 bytes_to_read = request.io_vectors[0].size; - const auto iterator = context.title_export.contents.find(content_id); - if (!context.title_export.valid || iterator == context.title_export.contents.end() || - iterator->second.content.m_position >= iterator->second.content.m_content.size) - { - return GetDefaultReply(ES_EINVAL); - } - - auto& metadata = iterator->second.content; - - const auto& content_loader = AccessContentDevice(metadata.m_title_id); - const auto* content = content_loader.GetContentByID(metadata.m_content.id); - content->m_Data->Open(); - - const u32 length = - std::min(static_cast(metadata.m_content.size - metadata.m_position), bytes_to_read); - std::vector buffer(length); - - if (!content->m_Data->GetRange(metadata.m_position, length, buffer.data())) - { - ERROR_LOG(IOS_ES, "ExportContentData: ES_SHORT_READ"); - return GetDefaultReply(ES_SHORT_READ); - } - - // IOS aligns the buffer to 32 bytes. Since we also need to align it to 16 bytes, - // let's just follow IOS here. - buffer.resize(Common::AlignUp(buffer.size(), 32)); - - const std::vector output = - Common::AES::Encrypt(context.title_export.title_key.data(), iterator->second.iv.data(), - buffer.data(), buffer.size()); - - Memory::CopyToEmu(request.io_vectors[0].address, output.data(), output.size()); - metadata.m_position += length; - return GetDefaultReply(IPC_SUCCESS); + return GetDefaultReply(ExportContentData(context, content_fd, data, bytes_to_read)); } -IPCCommandResult ES::ExportContentEnd(Context& context, const IOCtlVRequest& request) +ReturnCode ES::ExportContentEnd(Context& context, u32 content_fd) { - if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != 4) - return GetDefaultReply(ES_EINVAL); - - const u32 content_id = Memory::Read_U32(request.in_vectors[0].address); - - const auto iterator = context.title_export.contents.find(content_id); + const auto iterator = context.title_export.contents.find(content_fd); if (!context.title_export.valid || iterator == context.title_export.contents.end() || iterator->second.content.m_position != iterator->second.content.m_content.size) { - return GetDefaultReply(ES_EINVAL); + return ES_EINVAL; } // XXX: Check the content hash, as IOS does? @@ -544,16 +601,30 @@ IPCCommandResult ES::ExportContentEnd(Context& context, const IOCtlVRequest& req content_loader.GetContentByID(iterator->second.content.m_content.id)->m_Data->Close(); context.title_export.contents.erase(iterator); - return GetDefaultReply(IPC_SUCCESS); + return IPC_SUCCESS; +} + +IPCCommandResult ES::ExportContentEnd(Context& context, const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != 4) + return GetDefaultReply(ES_EINVAL); + + const u32 content_fd = Memory::Read_U32(request.in_vectors[0].address); + return GetDefaultReply(ExportContentEnd(context, content_fd)); +} + +ReturnCode ES::ExportTitleDone(Context& context) +{ + if (!context.title_export.valid) + return ES_EINVAL; + + context.title_export.valid = false; + return IPC_SUCCESS; } IPCCommandResult ES::ExportTitleDone(Context& context, const IOCtlVRequest& request) { - if (!context.title_export.valid) - return GetDefaultReply(ES_EINVAL); - - context.title_export.valid = false; - return GetDefaultReply(IPC_SUCCESS); + return GetDefaultReply(ExportTitleDone(context)); } } // namespace Device } // namespace HLE