From f8ffcb24834c98f9a3a652bfa136daad474a3060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sat, 13 May 2017 13:45:34 +0200 Subject: [PATCH 1/7] IOS/ES: Remove unused struct --- Source/Core/Core/IOS/ES/ES.h | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index b6d64c655f..7006b3badd 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -138,19 +138,6 @@ private: u32 m_position; }; - struct ecc_cert_t - { - u32 sig_type; - u8 sig[0x3c]; - u8 pad[0x40]; - u8 issuer[0x40]; - u32 key_type; - u8 key_name[0x40]; - u32 ng_key_id; - u8 ecc_pubkey[0x3c]; - u8 padding[0x3c]; - }; - struct TitleImportContext { IOS::ES::TMDReader tmd; From 5587342ca163ceaaf4720ed56e9c4f54c5486716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sat, 13 May 2017 21:39:53 +0200 Subject: [PATCH 2/7] IOS/ES: Expose title management ioctlvs This exposes all ES title management ioctlvs to avoid duplicating IOS code everywhere and to make it easier to reuse (since this way it's not unnecessarily tied to the PPC IPC mechanism anymore) and unit test. Some functions were also renamed for consistency with the other names, *and* with official names. --- Source/Core/Core/IOS/ES/ES.cpp | 16 +- Source/Core/Core/IOS/ES/ES.h | 117 +++-- Source/Core/Core/IOS/ES/TitleManagement.cpp | 495 +++++++++++--------- 3 files changed, 358 insertions(+), 270 deletions(-) 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 From 2058f3759661ebd062cabf3a0169232db81c85ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 14 May 2017 10:58:58 +0200 Subject: [PATCH 3/7] IOS/ES: Implement ImportTitleCancel properly Mistakenly used the wrong TMD to clean up the import. The original TMD is the one that is supposed to be used when cancelling an import, but I forgot it's in the /import directory after starting an import. --- Source/Core/Core/IOS/ES/TitleManagement.cpp | 26 +++++++++------------ 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index 1a62720443..4a199ee306 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -96,6 +96,15 @@ IPCCommandResult ES::ImportTmd(Context& context, const IOCtlVRequest& request) return GetDefaultReply(ImportTmd(context, tmd)); } +static void CleanUpStaleImport(const u64 title_id) +{ + const auto import_tmd = IOS::ES::FindImportTMD(title_id); + if (!import_tmd.IsValid()) + File::DeleteDirRecursively(Common::GetImportTitlePath(title_id) + "/content"); + else + IOS::ES::FinishImport(import_tmd); +} + ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_bytes) { INFO_LOG(IOS_ES, "ImportTitleInit"); @@ -107,10 +116,7 @@ ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_byte } // Finish a previous import (if it exists). - const IOS::ES::TMDReader previous_tmd = - IOS::ES::FindImportTMD(context.title_import.tmd.GetTitleId()); - if (previous_tmd.IsValid()) - FinishImport(previous_tmd); + CleanUpStaleImport(context.title_import.tmd.GetTitleId()); if (!IOS::ES::InitImport(context.title_import.tmd.GetTitleId())) return ES_EIO; @@ -312,17 +318,7 @@ 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 FS_ENOENT; - } - - if (!FinishImport(original_tmd)) - return ES_EIO; + CleanUpStaleImport(context.title_import.tmd.GetTitleId()); INFO_LOG(IOS_ES, "ImportTitleCancel: title %016" PRIx64, context.title_import.tmd.GetTitleId()); context.title_import.tmd.SetBytes({}); From 6916a3d85b004c043874d8c671eedfa6a8382ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 14 May 2017 00:00:01 +0200 Subject: [PATCH 4/7] Hide non-channel WADs These cannot be booted, so it is bad UX to show them in the UI as if they were regular titles, and yet have different behaviour for them. And technically, there is no reason to allow them to be used to boot in the first place. Another reason they should not be shown is that Dolphin fails spectacularly with WADs that have a valid boot content index, but are not PPC titles (e.g. IOS WADs). The only reliable way to avoid this is to check for the title type and only show channels, just like the Wii System Menu. --- Source/Core/Core/ConfigManager.cpp | 9 +++------ Source/Core/DolphinQt2/GameList/GameFile.cpp | 11 +++++++++++ Source/Core/DolphinQt2/GameList/GameFile.h | 2 +- Source/Core/DolphinWX/ISOFile.cpp | 12 ++++++++++++ Source/Core/DolphinWX/ISOFile.h | 2 +- 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index b85550c57a..f051e3bded 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -992,13 +992,10 @@ bool SConfig::AutoSetup(EBootBS2 _BootBS2) DiscIO::CNANDContentManager::Access().GetNANDLoader(m_strFilename); const IOS::ES::TMDReader& tmd = content_loader.GetTMD(); - if (content_loader.GetContentByIndex(tmd.GetBootIndex()) == nullptr) + if (!IOS::ES::IsChannel(tmd.GetTitleId())) { - // WAD is valid yet cannot be booted. Install instead. - u64 installed = DiscIO::CNANDContentManager::Access().Install_WiiWAD(m_strFilename); - if (installed) - SuccessAlertT("The WAD has been installed successfully"); - return false; // do not boot + PanicAlertT("This WAD is not bootable."); + return false; } SetRegion(tmd.GetRegion(), &set_region_dir); diff --git a/Source/Core/DolphinQt2/GameList/GameFile.cpp b/Source/Core/DolphinQt2/GameList/GameFile.cpp index 2f23107776..b81d491aff 100644 --- a/Source/Core/DolphinQt2/GameList/GameFile.cpp +++ b/Source/Core/DolphinQt2/GameList/GameFile.cpp @@ -63,6 +63,17 @@ GameFile::GameFile(const QString& path) : m_path(path) m_valid = true; } +bool GameFile::IsValid() const +{ + if (!m_valid) + return false; + + if (m_platform == DiscIO::Platform::WII_WAD && !IOS::ES::IsChannel(m_title_id)) + return false; + + return true; +} + QString GameFile::GetCacheFileName() const { QString folder = QString::fromStdString(File::GetUserPath(D_CACHE_IDX)); diff --git a/Source/Core/DolphinQt2/GameList/GameFile.h b/Source/Core/DolphinQt2/GameList/GameFile.h index 47eef18381..edbdc00d38 100644 --- a/Source/Core/DolphinQt2/GameList/GameFile.h +++ b/Source/Core/DolphinQt2/GameList/GameFile.h @@ -27,7 +27,7 @@ class GameFile final public: explicit GameFile(const QString& path); - bool IsValid() const { return m_valid; } + bool IsValid() const; // These will be properly initialized before we try to load the file. QString GetFilePath() const { return m_path; } QString GetFileName() const { return m_file_name; } diff --git a/Source/Core/DolphinWX/ISOFile.cpp b/Source/Core/DolphinWX/ISOFile.cpp index 46e076d734..e45f8b3fbf 100644 --- a/Source/Core/DolphinWX/ISOFile.cpp +++ b/Source/Core/DolphinWX/ISOFile.cpp @@ -28,6 +28,7 @@ #include "Core/Boot/Boot.h" #include "Core/ConfigManager.h" +#include "Core/IOS/ES/Formats.h" #include "DiscIO/Blob.h" #include "DiscIO/Enums.h" @@ -177,6 +178,17 @@ GameListItem::~GameListItem() { } +bool GameListItem::IsValid() const +{ + if (!m_Valid) + return false; + + if (m_Platform == DiscIO::Platform::WII_WAD && !IOS::ES::IsChannel(m_title_id)) + return false; + + return true; +} + void GameListItem::ReloadINI() { if (!IsValid()) diff --git a/Source/Core/DolphinWX/ISOFile.h b/Source/Core/DolphinWX/ISOFile.h index 2361014e31..cf3ec8d066 100644 --- a/Source/Core/DolphinWX/ISOFile.h +++ b/Source/Core/DolphinWX/ISOFile.h @@ -35,7 +35,7 @@ public: // Reload settings after INI changes void ReloadINI(); - bool IsValid() const { return m_Valid; } + bool IsValid() const; const std::string& GetFileName() const { return m_FileName; } std::string GetName(DiscIO::Language language) const; std::string GetName() const; From afcda22da9002f9ce70799b1a80af414b451ecc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 14 May 2017 12:05:43 +0200 Subject: [PATCH 5/7] DiscIO: Add GetContent() for reading content from WADs Direct access to the WAD bytes is required to read contents with proper padding data (since they can sometimes end up being outside of the data app section). Allowing the whole buffer to be accessed directly would be error prone, so this commit adds GetContent() to WiiWAD for getting raw content data by index. --- Source/Core/DiscIO/WiiWad.cpp | 39 +++++++++++++++++++++++++---------- Source/Core/DiscIO/WiiWad.h | 8 ++++++- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Source/Core/DiscIO/WiiWad.cpp b/Source/Core/DiscIO/WiiWad.cpp index 2576dac41f..89b687eb95 100644 --- a/Source/Core/DiscIO/WiiWad.cpp +++ b/Source/Core/DiscIO/WiiWad.cpp @@ -44,25 +44,24 @@ bool IsWiiWAD(const CBlobBigEndianReader& reader) } } // Anonymous namespace -WiiWAD::WiiWAD(const std::string& name) +WiiWAD::WiiWAD(const std::string& name) : m_reader(CreateBlobReader(name)) { - std::unique_ptr reader(CreateBlobReader(name)); - if (reader == nullptr || File::IsDirectory(name)) + if (m_reader == nullptr || File::IsDirectory(name)) { m_valid = false; return; } - m_valid = ParseWAD(*reader); + m_valid = ParseWAD(); } WiiWAD::~WiiWAD() { } -bool WiiWAD::ParseWAD(IBlobReader& reader) +bool WiiWAD::ParseWAD() { - CBlobBigEndianReader big_endian_reader(reader); + CBlobBigEndianReader big_endian_reader(*m_reader); if (!IsWiiWAD(big_endian_reader)) return false; @@ -86,18 +85,36 @@ bool WiiWAD::ParseWAD(IBlobReader& reader) _dbg_assert_msg_(BOOT, reserved == 0x00, "WiiWAD: Reserved must be 0x00"); u32 offset = 0x40; - m_certificate_chain = CreateWADEntry(reader, certificate_chain_size, offset); + m_certificate_chain = CreateWADEntry(*m_reader, certificate_chain_size, offset); offset += Common::AlignUp(certificate_chain_size, 0x40); - m_ticket.SetBytes(CreateWADEntry(reader, ticket_size, offset)); + m_ticket.SetBytes(CreateWADEntry(*m_reader, ticket_size, offset)); offset += Common::AlignUp(ticket_size, 0x40); - m_tmd.SetBytes(CreateWADEntry(reader, tmd_size, offset)); + m_tmd.SetBytes(CreateWADEntry(*m_reader, tmd_size, offset)); offset += Common::AlignUp(tmd_size, 0x40); - m_data_app = CreateWADEntry(reader, data_app_size, offset); + m_data_app_offset = offset; + m_data_app = CreateWADEntry(*m_reader, data_app_size, offset); offset += Common::AlignUp(data_app_size, 0x40); - m_footer = CreateWADEntry(reader, footer_size, offset); + m_footer = CreateWADEntry(*m_reader, footer_size, offset); offset += Common::AlignUp(footer_size, 0x40); return true; } +std::vector WiiWAD::GetContent(u16 index) const +{ + u64 offset = m_data_app_offset; + for (const IOS::ES::Content& content : m_tmd.GetContents()) + { + const u64 aligned_size = Common::AlignUp(content.size, 0x40); + if (content.index == index) + { + std::vector data(aligned_size); + if (!m_reader->Read(offset, aligned_size, data.data())) + return {}; + return data; + } + offset += aligned_size; + } + return {}; +} } // namespace DiscIO diff --git a/Source/Core/DiscIO/WiiWad.h b/Source/Core/DiscIO/WiiWad.h index 46480fdaa6..fae5bfbf86 100644 --- a/Source/Core/DiscIO/WiiWad.h +++ b/Source/Core/DiscIO/WiiWad.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include @@ -27,11 +28,16 @@ public: const IOS::ES::TMDReader& GetTMD() const { return m_tmd; } const std::vector& GetDataApp() const { return m_data_app; } const std::vector& GetFooter() const { return m_footer; } + std::vector GetContent(u16 index) const; + private: - bool ParseWAD(IBlobReader& reader); + bool ParseWAD(); bool m_valid; + std::unique_ptr m_reader; + + u64 m_data_app_offset = 0; std::vector m_certificate_chain; IOS::ES::TicketReader m_ticket; IOS::ES::TMDReader m_tmd; From c8bffb01531addda9f345c30d614500650ca82a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 14 May 2017 00:15:12 +0200 Subject: [PATCH 6/7] Reuse the IOS code for WAD installation * Less code and logic duplication. * Fixes a bug with the data dir not being created, steps being done in the wrong order. --- Source/Core/DiscIO/NANDContentLoader.cpp | 68 -------------------- Source/Core/DiscIO/NANDContentLoader.h | 1 - Source/Core/DolphinQt2/GameList/GameFile.cpp | 3 +- Source/Core/DolphinWX/FrameTools.cpp | 7 +- Source/Core/UICommon/CMakeLists.txt | 1 + Source/Core/UICommon/UICommon.vcxproj | 4 +- Source/Core/UICommon/WiiUtils.cpp | 64 ++++++++++++++++++ Source/Core/UICommon/WiiUtils.h | 14 ++++ 8 files changed, 87 insertions(+), 75 deletions(-) create mode 100644 Source/Core/UICommon/WiiUtils.cpp create mode 100644 Source/Core/UICommon/WiiUtils.h diff --git a/Source/Core/DiscIO/NANDContentLoader.cpp b/Source/Core/DiscIO/NANDContentLoader.cpp index 4e7f6f12ba..e6aca0ce79 100644 --- a/Source/Core/DiscIO/NANDContentLoader.cpp +++ b/Source/Core/DiscIO/NANDContentLoader.cpp @@ -246,74 +246,6 @@ void CNANDContentManager::ClearCache() m_map.clear(); } -u64 CNANDContentManager::Install_WiiWAD(const std::string& filename) -{ - if (filename.find(".wad") == std::string::npos) - return 0; - const CNANDContentLoader& content_loader = GetNANDLoader(filename); - if (content_loader.IsValid() == false) - return 0; - - const u64 title_id = content_loader.GetTMD().GetTitleId(); - - // copy WAD's TMD header and contents to content directory - - std::string content_path(Common::GetTitleContentPath(title_id, Common::FROM_CONFIGURED_ROOT)); - std::string tmd_filename(Common::GetTMDFileName(title_id, Common::FROM_CONFIGURED_ROOT)); - File::CreateFullPath(tmd_filename); - - File::IOFile tmd_file(tmd_filename, "wb"); - if (!tmd_file) - { - PanicAlertT("WAD installation failed: error creating %s", tmd_filename.c_str()); - return 0; - } - - const auto& raw_tmd = content_loader.GetTMD().GetRawTMD(); - tmd_file.WriteBytes(raw_tmd.data(), raw_tmd.size()); - - IOS::ES::SharedContentMap shared_content{Common::FromWhichRoot::FROM_CONFIGURED_ROOT}; - for (const auto& content : content_loader.GetContent()) - { - std::string app_filename; - if (content.m_metadata.IsShared()) - app_filename = shared_content.AddSharedContent(content.m_metadata.sha1); - else - app_filename = StringFromFormat("%s%08x.app", content_path.c_str(), content.m_metadata.id); - - if (!File::Exists(app_filename)) - { - File::CreateFullPath(app_filename); - File::IOFile app_file(app_filename, "wb"); - if (!app_file) - { - PanicAlertT("WAD installation failed: error creating %s", app_filename.c_str()); - return 0; - } - - app_file.WriteBytes(content.m_Data->Get().data(), content.m_metadata.size); - } - else - { - INFO_LOG(DISCIO, "Content %s already exists.", app_filename.c_str()); - } - } - - // Extract and copy WAD's ticket to ticket directory - if (!AddTicket(content_loader.GetTicket())) - { - PanicAlertT("WAD installation failed: error creating ticket"); - return 0; - } - - IOS::ES::UIDSys uid_sys{Common::FromWhichRoot::FROM_CONFIGURED_ROOT}; - uid_sys.GetOrInsertUIDForTitle(title_id); - - ClearCache(); - - return title_id; -} - bool AddTicket(const IOS::ES::TicketReader& signed_ticket) { if (!signed_ticket.IsValid()) diff --git a/Source/Core/DiscIO/NANDContentLoader.h b/Source/Core/DiscIO/NANDContentLoader.h index 3b16b82bda..af4ff4909c 100644 --- a/Source/Core/DiscIO/NANDContentLoader.h +++ b/Source/Core/DiscIO/NANDContentLoader.h @@ -106,7 +106,6 @@ public: static CNANDContentManager instance; return instance; } - u64 Install_WiiWAD(const std::string& fileName); const CNANDContentLoader& GetNANDLoader(const std::string& content_path); const CNANDContentLoader& GetNANDLoader(u64 title_id, Common::FromWhichRoot from); diff --git a/Source/Core/DolphinQt2/GameList/GameFile.cpp b/Source/Core/DolphinQt2/GameList/GameFile.cpp index b81d491aff..f3a644934d 100644 --- a/Source/Core/DolphinQt2/GameList/GameFile.cpp +++ b/Source/Core/DolphinQt2/GameList/GameFile.cpp @@ -23,6 +23,7 @@ #include "DolphinQt2/GameList/GameFile.h" #include "DolphinQt2/Resources.h" #include "DolphinQt2/Settings.h" +#include "UICommon/WiiUtils.h" static const int CACHE_VERSION = 13; // Last changed in PR #3261 static const int DATASTREAM_VERSION = QDataStream::Qt_5_5; @@ -331,7 +332,7 @@ bool GameFile::Install() { _assert_(m_platform == DiscIO::Platform::WII_WAD); - return DiscIO::CNANDContentManager::Access().Install_WiiWAD(m_path.toStdString()); + return WiiUtils::InstallWAD(m_path.toStdString()); } bool GameFile::Uninstall() diff --git a/Source/Core/DolphinWX/FrameTools.cpp b/Source/Core/DolphinWX/FrameTools.cpp index f8f99cdd81..1098852605 100644 --- a/Source/Core/DolphinWX/FrameTools.cpp +++ b/Source/Core/DolphinWX/FrameTools.cpp @@ -83,6 +83,8 @@ #include "InputCommon/ControllerInterface/ControllerInterface.h" +#include "UICommon/WiiUtils.h" + #include "VideoCommon/RenderBase.h" #include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoConfig.h" @@ -1209,11 +1211,8 @@ void CFrame::OnInstallWAD(wxCommandEvent& event) wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME | wxPD_SMOOTH); - u64 titleID = DiscIO::CNANDContentManager::Access().Install_WiiWAD(fileName); - if (titleID == TITLEID_SYSMENU) - { + if (WiiUtils::InstallWAD(fileName)) UpdateLoadWiiMenuItem(); - } } void CFrame::OnUninstallWAD(wxCommandEvent&) diff --git a/Source/Core/UICommon/CMakeLists.txt b/Source/Core/UICommon/CMakeLists.txt index 36648bbe7c..df1f3fd157 100644 --- a/Source/Core/UICommon/CMakeLists.txt +++ b/Source/Core/UICommon/CMakeLists.txt @@ -3,6 +3,7 @@ set(SRCS Disassembler.cpp UICommon.cpp USBUtils.cpp + WiiUtils.cpp ) if(USE_X11) diff --git a/Source/Core/UICommon/UICommon.vcxproj b/Source/Core/UICommon/UICommon.vcxproj index 18d1ee0966..d5982fc1b3 100644 --- a/Source/Core/UICommon/UICommon.vcxproj +++ b/Source/Core/UICommon/UICommon.vcxproj @@ -59,12 +59,14 @@ 4200;%(DisableSpecificWarnings) + + @@ -74,4 +76,4 @@ - \ No newline at end of file + diff --git a/Source/Core/UICommon/WiiUtils.cpp b/Source/Core/UICommon/WiiUtils.cpp new file mode 100644 index 0000000000..1575f272f7 --- /dev/null +++ b/Source/Core/UICommon/WiiUtils.cpp @@ -0,0 +1,64 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "UICommon/WiiUtils.h" +#include "Common/CommonTypes.h" +#include "Common/MsgHandler.h" +#include "Core/IOS/ES/ES.h" +#include "Core/IOS/ES/Formats.h" +#include "Core/IOS/IOS.h" +#include "DiscIO/NANDContentLoader.h" +#include "DiscIO/WiiWad.h" + +namespace WiiUtils +{ +bool InstallWAD(const std::string& wad_path) +{ + const DiscIO::WiiWAD wad{wad_path}; + if (!wad.IsValid()) + { + PanicAlertT("WAD installation failed: The selected file is not a valid WAD."); + return false; + } + + const auto tmd = wad.GetTMD(); + IOS::HLE::Kernel ios; + const auto es = ios.GetES(); + + IOS::HLE::Device::ES::Context context; + if (es->ImportTicket(wad.GetTicket().GetRawTicket()) < 0 || + es->ImportTitleInit(context, tmd.GetRawTMD()) < 0) + { + PanicAlertT("WAD installation failed: Could not initialise title import."); + return false; + } + + const bool contents_imported = [&]() { + const u64 title_id = tmd.GetTitleId(); + for (const IOS::ES::Content& content : tmd.GetContents()) + { + const std::vector data = wad.GetContent(content.index); + + if (es->ImportContentBegin(context, title_id, content.id) < 0 || + es->ImportContentData(context, 0, data.data(), static_cast(data.size())) < 0 || + es->ImportContentEnd(context, 0) < 0) + { + PanicAlertT("WAD installation failed: Could not import content %08x.", content.id); + return false; + } + } + return true; + }(); + + if ((contents_imported && es->ImportTitleDone(context) < 0) || + (!contents_imported && es->ImportTitleCancel(context) < 0)) + { + PanicAlertT("WAD installation failed: Could not finalise title import."); + return false; + } + + DiscIO::CNANDContentManager::Access().ClearCache(); + return true; +} +} diff --git a/Source/Core/UICommon/WiiUtils.h b/Source/Core/UICommon/WiiUtils.h new file mode 100644 index 0000000000..8419e01b9b --- /dev/null +++ b/Source/Core/UICommon/WiiUtils.h @@ -0,0 +1,14 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +// Small utility functions for common Wii related tasks. + +namespace WiiUtils +{ +bool InstallWAD(const std::string& wad_path); +} From aa3dc9a057ae6c8422f496bf01bbfb3c6b805b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 14 May 2017 16:05:10 +0200 Subject: [PATCH 7/7] IOS/ES: Consider hidden channels/system menu as channels --- Source/Core/Core/IOS/ES/Formats.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Core/Core/IOS/ES/Formats.cpp b/Source/Core/Core/IOS/ES/Formats.cpp index 43d56b7fdf..dce527519f 100644 --- a/Source/Core/Core/IOS/ES/Formats.cpp +++ b/Source/Core/Core/IOS/ES/Formats.cpp @@ -42,9 +42,13 @@ bool IsDiscTitle(u64 title_id) bool IsChannel(u64 title_id) { + if (title_id == TITLEID_SYSMENU) + return true; + return IsTitleType(title_id, TitleType::Channel) || IsTitleType(title_id, TitleType::SystemChannel) || - IsTitleType(title_id, TitleType::GameWithChannel); + IsTitleType(title_id, TitleType::GameWithChannel) || + IsTitleType(title_id, TitleType::HiddenChannel); } bool Content::IsShared() const