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,