From 0dff8a3bded0286cae62113e7b0a89e849ed3245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 30 Jun 2017 00:28:17 +0200 Subject: [PATCH 1/3] IOS/ES: Separate title content functions from IPC handlers Allows them to be reused easily. Still a bit too much duplicated code in my opinion (OpenContent/SeekContent/ReadContent should just call FS code), but this is a start. --- Source/Core/Core/IOS/ES/ES.h | 8 +- Source/Core/Core/IOS/ES/TitleContents.cpp | 137 ++++++++++++---------- 2 files changed, 79 insertions(+), 66 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index f87e7cf50a..cca8637354 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -113,6 +113,12 @@ public: u32 GetSharedContentsCount() const; std::vector> GetSharedContents() const; + // Title contents + s32 OpenContent(const IOS::ES::TMDReader& tmd, u16 content_index, u32 uid); + ReturnCode CloseContent(u32 cfd, u32 uid); + s32 ReadContent(u32 cfd, u8* buffer, u32 size, u32 uid); + s32 SeekContent(u32 cfd, u32 offset, SeekMode mode, u32 uid); + // Title management ReturnCode ImportTicket(const std::vector& ticket_bytes, const std::vector& cert_chain); ReturnCode ImportTmd(Context& context, const std::vector& tmd_bytes); @@ -342,8 +348,6 @@ private: static const DiscIO::NANDContentLoader& AccessContentDevice(u64 title_id); - s32 OpenContent(const IOS::ES::TMDReader& tmd, u16 content_index, u32 uid); - using ContentTable = std::array; ContentTable m_content_table; diff --git a/Source/Core/Core/IOS/ES/TitleContents.cpp b/Source/Core/Core/IOS/ES/TitleContents.cpp index 33dbd6d038..d6cba3fb9f 100644 --- a/Source/Core/Core/IOS/ES/TitleContents.cpp +++ b/Source/Core/Core/IOS/ES/TitleContents.cpp @@ -88,23 +88,16 @@ IPCCommandResult ES::OpenActiveTitleContent(u32 caller_uid, const IOCtlVRequest& return GetDefaultReply(OpenContent(GetTitleContext().tmd, content_index, caller_uid)); } -IPCCommandResult ES::ReadContent(u32 uid, const IOCtlVRequest& request) +s32 ES::ReadContent(u32 cfd, u8* buffer, u32 size, u32 uid) { - if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != sizeof(u32)) - return GetDefaultReply(ES_EINVAL); - - const u32 cfd = Memory::Read_U32(request.in_vectors[0].address); - u32 size = request.io_vectors[0].size; - const u32 addr = request.io_vectors[0].address; - if (cfd >= m_content_table.size()) - return GetDefaultReply(ES_EINVAL); + return ES_EINVAL; OpenedContent& entry = m_content_table[cfd]; if (entry.m_uid != uid) - return GetDefaultReply(ES_EACCES); + return ES_EACCES; if (!entry.m_opened) - return GetDefaultReply(IPC_EINVAL); + return IPC_EINVAL; // XXX: make this reuse the FS code... ES just does a simple "IOS_Read" call here // instead of all this duplicated filesystem logic. @@ -112,46 +105,45 @@ IPCCommandResult ES::ReadContent(u32 uid, const IOCtlVRequest& request) if (entry.m_position + size > entry.m_content.size) size = static_cast(entry.m_content.size) - entry.m_position; - if (size > 0) + const DiscIO::NANDContentLoader& ContentLoader = AccessContentDevice(entry.m_title_id); + // ContentLoader should never be invalid; rContent has been created by it. + if (ContentLoader.IsValid() && ContentLoader.GetTicket().IsValid()) { - if (addr) + const DiscIO::NANDContent* pContent = ContentLoader.GetContentByIndex(entry.m_content.index); + pContent->m_Data->Open(); + if (!pContent->m_Data->GetRange(entry.m_position, size, buffer)) { - const DiscIO::NANDContentLoader& ContentLoader = AccessContentDevice(entry.m_title_id); - // ContentLoader should never be invalid; rContent has been created by it. - if (ContentLoader.IsValid() && ContentLoader.GetTicket().IsValid()) - { - const DiscIO::NANDContent* pContent = - ContentLoader.GetContentByIndex(entry.m_content.index); - pContent->m_Data->Open(); - if (!pContent->m_Data->GetRange(entry.m_position, size, Memory::GetPointer(addr))) - ERROR_LOG(IOS_ES, "ES: failed to read %u bytes from %u!", size, entry.m_position); - } - - entry.m_position += size; - } - else - { - PanicAlert("IOCTL_ES_READCONTENT - bad destination"); + ERROR_LOG(IOS_ES, "ES: failed to read %u bytes from %u!", size, entry.m_position); + return ES_SHORT_READ; } } - return GetDefaultReply(size); + entry.m_position += size; + return size; } -IPCCommandResult ES::CloseContent(u32 uid, const IOCtlVRequest& request) +IPCCommandResult ES::ReadContent(u32 uid, const IOCtlVRequest& request) { - if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != sizeof(u32)) + if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != sizeof(u32)) return GetDefaultReply(ES_EINVAL); const u32 cfd = Memory::Read_U32(request.in_vectors[0].address); + const u32 size = request.io_vectors[0].size; + const u32 addr = request.io_vectors[0].address; + + return GetDefaultReply(ReadContent(cfd, Memory::GetPointer(addr), size, uid)); +} + +ReturnCode ES::CloseContent(u32 cfd, u32 uid) +{ if (cfd >= m_content_table.size()) - return GetDefaultReply(ES_EINVAL); + return ES_EINVAL; OpenedContent& entry = m_content_table[cfd]; if (entry.m_uid != uid) - return GetDefaultReply(ES_EACCES); + return ES_EACCES; if (!entry.m_opened) - return GetDefaultReply(IPC_EINVAL); + return IPC_EINVAL; // XXX: again, this should be a simple IOS_Close. const DiscIO::NANDContentLoader& ContentLoader = AccessContentDevice(entry.m_title_id); @@ -164,7 +156,49 @@ IPCCommandResult ES::CloseContent(u32 uid, const IOCtlVRequest& request) entry = {}; INFO_LOG(IOS_ES, "CloseContent: CFD %u", cfd); - return GetDefaultReply(IPC_SUCCESS); + return IPC_SUCCESS; +} + +IPCCommandResult ES::CloseContent(u32 uid, const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != sizeof(u32)) + return GetDefaultReply(ES_EINVAL); + + const u32 cfd = Memory::Read_U32(request.in_vectors[0].address); + return GetDefaultReply(CloseContent(cfd, uid)); +} + +s32 ES::SeekContent(u32 cfd, u32 offset, SeekMode mode, u32 uid) +{ + if (cfd >= m_content_table.size()) + return ES_EINVAL; + + OpenedContent& entry = m_content_table[cfd]; + if (entry.m_uid != uid) + return ES_EACCES; + if (!entry.m_opened) + return IPC_EINVAL; + + // XXX: This should be a simple IOS_Seek. + switch (mode) + { + case SeekMode::IOS_SEEK_SET: + entry.m_position = offset; + break; + + case SeekMode::IOS_SEEK_CUR: + entry.m_position += offset; + break; + + case SeekMode::IOS_SEEK_END: + entry.m_position = static_cast(entry.m_content.size) + offset; + break; + + default: + return FS_EINVAL; + } + + return entry.m_position; } IPCCommandResult ES::SeekContent(u32 uid, const IOCtlVRequest& request) @@ -173,35 +207,10 @@ IPCCommandResult ES::SeekContent(u32 uid, const IOCtlVRequest& request) return GetDefaultReply(ES_EINVAL); const u32 cfd = Memory::Read_U32(request.in_vectors[0].address); - if (cfd >= m_content_table.size()) - return GetDefaultReply(ES_EINVAL); + const u32 offset = Memory::Read_U32(request.in_vectors[1].address); + const SeekMode mode = static_cast(Memory::Read_U32(request.in_vectors[2].address)); - OpenedContent& entry = m_content_table[cfd]; - if (entry.m_uid != uid) - return GetDefaultReply(ES_EACCES); - if (!entry.m_opened) - return GetDefaultReply(IPC_EINVAL); - - u32 Addr = Memory::Read_U32(request.in_vectors[1].address); - u32 Mode = Memory::Read_U32(request.in_vectors[2].address); - - // XXX: This should be a simple IOS_Seek. - switch (Mode) - { - case 0: // SET - entry.m_position = Addr; - break; - - case 1: // CUR - entry.m_position += Addr; - break; - - case 2: // END - entry.m_position = static_cast(entry.m_content.size) + Addr; - break; - } - - return GetDefaultReply(entry.m_position); + return GetDefaultReply(SeekContent(cfd, offset, mode, uid)); } } // namespace Device } // namespace HLE From dc1707faa8304dcc55673f87131dc9f408735636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 18 Jun 2017 22:12:36 +0200 Subject: [PATCH 2/3] IOS/ES: Merge the title import and export contexts This commit merges the import and export contexts into a single context because this is what IOS does, which means we can only reproduce its behaviour correctly if we use a single context for both operations. The other reason is that having two separate and very similar structs is not really a good idea. While working on this commit, I was notified that our handling of ImportTmd/ExportTitleInit is not correct. In particular, we always use the title key for both importing and exporting, which is wrong. To make this easier to fix in a follow-up PR, the context now also has a title key field, just like ES. This also lets us avoid computing it every single time in ImportContentDone. --- Source/Core/Core/IOS/ES/ES.cpp | 10 +- Source/Core/Core/IOS/ES/ES.h | 46 ++--- Source/Core/Core/IOS/ES/TitleManagement.cpp | 206 +++++++++----------- Source/Core/Core/State.cpp | 2 +- 4 files changed, 119 insertions(+), 145 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index a0f3241e85..b35b969850 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -321,15 +321,7 @@ void ES::Context::DoState(PointerWrap& p) { p.Do(uid); p.Do(gid); - - title_import.tmd.DoState(p); - p.Do(title_import.content_id); - p.Do(title_import.content_buffer); - - p.Do(title_export.valid); - title_export.tmd.DoState(p); - p.Do(title_export.title_key); - p.Do(title_export.contents); + title_import.DoState(p); p.Do(active); p.Do(ipc_fd); diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index cca8637354..b4a3ef5afb 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -55,35 +55,21 @@ public: ReturnCode Close(u32 fd) override; IPCCommandResult IOCtlV(const IOCtlVRequest& request) override; - struct OpenedContent - { - bool m_opened = false; - u64 m_title_id = 0; - IOS::ES::Content m_content; - u32 m_position = 0; - u32 m_uid = 0; - }; - 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{}; - }; + void DoState(PointerWrap& p); bool valid = false; IOS::ES::TMDReader tmd; - std::array title_key; - std::map contents; + std::array key{}; + struct ContentContext + { + bool valid = false; + u32 id = 0; + std::array iv{}; + std::vector buffer; + }; + ContentContext content; }; struct Context @@ -92,8 +78,8 @@ public: u16 gid = 0; u32 uid = 0; + // The same context is used for both title imports and exports. TitleImportContext title_import; - TitleExportContext title_export; bool active = false; // We use this to associate an IPC fd with an ES context. s32 ipc_fd = -1; @@ -348,6 +334,16 @@ private: static const DiscIO::NANDContentLoader& AccessContentDevice(u64 title_id); + // TODO: reuse the FS code. + struct OpenedContent + { + bool m_opened = false; + u64 m_title_id = 0; + IOS::ES::Content m_content; + u32 m_position = 0; + u32 m_uid = 0; + }; + using ContentTable = std::array; ContentTable m_content_table; diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index 3654f90d85..0811ad1597 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -45,6 +45,17 @@ static ReturnCode WriteTicket(const IOS::ES::TicketReader& ticket) return ticket_file.WriteBytes(raw_ticket.data(), raw_ticket.size()) ? IPC_SUCCESS : ES_EIO; } +void ES::TitleImportContext::DoState(PointerWrap& p) +{ + p.Do(valid); + p.Do(key); + tmd.DoState(p); + p.Do(content.valid); + p.Do(content.id); + p.Do(content.iv); + p.Do(content.buffer); +} + ReturnCode ES::ImportTicket(const std::vector& ticket_bytes, const std::vector& cert_chain) { IOS::ES::TicketReader ticket{ticket_bytes}; @@ -98,6 +109,7 @@ 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 = {}; context.title_import.tmd.SetBytes(tmd_bytes); if (!context.title_import.tmd.IsValid()) return ES_EINVAL; @@ -110,15 +122,18 @@ ReturnCode ES::ImportTmd(Context& context, const std::vector& tmd_bytes) ret = VerifyContainer(VerifyContainerType::TMD, VerifyMode::UpdateCertStore, context.title_import.tmd, cert_store); if (ret != IPC_SUCCESS) - { - // Reset the import context so that further calls consider the state as invalid. - context.title_import.tmd.SetBytes({}); return ret; - } if (!InitImport(context.title_import.tmd.GetTitleId())) return ES_EIO; + // FIXME: ImportTmd does not use the ticket or the title key. + const auto ticket = DiscIO::FindSignedTicket(context.title_import.tmd.GetTitleId()); + if (!ticket.IsValid()) + return ES_NO_TICKET; + context.title_import.key = ticket.GetTitleKey(m_ios.GetIOSC()); + + context.title_import.valid = true; return IPC_SUCCESS; } @@ -139,6 +154,7 @@ ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_byte const std::vector& cert_chain) { INFO_LOG(IOS_ES, "ImportTitleInit"); + context.title_import = {}; context.title_import.tmd.SetBytes(tmd_bytes); if (!context.title_import.tmd.IsValid()) { @@ -152,15 +168,14 @@ ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_byte ReturnCode ret = VerifyContainer(VerifyContainerType::TMD, VerifyMode::UpdateCertStore, context.title_import.tmd, cert_chain); if (ret != IPC_SUCCESS) - { - context.title_import.tmd.SetBytes({}); return ret; - } const auto ticket = DiscIO::FindSignedTicket(context.title_import.tmd.GetTitleId()); if (!ticket.IsValid()) return ES_NO_TICKET; + context.title_import.key = ticket.GetTitleKey(m_ios.GetIOSC()); + std::vector cert_store; ret = ReadCertStore(&cert_store); if (ret != IPC_SUCCESS) @@ -169,14 +184,12 @@ ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_byte ret = VerifyContainer(VerifyContainerType::Ticket, VerifyMode::DoNotUpdateCertStore, ticket, cert_store); if (ret != IPC_SUCCESS) - { - context.title_import.tmd.SetBytes({}); return ret; - } if (!InitImport(context.title_import.tmd.GetTitleId())) return ES_EIO; + context.title_import.valid = true; return IPC_SUCCESS; } @@ -197,20 +210,19 @@ IPCCommandResult ES::ImportTitleInit(Context& context, const IOCtlVRequest& requ ReturnCode ES::ImportContentBegin(Context& context, u64 title_id, u32 content_id) { - if (context.title_import.content_id != 0xFFFFFFFF) + if (context.title_import.content.valid) { ERROR_LOG(IOS_ES, "Trying to add content when we haven't finished adding " "another content. Unsupported."); return ES_EINVAL; } - context.title_import.content_id = content_id; - - context.title_import.content_buffer.clear(); + context.title_import.content = {}; + context.title_import.content.id = content_id; INFO_LOG(IOS_ES, "ImportContentBegin: title %016" PRIx64 ", content ID %08x", title_id, - context.title_import.content_id); + context.title_import.content.id); - if (!context.title_import.tmd.IsValid()) + if (!context.title_import.valid) return ES_EINVAL; if (title_id != context.title_import.tmd.GetTitleId()) @@ -221,6 +233,16 @@ ReturnCode ES::ImportContentBegin(Context& context, u64 title_id, u32 content_id return ES_EINVAL; } + // 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 ES_EINVAL; + context.title_import.content.iv[0] = (content_info.index >> 8) & 0xFF; + context.title_import.content.iv[1] = content_info.index & 0xFF; + + context.title_import.content.valid = true; + // We're supposed to return a "content file descriptor" here, which is // passed to further AddContentData / AddContentFinish. But so far there is // no known content installer which performs content addition concurrently. @@ -242,7 +264,7 @@ IPCCommandResult ES::ImportContentBegin(Context& context, const IOCtlVRequest& r 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, + context.title_import.content.buffer.insert(context.title_import.content.buffer.end(), data, data + data_size); return IPC_SUCCESS; } @@ -274,29 +296,14 @@ ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd) { INFO_LOG(IOS_ES, "ImportContentEnd: content fd %08x", content_fd); - if (context.title_import.content_id == 0xFFFFFFFF) + if (!context.title_import.valid || !context.title_import.content.valid) return ES_EINVAL; - if (!context.title_import.tmd.IsValid()) - 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 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 ES_EINVAL; - - u8 iv[16] = {0}; - iv[0] = (content_info.index >> 8) & 0xFF; - iv[1] = content_info.index & 0xFF; std::vector decrypted_data = Common::AES::Decrypt( - ticket.GetTitleKey(m_ios.GetIOSC()).data(), iv, context.title_import.content_buffer.data(), - context.title_import.content_buffer.size()); + context.title_import.key.data(), context.title_import.content.iv.data(), + context.title_import.content.buffer.data(), context.title_import.content.buffer.size()); + IOS::ES::Content content_info; + context.title_import.tmd.FindContentById(context.title_import.content.id, &content_info); if (!CheckIfContentHashMatches(decrypted_data, content_info)) { ERROR_LOG(IOS_ES, "ImportContentEnd: Hash for content %08x doesn't match", content_info.id); @@ -312,12 +319,12 @@ ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd) else { content_path = GetImportContentPath(context.title_import.tmd.GetTitleId(), - context.title_import.content_id); + context.title_import.content.id); } File::CreateFullPath(content_path); const std::string temp_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + - StringFromFormat("/tmp/%08x.app", context.title_import.content_id); + StringFromFormat("/tmp/%08x.app", context.title_import.content.id); File::CreateFullPath(temp_path); { @@ -335,7 +342,7 @@ ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd) return ES_EIO; } - context.title_import.content_id = 0xFFFFFFFF; + context.title_import.content = {}; return IPC_SUCCESS; } @@ -350,7 +357,7 @@ IPCCommandResult ES::ImportContentEnd(Context& context, const IOCtlVRequest& req ReturnCode ES::ImportTitleDone(Context& context) { - if (!context.title_import.tmd.IsValid() || context.title_import.content_id != 0xFFFFFFFF) + if (!context.title_import.valid || context.title_import.content.valid) return ES_EINVAL; // Make sure all listed, non-optional contents have been imported. @@ -380,7 +387,7 @@ ReturnCode ES::ImportTitleDone(Context& context) return ES_EIO; INFO_LOG(IOS_ES, "ImportTitleDone: title %016" PRIx64, title_id); - context.title_import.tmd.SetBytes({}); + context.title_import = {}; return IPC_SUCCESS; } @@ -394,19 +401,24 @@ IPCCommandResult ES::ImportTitleDone(Context& context, const IOCtlVRequest& requ ReturnCode ES::ImportTitleCancel(Context& context) { - if (!context.title_import.tmd.IsValid()) + // The TMD buffer can exist without a valid title import context. + if (context.title_import.tmd.GetBytes().empty() || context.title_import.content.valid) return ES_EINVAL; - FinishStaleImport(context.title_import.tmd.GetTitleId()); + if (context.title_import.valid) + { + const u64 title_id = context.title_import.tmd.GetTitleId(); + FinishStaleImport(title_id); + INFO_LOG(IOS_ES, "ImportTitleCancel: title %016" PRIx64, title_id); + } - INFO_LOG(IOS_ES, "ImportTitleCancel: title %016" PRIx64, context.title_import.tmd.GetTitleId()); - context.title_import.tmd.SetBytes({}); + context.title_import = {}; return IPC_SUCCESS; } IPCCommandResult ES::ImportTitleCancel(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(ImportTitleCancel(context)); @@ -555,30 +567,32 @@ IPCCommandResult ES::DeleteContent(const IOCtlVRequest& request) ReturnCode ES::ExportTitleInit(Context& context, u64 title_id, u8* tmd_bytes, u32 tmd_size) { // No concurrent title import/export is allowed. - if (context.title_export.valid) + if (context.title_import.valid) return ES_EINVAL; const auto tmd = FindInstalledTMD(title_id); if (!tmd.IsValid()) return FS_ENOENT; - context.title_export.tmd = tmd; + context.title_import = {}; + context.title_import.tmd = tmd; - const auto ticket = DiscIO::FindSignedTicket(context.title_export.tmd.GetTitleId()); + const auto ticket = DiscIO::FindSignedTicket(context.title_import.tmd.GetTitleId()); if (!ticket.IsValid()) return ES_NO_TICKET; - if (ticket.GetTitleId() != context.title_export.tmd.GetTitleId()) + if (ticket.GetTitleId() != context.title_import.tmd.GetTitleId()) return ES_EINVAL; - context.title_export.title_key = ticket.GetTitleKey(m_ios.GetIOSC()); + // FIXME: this is wrong. The title key is *not* used here. Key #5 or a null key is. + context.title_import.key = ticket.GetTitleKey(m_ios.GetIOSC()); - const std::vector& raw_tmd = context.title_export.tmd.GetBytes(); + const std::vector& raw_tmd = context.title_import.tmd.GetBytes(); if (tmd_size != raw_tmd.size()) return ES_EINVAL; std::copy_n(raw_tmd.cbegin(), raw_tmd.size(), tmd_bytes); - context.title_export.valid = true; + context.title_import.valid = true; return IPC_SUCCESS; } @@ -596,38 +610,32 @@ IPCCommandResult ES::ExportTitleInit(Context& context, const IOCtlVRequest& requ ReturnCode ES::ExportContentBegin(Context& context, u64 title_id, u32 content_id) { - if (!context.title_export.valid || context.title_export.tmd.GetTitleId() != title_id) + context.title_import.content = {}; + if (!context.title_import.valid || context.title_import.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) + IOS::ES::Content content_info; + if (!context.title_import.tmd.FindContentById(content_id, &content_info)) 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(); + context.title_import.content.id = content_id; + context.title_import.content.valid = true; - u32 cfd = 0; - while (context.title_export.contents.find(cfd) != context.title_export.contents.end()) - cfd++; + const s32 ret = OpenContent(context.title_import.tmd, content_info.index, 0); + if (ret < 0) + { + context.title_import = {}; + return static_cast(ret); + } - 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_import.content.iv[0] = (content_info.index >> 8) & 0xFF; + context.title_import.content.iv[1] = content_info.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); + return static_cast(ret); } IPCCommandResult ES::ExportContentBegin(Context& context, const IOCtlVRequest& request) @@ -644,26 +652,19 @@ IPCCommandResult ES::ExportContentBegin(Context& context, const IOCtlVRequest& r 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) + if (!context.title_import.valid || !context.title_import.content.valid || !data || data_size == 0) { + CloseContent(content_fd, 0); + context.title_import = {}; return 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), data_size); - std::vector buffer(length); - - if (!content->m_Data->GetRange(metadata.m_position, length, buffer.data())) + std::vector buffer(data_size); + const s32 read_size = ReadContent(content_fd, buffer.data(), static_cast(buffer.size()), 0); + if (read_size < 0) { - ERROR_LOG(IOS_ES, "ExportContentData: ES_SHORT_READ"); + CloseContent(content_fd, 0); + context.title_import = {}; return ES_SHORT_READ; } @@ -672,10 +673,9 @@ ReturnCode ES::ExportContentData(Context& context, u32 content_fd, u8* data, u32 buffer.resize(Common::AlignUp(buffer.size(), 32)); const std::vector output = - Common::AES::Encrypt(context.title_export.title_key.data(), iterator->second.iv.data(), + Common::AES::Encrypt(context.title_import.key.data(), context.title_import.content.iv.data(), buffer.data(), buffer.size()); - std::copy_n(output.cbegin(), output.size(), data); - metadata.m_position += length; + std::copy(output.cbegin(), output.cend(), data); return IPC_SUCCESS; } @@ -696,20 +696,9 @@ IPCCommandResult ES::ExportContentData(Context& context, const IOCtlVRequest& re ReturnCode ES::ExportContentEnd(Context& context, u32 content_fd) { - 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) - { + if (!context.title_import.valid || !context.title_import.content.valid) return ES_EINVAL; - } - - // XXX: Check the content hash, as IOS does? - - const auto& content_loader = AccessContentDevice(iterator->second.content.m_title_id); - content_loader.GetContentByID(iterator->second.content.m_content.id)->m_Data->Close(); - - context.title_export.contents.erase(iterator); - return IPC_SUCCESS; + return CloseContent(content_fd, 0); } IPCCommandResult ES::ExportContentEnd(Context& context, const IOCtlVRequest& request) @@ -723,10 +712,7 @@ IPCCommandResult ES::ExportContentEnd(Context& context, const IOCtlVRequest& req ReturnCode ES::ExportTitleDone(Context& context) { - if (!context.title_export.valid) - return ES_EINVAL; - - context.title_export.valid = false; + context.title_import = {}; return IPC_SUCCESS; } diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index bc2a0f20aa..5fbc446e16 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -73,7 +73,7 @@ static Common::Event g_compressAndDumpStateSyncEvent; static std::thread g_save_thread; // Don't forget to increase this after doing changes on the savestate system -static const u32 STATE_VERSION = 87; // Last changed in PR 5707 +static const u32 STATE_VERSION = 88; // Last changed in PR 5733 // Maps savestate versions to Dolphin versions. // Versions after 42 don't need to be added to this list, From 1ba1b51606b2ed7f2c65da8733f59ab86b00561d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 14 Jul 2017 13:22:26 +0800 Subject: [PATCH 3/3] IOS/ES: Rename context.title_import This makes it obvious that the same context is used for both title imports and exports. --- Source/Core/Core/IOS/ES/ES.cpp | 2 +- Source/Core/Core/IOS/ES/ES.h | 5 +- Source/Core/Core/IOS/ES/TitleManagement.cpp | 157 ++++++++++---------- 3 files changed, 85 insertions(+), 79 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index b35b969850..edcb89dca8 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -321,7 +321,7 @@ void ES::Context::DoState(PointerWrap& p) { p.Do(uid); p.Do(gid); - title_import.DoState(p); + title_import_export.DoState(p); p.Do(active); p.Do(ipc_fd); diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index b4a3ef5afb..929638ecb5 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -55,7 +55,7 @@ public: ReturnCode Close(u32 fd) override; IPCCommandResult IOCtlV(const IOCtlVRequest& request) override; - struct TitleImportContext + struct TitleImportExportContext { void DoState(PointerWrap& p); @@ -78,8 +78,7 @@ public: u16 gid = 0; u32 uid = 0; - // The same context is used for both title imports and exports. - TitleImportContext title_import; + TitleImportExportContext title_import_export; bool active = false; // We use this to associate an IPC fd with an ES context. s32 ipc_fd = -1; diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index 0811ad1597..7066eacfb4 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -45,7 +45,7 @@ static ReturnCode WriteTicket(const IOS::ES::TicketReader& ticket) return ticket_file.WriteBytes(raw_ticket.data(), raw_ticket.size()) ? IPC_SUCCESS : ES_EIO; } -void ES::TitleImportContext::DoState(PointerWrap& p) +void ES::TitleImportExportContext::DoState(PointerWrap& p) { p.Do(valid); p.Do(key); @@ -109,9 +109,9 @@ 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 = {}; - context.title_import.tmd.SetBytes(tmd_bytes); - if (!context.title_import.tmd.IsValid()) + context.title_import_export = {}; + context.title_import_export.tmd.SetBytes(tmd_bytes); + if (!context.title_import_export.tmd.IsValid()) return ES_EINVAL; std::vector cert_store; @@ -120,20 +120,20 @@ ReturnCode ES::ImportTmd(Context& context, const std::vector& tmd_bytes) return ret; ret = VerifyContainer(VerifyContainerType::TMD, VerifyMode::UpdateCertStore, - context.title_import.tmd, cert_store); + context.title_import_export.tmd, cert_store); if (ret != IPC_SUCCESS) return ret; - if (!InitImport(context.title_import.tmd.GetTitleId())) + if (!InitImport(context.title_import_export.tmd.GetTitleId())) return ES_EIO; // FIXME: ImportTmd does not use the ticket or the title key. - const auto ticket = DiscIO::FindSignedTicket(context.title_import.tmd.GetTitleId()); + const auto ticket = DiscIO::FindSignedTicket(context.title_import_export.tmd.GetTitleId()); if (!ticket.IsValid()) return ES_NO_TICKET; - context.title_import.key = ticket.GetTitleKey(m_ios.GetIOSC()); + context.title_import_export.key = ticket.GetTitleKey(m_ios.GetIOSC()); - context.title_import.valid = true; + context.title_import_export.valid = true; return IPC_SUCCESS; } @@ -154,27 +154,27 @@ ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_byte const std::vector& cert_chain) { INFO_LOG(IOS_ES, "ImportTitleInit"); - context.title_import = {}; - context.title_import.tmd.SetBytes(tmd_bytes); - if (!context.title_import.tmd.IsValid()) + context.title_import_export = {}; + context.title_import_export.tmd.SetBytes(tmd_bytes); + if (!context.title_import_export.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). - FinishStaleImport(context.title_import.tmd.GetTitleId()); + FinishStaleImport(context.title_import_export.tmd.GetTitleId()); ReturnCode ret = VerifyContainer(VerifyContainerType::TMD, VerifyMode::UpdateCertStore, - context.title_import.tmd, cert_chain); + context.title_import_export.tmd, cert_chain); if (ret != IPC_SUCCESS) return ret; - const auto ticket = DiscIO::FindSignedTicket(context.title_import.tmd.GetTitleId()); + const auto ticket = DiscIO::FindSignedTicket(context.title_import_export.tmd.GetTitleId()); if (!ticket.IsValid()) return ES_NO_TICKET; - context.title_import.key = ticket.GetTitleKey(m_ios.GetIOSC()); + context.title_import_export.key = ticket.GetTitleKey(m_ios.GetIOSC()); std::vector cert_store; ret = ReadCertStore(&cert_store); @@ -186,10 +186,10 @@ ReturnCode ES::ImportTitleInit(Context& context, const std::vector& tmd_byte if (ret != IPC_SUCCESS) return ret; - if (!InitImport(context.title_import.tmd.GetTitleId())) + if (!InitImport(context.title_import_export.tmd.GetTitleId())) return ES_EIO; - context.title_import.valid = true; + context.title_import_export.valid = true; return IPC_SUCCESS; } @@ -210,38 +210,39 @@ IPCCommandResult ES::ImportTitleInit(Context& context, const IOCtlVRequest& requ ReturnCode ES::ImportContentBegin(Context& context, u64 title_id, u32 content_id) { - if (context.title_import.content.valid) + if (context.title_import_export.content.valid) { ERROR_LOG(IOS_ES, "Trying to add content when we haven't finished adding " "another content. Unsupported."); return ES_EINVAL; } - context.title_import.content = {}; - context.title_import.content.id = content_id; + context.title_import_export.content = {}; + context.title_import_export.content.id = content_id; INFO_LOG(IOS_ES, "ImportContentBegin: title %016" PRIx64 ", content ID %08x", title_id, - context.title_import.content.id); + context.title_import_export.content.id); - if (!context.title_import.valid) + if (!context.title_import_export.valid) return ES_EINVAL; - if (title_id != context.title_import.tmd.GetTitleId()) + if (title_id != context.title_import_export.tmd.GetTitleId()) { ERROR_LOG(IOS_ES, "ImportContentBegin: title id %016" PRIx64 " != " "TMD title id %016" PRIx64 ", ignoring", - title_id, context.title_import.tmd.GetTitleId()); + title_id, context.title_import_export.tmd.GetTitleId()); return ES_EINVAL; } // 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)) + if (!context.title_import_export.tmd.FindContentById(context.title_import_export.content.id, + &content_info)) return ES_EINVAL; - context.title_import.content.iv[0] = (content_info.index >> 8) & 0xFF; - context.title_import.content.iv[1] = content_info.index & 0xFF; + context.title_import_export.content.iv[0] = (content_info.index >> 8) & 0xFF; + context.title_import_export.content.iv[1] = content_info.index & 0xFF; - context.title_import.content.valid = true; + context.title_import_export.content.valid = true; // We're supposed to return a "content file descriptor" here, which is // passed to further AddContentData / AddContentFinish. But so far there is @@ -264,8 +265,8 @@ IPCCommandResult ES::ImportContentBegin(Context& context, const IOCtlVRequest& r 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); + context.title_import_export.content.buffer.insert( + context.title_import_export.content.buffer.end(), data, data + data_size); return IPC_SUCCESS; } @@ -296,14 +297,16 @@ ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd) { INFO_LOG(IOS_ES, "ImportContentEnd: content fd %08x", content_fd); - if (!context.title_import.valid || !context.title_import.content.valid) + if (!context.title_import_export.valid || !context.title_import_export.content.valid) return ES_EINVAL; std::vector decrypted_data = Common::AES::Decrypt( - context.title_import.key.data(), context.title_import.content.iv.data(), - context.title_import.content.buffer.data(), context.title_import.content.buffer.size()); + context.title_import_export.key.data(), context.title_import_export.content.iv.data(), + context.title_import_export.content.buffer.data(), + context.title_import_export.content.buffer.size()); IOS::ES::Content content_info; - context.title_import.tmd.FindContentById(context.title_import.content.id, &content_info); + context.title_import_export.tmd.FindContentById(context.title_import_export.content.id, + &content_info); if (!CheckIfContentHashMatches(decrypted_data, content_info)) { ERROR_LOG(IOS_ES, "ImportContentEnd: Hash for content %08x doesn't match", content_info.id); @@ -318,13 +321,14 @@ ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd) } else { - content_path = GetImportContentPath(context.title_import.tmd.GetTitleId(), - context.title_import.content.id); + content_path = GetImportContentPath(context.title_import_export.tmd.GetTitleId(), + context.title_import_export.content.id); } File::CreateFullPath(content_path); - const std::string temp_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + - StringFromFormat("/tmp/%08x.app", context.title_import.content.id); + const std::string temp_path = + Common::RootUserPath(Common::FROM_SESSION_ROOT) + + StringFromFormat("/tmp/%08x.app", context.title_import_export.content.id); File::CreateFullPath(temp_path); { @@ -342,7 +346,7 @@ ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd) return ES_EIO; } - context.title_import.content = {}; + context.title_import_export.content = {}; return IPC_SUCCESS; } @@ -357,12 +361,12 @@ IPCCommandResult ES::ImportContentEnd(Context& context, const IOCtlVRequest& req ReturnCode ES::ImportTitleDone(Context& context) { - if (!context.title_import.valid || context.title_import.content.valid) + if (!context.title_import_export.valid || context.title_import_export.content.valid) return ES_EINVAL; // Make sure all listed, non-optional contents have been imported. - const u64 title_id = context.title_import.tmd.GetTitleId(); - const std::vector contents = context.title_import.tmd.GetContents(); + const u64 title_id = context.title_import_export.tmd.GetTitleId(); + const std::vector contents = context.title_import_export.tmd.GetContents(); const IOS::ES::SharedContentMap shared_content_map{Common::FROM_SESSION_ROOT}; const bool has_all_required_contents = std::all_of(contents.cbegin(), contents.cend(), [&](const IOS::ES::Content& content) { @@ -380,14 +384,14 @@ ReturnCode ES::ImportTitleDone(Context& context) if (!has_all_required_contents) return ES_EINVAL; - if (!WriteImportTMD(context.title_import.tmd)) + if (!WriteImportTMD(context.title_import_export.tmd)) return ES_EIO; - if (!FinishImport(context.title_import.tmd)) + if (!FinishImport(context.title_import_export.tmd)) return ES_EIO; INFO_LOG(IOS_ES, "ImportTitleDone: title %016" PRIx64, title_id); - context.title_import = {}; + context.title_import_export = {}; return IPC_SUCCESS; } @@ -402,17 +406,18 @@ IPCCommandResult ES::ImportTitleDone(Context& context, const IOCtlVRequest& requ ReturnCode ES::ImportTitleCancel(Context& context) { // The TMD buffer can exist without a valid title import context. - if (context.title_import.tmd.GetBytes().empty() || context.title_import.content.valid) + if (context.title_import_export.tmd.GetBytes().empty() || + context.title_import_export.content.valid) return ES_EINVAL; - if (context.title_import.valid) + if (context.title_import_export.valid) { - const u64 title_id = context.title_import.tmd.GetTitleId(); + const u64 title_id = context.title_import_export.tmd.GetTitleId(); FinishStaleImport(title_id); INFO_LOG(IOS_ES, "ImportTitleCancel: title %016" PRIx64, title_id); } - context.title_import = {}; + context.title_import_export = {}; return IPC_SUCCESS; } @@ -567,32 +572,32 @@ IPCCommandResult ES::DeleteContent(const IOCtlVRequest& request) ReturnCode ES::ExportTitleInit(Context& context, u64 title_id, u8* tmd_bytes, u32 tmd_size) { // No concurrent title import/export is allowed. - if (context.title_import.valid) + if (context.title_import_export.valid) return ES_EINVAL; const auto tmd = FindInstalledTMD(title_id); if (!tmd.IsValid()) return FS_ENOENT; - context.title_import = {}; - context.title_import.tmd = tmd; + context.title_import_export = {}; + context.title_import_export.tmd = tmd; - const auto ticket = DiscIO::FindSignedTicket(context.title_import.tmd.GetTitleId()); + const auto ticket = DiscIO::FindSignedTicket(context.title_import_export.tmd.GetTitleId()); if (!ticket.IsValid()) return ES_NO_TICKET; - if (ticket.GetTitleId() != context.title_import.tmd.GetTitleId()) + if (ticket.GetTitleId() != context.title_import_export.tmd.GetTitleId()) return ES_EINVAL; // FIXME: this is wrong. The title key is *not* used here. Key #5 or a null key is. - context.title_import.key = ticket.GetTitleKey(m_ios.GetIOSC()); + context.title_import_export.key = ticket.GetTitleKey(m_ios.GetIOSC()); - const std::vector& raw_tmd = context.title_import.tmd.GetBytes(); + const std::vector& raw_tmd = context.title_import_export.tmd.GetBytes(); if (tmd_size != raw_tmd.size()) return ES_EINVAL; std::copy_n(raw_tmd.cbegin(), raw_tmd.size(), tmd_bytes); - context.title_import.valid = true; + context.title_import_export.valid = true; return IPC_SUCCESS; } @@ -610,29 +615,30 @@ IPCCommandResult ES::ExportTitleInit(Context& context, const IOCtlVRequest& requ ReturnCode ES::ExportContentBegin(Context& context, u64 title_id, u32 content_id) { - context.title_import.content = {}; - if (!context.title_import.valid || context.title_import.tmd.GetTitleId() != title_id) + context.title_import_export.content = {}; + if (!context.title_import_export.valid || + context.title_import_export.tmd.GetTitleId() != title_id) { ERROR_LOG(IOS_ES, "Tried to use ExportContentBegin with an invalid title export context."); return ES_EINVAL; } IOS::ES::Content content_info; - if (!context.title_import.tmd.FindContentById(content_id, &content_info)) + if (!context.title_import_export.tmd.FindContentById(content_id, &content_info)) return ES_EINVAL; - context.title_import.content.id = content_id; - context.title_import.content.valid = true; + context.title_import_export.content.id = content_id; + context.title_import_export.content.valid = true; - const s32 ret = OpenContent(context.title_import.tmd, content_info.index, 0); + const s32 ret = OpenContent(context.title_import_export.tmd, content_info.index, 0); if (ret < 0) { - context.title_import = {}; + context.title_import_export = {}; return static_cast(ret); } - context.title_import.content.iv[0] = (content_info.index >> 8) & 0xFF; - context.title_import.content.iv[1] = content_info.index & 0xFF; + context.title_import_export.content.iv[0] = (content_info.index >> 8) & 0xFF; + context.title_import_export.content.iv[1] = content_info.index & 0xFF; // IOS returns a content ID which is passed to further content calls. return static_cast(ret); @@ -652,10 +658,11 @@ IPCCommandResult ES::ExportContentBegin(Context& context, const IOCtlVRequest& r ReturnCode ES::ExportContentData(Context& context, u32 content_fd, u8* data, u32 data_size) { - if (!context.title_import.valid || !context.title_import.content.valid || !data || data_size == 0) + if (!context.title_import_export.valid || !context.title_import_export.content.valid || !data || + data_size == 0) { CloseContent(content_fd, 0); - context.title_import = {}; + context.title_import_export = {}; return ES_EINVAL; } @@ -664,7 +671,7 @@ ReturnCode ES::ExportContentData(Context& context, u32 content_fd, u8* data, u32 if (read_size < 0) { CloseContent(content_fd, 0); - context.title_import = {}; + context.title_import_export = {}; return ES_SHORT_READ; } @@ -672,9 +679,9 @@ ReturnCode ES::ExportContentData(Context& context, u32 content_fd, u8* data, u32 // let's just follow IOS here. buffer.resize(Common::AlignUp(buffer.size(), 32)); - const std::vector output = - Common::AES::Encrypt(context.title_import.key.data(), context.title_import.content.iv.data(), - buffer.data(), buffer.size()); + const std::vector output = Common::AES::Encrypt(context.title_import_export.key.data(), + context.title_import_export.content.iv.data(), + buffer.data(), buffer.size()); std::copy(output.cbegin(), output.cend(), data); return IPC_SUCCESS; } @@ -696,7 +703,7 @@ IPCCommandResult ES::ExportContentData(Context& context, const IOCtlVRequest& re ReturnCode ES::ExportContentEnd(Context& context, u32 content_fd) { - if (!context.title_import.valid || !context.title_import.content.valid) + if (!context.title_import_export.valid || !context.title_import_export.content.valid) return ES_EINVAL; return CloseContent(content_fd, 0); } @@ -712,7 +719,7 @@ IPCCommandResult ES::ExportContentEnd(Context& context, const IOCtlVRequest& req ReturnCode ES::ExportTitleDone(Context& context) { - context.title_import = {}; + context.title_import_export = {}; return IPC_SUCCESS; }