diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index b4fd424d5a..3640d77f4e 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -983,13 +983,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/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 b6d64c655f..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,80 +189,26 @@ private: IOCTL_ES_CHECKKOREAREGION = 0x45, }; - struct OpenedContent - { - u64 m_title_id; - IOS::ES::Content m_content; - 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; - 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/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 diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index 254276994d..4a199ee306 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,105 +43,123 @@ 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) +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"); + 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_bytes.size()); + return ES_EINVAL; + } + + // Finish a previous import (if it exists). + CleanUpStaleImport(context.title_import.tmd.GetTitleId()); + + if (!IOS::ES::InitImport(context.title_import.tmd.GetTitleId())) + return ES_EIO; + + // TODO: check and use the other vectors. + + return IPC_SUCCESS; +} + +IPCCommandResult ES::ImportTitleInit(Context& context, const IOCtlVRequest& request) { 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); - if (!context.title_import.tmd.IsValid()) - { - ERROR_LOG(IOS_ES, "Invalid TMD while adding title (size = %zd)", tmd.size()); - return GetDefaultReply(ES_EINVAL); - } - - // 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); - - if (!IOS::ES::InitImport(context.title_import.tmd.GetTitleId())) - return GetDefaultReply(FS_EIO); - - // TODO: check and use the other vectors. - - return GetDefaultReply(IPC_SUCCESS); + return GetDefaultReply(ImportTitleInit(context, tmd)); } -IPCCommandResult ES::AddContentStart(Context& context, const IOCtlVRequest& request) +ReturnCode ES::ImportContentBegin(Context& context, u64 title_id, u32 content_id) { - 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); - 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 +167,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 +211,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 +240,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 +265,72 @@ 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)) + return GetDefaultReply(ES_EINVAL); + + return GetDefaultReply(ImportTitleDone(context)); +} + +ReturnCode ES::ImportTitleCancel(Context& context) +{ + if (!context.title_import.tmd.IsValid()) + return ES_EINVAL; + + CleanUpStaleImport(context.title_import.tmd.GetTitleId()); + + INFO_LOG(IOS_ES, "ImportTitleCancel: title %016" PRIx64, context.title_import.tmd.GetTitleId()); + context.title_import.tmd.SetBytes({}); + 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); - 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); - } - - if (!FinishImport(original_tmd)) - return GetDefaultReply(FS_EIO); - - context.title_import.tmd.SetBytes({}); - return GetDefaultReply(IPC_SUCCESS); + return GetDefaultReply(ImportTitleCancel(context)); } static bool CanDeleteTitle(u64 title_id) @@ -306,51 +339,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 +387,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 +402,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 +440,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 +527,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 +575,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 +597,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 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/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; diff --git a/Source/Core/DolphinQt2/GameList/GameFile.cpp b/Source/Core/DolphinQt2/GameList/GameFile.cpp index 2f23107776..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; @@ -63,6 +64,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)); @@ -320,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/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/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/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; 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); +}