From 3cf3b50ab08dc0300e93b7cf81ed5997a09d8f78 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Mon, 23 Jan 2017 22:15:02 -0500 Subject: [PATCH 1/3] ES: Minor header reorganization Places elements in the order: - Structs/Enums definitions - Functions - Member variables --- Source/Core/Core/IOS/ES/ES.h | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index f1241e63fd..0571c37f8a 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -138,21 +138,6 @@ private: u32 m_Size; }; - typedef std::map CContentAccessMap; - CContentAccessMap m_ContentAccessMap; - - std::vector m_TitleIDs; - u64 m_TitleID = -1; - u32 m_AccessIdentID = 0x6000000; - - // For title installation (ioctls IOCTL_ES_ADDTITLE*). - TMDReader m_addtitle_tmd; - u32 m_addtitle_content_id = 0xFFFFFFFF; - std::vector m_addtitle_content_buffer; - - const DiscIO::CNANDContentLoader& AccessContentDevice(u64 title_id); - u32 OpenTitleContent(u32 CFD, u64 TitleID, u16 Index); - struct ecc_cert_t { u32 sig_type; @@ -165,6 +150,21 @@ private: u8 ecc_pubkey[0x3c]; u8 padding[0x3c]; }; + + const DiscIO::CNANDContentLoader& AccessContentDevice(u64 title_id); + u32 OpenTitleContent(u32 CFD, u64 TitleID, u16 Index); + + typedef std::map CContentAccessMap; + CContentAccessMap m_ContentAccessMap; + + std::vector m_TitleIDs; + u64 m_TitleID = -1; + u32 m_AccessIdentID = 0x6000000; + + // For title installation (ioctls IOCTL_ES_ADDTITLE*). + TMDReader m_addtitle_tmd; + u32 m_addtitle_content_id = 0xFFFFFFFF; + std::vector m_addtitle_content_buffer; }; } // namespace Device } // namespace HLE From 3c88c248dda790dd8bd1b7a734258ac58981b10c Mon Sep 17 00:00:00 2001 From: Lioncash Date: Mon, 23 Jan 2017 22:16:34 -0500 Subject: [PATCH 2/3] ES: Convert typedef into a using alias --- Source/Core/Core/IOS/ES/ES.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 0571c37f8a..cf9642edd4 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -154,7 +154,7 @@ private: const DiscIO::CNANDContentLoader& AccessContentDevice(u64 title_id); u32 OpenTitleContent(u32 CFD, u64 TitleID, u16 Index); - typedef std::map CContentAccessMap; + using CContentAccessMap = std::map; CContentAccessMap m_ContentAccessMap; std::vector m_TitleIDs; From ac973e61bbc2309ca76e7cf93d157c5a1a6342df Mon Sep 17 00:00:00 2001 From: Lioncash Date: Mon, 23 Jan 2017 22:22:31 -0500 Subject: [PATCH 3/3] ES: Separate behavior of IOCtlV into separate functions This function is exceptionally large. Everything within a switch like this also makes it quite error prone. Separating the functions out makes it easier to change a certain request implementation as well as improving code locality. --- Source/Core/Core/IOS/ES/ES.cpp | 1906 +++++++++++++++++--------------- Source/Core/Core/IOS/ES/ES.h | 38 + 2 files changed, 1029 insertions(+), 915 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 1cac5ee73c..b974c92a7c 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -249,970 +249,1046 @@ u32 ES::OpenTitleContent(u32 CFD, u64 TitleID, u16 Index) IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request) { DEBUG_LOG(IOS_ES, "%s (0x%x)", GetDeviceName().c_str(), request.request); + // Clear the IO buffers. Note that this is unsafe for other ioctlvs. for (const auto& io_vector : request.io_vectors) { if (!request.HasInputVectorWithAddress(io_vector.address)) Memory::Memset(io_vector.address, 0, io_vector.size); } + switch (request.request) { case IOCTL_ES_ADDTICKET: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 3, - "IOCTL_ES_ADDTICKET wrong number of inputs"); - - INFO_LOG(IOS_ES, "IOCTL_ES_ADDTICKET"); - std::vector ticket(request.in_vectors[0].size); - Memory::CopyFromEmu(ticket.data(), request.in_vectors[0].address, request.in_vectors[0].size); - DiscIO::AddTicket(ticket); - break; - } - + return AddTicket(request); case IOCTL_ES_ADDTITLESTART: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 4, - "IOCTL_ES_ADDTITLESTART wrong number of inputs"); - - 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); - - m_addtitle_tmd.SetBytes(tmd); - if (!m_addtitle_tmd.IsValid()) - { - ERROR_LOG(IOS_ES, "Invalid TMD while adding title (size = %zd)", tmd.size()); - return GetDefaultReply(ES_INVALID_TMD); - } - - // Write the TMD to title storage. - std::string tmd_path = - Common::GetTMDFileName(m_addtitle_tmd.GetTitleId(), Common::FROM_SESSION_ROOT); - File::CreateFullPath(tmd_path); - - File::IOFile fp(tmd_path, "wb"); - fp.WriteBytes(tmd.data(), tmd.size()); - break; - } - + return AddTitleStart(request); case IOCTL_ES_ADDCONTENTSTART: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 2, - "IOCTL_ES_ADDCONTENTSTART wrong number of inputs"); - - u64 title_id = Memory::Read_U64(request.in_vectors[0].address); - u32 content_id = Memory::Read_U32(request.in_vectors[1].address); - - if (m_addtitle_content_id != 0xFFFFFFFF) - { - ERROR_LOG(IOS_ES, "Trying to add content when we haven't finished adding " - "another content. Unsupported."); - return GetDefaultReply(ES_WRITE_FAILURE); - } - m_addtitle_content_id = content_id; - - m_addtitle_content_buffer.clear(); - - INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTSTART: title id %016" PRIx64 ", " - "content id %08x", - title_id, m_addtitle_content_id); - - if (title_id != m_addtitle_tmd.GetTitleId()) - { - ERROR_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTSTART: title id %016" PRIx64 " != " - "TMD title id %016lx, ignoring", - title_id, m_addtitle_tmd.GetTitleId()); - } - - // 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. - // Instead we just log an error (see above) if this condition is detected. - s32 content_fd = 0; - return GetDefaultReply(content_fd); - } - + return AddContentStart(request); case IOCTL_ES_ADDCONTENTDATA: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 2, - "IOCTL_ES_ADDCONTENTDATA wrong number of inputs"); - - 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; - m_addtitle_content_buffer.insert(m_addtitle_content_buffer.end(), data_start, data_end); - break; - } - + return AddContentData(request); case IOCTL_ES_ADDCONTENTFINISH: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 1, - "IOCTL_ES_ADDCONTENTFINISH wrong number of inputs"); - - u32 content_fd = Memory::Read_U32(request.in_vectors[0].address); - INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTFINISH: content fd %08x", content_fd); - - // Try to find the title key from a pre-installed ticket. - std::vector ticket = DiscIO::FindSignedTicket(m_addtitle_tmd.GetTitleId()); - if (ticket.size() == 0) - { - return GetDefaultReply(ES_NO_TICKET_INSTALLED); - } - - mbedtls_aes_context aes_ctx; - mbedtls_aes_setkey_dec(&aes_ctx, DiscIO::GetKeyFromTicket(ticket).data(), 128); - - // The IV for title content decryption is the lower two bytes of the - // content index, zero extended. - TMDReader::Content content_info; - if (!m_addtitle_tmd.FindContentById(m_addtitle_content_id, &content_info)) - { - return GetDefaultReply(ES_INVALID_TMD); - } - u8 iv[16] = {0}; - iv[0] = (content_info.index >> 8) & 0xFF; - iv[1] = content_info.index & 0xFF; - std::vector decrypted_data(m_addtitle_content_buffer.size()); - mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, m_addtitle_content_buffer.size(), iv, - m_addtitle_content_buffer.data(), decrypted_data.data()); - - std::string path = StringFromFormat( - "%s%08x.app", - Common::GetTitleContentPath(m_addtitle_tmd.GetTitleId(), Common::FROM_SESSION_ROOT).c_str(), - m_addtitle_content_id); - - File::IOFile fp(path, "wb"); - fp.WriteBytes(decrypted_data.data(), decrypted_data.size()); - - m_addtitle_content_id = 0xFFFFFFFF; - break; - } - + return AddContentFinish(request); case IOCTL_ES_ADDTITLEFINISH: - { - INFO_LOG(IOS_ES, "IOCTL_ES_ADDTITLEFINISH"); - break; - } - + return AddTitleFinish(request); case IOCTL_ES_GETDEVICEID: - { - _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, "IOCTL_ES_GETDEVICEID no io vectors"); - - EcWii& ec = EcWii::GetInstance(); - INFO_LOG(IOS_ES, "IOCTL_ES_GETDEVICEID %08X", ec.getNgId()); - Memory::Write_U32(ec.getNgId(), request.io_vectors[0].address); - return GetDefaultReply(IPC_SUCCESS); - } - + return ESGetDeviceID(request); case IOCTL_ES_GETTITLECONTENTSCNT: - { - _dbg_assert_(IOS_ES, request.in_vectors.size() == 1); - _dbg_assert_(IOS_ES, request.io_vectors.size() == 1); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - - const DiscIO::CNANDContentLoader& rNANDContent = AccessContentDevice(TitleID); - u16 NumberOfPrivateContent = 0; - s32 return_value = IPC_SUCCESS; - if (rNANDContent.IsValid()) // Not sure if dolphin will ever fail this check - { - NumberOfPrivateContent = rNANDContent.GetNumEntries(); - - if ((u32)(TitleID >> 32) == 0x00010000) - Memory::Write_U32(0, request.io_vectors[0].address); - else - Memory::Write_U32(NumberOfPrivateContent, request.io_vectors[0].address); - } - else - { - return_value = static_cast(rNANDContent.GetContentSize()); - } - - INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLECONTENTSCNT: TitleID: %08x/%08x content count %i", - (u32)(TitleID >> 32), (u32)TitleID, - rNANDContent.IsValid() ? NumberOfPrivateContent : (u32)rNANDContent.GetContentSize()); - - return GetDefaultReply(return_value); - } - break; - + return GetTitleContentsCount(request); case IOCTL_ES_GETTITLECONTENTS: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 2, - "IOCTL_ES_GETTITLECONTENTS bad in buffer"); - _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, - "IOCTL_ES_GETTITLECONTENTS bad out buffer"); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - - const DiscIO::CNANDContentLoader& rNANDContent = AccessContentDevice(TitleID); - s32 return_value = IPC_SUCCESS; - if (rNANDContent.IsValid()) // Not sure if dolphin will ever fail this check - { - for (u16 i = 0; i < rNANDContent.GetNumEntries(); i++) - { - Memory::Write_U32(rNANDContent.GetContentByIndex(i)->m_ContentID, - request.io_vectors[0].address + i * 4); - INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLECONTENTS: Index %d: %08x", i, - rNANDContent.GetContentByIndex(i)->m_ContentID); - } - } - else - { - return_value = static_cast(rNANDContent.GetContentSize()); - ERROR_LOG(IOS_ES, "IOCTL_ES_GETTITLECONTENTS: Unable to open content %zu", - rNANDContent.GetContentSize()); - } - - return GetDefaultReply(return_value); - } - break; - + return GetTitleContents(request); case IOCTL_ES_OPENTITLECONTENT: - { - _dbg_assert_(IOS_ES, request.in_vectors.size() == 3); - _dbg_assert_(IOS_ES, request.io_vectors.size() == 0); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - u32 Index = Memory::Read_U32(request.in_vectors[2].address); - - s32 CFD = OpenTitleContent(m_AccessIdentID++, TitleID, Index); - - INFO_LOG(IOS_ES, "IOCTL_ES_OPENTITLECONTENT: TitleID: %08x/%08x Index %i -> got CFD %x", - (u32)(TitleID >> 32), (u32)TitleID, Index, CFD); - - return GetDefaultReply(CFD); - } - break; - + return OpenTitleContent(request); case IOCTL_ES_OPENCONTENT: - { - _dbg_assert_(IOS_ES, request.in_vectors.size() == 1); - _dbg_assert_(IOS_ES, request.io_vectors.size() == 0); - u32 Index = Memory::Read_U32(request.in_vectors[0].address); - - s32 CFD = OpenTitleContent(m_AccessIdentID++, m_TitleID, Index); - INFO_LOG(IOS_ES, "IOCTL_ES_OPENCONTENT: Index %i -> got CFD %x", Index, CFD); - - return GetDefaultReply(CFD); - } - break; - + return OpenContent(request); case IOCTL_ES_READCONTENT: - { - _dbg_assert_(IOS_ES, request.in_vectors.size() == 1); - _dbg_assert_(IOS_ES, request.io_vectors.size() == 1); - - u32 CFD = Memory::Read_U32(request.in_vectors[0].address); - u32 Size = request.io_vectors[0].size; - u32 Addr = request.io_vectors[0].address; - - auto itr = m_ContentAccessMap.find(CFD); - if (itr == m_ContentAccessMap.end()) - { - return GetDefaultReply(-1); - } - SContentAccess& rContent = itr->second; - - u8* pDest = Memory::GetPointer(Addr); - - if (rContent.m_Position + Size > rContent.m_Size) - { - Size = rContent.m_Size - rContent.m_Position; - } - - if (Size > 0) - { - if (pDest) - { - const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(rContent.m_TitleID); - // ContentLoader should never be invalid; rContent has been created by it. - if (ContentLoader.IsValid()) - { - const DiscIO::SNANDContent* pContent = ContentLoader.GetContentByIndex(rContent.m_Index); - if (!pContent->m_Data->GetRange(rContent.m_Position, Size, pDest)) - ERROR_LOG(IOS_ES, "ES: failed to read %u bytes from %u!", Size, rContent.m_Position); - } - - rContent.m_Position += Size; - } - else - { - PanicAlert("IOCTL_ES_READCONTENT - bad destination"); - } - } - - DEBUG_LOG(IOS_ES, - "IOCTL_ES_READCONTENT: CFD %x, Address 0x%x, Size %i -> stream pos %i (Index %i)", - CFD, Addr, Size, rContent.m_Position, rContent.m_Index); - - return GetDefaultReply(Size); - } - break; - + return ReadContent(request); case IOCTL_ES_CLOSECONTENT: - { - _dbg_assert_(IOS_ES, request.in_vectors.size() == 1); - _dbg_assert_(IOS_ES, request.io_vectors.size() == 0); - - u32 CFD = Memory::Read_U32(request.in_vectors[0].address); - - INFO_LOG(IOS_ES, "IOCTL_ES_CLOSECONTENT: CFD %x", CFD); - - auto itr = m_ContentAccessMap.find(CFD); - if (itr == m_ContentAccessMap.end()) - { - return GetDefaultReply(-1); - } - - const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(itr->second.m_TitleID); - // ContentLoader should never be invalid; we shouldn't be here if ES_OPENCONTENT failed before. - if (ContentLoader.IsValid()) - { - const DiscIO::SNANDContent* pContent = ContentLoader.GetContentByIndex(itr->second.m_Index); - pContent->m_Data->Close(); - } - - m_ContentAccessMap.erase(itr); - - return GetDefaultReply(IPC_SUCCESS); - } - break; - + return CloseContent(request); case IOCTL_ES_SEEKCONTENT: - { - _dbg_assert_(IOS_ES, request.in_vectors.size() == 3); - _dbg_assert_(IOS_ES, request.io_vectors.size() == 0); - - u32 CFD = Memory::Read_U32(request.in_vectors[0].address); - u32 Addr = Memory::Read_U32(request.in_vectors[1].address); - u32 Mode = Memory::Read_U32(request.in_vectors[2].address); - - auto itr = m_ContentAccessMap.find(CFD); - if (itr == m_ContentAccessMap.end()) - { - return GetDefaultReply(-1); - } - SContentAccess& rContent = itr->second; - - switch (Mode) - { - case 0: // SET - rContent.m_Position = Addr; - break; - - case 1: // CUR - rContent.m_Position += Addr; - break; - - case 2: // END - rContent.m_Position = rContent.m_Size + Addr; - break; - } - - DEBUG_LOG(IOS_ES, "IOCTL_ES_SEEKCONTENT: CFD %x, Address 0x%x, Mode %i -> Pos %i", CFD, Addr, - Mode, rContent.m_Position); - - return GetDefaultReply(rContent.m_Position); - } - break; - + return SeekContent(request); case IOCTL_ES_GETTITLEDIR: - { - _dbg_assert_(IOS_ES, request.in_vectors.size() == 1); - _dbg_assert_(IOS_ES, request.io_vectors.size() == 1); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - - char* Path = (char*)Memory::GetPointer(request.io_vectors[0].address); - sprintf(Path, "/title/%08x/%08x/data", (u32)(TitleID >> 32), (u32)TitleID); - - INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLEDIR: %s", Path); - } - break; - + return GetTitleDirectory(request); case IOCTL_ES_GETTITLEID: - { - _dbg_assert_(IOS_ES, request.in_vectors.size() == 0); - _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, "IOCTL_ES_GETTITLEID no out buffer"); - - Memory::Write_U64(m_TitleID, request.io_vectors[0].address); - INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLEID: %08x/%08x", (u32)(m_TitleID >> 32), (u32)m_TitleID); - } - break; - + return GetTitleID(request); case IOCTL_ES_SETUID: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 1, "IOCTL_ES_SETUID no in buffer"); - _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 0, - "IOCTL_ES_SETUID has a payload, it shouldn't"); - // TODO: fs permissions based on this - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - INFO_LOG(IOS_ES, "IOCTL_ES_SETUID titleID: %08x/%08x", (u32)(TitleID >> 32), (u32)TitleID); - } - break; - + return SetUID(request); case IOCTL_ES_GETTITLECNT: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 0, - "IOCTL_ES_GETTITLECNT has an in buffer"); - _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, - "IOCTL_ES_GETTITLECNT has no out buffer"); - _dbg_assert_msg_(IOS_ES, request.io_vectors[0].size == 4, - "IOCTL_ES_GETTITLECNT payload[0].size != 4"); - - Memory::Write_U32((u32)m_TitleIDs.size(), request.io_vectors[0].address); - - INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLECNT: Number of Titles %zu", m_TitleIDs.size()); - - return GetDefaultReply(IPC_SUCCESS); - } - break; - + return GetTitleCount(request); case IOCTL_ES_GETTITLES: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 1, "IOCTL_ES_GETTITLES has an in buffer"); - _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, - "IOCTL_ES_GETTITLES has no out buffer"); - - u32 MaxCount = Memory::Read_U32(request.in_vectors[0].address); - u32 Count = 0; - for (int i = 0; i < (int)m_TitleIDs.size(); i++) - { - Memory::Write_U64(m_TitleIDs[i], request.io_vectors[0].address + i * 8); - INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLES: %08x/%08x", (u32)(m_TitleIDs[i] >> 32), - (u32)m_TitleIDs[i]); - Count++; - if (Count >= MaxCount) - break; - } - - INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLES: Number of titles returned %i", Count); - return GetDefaultReply(IPC_SUCCESS); - } - break; - + return GetTitles(request); case IOCTL_ES_GETVIEWCNT: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 1, "IOCTL_ES_GETVIEWCNT no in buffer"); - _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, "IOCTL_ES_GETVIEWCNT no out buffer"); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - - u32 retVal = 0; - const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); - u32 ViewCount = - static_cast(Loader.GetTicket().size()) / DiscIO::CNANDContentLoader::TICKET_SIZE; - - if (!ViewCount) - { - std::string TicketFilename = Common::GetTicketFileName(TitleID, Common::FROM_SESSION_ROOT); - if (File::Exists(TicketFilename)) - { - u32 FileSize = (u32)File::GetSize(TicketFilename); - _dbg_assert_msg_(IOS_ES, (FileSize % DiscIO::CNANDContentLoader::TICKET_SIZE) == 0, - "IOCTL_ES_GETVIEWCNT ticket file size seems to be wrong"); - - ViewCount = FileSize / DiscIO::CNANDContentLoader::TICKET_SIZE; - _dbg_assert_msg_(IOS_ES, (ViewCount > 0) && (ViewCount <= 4), - "IOCTL_ES_GETVIEWCNT ticket count seems to be wrong"); - } - else if (TitleID >> 32 == 0x00000001) - { - // Fake a ticket view to make IOS reload work. - ViewCount = 1; - } - else - { - ViewCount = 0; - if (TitleID == TITLEID_SYSMENU) - { - PanicAlertT("There must be a ticket for 00000001/00000002. Your NAND dump is probably " - "incomplete."); - } - // retVal = ES_NO_TICKET_INSTALLED; - } - } - - INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWCNT for titleID: %08x/%08x (View Count = %i)", - (u32)(TitleID >> 32), (u32)TitleID, ViewCount); - - Memory::Write_U32(ViewCount, request.io_vectors[0].address); - return GetDefaultReply(retVal); - } - break; - + return GetViewCount(request); case IOCTL_ES_GETVIEWS: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 2, "IOCTL_ES_GETVIEWS no in buffer"); - _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, "IOCTL_ES_GETVIEWS no out buffer"); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - u32 maxViews = Memory::Read_U32(request.in_vectors[1].address); - u32 retVal = 0; - - const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); - - const std::vector& ticket = Loader.GetTicket(); - - if (ticket.empty()) - { - std::string TicketFilename = Common::GetTicketFileName(TitleID, Common::FROM_SESSION_ROOT); - if (File::Exists(TicketFilename)) - { - File::IOFile pFile(TicketFilename, "rb"); - if (pFile) - { - u8 FileTicket[DiscIO::CNANDContentLoader::TICKET_SIZE]; - for (unsigned int View = 0; - View != maxViews && - pFile.ReadBytes(FileTicket, DiscIO::CNANDContentLoader::TICKET_SIZE); - ++View) - { - Memory::Write_U32(View, request.io_vectors[0].address + View * 0xD8); - Memory::CopyToEmu(request.io_vectors[0].address + 4 + View * 0xD8, FileTicket + 0x1D0, - 212); - } - } - } - else if (TitleID >> 32 == 0x00000001) - { - // For IOS titles, the ticket view isn't normally parsed by either the - // SDK or libogc, just passed to LaunchTitle, so this - // shouldn't matter at all. Just fill out some fields just - // to be on the safe side. - u32 Address = request.io_vectors[0].address; - Memory::Memset(Address, 0, 0xD8); - Memory::Write_U64(TitleID, Address + 4 + (0x1dc - 0x1d0)); // title ID - Memory::Write_U16(0xffff, Address + 4 + (0x1e4 - 0x1d0)); // unnnown - Memory::Write_U32(0xff00, Address + 4 + (0x1ec - 0x1d0)); // access mask - Memory::Memset(Address + 4 + (0x222 - 0x1d0), 0xff, 0x20); // content permissions - } - else - { - // retVal = ES_NO_TICKET_INSTALLED; - PanicAlertT("IOCTL_ES_GETVIEWS: Tried to get data from an unknown ticket: %08x/%08x", - (u32)(TitleID >> 32), (u32)TitleID); - } - } - else - { - u32 view_count = - static_cast(Loader.GetTicket().size()) / DiscIO::CNANDContentLoader::TICKET_SIZE; - for (unsigned int view = 0; view != maxViews && view < view_count; ++view) - { - Memory::Write_U32(view, request.io_vectors[0].address + view * 0xD8); - Memory::CopyToEmu(request.io_vectors[0].address + 4 + view * 0xD8, - &ticket[0x1D0 + (view * DiscIO::CNANDContentLoader::TICKET_SIZE)], 212); - } - } - - INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWS for titleID: %08x/%08x (MaxViews = %i)", - (u32)(TitleID >> 32), (u32)TitleID, maxViews); - - return GetDefaultReply(retVal); - } - break; - + return GetViews(request); case IOCTL_ES_GETTMDVIEWCNT: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 1, "IOCTL_ES_GETTMDVIEWCNT no in buffer"); - _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, - "IOCTL_ES_GETTMDVIEWCNT no out buffer"); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - - const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); - - u32 TMDViewCnt = 0; - if (Loader.IsValid()) - { - TMDViewCnt += DiscIO::CNANDContentLoader::TMD_VIEW_SIZE; - TMDViewCnt += 2; // title version - TMDViewCnt += 2; // num entries - TMDViewCnt += - (u32)Loader.GetContentSize() * (4 + 2 + 2 + 8); // content id, index, type, size - } - Memory::Write_U32(TMDViewCnt, request.io_vectors[0].address); - - INFO_LOG(IOS_ES, "IOCTL_ES_GETTMDVIEWCNT: title: %08x/%08x (view size %i)", - (u32)(TitleID >> 32), (u32)TitleID, TMDViewCnt); - return GetDefaultReply(IPC_SUCCESS); - } - break; - + return GetTMDViewCount(request); case IOCTL_ES_GETTMDVIEWS: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 2, "IOCTL_ES_GETTMDVIEWCNT no in buffer"); - _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, - "IOCTL_ES_GETTMDVIEWCNT no out buffer"); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - u32 MaxCount = Memory::Read_U32(request.in_vectors[1].address); - - const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); - - INFO_LOG(IOS_ES, "IOCTL_ES_GETTMDVIEWCNT: title: %08x/%08x buffer size: %i", - (u32)(TitleID >> 32), (u32)TitleID, MaxCount); - - if (Loader.IsValid()) - { - u32 Address = request.io_vectors[0].address; - - Memory::CopyToEmu(Address, Loader.GetTMDView(), DiscIO::CNANDContentLoader::TMD_VIEW_SIZE); - Address += DiscIO::CNANDContentLoader::TMD_VIEW_SIZE; - - Memory::Write_U16(Loader.GetTitleVersion(), Address); - Address += 2; - Memory::Write_U16(Loader.GetNumEntries(), Address); - Address += 2; - - const std::vector& rContent = Loader.GetContent(); - for (size_t i = 0; i < Loader.GetContentSize(); i++) - { - Memory::Write_U32(rContent[i].m_ContentID, Address); - Address += 4; - Memory::Write_U16(rContent[i].m_Index, Address); - Address += 2; - Memory::Write_U16(rContent[i].m_Type, Address); - Address += 2; - Memory::Write_U64(rContent[i].m_Size, Address); - Address += 8; - } - - _dbg_assert_(IOS_ES, (Address - request.io_vectors[0].address) == request.io_vectors[0].size); - } - - INFO_LOG(IOS_ES, "IOCTL_ES_GETTMDVIEWS: title: %08x/%08x (buffer size: %i)", - (u32)(TitleID >> 32), (u32)TitleID, MaxCount); - return GetDefaultReply(IPC_SUCCESS); - } - break; - - case IOCTL_ES_GETCONSUMPTION: // This is at least what crediar's ES module does - Memory::Write_U32(0, request.io_vectors[1].address); - INFO_LOG(IOS_ES, "IOCTL_ES_GETCONSUMPTION"); - return GetDefaultReply(IPC_SUCCESS); - + return GetTMDViews(request); + case IOCTL_ES_GETCONSUMPTION: + return GetConsumption(request); case IOCTL_ES_DELETETICKET: - { - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - INFO_LOG(IOS_ES, "IOCTL_ES_DELETETICKET: title: %08x/%08x", (u32)(TitleID >> 32), (u32)TitleID); - // Presumably return -1017 when delete fails - if (!File::Delete(Common::GetTicketFileName(TitleID, Common::FROM_SESSION_ROOT))) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - return GetDefaultReply(IPC_SUCCESS); - } - + return DeleteTicket(request); case IOCTL_ES_DELETETITLECONTENT: - { - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - INFO_LOG(IOS_ES, "IOCTL_ES_DELETETITLECONTENT: title: %08x/%08x", (u32)(TitleID >> 32), - (u32)TitleID); - // Presumably return -1017 when title not installed TODO verify - if (!DiscIO::CNANDContentManager::Access().RemoveTitle(TitleID, Common::FROM_SESSION_ROOT)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - return GetDefaultReply(IPC_SUCCESS); - } - + return DeleteTitleContent(request); case IOCTL_ES_GETSTOREDTMDSIZE: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 1, - "IOCTL_ES_GETSTOREDTMDSIZE no in buffer"); - // _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, "IOCTL_ES_ES_GETSTOREDTMDSIZE - // no out buffer"); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); - - _dbg_assert_(IOS_ES, Loader.IsValid()); - u32 TMDCnt = 0; - if (Loader.IsValid()) - { - TMDCnt += DiscIO::CNANDContentLoader::TMD_HEADER_SIZE; - TMDCnt += (u32)Loader.GetContentSize() * DiscIO::CNANDContentLoader::CONTENT_HEADER_SIZE; - } - if (request.io_vectors.size()) - Memory::Write_U32(TMDCnt, request.io_vectors[0].address); - - INFO_LOG(IOS_ES, "IOCTL_ES_GETSTOREDTMDSIZE: title: %08x/%08x (view size %i)", - (u32)(TitleID >> 32), (u32)TitleID, TMDCnt); - return GetDefaultReply(IPC_SUCCESS); - } - break; + return GetStoredTMDSize(request); case IOCTL_ES_GETSTOREDTMD: - { - _dbg_assert_msg_(IOS_ES, request.in_vectors.size() > 0, "IOCTL_ES_GETSTOREDTMD no in buffer"); - // requires 1 inbuffer and no outbuffer, presumably outbuffer required when second inbuffer is - // used for maxcount (allocated mem?) - // called with 1 inbuffer after deleting a titleid - //_dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, "IOCTL_ES_GETSTOREDTMD no out - // buffer"); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - u32 MaxCount = 0; - if (request.in_vectors.size() > 1) - { - // TODO: actually use this param in when writing to the outbuffer :/ - MaxCount = Memory::Read_U32(request.in_vectors[1].address); - } - const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); - - INFO_LOG(IOS_ES, "IOCTL_ES_GETSTOREDTMD: title: %08x/%08x buffer size: %i", - (u32)(TitleID >> 32), (u32)TitleID, MaxCount); - - if (Loader.IsValid() && request.io_vectors.size()) - { - u32 Address = request.io_vectors[0].address; - - Memory::CopyToEmu(Address, Loader.GetTMDHeader(), - DiscIO::CNANDContentLoader::TMD_HEADER_SIZE); - Address += DiscIO::CNANDContentLoader::TMD_HEADER_SIZE; - - const std::vector& rContent = Loader.GetContent(); - for (size_t i = 0; i < Loader.GetContentSize(); i++) - { - Memory::CopyToEmu(Address, rContent[i].m_Header, - DiscIO::CNANDContentLoader::CONTENT_HEADER_SIZE); - Address += DiscIO::CNANDContentLoader::CONTENT_HEADER_SIZE; - } - - _dbg_assert_(IOS_ES, (Address - request.io_vectors[0].address) == request.io_vectors[0].size); - } - - INFO_LOG(IOS_ES, "IOCTL_ES_GETSTOREDTMD: title: %08x/%08x (buffer size: %i)", - (u32)(TitleID >> 32), (u32)TitleID, MaxCount); - return GetDefaultReply(IPC_SUCCESS); - } - break; - + return GetStoredTMD(request); case IOCTL_ES_ENCRYPT: - { - u32 keyIndex = Memory::Read_U32(request.in_vectors[0].address); - u8* IV = Memory::GetPointer(request.in_vectors[1].address); - u8* source = Memory::GetPointer(request.in_vectors[2].address); - u32 size = request.in_vectors[2].size; - u8* newIV = Memory::GetPointer(request.io_vectors[0].address); - u8* destination = Memory::GetPointer(request.io_vectors[1].address); - - mbedtls_aes_context AES_ctx; - mbedtls_aes_setkey_enc(&AES_ctx, s_key_table[keyIndex], 128); - memcpy(newIV, IV, 16); - mbedtls_aes_crypt_cbc(&AES_ctx, MBEDTLS_AES_ENCRYPT, size, newIV, source, destination); - - _dbg_assert_msg_(IOS_ES, keyIndex == 6, - "IOCTL_ES_ENCRYPT: Key type is not SD, data will be crap"); - } - break; - + return Encrypt(request); case IOCTL_ES_DECRYPT: - { - u32 keyIndex = Memory::Read_U32(request.in_vectors[0].address); - u8* IV = Memory::GetPointer(request.in_vectors[1].address); - u8* source = Memory::GetPointer(request.in_vectors[2].address); - u32 size = request.in_vectors[2].size; - u8* newIV = Memory::GetPointer(request.io_vectors[0].address); - u8* destination = Memory::GetPointer(request.io_vectors[1].address); - - DecryptContent(keyIndex, IV, source, size, newIV, destination); - - _dbg_assert_msg_(IOS_ES, keyIndex == 6, - "IOCTL_ES_DECRYPT: Key type is not SD, data will be crap"); - } - break; - + return Decrypt(request); case IOCTL_ES_LAUNCH: - { - _dbg_assert_(IOS_ES, request.in_vectors.size() == 2); - bool bSuccess = false; - bool bReset = false; - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - u32 view = Memory::Read_U32(request.in_vectors[1].address); - u64 ticketid = Memory::Read_U64(request.in_vectors[1].address + 4); - u32 devicetype = Memory::Read_U32(request.in_vectors[1].address + 12); - u64 titleid = Memory::Read_U64(request.in_vectors[1].address + 16); - u16 access = Memory::Read_U16(request.in_vectors[1].address + 24); - - // ES_LAUNCH should probably reset thw whole state, which at least means closing all open files. - // leaving them open through ES_LAUNCH may cause hangs and other funky behavior - // (supposedly when trying to re-open those files). - DiscIO::CNANDContentManager::Access().ClearCache(); - - u64 ios_to_load = 0; - std::string tContentFile; - if ((u32)(TitleID >> 32) == 0x00000001 && TitleID != TITLEID_SYSMENU) - { - ios_to_load = TitleID; - bSuccess = true; - } - else - { - const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(TitleID); - if (ContentLoader.IsValid()) - { - ios_to_load = 0x0000000100000000ULL | ContentLoader.GetIosVersion(); - - u32 bootInd = ContentLoader.GetBootIndex(); - const DiscIO::SNANDContent* pContent = ContentLoader.GetContentByIndex(bootInd); - if (pContent) - { - tContentFile = Common::GetTitleContentPath(TitleID, Common::FROM_SESSION_ROOT); - std::unique_ptr pDolLoader = - std::make_unique(pContent->m_Data->Get()); - - if (pDolLoader->IsValid()) - { - pDolLoader->Load(); - // TODO: Check why sysmenu does not load the DOL correctly - // NAND titles start with address translation off at 0x3400 (via the PPC bootstub) - // - // The state of other CPU registers (like the BAT registers) doesn't matter much - // because the realmode code at 0x3400 initializes everything itself anyway. - MSR = 0; - PC = 0x3400; - bSuccess = true; - } - else - { - PanicAlertT("IOCTL_ES_LAUNCH: The DOL file is invalid!"); - } - } - } - } - - if (!bSuccess) - { - PanicAlertT( - "IOCTL_ES_LAUNCH: Game tried to reload a title that is not available in your NAND dump\n" - "TitleID %016" PRIx64 ".\n Dolphin will likely hang now.", - TitleID); - } - else - { - bool* wiiMoteConnected = new bool[MAX_BBMOTES]; - if (!SConfig::GetInstance().m_bt_passthrough_enabled) - { - BluetoothEmu* s_Usb = GetUsbPointer(); - for (unsigned int i = 0; i < MAX_BBMOTES; i++) - wiiMoteConnected[i] = s_Usb->m_WiiMotes[i].IsConnected(); - } - - Reset(true); - Reinit(); - SetupMemory(ios_to_load); - bReset = true; - - if (!SConfig::GetInstance().m_bt_passthrough_enabled) - { - BluetoothEmu* s_Usb = GetUsbPointer(); - for (unsigned int i = 0; i < MAX_BBMOTES; i++) - { - if (wiiMoteConnected[i]) - { - s_Usb->m_WiiMotes[i].Activate(false); - s_Usb->m_WiiMotes[i].Activate(true); - } - else - { - s_Usb->m_WiiMotes[i].Activate(false); - } - } - } - delete[] wiiMoteConnected; - SetDefaultContentFile(tContentFile); - } - - // Note: If we just reset the PPC, don't write anything to the command buffer. This - // could clobber the DOL we just loaded. - - ERROR_LOG(IOS_ES, "IOCTL_ES_LAUNCH %016" PRIx64 " %08x %016" PRIx64 " %08x %016" PRIx64 " %04x", - TitleID, view, ticketid, devicetype, titleid, access); - // IOCTL_ES_LAUNCH 0001000248414341 00000001 0001c0fef3df2cfa 00000000 - // 0001000248414341 ffff - - // This is necessary because Reset(true) above deleted this object. Ew. - - if (!bReset) - { - // The command type is overwritten with the reply type. - Memory::Write_U32(IPC_REPLY, request.address); - // IOS also writes back the command that was responded to in the FD field. - Memory::Write_U32(IPC_CMD_IOCTLV, request.address + 8); - } - - // Generate a "reply" to the IPC command. ES_LAUNCH is unique because it - // involves restarting IOS; IOS generates two acknowledgements in a row. - EnqueueCommandAcknowledgement(request.address, 0); - return GetNoReply(); - } - break; - - case IOCTL_ES_CHECKKOREAREGION: // note by DacoTaco : name is unknown, i just tried to name it - // SOMETHING - // IOS70 has this to let system menu 4.2 check if the console is region changed. it returns - // -1017 - // if the IOS didn't find the Korean keys and 0 if it does. 0 leads to a error 003 - INFO_LOG(IOS_ES, "IOCTL_ES_CHECKKOREAREGION: Title checked for Korean keys."); - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - case IOCTL_ES_GETDEVICECERT: // (Input: none, Output: 384 bytes) - { - INFO_LOG(IOS_ES, "IOCTL_ES_GETDEVICECERT"); - _dbg_assert_(IOS_ES, request.io_vectors.size() == 1); - u8* destination = Memory::GetPointer(request.io_vectors[0].address); - - EcWii& ec = EcWii::GetInstance(); - get_ng_cert(destination, ec.getNgId(), ec.getNgKeyId(), ec.getNgPriv(), ec.getNgSig()); - } - break; - + return Launch(request); + case IOCTL_ES_CHECKKOREAREGION: + return CheckKoreaRegion(request); + case IOCTL_ES_GETDEVICECERT: + return GetDeviceCertificate(request); case IOCTL_ES_SIGN: - { - INFO_LOG(IOS_ES, "IOCTL_ES_SIGN"); - u8* ap_cert_out = Memory::GetPointer(request.io_vectors[1].address); - u8* data = Memory::GetPointer(request.in_vectors[0].address); - u32 data_size = request.in_vectors[0].size; - u8* sig_out = Memory::GetPointer(request.io_vectors[0].address); - - EcWii& ec = EcWii::GetInstance(); - get_ap_sig_and_cert(sig_out, ap_cert_out, m_TitleID, data, data_size, ec.getNgPriv(), - ec.getNgId()); - } - break; - + return Sign(request); case IOCTL_ES_GETBOOT2VERSION: - { - INFO_LOG(IOS_ES, "IOCTL_ES_GETBOOT2VERSION"); - - Memory::Write_U32( - 4, request.io_vectors[0].address); // as of 26/02/2012, this was latest bootmii version - } - break; - - // =============================================================================================== - // unsupported functions - // =============================================================================================== - case IOCTL_ES_DIGETTICKETVIEW: // (Input: none, Output: 216 bytes) bug crediar :D - WARN_LOG(IOS_ES, "IOCTL_ES_DIGETTICKETVIEW: this looks really wrong..."); - break; + return GetBoot2Version(request); + // Unsupported functions + case IOCTL_ES_DIGETTICKETVIEW: + return DIGetTicketView(request); case IOCTL_ES_GETOWNEDTITLECNT: - INFO_LOG(IOS_ES, "IOCTL_ES_GETOWNEDTITLECNT"); - Memory::Write_U32(0, request.io_vectors[0].address); - break; - + return GetOwnedTitleCount(request); default: request.DumpUnknown(GetDeviceName(), LogTypes::IOS); + break; } return GetDefaultReply(IPC_SUCCESS); } +IPCCommandResult ES::AddTicket(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 3, + "IOCTL_ES_ADDTICKET wrong number of inputs"); + + INFO_LOG(IOS_ES, "IOCTL_ES_ADDTICKET"); + std::vector ticket(request.in_vectors[0].size); + Memory::CopyFromEmu(ticket.data(), request.in_vectors[0].address, request.in_vectors[0].size); + DiscIO::AddTicket(ticket); + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::AddTitleStart(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 4, + "IOCTL_ES_ADDTITLESTART wrong number of inputs"); + + 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); + + m_addtitle_tmd.SetBytes(tmd); + if (!m_addtitle_tmd.IsValid()) + { + ERROR_LOG(IOS_ES, "Invalid TMD while adding title (size = %zd)", tmd.size()); + return GetDefaultReply(ES_INVALID_TMD); + } + + // Write the TMD to title storage. + std::string tmd_path = + Common::GetTMDFileName(m_addtitle_tmd.GetTitleId(), Common::FROM_SESSION_ROOT); + File::CreateFullPath(tmd_path); + + File::IOFile fp(tmd_path, "wb"); + fp.WriteBytes(tmd.data(), tmd.size()); + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::AddContentStart(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 2, + "IOCTL_ES_ADDCONTENTSTART wrong number of inputs"); + + u64 title_id = Memory::Read_U64(request.in_vectors[0].address); + u32 content_id = Memory::Read_U32(request.in_vectors[1].address); + + if (m_addtitle_content_id != 0xFFFFFFFF) + { + ERROR_LOG(IOS_ES, "Trying to add content when we haven't finished adding " + "another content. Unsupported."); + return GetDefaultReply(ES_WRITE_FAILURE); + } + m_addtitle_content_id = content_id; + + m_addtitle_content_buffer.clear(); + + INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTSTART: title id %016" PRIx64 ", " + "content id %08x", + title_id, m_addtitle_content_id); + + if (title_id != m_addtitle_tmd.GetTitleId()) + { + ERROR_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTSTART: title id %016" PRIx64 " != " + "TMD title id %016lx, ignoring", + title_id, m_addtitle_tmd.GetTitleId()); + } + + // 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. + // Instead we just log an error (see above) if this condition is detected. + s32 content_fd = 0; + return GetDefaultReply(content_fd); +} + +IPCCommandResult ES::AddContentData(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 2, + "IOCTL_ES_ADDCONTENTDATA wrong number of inputs"); + + 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; + m_addtitle_content_buffer.insert(m_addtitle_content_buffer.end(), data_start, data_end); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::AddContentFinish(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 1, + "IOCTL_ES_ADDCONTENTFINISH wrong number of inputs"); + + u32 content_fd = Memory::Read_U32(request.in_vectors[0].address); + INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTFINISH: content fd %08x", content_fd); + + // Try to find the title key from a pre-installed ticket. + std::vector ticket = DiscIO::FindSignedTicket(m_addtitle_tmd.GetTitleId()); + if (ticket.size() == 0) + { + return GetDefaultReply(ES_NO_TICKET_INSTALLED); + } + + mbedtls_aes_context aes_ctx; + mbedtls_aes_setkey_dec(&aes_ctx, DiscIO::GetKeyFromTicket(ticket).data(), 128); + + // The IV for title content decryption is the lower two bytes of the + // content index, zero extended. + TMDReader::Content content_info; + if (!m_addtitle_tmd.FindContentById(m_addtitle_content_id, &content_info)) + { + return GetDefaultReply(ES_INVALID_TMD); + } + u8 iv[16] = {0}; + iv[0] = (content_info.index >> 8) & 0xFF; + iv[1] = content_info.index & 0xFF; + std::vector decrypted_data(m_addtitle_content_buffer.size()); + mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, m_addtitle_content_buffer.size(), iv, + m_addtitle_content_buffer.data(), decrypted_data.data()); + + std::string path = StringFromFormat( + "%s%08x.app", + Common::GetTitleContentPath(m_addtitle_tmd.GetTitleId(), Common::FROM_SESSION_ROOT).c_str(), + m_addtitle_content_id); + + File::IOFile fp(path, "wb"); + fp.WriteBytes(decrypted_data.data(), decrypted_data.size()); + + m_addtitle_content_id = 0xFFFFFFFF; + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::AddTitleFinish(const IOCtlVRequest& request) +{ + INFO_LOG(IOS_ES, "IOCTL_ES_ADDTITLEFINISH"); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::ESGetDeviceID(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, "IOCTL_ES_GETDEVICEID no io vectors"); + + EcWii& ec = EcWii::GetInstance(); + INFO_LOG(IOS_ES, "IOCTL_ES_GETDEVICEID %08X", ec.getNgId()); + Memory::Write_U32(ec.getNgId(), request.io_vectors[0].address); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetTitleContentsCount(const IOCtlVRequest& request) +{ + _dbg_assert_(IOS_ES, request.in_vectors.size() == 1); + _dbg_assert_(IOS_ES, request.io_vectors.size() == 1); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + + const DiscIO::CNANDContentLoader& rNANDContent = AccessContentDevice(TitleID); + u16 NumberOfPrivateContent = 0; + s32 return_value = IPC_SUCCESS; + if (rNANDContent.IsValid()) // Not sure if dolphin will ever fail this check + { + NumberOfPrivateContent = rNANDContent.GetNumEntries(); + + if ((u32)(TitleID >> 32) == 0x00010000) + Memory::Write_U32(0, request.io_vectors[0].address); + else + Memory::Write_U32(NumberOfPrivateContent, request.io_vectors[0].address); + } + else + { + return_value = static_cast(rNANDContent.GetContentSize()); + } + + INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLECONTENTSCNT: TitleID: %08x/%08x content count %i", + (u32)(TitleID >> 32), (u32)TitleID, + rNANDContent.IsValid() ? NumberOfPrivateContent : (u32)rNANDContent.GetContentSize()); + + return GetDefaultReply(return_value); +} + +IPCCommandResult ES::GetTitleContents(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 2, + "IOCTL_ES_GETTITLECONTENTS bad in buffer"); + _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, + "IOCTL_ES_GETTITLECONTENTS bad out buffer"); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + + const DiscIO::CNANDContentLoader& rNANDContent = AccessContentDevice(TitleID); + s32 return_value = IPC_SUCCESS; + if (rNANDContent.IsValid()) // Not sure if dolphin will ever fail this check + { + for (u16 i = 0; i < rNANDContent.GetNumEntries(); i++) + { + Memory::Write_U32(rNANDContent.GetContentByIndex(i)->m_ContentID, + request.io_vectors[0].address + i * 4); + INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLECONTENTS: Index %d: %08x", i, + rNANDContent.GetContentByIndex(i)->m_ContentID); + } + } + else + { + return_value = static_cast(rNANDContent.GetContentSize()); + ERROR_LOG(IOS_ES, "IOCTL_ES_GETTITLECONTENTS: Unable to open content %zu", + rNANDContent.GetContentSize()); + } + + return GetDefaultReply(return_value); +} + +IPCCommandResult ES::OpenTitleContent(const IOCtlVRequest& request) +{ + _dbg_assert_(IOS_ES, request.in_vectors.size() == 3); + _dbg_assert_(IOS_ES, request.io_vectors.size() == 0); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + u32 Index = Memory::Read_U32(request.in_vectors[2].address); + + s32 CFD = OpenTitleContent(m_AccessIdentID++, TitleID, Index); + + INFO_LOG(IOS_ES, "IOCTL_ES_OPENTITLECONTENT: TitleID: %08x/%08x Index %i -> got CFD %x", + (u32)(TitleID >> 32), (u32)TitleID, Index, CFD); + + return GetDefaultReply(CFD); +} + +IPCCommandResult ES::OpenContent(const IOCtlVRequest& request) +{ + _dbg_assert_(IOS_ES, request.in_vectors.size() == 1); + _dbg_assert_(IOS_ES, request.io_vectors.size() == 0); + u32 Index = Memory::Read_U32(request.in_vectors[0].address); + + s32 CFD = OpenTitleContent(m_AccessIdentID++, m_TitleID, Index); + INFO_LOG(IOS_ES, "IOCTL_ES_OPENCONTENT: Index %i -> got CFD %x", Index, CFD); + + return GetDefaultReply(CFD); +} + +IPCCommandResult ES::ReadContent(const IOCtlVRequest& request) +{ + _dbg_assert_(IOS_ES, request.in_vectors.size() == 1); + _dbg_assert_(IOS_ES, request.io_vectors.size() == 1); + + u32 CFD = Memory::Read_U32(request.in_vectors[0].address); + u32 Size = request.io_vectors[0].size; + u32 Addr = request.io_vectors[0].address; + + auto itr = m_ContentAccessMap.find(CFD); + if (itr == m_ContentAccessMap.end()) + { + return GetDefaultReply(-1); + } + SContentAccess& rContent = itr->second; + + u8* pDest = Memory::GetPointer(Addr); + + if (rContent.m_Position + Size > rContent.m_Size) + { + Size = rContent.m_Size - rContent.m_Position; + } + + if (Size > 0) + { + if (pDest) + { + const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(rContent.m_TitleID); + // ContentLoader should never be invalid; rContent has been created by it. + if (ContentLoader.IsValid()) + { + const DiscIO::SNANDContent* pContent = ContentLoader.GetContentByIndex(rContent.m_Index); + if (!pContent->m_Data->GetRange(rContent.m_Position, Size, pDest)) + ERROR_LOG(IOS_ES, "ES: failed to read %u bytes from %u!", Size, rContent.m_Position); + } + + rContent.m_Position += Size; + } + else + { + PanicAlert("IOCTL_ES_READCONTENT - bad destination"); + } + } + + DEBUG_LOG(IOS_ES, + "IOCTL_ES_READCONTENT: CFD %x, Address 0x%x, Size %i -> stream pos %i (Index %i)", CFD, + Addr, Size, rContent.m_Position, rContent.m_Index); + + return GetDefaultReply(Size); +} + +IPCCommandResult ES::CloseContent(const IOCtlVRequest& request) +{ + _dbg_assert_(IOS_ES, request.in_vectors.size() == 1); + _dbg_assert_(IOS_ES, request.io_vectors.size() == 0); + + u32 CFD = Memory::Read_U32(request.in_vectors[0].address); + + INFO_LOG(IOS_ES, "IOCTL_ES_CLOSECONTENT: CFD %x", CFD); + + auto itr = m_ContentAccessMap.find(CFD); + if (itr == m_ContentAccessMap.end()) + { + return GetDefaultReply(-1); + } + + const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(itr->second.m_TitleID); + // ContentLoader should never be invalid; we shouldn't be here if ES_OPENCONTENT failed before. + if (ContentLoader.IsValid()) + { + const DiscIO::SNANDContent* pContent = ContentLoader.GetContentByIndex(itr->second.m_Index); + pContent->m_Data->Close(); + } + + m_ContentAccessMap.erase(itr); + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::SeekContent(const IOCtlVRequest& request) +{ + _dbg_assert_(IOS_ES, request.in_vectors.size() == 3); + _dbg_assert_(IOS_ES, request.io_vectors.size() == 0); + + u32 CFD = Memory::Read_U32(request.in_vectors[0].address); + u32 Addr = Memory::Read_U32(request.in_vectors[1].address); + u32 Mode = Memory::Read_U32(request.in_vectors[2].address); + + auto itr = m_ContentAccessMap.find(CFD); + if (itr == m_ContentAccessMap.end()) + { + return GetDefaultReply(-1); + } + SContentAccess& rContent = itr->second; + + switch (Mode) + { + case 0: // SET + rContent.m_Position = Addr; + break; + + case 1: // CUR + rContent.m_Position += Addr; + break; + + case 2: // END + rContent.m_Position = rContent.m_Size + Addr; + break; + } + + DEBUG_LOG(IOS_ES, "IOCTL_ES_SEEKCONTENT: CFD %x, Address 0x%x, Mode %i -> Pos %i", CFD, Addr, + Mode, rContent.m_Position); + + return GetDefaultReply(rContent.m_Position); +} + +IPCCommandResult ES::GetTitleDirectory(const IOCtlVRequest& request) +{ + _dbg_assert_(IOS_ES, request.in_vectors.size() == 1); + _dbg_assert_(IOS_ES, request.io_vectors.size() == 1); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + + char* Path = (char*)Memory::GetPointer(request.io_vectors[0].address); + sprintf(Path, "/title/%08x/%08x/data", (u32)(TitleID >> 32), (u32)TitleID); + + INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLEDIR: %s", Path); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetTitleID(const IOCtlVRequest& request) +{ + _dbg_assert_(IOS_ES, request.in_vectors.size() == 0); + _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, "IOCTL_ES_GETTITLEID no out buffer"); + + Memory::Write_U64(m_TitleID, request.io_vectors[0].address); + INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLEID: %08x/%08x", (u32)(m_TitleID >> 32), (u32)m_TitleID); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::SetUID(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 1, "IOCTL_ES_SETUID no in buffer"); + _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 0, + "IOCTL_ES_SETUID has a payload, it shouldn't"); + + // TODO: fs permissions based on this + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + INFO_LOG(IOS_ES, "IOCTL_ES_SETUID titleID: %08x/%08x", (u32)(TitleID >> 32), (u32)TitleID); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetTitleCount(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 0, "IOCTL_ES_GETTITLECNT has an in buffer"); + _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, + "IOCTL_ES_GETTITLECNT has no out buffer"); + _dbg_assert_msg_(IOS_ES, request.io_vectors[0].size == 4, + "IOCTL_ES_GETTITLECNT payload[0].size != 4"); + + Memory::Write_U32((u32)m_TitleIDs.size(), request.io_vectors[0].address); + + INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLECNT: Number of Titles %zu", m_TitleIDs.size()); + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetTitles(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 1, "IOCTL_ES_GETTITLES has an in buffer"); + _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, "IOCTL_ES_GETTITLES has no out buffer"); + + u32 MaxCount = Memory::Read_U32(request.in_vectors[0].address); + u32 Count = 0; + for (int i = 0; i < (int)m_TitleIDs.size(); i++) + { + Memory::Write_U64(m_TitleIDs[i], request.io_vectors[0].address + i * 8); + INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLES: %08x/%08x", (u32)(m_TitleIDs[i] >> 32), + (u32)m_TitleIDs[i]); + Count++; + if (Count >= MaxCount) + break; + } + + INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLES: Number of titles returned %i", Count); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetViewCount(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 1, "IOCTL_ES_GETVIEWCNT no in buffer"); + _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, "IOCTL_ES_GETVIEWCNT no out buffer"); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + + u32 retVal = 0; + const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); + u32 ViewCount = + static_cast(Loader.GetTicket().size()) / DiscIO::CNANDContentLoader::TICKET_SIZE; + + if (!ViewCount) + { + std::string TicketFilename = Common::GetTicketFileName(TitleID, Common::FROM_SESSION_ROOT); + if (File::Exists(TicketFilename)) + { + u32 FileSize = (u32)File::GetSize(TicketFilename); + _dbg_assert_msg_(IOS_ES, (FileSize % DiscIO::CNANDContentLoader::TICKET_SIZE) == 0, + "IOCTL_ES_GETVIEWCNT ticket file size seems to be wrong"); + + ViewCount = FileSize / DiscIO::CNANDContentLoader::TICKET_SIZE; + _dbg_assert_msg_(IOS_ES, (ViewCount > 0) && (ViewCount <= 4), + "IOCTL_ES_GETVIEWCNT ticket count seems to be wrong"); + } + else if (TitleID >> 32 == 0x00000001) + { + // Fake a ticket view to make IOS reload work. + ViewCount = 1; + } + else + { + ViewCount = 0; + if (TitleID == TITLEID_SYSMENU) + { + PanicAlertT("There must be a ticket for 00000001/00000002. Your NAND dump is probably " + "incomplete."); + } + // retVal = ES_NO_TICKET_INSTALLED; + } + } + + INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWCNT for titleID: %08x/%08x (View Count = %i)", + (u32)(TitleID >> 32), (u32)TitleID, ViewCount); + + Memory::Write_U32(ViewCount, request.io_vectors[0].address); + return GetDefaultReply(retVal); +} + +IPCCommandResult ES::GetViews(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 2, "IOCTL_ES_GETVIEWS no in buffer"); + _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, "IOCTL_ES_GETVIEWS no out buffer"); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + u32 maxViews = Memory::Read_U32(request.in_vectors[1].address); + u32 retVal = 0; + + const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); + + const std::vector& ticket = Loader.GetTicket(); + + if (ticket.empty()) + { + std::string TicketFilename = Common::GetTicketFileName(TitleID, Common::FROM_SESSION_ROOT); + if (File::Exists(TicketFilename)) + { + File::IOFile pFile(TicketFilename, "rb"); + if (pFile) + { + u8 FileTicket[DiscIO::CNANDContentLoader::TICKET_SIZE]; + for (unsigned int View = 0; + View != maxViews && + pFile.ReadBytes(FileTicket, DiscIO::CNANDContentLoader::TICKET_SIZE); + ++View) + { + Memory::Write_U32(View, request.io_vectors[0].address + View * 0xD8); + Memory::CopyToEmu(request.io_vectors[0].address + 4 + View * 0xD8, FileTicket + 0x1D0, + 212); + } + } + } + else if (TitleID >> 32 == 0x00000001) + { + // For IOS titles, the ticket view isn't normally parsed by either the + // SDK or libogc, just passed to LaunchTitle, so this + // shouldn't matter at all. Just fill out some fields just + // to be on the safe side. + u32 Address = request.io_vectors[0].address; + Memory::Memset(Address, 0, 0xD8); + Memory::Write_U64(TitleID, Address + 4 + (0x1dc - 0x1d0)); // title ID + Memory::Write_U16(0xffff, Address + 4 + (0x1e4 - 0x1d0)); // unnnown + Memory::Write_U32(0xff00, Address + 4 + (0x1ec - 0x1d0)); // access mask + Memory::Memset(Address + 4 + (0x222 - 0x1d0), 0xff, 0x20); // content permissions + } + else + { + // retVal = ES_NO_TICKET_INSTALLED; + PanicAlertT("IOCTL_ES_GETVIEWS: Tried to get data from an unknown ticket: %08x/%08x", + (u32)(TitleID >> 32), (u32)TitleID); + } + } + else + { + u32 view_count = + static_cast(Loader.GetTicket().size()) / DiscIO::CNANDContentLoader::TICKET_SIZE; + for (unsigned int view = 0; view != maxViews && view < view_count; ++view) + { + Memory::Write_U32(view, request.io_vectors[0].address + view * 0xD8); + Memory::CopyToEmu(request.io_vectors[0].address + 4 + view * 0xD8, + &ticket[0x1D0 + (view * DiscIO::CNANDContentLoader::TICKET_SIZE)], 212); + } + } + + INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWS for titleID: %08x/%08x (MaxViews = %i)", (u32)(TitleID >> 32), + (u32)TitleID, maxViews); + + return GetDefaultReply(retVal); +} + +IPCCommandResult ES::GetTMDViewCount(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 1, "IOCTL_ES_GETTMDVIEWCNT no in buffer"); + _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, "IOCTL_ES_GETTMDVIEWCNT no out buffer"); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + + const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); + + u32 TMDViewCnt = 0; + if (Loader.IsValid()) + { + TMDViewCnt += DiscIO::CNANDContentLoader::TMD_VIEW_SIZE; + TMDViewCnt += 2; // title version + TMDViewCnt += 2; // num entries + TMDViewCnt += (u32)Loader.GetContentSize() * (4 + 2 + 2 + 8); // content id, index, type, size + } + Memory::Write_U32(TMDViewCnt, request.io_vectors[0].address); + + INFO_LOG(IOS_ES, "IOCTL_ES_GETTMDVIEWCNT: title: %08x/%08x (view size %i)", (u32)(TitleID >> 32), + (u32)TitleID, TMDViewCnt); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetTMDViews(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 2, "IOCTL_ES_GETTMDVIEWCNT no in buffer"); + _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, "IOCTL_ES_GETTMDVIEWCNT no out buffer"); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + u32 MaxCount = Memory::Read_U32(request.in_vectors[1].address); + + const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); + + INFO_LOG(IOS_ES, "IOCTL_ES_GETTMDVIEWCNT: title: %08x/%08x buffer size: %i", + (u32)(TitleID >> 32), (u32)TitleID, MaxCount); + + if (Loader.IsValid()) + { + u32 Address = request.io_vectors[0].address; + + Memory::CopyToEmu(Address, Loader.GetTMDView(), DiscIO::CNANDContentLoader::TMD_VIEW_SIZE); + Address += DiscIO::CNANDContentLoader::TMD_VIEW_SIZE; + + Memory::Write_U16(Loader.GetTitleVersion(), Address); + Address += 2; + Memory::Write_U16(Loader.GetNumEntries(), Address); + Address += 2; + + const std::vector& rContent = Loader.GetContent(); + for (size_t i = 0; i < Loader.GetContentSize(); i++) + { + Memory::Write_U32(rContent[i].m_ContentID, Address); + Address += 4; + Memory::Write_U16(rContent[i].m_Index, Address); + Address += 2; + Memory::Write_U16(rContent[i].m_Type, Address); + Address += 2; + Memory::Write_U64(rContent[i].m_Size, Address); + Address += 8; + } + + _dbg_assert_(IOS_ES, (Address - request.io_vectors[0].address) == request.io_vectors[0].size); + } + + INFO_LOG(IOS_ES, "IOCTL_ES_GETTMDVIEWS: title: %08x/%08x (buffer size: %i)", (u32)(TitleID >> 32), + (u32)TitleID, MaxCount); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetConsumption(const IOCtlVRequest& request) +{ + // This is at least what crediar's ES module does + Memory::Write_U32(0, request.io_vectors[1].address); + INFO_LOG(IOS_ES, "IOCTL_ES_GETCONSUMPTION"); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request) +{ + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + INFO_LOG(IOS_ES, "IOCTL_ES_DELETETICKET: title: %08x/%08x", (u32)(TitleID >> 32), (u32)TitleID); + + // Presumably return -1017 when delete fails + if (!File::Delete(Common::GetTicketFileName(TitleID, Common::FROM_SESSION_ROOT))) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::DeleteTitleContent(const IOCtlVRequest& request) +{ + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + INFO_LOG(IOS_ES, "IOCTL_ES_DELETETITLECONTENT: title: %08x/%08x", (u32)(TitleID >> 32), + (u32)TitleID); + + // Presumably return -1017 when title not installed TODO verify + if (!DiscIO::CNANDContentManager::Access().RemoveTitle(TitleID, Common::FROM_SESSION_ROOT)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetStoredTMDSize(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 1, + "IOCTL_ES_GETSTOREDTMDSIZE no in buffer"); + // _dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, + // "IOCTL_ES_ES_GETSTOREDTMDSIZE no out buffer"); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); + + _dbg_assert_(IOS_ES, Loader.IsValid()); + u32 TMDCnt = 0; + if (Loader.IsValid()) + { + TMDCnt += DiscIO::CNANDContentLoader::TMD_HEADER_SIZE; + TMDCnt += (u32)Loader.GetContentSize() * DiscIO::CNANDContentLoader::CONTENT_HEADER_SIZE; + } + + if (request.io_vectors.size()) + Memory::Write_U32(TMDCnt, request.io_vectors[0].address); + + INFO_LOG(IOS_ES, "IOCTL_ES_GETSTOREDTMDSIZE: title: %08x/%08x (view size %i)", + (u32)(TitleID >> 32), (u32)TitleID, TMDCnt); + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetStoredTMD(const IOCtlVRequest& request) +{ + _dbg_assert_msg_(IOS_ES, request.in_vectors.size() > 0, "IOCTL_ES_GETSTOREDTMD no in buffer"); + // requires 1 inbuffer and no outbuffer, presumably outbuffer required when second inbuffer is + // used for maxcount (allocated mem?) + // called with 1 inbuffer after deleting a titleid + //_dbg_assert_msg_(IOS_ES, request.io_vectors.size() == 1, + // "IOCTL_ES_GETSTOREDTMD no out buffer"); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + u32 MaxCount = 0; + if (request.in_vectors.size() > 1) + { + // TODO: actually use this param in when writing to the outbuffer :/ + MaxCount = Memory::Read_U32(request.in_vectors[1].address); + } + const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); + + INFO_LOG(IOS_ES, "IOCTL_ES_GETSTOREDTMD: title: %08x/%08x buffer size: %i", + (u32)(TitleID >> 32), (u32)TitleID, MaxCount); + + if (Loader.IsValid() && request.io_vectors.size()) + { + u32 Address = request.io_vectors[0].address; + + Memory::CopyToEmu(Address, Loader.GetTMDHeader(), DiscIO::CNANDContentLoader::TMD_HEADER_SIZE); + Address += DiscIO::CNANDContentLoader::TMD_HEADER_SIZE; + + const std::vector& rContent = Loader.GetContent(); + for (size_t i = 0; i < Loader.GetContentSize(); i++) + { + Memory::CopyToEmu(Address, rContent[i].m_Header, + DiscIO::CNANDContentLoader::CONTENT_HEADER_SIZE); + Address += DiscIO::CNANDContentLoader::CONTENT_HEADER_SIZE; + } + + _dbg_assert_(IOS_ES, (Address - request.io_vectors[0].address) == request.io_vectors[0].size); + } + + INFO_LOG(IOS_ES, "IOCTL_ES_GETSTOREDTMD: title: %08x/%08x (buffer size: %i)", + (u32)(TitleID >> 32), (u32)TitleID, MaxCount); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::Encrypt(const IOCtlVRequest& request) +{ + u32 keyIndex = Memory::Read_U32(request.in_vectors[0].address); + u8* IV = Memory::GetPointer(request.in_vectors[1].address); + u8* source = Memory::GetPointer(request.in_vectors[2].address); + u32 size = request.in_vectors[2].size; + u8* newIV = Memory::GetPointer(request.io_vectors[0].address); + u8* destination = Memory::GetPointer(request.io_vectors[1].address); + + mbedtls_aes_context AES_ctx; + mbedtls_aes_setkey_enc(&AES_ctx, s_key_table[keyIndex], 128); + memcpy(newIV, IV, 16); + mbedtls_aes_crypt_cbc(&AES_ctx, MBEDTLS_AES_ENCRYPT, size, newIV, source, destination); + + _dbg_assert_msg_(IOS_ES, keyIndex == 6, + "IOCTL_ES_ENCRYPT: Key type is not SD, data will be crap"); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::Decrypt(const IOCtlVRequest& request) +{ + u32 keyIndex = Memory::Read_U32(request.in_vectors[0].address); + u8* IV = Memory::GetPointer(request.in_vectors[1].address); + u8* source = Memory::GetPointer(request.in_vectors[2].address); + u32 size = request.in_vectors[2].size; + u8* newIV = Memory::GetPointer(request.io_vectors[0].address); + u8* destination = Memory::GetPointer(request.io_vectors[1].address); + + DecryptContent(keyIndex, IV, source, size, newIV, destination); + + _dbg_assert_msg_(IOS_ES, keyIndex == 6, + "IOCTL_ES_DECRYPT: Key type is not SD, data will be crap"); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::Launch(const IOCtlVRequest& request) +{ + _dbg_assert_(IOS_ES, request.in_vectors.size() == 2); + bool bSuccess = false; + bool bReset = false; + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + u32 view = Memory::Read_U32(request.in_vectors[1].address); + u64 ticketid = Memory::Read_U64(request.in_vectors[1].address + 4); + u32 devicetype = Memory::Read_U32(request.in_vectors[1].address + 12); + u64 titleid = Memory::Read_U64(request.in_vectors[1].address + 16); + u16 access = Memory::Read_U16(request.in_vectors[1].address + 24); + + // ES_LAUNCH should probably reset thw whole state, which at least means closing all open files. + // leaving them open through ES_LAUNCH may cause hangs and other funky behavior + // (supposedly when trying to re-open those files). + DiscIO::CNANDContentManager::Access().ClearCache(); + + u64 ios_to_load = 0; + std::string tContentFile; + if ((u32)(TitleID >> 32) == 0x00000001 && TitleID != TITLEID_SYSMENU) + { + ios_to_load = TitleID; + bSuccess = true; + } + else + { + const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(TitleID); + if (ContentLoader.IsValid()) + { + ios_to_load = 0x0000000100000000ULL | ContentLoader.GetIosVersion(); + + u32 bootInd = ContentLoader.GetBootIndex(); + const DiscIO::SNANDContent* pContent = ContentLoader.GetContentByIndex(bootInd); + if (pContent) + { + tContentFile = Common::GetTitleContentPath(TitleID, Common::FROM_SESSION_ROOT); + std::unique_ptr pDolLoader = + std::make_unique(pContent->m_Data->Get()); + + if (pDolLoader->IsValid()) + { + pDolLoader->Load(); + // TODO: Check why sysmenu does not load the DOL correctly + // NAND titles start with address translation off at 0x3400 (via the PPC bootstub) + // + // The state of other CPU registers (like the BAT registers) doesn't matter much + // because the realmode code at 0x3400 initializes everything itself anyway. + MSR = 0; + PC = 0x3400; + bSuccess = true; + } + else + { + PanicAlertT("IOCTL_ES_LAUNCH: The DOL file is invalid!"); + } + } + } + } + + if (!bSuccess) + { + PanicAlertT( + "IOCTL_ES_LAUNCH: Game tried to reload a title that is not available in your NAND dump\n" + "TitleID %016" PRIx64 ".\n Dolphin will likely hang now.", + TitleID); + } + else + { + bool* wiiMoteConnected = new bool[MAX_BBMOTES]; + if (!SConfig::GetInstance().m_bt_passthrough_enabled) + { + BluetoothEmu* s_Usb = GetUsbPointer(); + for (unsigned int i = 0; i < MAX_BBMOTES; i++) + wiiMoteConnected[i] = s_Usb->m_WiiMotes[i].IsConnected(); + } + + Reset(true); + Reinit(); + SetupMemory(ios_to_load); + bReset = true; + + if (!SConfig::GetInstance().m_bt_passthrough_enabled) + { + BluetoothEmu* s_Usb = GetUsbPointer(); + for (unsigned int i = 0; i < MAX_BBMOTES; i++) + { + if (wiiMoteConnected[i]) + { + s_Usb->m_WiiMotes[i].Activate(false); + s_Usb->m_WiiMotes[i].Activate(true); + } + else + { + s_Usb->m_WiiMotes[i].Activate(false); + } + } + } + delete[] wiiMoteConnected; + SetDefaultContentFile(tContentFile); + } + + // Note: If we just reset the PPC, don't write anything to the command buffer. This + // could clobber the DOL we just loaded. + + ERROR_LOG(IOS_ES, "IOCTL_ES_LAUNCH %016" PRIx64 " %08x %016" PRIx64 " %08x %016" PRIx64 " %04x", + TitleID, view, ticketid, devicetype, titleid, access); + // IOCTL_ES_LAUNCH 0001000248414341 00000001 0001c0fef3df2cfa 00000000 + // 0001000248414341 ffff + + // This is necessary because Reset(true) above deleted this object. Ew. + + if (!bReset) + { + // The command type is overwritten with the reply type. + Memory::Write_U32(IPC_REPLY, request.address); + // IOS also writes back the command that was responded to in the FD field. + Memory::Write_U32(IPC_CMD_IOCTLV, request.address + 8); + } + + // Generate a "reply" to the IPC command. ES_LAUNCH is unique because it + // involves restarting IOS; IOS generates two acknowledgements in a row. + EnqueueCommandAcknowledgement(request.address, 0); + return GetNoReply(); +} + +IPCCommandResult ES::CheckKoreaRegion(const IOCtlVRequest& request) +{ + // note by DacoTaco : name is unknown, I just tried to name it SOMETHING. + // IOS70 has this to let system menu 4.2 check if the console is region changed. it returns + // -1017 + // if the IOS didn't find the Korean keys and 0 if it does. 0 leads to a error 003 + INFO_LOG(IOS_ES, "IOCTL_ES_CHECKKOREAREGION: Title checked for Korean keys."); + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); +} + +IPCCommandResult ES::GetDeviceCertificate(const IOCtlVRequest& request) +{ + // (Input: none, Output: 384 bytes) + INFO_LOG(IOS_ES, "IOCTL_ES_GETDEVICECERT"); + _dbg_assert_(IOS_ES, request.io_vectors.size() == 1); + u8* destination = Memory::GetPointer(request.io_vectors[0].address); + + EcWii& ec = EcWii::GetInstance(); + get_ng_cert(destination, ec.getNgId(), ec.getNgKeyId(), ec.getNgPriv(), ec.getNgSig()); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::Sign(const IOCtlVRequest& request) +{ + INFO_LOG(IOS_ES, "IOCTL_ES_SIGN"); + u8* ap_cert_out = Memory::GetPointer(request.io_vectors[1].address); + u8* data = Memory::GetPointer(request.in_vectors[0].address); + u32 data_size = request.in_vectors[0].size; + u8* sig_out = Memory::GetPointer(request.io_vectors[0].address); + + EcWii& ec = EcWii::GetInstance(); + get_ap_sig_and_cert(sig_out, ap_cert_out, m_TitleID, data, data_size, ec.getNgPriv(), + ec.getNgId()); + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetBoot2Version(const IOCtlVRequest& request) +{ + INFO_LOG(IOS_ES, "IOCTL_ES_GETBOOT2VERSION"); + + // as of 26/02/2012, this was latest bootmii version + Memory::Write_U32(4, request.io_vectors[0].address); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::DIGetTicketView(const IOCtlVRequest& request) +{ + // (Input: none, Output: 216 bytes) bug crediar :D + WARN_LOG(IOS_ES, "IOCTL_ES_DIGETTICKETVIEW: this looks really wrong..."); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetOwnedTitleCount(const IOCtlVRequest& request) +{ + INFO_LOG(IOS_ES, "IOCTL_ES_GETOWNEDTITLECNT"); + Memory::Write_U32(0, request.io_vectors[0].address); + return GetDefaultReply(IPC_SUCCESS); +} + const DiscIO::CNANDContentLoader& ES::AccessContentDevice(u64 title_id) { // for WADs, the passed title id and the stored title id match; along with m_ContentFile being set diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index cf9642edd4..83acd77865 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -151,6 +151,44 @@ private: u8 padding[0x3c]; }; + IPCCommandResult AddTicket(const IOCtlVRequest& request); + IPCCommandResult AddTitleStart(const IOCtlVRequest& request); + IPCCommandResult AddContentStart(const IOCtlVRequest& request); + IPCCommandResult AddContentData(const IOCtlVRequest& request); + IPCCommandResult AddContentFinish(const IOCtlVRequest& request); + IPCCommandResult AddTitleFinish(const IOCtlVRequest& request); + IPCCommandResult ESGetDeviceID(const IOCtlVRequest& request); + IPCCommandResult GetTitleContentsCount(const IOCtlVRequest& request); + IPCCommandResult GetTitleContents(const IOCtlVRequest& request); + IPCCommandResult OpenTitleContent(const IOCtlVRequest& request); + IPCCommandResult OpenContent(const IOCtlVRequest& request); + IPCCommandResult ReadContent(const IOCtlVRequest& request); + IPCCommandResult CloseContent(const IOCtlVRequest& request); + IPCCommandResult SeekContent(const IOCtlVRequest& request); + IPCCommandResult GetTitleDirectory(const IOCtlVRequest& request); + IPCCommandResult GetTitleID(const IOCtlVRequest& request); + IPCCommandResult SetUID(const IOCtlVRequest& request); + IPCCommandResult GetTitleCount(const IOCtlVRequest& request); + IPCCommandResult GetTitles(const IOCtlVRequest& request); + IPCCommandResult GetViewCount(const IOCtlVRequest& request); + IPCCommandResult GetViews(const IOCtlVRequest& request); + IPCCommandResult GetTMDViewCount(const IOCtlVRequest& request); + IPCCommandResult GetTMDViews(const IOCtlVRequest& request); + IPCCommandResult GetConsumption(const IOCtlVRequest& request); + IPCCommandResult DeleteTicket(const IOCtlVRequest& request); + IPCCommandResult DeleteTitleContent(const IOCtlVRequest& request); + IPCCommandResult GetStoredTMDSize(const IOCtlVRequest& request); + IPCCommandResult GetStoredTMD(const IOCtlVRequest& request); + IPCCommandResult Encrypt(const IOCtlVRequest& request); + IPCCommandResult Decrypt(const IOCtlVRequest& request); + IPCCommandResult Launch(const IOCtlVRequest& request); + IPCCommandResult CheckKoreaRegion(const IOCtlVRequest& request); + IPCCommandResult GetDeviceCertificate(const IOCtlVRequest& request); + IPCCommandResult Sign(const IOCtlVRequest& request); + IPCCommandResult GetBoot2Version(const IOCtlVRequest& request); + IPCCommandResult DIGetTicketView(const IOCtlVRequest& request); + IPCCommandResult GetOwnedTitleCount(const IOCtlVRequest& request); + const DiscIO::CNANDContentLoader& AccessContentDevice(u64 title_id); u32 OpenTitleContent(u32 CFD, u64 TitleID, u16 Index);