diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 83f36fbc6a..7f737dc1a3 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -149,6 +149,11 @@ set(SRCS IOS/DI/DI.cpp IOS/ES/ES.cpp IOS/ES/Formats.cpp + IOS/ES/Identity.cpp + IOS/ES/TitleContents.cpp + IOS/ES/TitleInformation.cpp + IOS/ES/TitleManagement.cpp + IOS/ES/Views.cpp IOS/FS/FileIO.cpp IOS/FS/FS.cpp IOS/Network/ICMPLin.cpp diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index 9188e95c17..ae8fd022ec 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -176,6 +176,11 @@ + + + + + diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index 89ff3f5277..4a5ecc2a3d 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -756,6 +756,21 @@ IOS\ES + + IOS\ES + + + IOS\ES + + + IOS\ES + + + IOS\ES + + + IOS\ES + IOS\FS diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 5528cf1f97..c62c81ce6b 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -4,32 +4,21 @@ #include "Core/IOS/ES/ES.h" -#include -#include #include #include -#include #include #include #include -#include -#include - -#include "Common/Align.h" -#include "Common/Assert.h" #include "Common/ChunkFile.h" #include "Common/FileUtil.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/NandPaths.h" -#include "Common/Swap.h" #include "Core/ConfigManager.h" #include "Core/HW/Memmap.h" #include "Core/IOS/ES/Formats.h" -#include "Core/ec_wii.h" #include "DiscIO/NANDContentLoader.h" -#include "DiscIO/Volume.h" namespace IOS { @@ -37,19 +26,6 @@ namespace HLE { namespace Device { -struct TitleContext -{ - void Clear(); - void DoState(PointerWrap& p); - void Update(const DiscIO::CNANDContentLoader& content_loader); - void Update(const IOS::ES::TMDReader& tmd_, const IOS::ES::TicketReader& ticket_); - - IOS::ES::TicketReader ticket; - IOS::ES::TMDReader tmd; - bool active = false; - bool first_change = true; -}; - // Shared across all ES instances. static std::string s_content_file; static TitleContext s_title_context; @@ -57,29 +33,6 @@ static TitleContext s_title_context; // Title to launch after IOS has been reset and reloaded (similar to /sys/launch.sys). static u64 s_title_to_launch; -constexpr u8 s_key_sd[0x10] = {0xab, 0x01, 0xb9, 0xd8, 0xe1, 0x62, 0x2b, 0x08, - 0xaf, 0xba, 0xd8, 0x4d, 0xbf, 0xc2, 0xa5, 0x5d}; -constexpr u8 s_key_ecc[0x1e] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; -constexpr u8 s_key_empty[0x10] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - -// default key table -constexpr const u8* s_key_table[11] = { - s_key_ecc, // ECC Private Key - s_key_empty, // Console ID - s_key_empty, // NAND AES Key - s_key_empty, // NAND HMAC - s_key_empty, // Common Key - s_key_empty, // PRNG seed - s_key_sd, // SD Key - s_key_empty, // Unknown - s_key_empty, // Unknown - s_key_empty, // Unknown - s_key_empty, // Unknown -}; - ES::ES(u32 device_id, const std::string& device_name) : Device(device_id, device_name) { } @@ -101,6 +54,11 @@ void ES::Init() } } +TitleContext& ES::GetTitleContext() +{ + return s_title_context; +} + void TitleContext::Clear() { ticket.SetBytes({}); @@ -152,12 +110,44 @@ void ES::LoadWAD(const std::string& _rContentFile) INFO_LOG(IOS_ES, "LoadWAD: Title context changed: %016" PRIx64, s_title_context.tmd.GetTitleId()); } -void ES::DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv, u8* output) +IPCCommandResult ES::GetTitleDirectory(const IOCtlVRequest& request) { - mbedtls_aes_context AES_ctx; - mbedtls_aes_setkey_dec(&AES_ctx, s_key_table[key_index], 128); - memcpy(new_iv, iv, 16); - mbedtls_aes_crypt_cbc(&AES_ctx, MBEDTLS_AES_DECRYPT, size, new_iv, input, output); + if (!request.HasNumberOfValidVectors(1, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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) +{ + if (!request.HasNumberOfValidVectors(0, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + if (!s_title_context.active) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const u64 title_id = s_title_context.tmd.GetTitleId(); + Memory::Write_U64(title_id, request.io_vectors[0].address); + INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLEID: %08x/%08x", static_cast(title_id >> 32), + static_cast(title_id)); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::SetUID(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 0)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + // 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); } bool ES::LaunchTitle(u64 title_id, bool skip_reload) @@ -271,34 +261,6 @@ void ES::Close() DiscIO::CNANDContentManager::Access().ClearCache(); } -u32 ES::OpenTitleContent(u32 CFD, u64 TitleID, u16 Index) -{ - const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); - - if (!Loader.IsValid() || !Loader.GetTMD().IsValid() || !Loader.GetTicket().IsValid()) - { - WARN_LOG(IOS_ES, "ES: loader not valid for %" PRIx64, TitleID); - return 0xffffffff; - } - - const DiscIO::SNANDContent* pContent = Loader.GetContentByIndex(Index); - - if (pContent == nullptr) - { - return 0xffffffff; // TODO: what is the correct error value here? - } - - OpenedContent content; - content.m_position = 0; - content.m_content = pContent->m_metadata; - content.m_title_id = TitleID; - - pContent->m_Data->Open(); - - m_ContentAccessMap[CFD] = content; - return CFD; -} - IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request) { DEBUG_LOG(IOS_ES, "%s (0x%x)", GetDeviceName().c_str(), request.request); @@ -329,7 +291,7 @@ IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request) case IOCTL_ES_ADDTITLECANCEL: return AddTitleCancel(request); case IOCTL_ES_GETDEVICEID: - return ESGetDeviceID(request); + return GetConsoleID(request); case IOCTL_ES_OPENTITLECONTENT: return OpenTitleContent(request); case IOCTL_ES_OPENCONTENT: @@ -366,9 +328,9 @@ IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request) return GetTMDStoredContents(request); case IOCTL_ES_GETVIEWCNT: - return GetViewCount(request); + return GetTicketViewCount(request); case IOCTL_ES_GETVIEWS: - return GetViews(request); + return GetTicketViews(request); case IOCTL_ES_DIGETTICKETVIEW: return DIGetTicketView(request); @@ -428,937 +390,6 @@ IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request) return GetDefaultReply(IPC_SUCCESS); } -IPCCommandResult ES::AddTicket(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(3, 0)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - std::vector bytes(request.in_vectors[0].size); - Memory::CopyFromEmu(bytes.data(), request.in_vectors[0].address, request.in_vectors[0].size); - - IOS::ES::TicketReader ticket{std::move(bytes)}; - if (!ticket.IsValid()) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const u32 ticket_device_id = ticket.GetDeviceId(); - const u32 device_id = EcWii::GetInstance().GetNGID(); - if (ticket_device_id != 0) - { - if (device_id != ticket_device_id) - { - WARN_LOG(IOS_ES, "Device ID mismatch: ticket %08x, device %08x", ticket_device_id, device_id); - return GetDefaultReply(ES_DEVICE_ID_MISMATCH); - } - const s32 ret = ticket.Unpersonalise(); - if (ret < 0) - { - ERROR_LOG(IOS_ES, "AddTicket: Failed to unpersonalise ticket for %016" PRIx64 " (ret = %d)", - ticket.GetTitleId(), ret); - return GetDefaultReply(ret); - } - } - - if (!DiscIO::AddTicket(ticket)) - return GetDefaultReply(ES_WRITE_FAILURE); - - INFO_LOG(IOS_ES, "AddTicket: Imported ticket for title %016" PRIx64, ticket.GetTitleId()); - return GetDefaultReply(IPC_SUCCESS); -} - -static bool WriteImportTMD(const IOS::ES::TMDReader& tmd) -{ - const std::string tmd_path = Common::GetImportTitlePath(tmd.GetTitleId()) + "/content/title.tmd"; - File::CreateFullPath(tmd_path); - - File::IOFile file(tmd_path, "wb"); - return file.WriteBytes(tmd.GetRawTMD().data(), tmd.GetRawTMD().size()); -} - -static bool MoveImportTMDToTitleDirectory(const IOS::ES::TMDReader& tmd) -{ - const std::string src = Common::GetImportTitlePath(tmd.GetTitleId()) + "/content/title.tmd"; - const std::string dest = Common::GetTMDFileName(tmd.GetTitleId(), Common::FROM_SESSION_ROOT); - return File::RenameSync(src, dest); -} - -static std::string GetImportContentPath(const IOS::ES::TMDReader& tmd, u32 content_id) -{ - return Common::GetImportTitlePath(tmd.GetTitleId()) + - StringFromFormat("/content/%08x.app", content_id); -} - -IPCCommandResult ES::AddTMD(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 0)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - std::vector tmd(request.in_vectors[0].size); - Memory::CopyFromEmu(tmd.data(), request.in_vectors[0].address, request.in_vectors[0].size); - - // Ioctlv 0x2b writes the TMD to /tmp/title.tmd (for imports) and doesn't seem to write it - // to either /import or /title. So here we simply have to set the import TMD. - m_addtitle_tmd.SetBytes(std::move(tmd)); - if (!m_addtitle_tmd.IsValid()) - return GetDefaultReply(ES_INVALID_TMD); - - DiscIO::cUIDsys uid_sys{Common::FROM_CONFIGURED_ROOT}; - uid_sys.AddTitle(m_addtitle_tmd.GetTitleId()); - - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::AddTitleStart(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(4, 0)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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); - } - - DiscIO::cUIDsys uid_sys{Common::FROM_CONFIGURED_ROOT}; - uid_sys.AddTitle(m_addtitle_tmd.GetTitleId()); - - // TODO: check and use the other vectors. - - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::AddContentStart(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(2, 0)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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 (!m_addtitle_tmd.IsValid()) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - if (title_id != m_addtitle_tmd.GetTitleId()) - { - ERROR_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTSTART: title id %016" PRIx64 " != " - "TMD title id %016" PRIx64 ", 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) -{ - if (!request.HasNumberOfValidVectors(2, 0)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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); -} - -static bool CheckIfContentHashMatches(const std::vector& content, const IOS::ES::Content& info) -{ - std::array sha1; - mbedtls_sha1(content.data(), info.size, sha1.data()); - return sha1 == info.sha1; -} - -IPCCommandResult ES::AddContentFinish(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 0)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - if (m_addtitle_content_id == 0xFFFFFFFF) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - u32 content_fd = Memory::Read_U32(request.in_vectors[0].address); - INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTFINISH: content fd %08x", content_fd); - - if (!m_addtitle_tmd.IsValid()) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - // Try to find the title key from a pre-installed ticket. - IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(m_addtitle_tmd.GetTitleId()); - if (!ticket.IsValid()) - { - return GetDefaultReply(ES_NO_TICKET_INSTALLED); - } - - mbedtls_aes_context aes_ctx; - mbedtls_aes_setkey_dec(&aes_ctx, ticket.GetTitleKey().data(), 128); - - // The IV for title content decryption is the lower two bytes of the - // content index, zero extended. - IOS::ES::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()); - if (!CheckIfContentHashMatches(decrypted_data, content_info)) - { - ERROR_LOG(IOS_ES, "AddContentFinish: Hash for content %08x doesn't match", content_info.id); - return GetDefaultReply(ES_HASH_DOESNT_MATCH); - } - - // Just write all contents to the title import directory. AddTitleFinish will - // move the contents to the proper location. - const std::string tmp_path = GetImportContentPath(m_addtitle_tmd, m_addtitle_content_id); - File::CreateFullPath(tmp_path); - - File::IOFile fp(tmp_path, "wb"); - if (!fp.WriteBytes(decrypted_data.data(), content_info.size)) - { - ERROR_LOG(IOS_ES, "AddContentFinish: Failed to write to %s", tmp_path.c_str()); - return GetDefaultReply(ES_WRITE_FAILURE); - } - - m_addtitle_content_id = 0xFFFFFFFF; - return GetDefaultReply(IPC_SUCCESS); -} - -static void AbortImport(const u64 title_id, const std::vector& processed_paths) -{ - for (const auto& path : processed_paths) - File::Delete(path); - - const std::string import_dir = Common::GetImportTitlePath(title_id); - File::DeleteDirRecursively(import_dir); -} - -IPCCommandResult ES::AddTitleFinish(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(0, 0) || !m_addtitle_tmd.IsValid()) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - std::vector processed_paths; - - for (const auto& content_info : m_addtitle_tmd.GetContents()) - { - const std::string source = GetImportContentPath(m_addtitle_tmd, content_info.id); - - // Contents may not have been all imported. This is normal and this isn't an error condition. - if (!File::Exists(source)) - continue; - - std::string content_path; - if (content_info.IsShared()) - { - DiscIO::CSharedContent shared_content{Common::FROM_SESSION_ROOT}; - content_path = shared_content.AddSharedContent(content_info.sha1.data()); - } - else - { - content_path = - StringFromFormat("%s%08x.app", Common::GetTitleContentPath(m_addtitle_tmd.GetTitleId(), - Common::FROM_SESSION_ROOT) - .c_str(), - content_info.id); - } - - File::CreateFullPath(content_path); - if (!File::RenameSync(source, content_path)) - { - ERROR_LOG(IOS_ES, "AddTitleFinish: Failed to rename %s to %s", source.c_str(), - content_path.c_str()); - AbortImport(m_addtitle_tmd.GetTitleId(), processed_paths); - return GetDefaultReply(ES_WRITE_FAILURE); - } - - // Do not delete shared contents even if the import fails. This is because - // they can be used by several titles and it's not safe to delete them. - // - // The reason we delete private contents is to avoid having a title with half-complete - // contents, as it can cause issues with the system menu. On the other hand, leaving - // shared contents does not cause any issue. - if (!content_info.IsShared()) - processed_paths.push_back(content_path); - } - - if (!WriteImportTMD(m_addtitle_tmd) || !MoveImportTMDToTitleDirectory(m_addtitle_tmd)) - return GetDefaultReply(ES_WRITE_FAILURE); - - INFO_LOG(IOS_ES, "IOCTL_ES_ADDTITLEFINISH"); - m_addtitle_tmd.SetBytes({}); - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::AddTitleCancel(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(0, 0) || !m_addtitle_tmd.IsValid()) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - AbortImport(m_addtitle_tmd.GetTitleId(), {}); - m_addtitle_tmd.SetBytes({}); - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::ESGetDeviceID(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(0, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const 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); -} - -static std::vector GetStoredContentsFromTMD(const IOS::ES::TMDReader& tmd) -{ - if (!tmd.IsValid()) - return {}; - - const DiscIO::CSharedContent shared{Common::FROM_SESSION_ROOT}; - const std::vector contents = tmd.GetContents(); - - std::vector stored_contents; - - std::copy_if(contents.begin(), contents.end(), std::back_inserter(stored_contents), - [&tmd, &shared](const auto& content) { - if (content.IsShared()) - { - const std::string path = shared.GetFilenameFromSHA1(content.sha1.data()); - return path != "unk" && File::Exists(path); - } - return File::Exists( - Common::GetTitleContentPath(tmd.GetTitleId(), Common::FROM_SESSION_ROOT) + - StringFromFormat("%08x.app", content.id)); - }); - - return stored_contents; -} - -// Used by the GetStoredContents ioctlvs. This assumes that the first output vector -// is used for the content count (u32). -IPCCommandResult ES::GetStoredContentsCount(const IOS::ES::TMDReader& tmd, - const IOCtlVRequest& request) -{ - if (request.io_vectors[0].size != sizeof(u32) || !tmd.IsValid()) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const u16 num_contents = static_cast(GetStoredContentsFromTMD(tmd).size()); - Memory::Write_U32(num_contents, request.io_vectors[0].address); - - INFO_LOG(IOS_ES, "GetStoredContentsCount (0x%x): %u content(s) for %016" PRIx64, request.request, - num_contents, tmd.GetTitleId()); - return GetDefaultReply(IPC_SUCCESS); -} - -// Used by the GetStoredContents ioctlvs. This assumes that the second input vector is used -// for the content count and the output vector is used to store a list of content IDs (u32s). -IPCCommandResult ES::GetStoredContents(const IOS::ES::TMDReader& tmd, const IOCtlVRequest& request) -{ - if (!tmd.IsValid()) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - if (request.in_vectors[1].size != sizeof(u32) || - request.io_vectors[0].size != Memory::Read_U32(request.in_vectors[1].address) * sizeof(u32)) - { - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - } - - const auto contents = GetStoredContentsFromTMD(tmd); - const u32 max_content_count = Memory::Read_U32(request.in_vectors[1].address); - for (u32 i = 0; i < std::min(static_cast(contents.size()), max_content_count); ++i) - Memory::Write_U32(contents[i].id, request.io_vectors[0].address + i * sizeof(u32)); - - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::GetStoredContentsCount(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != sizeof(u64)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const u64 title_id = Memory::Read_U64(request.in_vectors[0].address); - const DiscIO::CNANDContentLoader& content_loader = AccessContentDevice(title_id); - if (!content_loader.IsValid()) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - return GetStoredContentsCount(content_loader.GetTMD(), request); -} - -IPCCommandResult ES::GetStoredContents(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(2, 1) || request.in_vectors[0].size != sizeof(u64)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const u64 title_id = Memory::Read_U64(request.in_vectors[0].address); - const DiscIO::CNANDContentLoader& content_loader = AccessContentDevice(title_id); - if (!content_loader.IsValid()) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - return GetStoredContents(content_loader.GetTMD(), request); -} - -IPCCommandResult ES::GetTMDStoredContentsCount(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - std::vector tmd_bytes(request.in_vectors[0].size); - Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[0].address, tmd_bytes.size()); - return GetStoredContentsCount(IOS::ES::TMDReader{std::move(tmd_bytes)}, request); -} - -IPCCommandResult ES::GetTMDStoredContents(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(2, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - std::vector tmd_bytes(request.in_vectors[0].size); - Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[0].address, tmd_bytes.size()); - return GetStoredContents(IOS::ES::TMDReader{std::move(tmd_bytes)}, request); -} - -IPCCommandResult ES::OpenTitleContent(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(3, 0)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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) -{ - if (!request.HasNumberOfValidVectors(1, 0)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - u32 Index = Memory::Read_U32(request.in_vectors[0].address); - - if (!s_title_context.active) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - s32 CFD = OpenTitleContent(m_AccessIdentID++, s_title_context.tmd.GetTitleId(), Index); - INFO_LOG(IOS_ES, "IOCTL_ES_OPENCONTENT: Index %i -> got CFD %x", Index, CFD); - - return GetDefaultReply(CFD); -} - -IPCCommandResult ES::ReadContent(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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); - } - OpenedContent& rContent = itr->second; - - u8* pDest = Memory::GetPointer(Addr); - - if (rContent.m_position + Size > rContent.m_content.size) - { - Size = static_cast(rContent.m_content.size) - rContent.m_position; - } - - if (Size > 0) - { - if (pDest) - { - const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(rContent.m_title_id); - // ContentLoader should never be invalid; rContent has been created by it. - if (ContentLoader.IsValid() && ContentLoader.GetTicket().IsValid()) - { - const DiscIO::SNANDContent* pContent = - ContentLoader.GetContentByIndex(rContent.m_content.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_content.index); - - return GetDefaultReply(Size); -} - -IPCCommandResult ES::CloseContent(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 0)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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_title_id); - // 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_content.index); - pContent->m_Data->Close(); - } - - m_ContentAccessMap.erase(itr); - - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::SeekContent(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(3, 0)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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); - } - OpenedContent& 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 = static_cast(rContent.m_content.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) -{ - if (!request.HasNumberOfValidVectors(1, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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) -{ - if (!request.HasNumberOfValidVectors(0, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - if (!s_title_context.active) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const u64 title_id = s_title_context.tmd.GetTitleId(); - Memory::Write_U64(title_id, request.io_vectors[0].address); - INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLEID: %08x/%08x", static_cast(title_id >> 32), - static_cast(title_id)); - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::SetUID(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 0)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - // 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); -} - -static bool IsValidPartOfTitleID(const std::string& string) -{ - if (string.length() != 8) - return false; - return std::all_of(string.begin(), string.end(), - [](const auto character) { return std::isxdigit(character) != 0; }); -} - -// Returns a vector of title IDs. IOS does not check the TMD at all here. -static std::vector GetInstalledTitles() -{ - const std::string titles_dir = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/title"; - if (!File::IsDirectory(titles_dir)) - { - ERROR_LOG(IOS_ES, "/title is not a directory"); - return {}; - } - - std::vector title_ids; - - // The /title directory contains one directory per title type, and each of them contains - // a directory per title (where the name is the low 32 bits of the title ID in %08x format). - const auto entries = File::ScanDirectoryTree(titles_dir, true); - for (const File::FSTEntry& title_type : entries.children) - { - if (!title_type.isDirectory || !IsValidPartOfTitleID(title_type.virtualName)) - continue; - - if (title_type.children.empty()) - continue; - - for (const File::FSTEntry& title_identifier : title_type.children) - { - if (!title_identifier.isDirectory || !IsValidPartOfTitleID(title_identifier.virtualName)) - continue; - - const u32 type = std::stoul(title_type.virtualName, nullptr, 16); - const u32 identifier = std::stoul(title_identifier.virtualName, nullptr, 16); - title_ids.push_back(static_cast(type) << 32 | identifier); - } - } - - return title_ids; -} - -// Returns a vector of title IDs for which there is a ticket. -static std::vector GetTitlesWithTickets() -{ - const std::string titles_dir = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/ticket"; - if (!File::IsDirectory(titles_dir)) - { - ERROR_LOG(IOS_ES, "/ticket is not a directory"); - return {}; - } - - std::vector title_ids; - - // The /ticket directory contains one directory per title type, and each of them contains - // one ticket per title (where the name is the low 32 bits of the title ID in %08x format). - const auto entries = File::ScanDirectoryTree(titles_dir, true); - for (const File::FSTEntry& title_type : entries.children) - { - if (!title_type.isDirectory || !IsValidPartOfTitleID(title_type.virtualName)) - continue; - - if (title_type.children.empty()) - continue; - - for (const File::FSTEntry& ticket : title_type.children) - { - const std::string name_without_ext = ticket.virtualName.substr(0, 8); - if (ticket.isDirectory || !IsValidPartOfTitleID(name_without_ext) || - name_without_ext + ".tik" != ticket.virtualName) - { - continue; - } - - const u32 type = std::stoul(title_type.virtualName, nullptr, 16); - const u32 identifier = std::stoul(name_without_ext, nullptr, 16); - title_ids.push_back(static_cast(type) << 32 | identifier); - } - } - - return title_ids; -} - -IPCCommandResult ES::GetTitleCount(const std::vector& titles, const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(0, 1) || request.io_vectors[0].size != 4) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - Memory::Write_U32(static_cast(titles.size()), request.io_vectors[0].address); - - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::GetTitles(const std::vector& titles, const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const size_t max_count = Memory::Read_U32(request.in_vectors[0].address); - for (size_t i = 0; i < std::min(max_count, titles.size()); i++) - { - Memory::Write_U64(titles[i], request.io_vectors[0].address + static_cast(i) * sizeof(u64)); - INFO_LOG(IOS_ES, " title %016" PRIx64, titles[i]); - } - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::GetTitleCount(const IOCtlVRequest& request) -{ - const std::vector titles = GetInstalledTitles(); - INFO_LOG(IOS_ES, "GetTitleCount: %zu titles", titles.size()); - return GetTitleCount(titles, request); -} - -IPCCommandResult ES::GetTitles(const IOCtlVRequest& request) -{ - return GetTitles(GetInstalledTitles(), request); -} - -// HACK: Since we do not want to require users to install disc updates when launching -// Wii games from the game list (which is the inaccurate game boot path anyway), -// IOSes have to be faked for games which reload IOS to work properly. -// To minimize the effect of this hack, we should only do this for disc titles -// booted from the game list, though. -static bool ShouldReturnFakeViewsForIOSes(u64 title_id) -{ - const bool ios = IsTitleType(title_id, IOS::ES::TitleType::System) && title_id != TITLEID_SYSMENU; - const bool disc_title = - s_title_context.active && IOS::ES::IsDiscTitle(s_title_context.tmd.GetTitleId()); - return ios && SConfig::GetInstance().m_BootType == SConfig::BOOT_ISO && disc_title; -} - -IPCCommandResult ES::GetViewCount(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - - const IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(TitleID); - u32 view_count = ticket.IsValid() ? ticket.GetNumberOfTickets() : 0; - - if (ShouldReturnFakeViewsForIOSes(TitleID)) - { - view_count = 1; - WARN_LOG(IOS_ES, "GetViewCount: Faking IOS title %016" PRIx64 " being present", TitleID); - } - - INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWCNT for titleID: %08x/%08x (View Count = %u)", - static_cast(TitleID >> 32), static_cast(TitleID), view_count); - - Memory::Write_U32(view_count, request.io_vectors[0].address); - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::GetViews(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(2, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - u32 maxViews = Memory::Read_U32(request.in_vectors[1].address); - - const IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(TitleID); - - if (ticket.IsValid()) - { - u32 number_of_views = std::min(maxViews, ticket.GetNumberOfTickets()); - for (u32 view = 0; view < number_of_views; ++view) - { - const std::vector ticket_view = ticket.GetRawTicketView(view); - Memory::CopyToEmu(request.io_vectors[0].address + view * sizeof(IOS::ES::TicketView), - ticket_view.data(), ticket_view.size()); - } - } - else if (ShouldReturnFakeViewsForIOSes(TitleID)) - { - Memory::Memset(request.io_vectors[0].address, 0, sizeof(IOS::ES::TicketView)); - WARN_LOG(IOS_ES, "GetViews: Faking IOS title %016" PRIx64 " being present", TitleID); - } - - INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWS for titleID: %08x/%08x (MaxViews = %i)", (u32)(TitleID >> 32), - (u32)TitleID, maxViews); - - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::GetTMDViewSize(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - - const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); - - if (!Loader.IsValid()) - return GetDefaultReply(FS_ENOENT); - - const u32 view_size = static_cast(Loader.GetTMD().GetRawView().size()); - Memory::Write_U32(view_size, request.io_vectors[0].address); - - INFO_LOG(IOS_ES, "IOCTL_ES_GETTMDVIEWCNT: title: %08x/%08x (view size %i)", (u32)(TitleID >> 32), - (u32)TitleID, view_size); - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::GetTMDViews(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(2, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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()) - return GetDefaultReply(FS_ENOENT); - - const std::vector raw_view = Loader.GetTMD().GetRawView(); - if (raw_view.size() != request.io_vectors[0].size) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - Memory::CopyToEmu(request.io_vectors[0].address, raw_view.data(), raw_view.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::DIGetTMDViewSize(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - // Sanity check the TMD size. - if (request.in_vectors[0].size >= 4 * 1024 * 1024) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - if (request.io_vectors[0].size != sizeof(u32)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const bool has_tmd = request.in_vectors[0].size != 0; - size_t tmd_view_size = 0; - - if (has_tmd) - { - std::vector tmd_bytes(request.in_vectors[0].size); - Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[0].address, tmd_bytes.size()); - const IOS::ES::TMDReader tmd{std::move(tmd_bytes)}; - - // Yes, this returns -1017, not ES_INVALID_TMD. - // IOS simply checks whether the TMD has all required content entries. - if (!tmd.IsValid()) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - tmd_view_size = tmd.GetRawView().size(); - } - else - { - // If no TMD was passed in and no title is active, IOS returns -1017. - if (!s_title_context.active) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - tmd_view_size = s_title_context.tmd.GetRawView().size(); - } - - Memory::Write_U32(static_cast(tmd_view_size), request.io_vectors[0].address); - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::DIGetTMDView(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(2, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - // Sanity check the TMD size. - if (request.in_vectors[0].size >= 4 * 1024 * 1024) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - // Check whether the TMD view size is consistent. - if (request.in_vectors[1].size != sizeof(u32) || - Memory::Read_U32(request.in_vectors[1].address) != request.io_vectors[0].size) - { - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - } - - const bool has_tmd = request.in_vectors[0].size != 0; - std::vector tmd_view; - - if (has_tmd) - { - std::vector tmd_bytes(request.in_vectors[0].size); - Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[0].address, tmd_bytes.size()); - const IOS::ES::TMDReader tmd{std::move(tmd_bytes)}; - - if (!tmd.IsValid()) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - tmd_view = tmd.GetRawView(); - } - else - { - // If no TMD was passed in and no title is active, IOS returns -1017. - if (!s_title_context.active) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - tmd_view = s_title_context.tmd.GetRawView(); - } - - if (tmd_view.size() != request.io_vectors[0].size) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - Memory::CopyToEmu(request.io_vectors[0].address, tmd_view.data(), tmd_view.size()); - return GetDefaultReply(IPC_SUCCESS); -} - IPCCommandResult ES::GetConsumption(const IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(1, 2)) @@ -1370,156 +401,6 @@ IPCCommandResult ES::GetConsumption(const IOCtlVRequest& request) return GetDefaultReply(IPC_SUCCESS); } -static bool CanDeleteTitle(u64 title_id) -{ - // IOS only allows deleting non-system titles (or a system title higher than 00000001-00000101). - return static_cast(title_id >> 32) != 0x00000001 || static_cast(title_id) > 0x101; -} - -IPCCommandResult ES::DeleteTitle(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != 8) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const u64 title_id = Memory::Read_U64(request.in_vectors[0].address); - - if (!CanDeleteTitle(title_id)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const std::string title_dir = - StringFromFormat("%s/title/%08x/%08x/", RootUserPath(Common::FROM_SESSION_ROOT).c_str(), - static_cast(title_id >> 32), static_cast(title_id)); - if (!File::IsDirectory(title_dir) || - !DiscIO::CNANDContentManager::Access().RemoveTitle(title_id, Common::FROM_SESSION_ROOT)) - { - return GetDefaultReply(FS_ENOENT); - } - - if (!File::DeleteDirRecursively(title_dir)) - { - ERROR_LOG(IOS_ES, "DeleteTitle: Failed to delete title directory: %s", title_dir.c_str()); - return GetDefaultReply(FS_EACCESS); - } - - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 0)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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) -{ - if (!request.HasNumberOfValidVectors(1, 0)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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) -{ - if (!request.HasNumberOfValidVectors(1, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); - - if (!Loader.IsValid() || !Loader.GetTMD().IsValid()) - return GetDefaultReply(FS_ENOENT); - - const u32 tmd_size = static_cast(Loader.GetTMD().GetRawTMD().size()); - Memory::Write_U32(tmd_size, request.io_vectors[0].address); - - INFO_LOG(IOS_ES, "IOCTL_ES_GETSTOREDTMDSIZE: title: %08x/%08x (view size %i)", - (u32)(TitleID >> 32), (u32)TitleID, tmd_size); - - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::GetStoredTMD(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(2, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); - // TODO: actually use this param in when writing to the outbuffer :/ - const u32 MaxCount = Memory::Read_U32(request.in_vectors[1].address); - const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); - - if (!Loader.IsValid() || !Loader.GetTMD().IsValid()) - return GetDefaultReply(FS_ENOENT); - - const std::vector raw_tmd = Loader.GetTMD().GetRawTMD(); - if (raw_tmd.size() != request.io_vectors[0].size) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.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) -{ - if (!request.HasNumberOfValidVectors(3, 2)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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) -{ - if (!request.HasNumberOfValidVectors(3, 2)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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) { if (!request.HasNumberOfValidVectors(2, 0)) @@ -1565,280 +446,6 @@ IPCCommandResult ES::LaunchBC(const IOCtlVRequest& request) return GetNoReply(); } -IPCCommandResult ES::ExportTitleInit(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != 8) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - // No concurrent title import/export is allowed. - if (m_export_title_context.valid) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const auto& content_loader = AccessContentDevice(Memory::Read_U64(request.in_vectors[0].address)); - if (!content_loader.IsValid()) - return GetDefaultReply(FS_ENOENT); - if (!content_loader.GetTMD().IsValid()) - return GetDefaultReply(ES_INVALID_TMD); - - m_export_title_context.tmd = content_loader.GetTMD(); - - const auto ticket = DiscIO::FindSignedTicket(m_export_title_context.tmd.GetTitleId()); - if (!ticket.IsValid()) - return GetDefaultReply(ES_NO_TICKET_INSTALLED); - if (ticket.GetTitleId() != m_export_title_context.tmd.GetTitleId()) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - m_export_title_context.title_key = ticket.GetTitleKey(); - - const auto& raw_tmd = m_export_title_context.tmd.GetRawTMD(); - if (request.io_vectors[0].size != raw_tmd.size()) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size()); - - m_export_title_context.valid = true; - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::ExportContentBegin(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(2, 0) || request.in_vectors[0].size != 8 || - request.in_vectors[1].size != 4) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const u64 title_id = Memory::Read_U64(request.in_vectors[0].address); - const u32 content_id = Memory::Read_U32(request.in_vectors[1].address); - - if (!m_export_title_context.valid || m_export_title_context.tmd.GetTitleId() != title_id) - { - ERROR_LOG(IOS_ES, "Tried to use ExportContentBegin with an invalid title export context."); - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - } - - const auto& content_loader = AccessContentDevice(title_id); - if (!content_loader.IsValid()) - return GetDefaultReply(FS_ENOENT); - - const auto* content = content_loader.GetContentByID(content_id); - if (!content) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - OpenedContent entry; - entry.m_position = 0; - entry.m_content = content->m_metadata; - entry.m_title_id = title_id; - content->m_Data->Open(); - - u32 cid = 0; - while (m_export_title_context.contents.find(cid) != m_export_title_context.contents.end()) - cid++; - - 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; - - m_export_title_context.contents.emplace(cid, content_export); - // IOS returns a content ID which is passed to further content calls. - return GetDefaultReply(cid); -} - -IPCCommandResult ES::ExportContentData(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != 4 || - request.io_vectors[0].size == 0) - { - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - } - - const u32 content_id = Memory::Read_U32(request.in_vectors[0].address); - const u32 bytes_to_read = request.io_vectors[0].size; - - const auto iterator = m_export_title_context.contents.find(content_id); - if (!m_export_title_context.valid || iterator == m_export_title_context.contents.end() || - iterator->second.content.m_position >= iterator->second.content.m_content.size) - { - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - } - - auto& metadata = iterator->second.content; - - const auto& content_loader = AccessContentDevice(metadata.m_title_id); - const auto* content = content_loader.GetContentByID(metadata.m_content.id); - content->m_Data->Open(); - - const u32 length = - std::min(static_cast(metadata.m_content.size - metadata.m_position), bytes_to_read); - std::vector buffer(length); - - if (!content->m_Data->GetRange(metadata.m_position, length, buffer.data())) - { - ERROR_LOG(IOS_ES, "ExportContentData: ES_READ_LESS_DATA_THAN_EXPECTED"); - return GetDefaultReply(ES_READ_LESS_DATA_THAN_EXPECTED); - } - - // IOS aligns the buffer to 32 bytes. Since we also need to align it to 16 bytes, - // let's just follow IOS here. - buffer.resize(Common::AlignUp(buffer.size(), 32)); - std::vector output(buffer.size()); - - mbedtls_aes_context aes_ctx; - mbedtls_aes_setkey_enc(&aes_ctx, m_export_title_context.title_key.data(), 128); - const int ret = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_ENCRYPT, buffer.size(), - iterator->second.iv.data(), buffer.data(), output.data()); - if (ret != 0) - { - // XXX: proper error code when IOSC_Encrypt fails. - ERROR_LOG(IOS_ES, "ExportContentData: Failed to encrypt content."); - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - } - - Memory::CopyToEmu(request.io_vectors[0].address, output.data(), output.size()); - metadata.m_position += length; - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::ExportContentEnd(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != 4) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const u32 content_id = Memory::Read_U32(request.in_vectors[0].address); - - const auto iterator = m_export_title_context.contents.find(content_id); - if (!m_export_title_context.valid || iterator == m_export_title_context.contents.end() || - iterator->second.content.m_position != iterator->second.content.m_content.size) - { - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - } - - // 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(); - - m_export_title_context.contents.erase(iterator); - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::ExportTitleDone(const IOCtlVRequest& request) -{ - if (!m_export_title_context.valid) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - m_export_title_context.valid = false; - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::CheckKoreaRegion(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(0, 0)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - // 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) -{ - if (!request.HasNumberOfValidVectors(0, 1) || request.io_vectors[0].size != 0x180) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - INFO_LOG(IOS_ES, "IOCTL_ES_GETDEVICECERT"); - u8* destination = Memory::GetPointer(request.io_vectors[0].address); - - const EcWii& ec = EcWii::GetInstance(); - MakeNGCert(destination, ec.GetNGID(), ec.GetNGKeyID(), ec.GetNGPriv(), ec.GetNGSig()); - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::Sign(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(1, 2)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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); - - if (!s_title_context.active) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const EcWii& ec = EcWii::GetInstance(); - MakeAPSigAndCert(sig_out, ap_cert_out, s_title_context.tmd.GetTitleId(), data, data_size, - ec.GetNGPriv(), ec.GetNGID()); - - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::GetBoot2Version(const IOCtlVRequest& request) -{ - if (!request.HasNumberOfValidVectors(0, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - 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) -{ - if (!request.HasNumberOfValidVectors(1, 1) || - request.io_vectors[0].size != sizeof(IOS::ES::TicketView)) - { - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - } - - const bool has_ticket_vector = request.in_vectors[0].size == 0x2A4; - - // This ioctlv takes either a signed ticket or no ticket, in which case the ticket size must be 0. - if (!has_ticket_vector && request.in_vectors[0].size != 0) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - std::vector view; - - // If no ticket was passed in, IOS returns the ticket view for the current title. - // Of course, this returns -1017 if no title is active and no ticket is passed. - if (!has_ticket_vector) - { - if (!s_title_context.active) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - view = s_title_context.ticket.GetRawTicketView(0); - } - else - { - std::vector ticket_bytes(request.in_vectors[0].size); - Memory::CopyFromEmu(ticket_bytes.data(), request.in_vectors[0].address, ticket_bytes.size()); - const IOS::ES::TicketReader ticket{std::move(ticket_bytes)}; - - view = ticket.GetRawTicketView(0); - } - - Memory::CopyToEmu(request.io_vectors[0].address, view.data(), view.size()); - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult ES::GetOwnedTitleCount(const IOCtlVRequest& request) -{ - const std::vector titles = GetTitlesWithTickets(); - INFO_LOG(IOS_ES, "GetOwnedTitleCount: %zu titles", titles.size()); - return GetTitleCount(titles, request); -} - -IPCCommandResult ES::GetOwnedTitles(const IOCtlVRequest& request) -{ - return GetTitles(GetTitlesWithTickets(), request); -} - const DiscIO::CNANDContentLoader& ES::AccessContentDevice(u64 title_id) { // for WADs, the passed title id and the stored title id match; along with s_content_file diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index ee2acc7c55..829729e31a 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -6,7 +6,6 @@ #include #include -#include #include #include @@ -20,7 +19,6 @@ class PointerWrap; namespace DiscIO { class CNANDContentLoader; -struct SNANDContent; } namespace IOS @@ -29,7 +27,20 @@ namespace HLE { namespace Device { -class ES : public Device +struct TitleContext +{ + void Clear(); + void DoState(PointerWrap& p); + void Update(const DiscIO::CNANDContentLoader& content_loader); + void Update(const IOS::ES::TMDReader& tmd_, const IOS::ES::TicketReader& ticket_); + + IOS::ES::TicketReader ticket; + IOS::ES::TMDReader tmd; + bool active = false; + bool first_change = true; +}; + +class ES final : public Device { public: ES(u32 device_id, const std::string& device_name); @@ -150,6 +161,7 @@ private: u8 padding[0x3c]; }; + // Title management IPCCommandResult AddTicket(const IOCtlVRequest& request); IPCCommandResult AddTMD(const IOCtlVRequest& request); IPCCommandResult AddTitleStart(const IOCtlVRequest& request); @@ -158,23 +170,46 @@ private: IPCCommandResult AddContentFinish(const IOCtlVRequest& request); IPCCommandResult AddTitleFinish(const IOCtlVRequest& request); IPCCommandResult AddTitleCancel(const IOCtlVRequest& request); - IPCCommandResult ESGetDeviceID(const IOCtlVRequest& request); + IPCCommandResult ExportTitleInit(const IOCtlVRequest& request); + IPCCommandResult ExportContentBegin(const IOCtlVRequest& request); + IPCCommandResult ExportContentData(const IOCtlVRequest& request); + IPCCommandResult ExportContentEnd(const IOCtlVRequest& request); + IPCCommandResult ExportTitleDone(const IOCtlVRequest& request); + IPCCommandResult DeleteTitle(const IOCtlVRequest& request); + IPCCommandResult DeleteTicket(const IOCtlVRequest& request); + IPCCommandResult DeleteTitleContent(const IOCtlVRequest& request); + + // Device identity and encryption + IPCCommandResult GetConsoleID(const IOCtlVRequest& request); + IPCCommandResult GetDeviceCertificate(const IOCtlVRequest& request); + IPCCommandResult CheckKoreaRegion(const IOCtlVRequest& request); + IPCCommandResult Sign(const IOCtlVRequest& request); + IPCCommandResult Encrypt(const IOCtlVRequest& request); + IPCCommandResult Decrypt(const IOCtlVRequest& request); + + // Misc + IPCCommandResult SetUID(const IOCtlVRequest& request); + IPCCommandResult GetTitleDirectory(const IOCtlVRequest& request); + IPCCommandResult GetTitleID(const IOCtlVRequest& request); + IPCCommandResult GetConsumption(const IOCtlVRequest& request); + IPCCommandResult Launch(const IOCtlVRequest& request); + IPCCommandResult LaunchBC(const IOCtlVRequest& request); + + // Title contents 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); + // Title information IPCCommandResult GetTitleCount(const std::vector& titles, const IOCtlVRequest& request); IPCCommandResult GetTitles(const std::vector& titles, const IOCtlVRequest& request); IPCCommandResult GetOwnedTitleCount(const IOCtlVRequest& request); IPCCommandResult GetOwnedTitles(const IOCtlVRequest& request); IPCCommandResult GetTitleCount(const IOCtlVRequest& request); IPCCommandResult GetTitles(const IOCtlVRequest& request); - + IPCCommandResult GetBoot2Version(const IOCtlVRequest& request); IPCCommandResult GetStoredContentsCount(const IOS::ES::TMDReader& tmd, const IOCtlVRequest& request); IPCCommandResult GetStoredContents(const IOS::ES::TMDReader& tmd, const IOCtlVRequest& request); @@ -182,41 +217,21 @@ private: IPCCommandResult GetStoredContents(const IOCtlVRequest& request); IPCCommandResult GetTMDStoredContentsCount(const IOCtlVRequest& request); IPCCommandResult GetTMDStoredContents(const IOCtlVRequest& request); + IPCCommandResult GetStoredTMDSize(const IOCtlVRequest& request); + IPCCommandResult GetStoredTMD(const IOCtlVRequest& request); - IPCCommandResult GetViewCount(const IOCtlVRequest& request); - IPCCommandResult GetViews(const IOCtlVRequest& request); - IPCCommandResult DIGetTicketView(const IOCtlVRequest& request); - + // Views for tickets and TMDs + IPCCommandResult GetTicketViewCount(const IOCtlVRequest& request); + IPCCommandResult GetTicketViews(const IOCtlVRequest& request); IPCCommandResult GetTMDViewSize(const IOCtlVRequest& request); IPCCommandResult GetTMDViews(const IOCtlVRequest& request); - + IPCCommandResult DIGetTicketView(const IOCtlVRequest& request); IPCCommandResult DIGetTMDViewSize(const IOCtlVRequest& request); IPCCommandResult DIGetTMDView(const IOCtlVRequest& request); - IPCCommandResult GetConsumption(const IOCtlVRequest& request); - IPCCommandResult DeleteTitle(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 LaunchBC(const IOCtlVRequest& request); - - IPCCommandResult ExportTitleInit(const IOCtlVRequest& request); - IPCCommandResult ExportContentBegin(const IOCtlVRequest& request); - IPCCommandResult ExportContentData(const IOCtlVRequest& request); - IPCCommandResult ExportContentEnd(const IOCtlVRequest& request); - IPCCommandResult ExportTitleDone(const IOCtlVRequest& request); - - IPCCommandResult CheckKoreaRegion(const IOCtlVRequest& request); - IPCCommandResult GetDeviceCertificate(const IOCtlVRequest& request); - IPCCommandResult Sign(const IOCtlVRequest& request); - IPCCommandResult GetBoot2Version(const IOCtlVRequest& request); - static bool LaunchIOS(u64 ios_title_id); static bool LaunchPPCTitle(u64 title_id, bool skip_reload); + static TitleContext& GetTitleContext(); static const DiscIO::CNANDContentLoader& AccessContentDevice(u64 title_id); diff --git a/Source/Core/Core/IOS/ES/Identity.cpp b/Source/Core/Core/IOS/ES/Identity.cpp new file mode 100644 index 0000000000..4951247fb8 --- /dev/null +++ b/Source/Core/Core/IOS/ES/Identity.cpp @@ -0,0 +1,155 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/IOS/ES/ES.h" + +#include +#include + +#include + +#include "Common/Assert.h" +#include "Common/Logging/Log.h" +#include "Core/HW/Memmap.h" +#include "Core/IOS/ES/Formats.h" +#include "Core/ec_wii.h" + +namespace IOS +{ +namespace HLE +{ +namespace Device +{ +constexpr u8 s_key_sd[0x10] = {0xab, 0x01, 0xb9, 0xd8, 0xe1, 0x62, 0x2b, 0x08, + 0xaf, 0xba, 0xd8, 0x4d, 0xbf, 0xc2, 0xa5, 0x5d}; +constexpr u8 s_key_ecc[0x1e] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; +constexpr u8 s_key_empty[0x10] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +// default key table +constexpr const u8* s_key_table[11] = { + s_key_ecc, // ECC Private Key + s_key_empty, // Console ID + s_key_empty, // NAND AES Key + s_key_empty, // NAND HMAC + s_key_empty, // Common Key + s_key_empty, // PRNG seed + s_key_sd, // SD Key + s_key_empty, // Unknown + s_key_empty, // Unknown + s_key_empty, // Unknown + s_key_empty, // Unknown +}; + +void ES::DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv, u8* output) +{ + mbedtls_aes_context AES_ctx; + mbedtls_aes_setkey_dec(&AES_ctx, s_key_table[key_index], 128); + memcpy(new_iv, iv, 16); + mbedtls_aes_crypt_cbc(&AES_ctx, MBEDTLS_AES_DECRYPT, size, new_iv, input, output); +} + +IPCCommandResult ES::GetConsoleID(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(0, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const 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::Encrypt(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(3, 2)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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) +{ + if (!request.HasNumberOfValidVectors(3, 2)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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::CheckKoreaRegion(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(0, 0)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + // 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) +{ + if (!request.HasNumberOfValidVectors(0, 1) || request.io_vectors[0].size != 0x180) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + INFO_LOG(IOS_ES, "IOCTL_ES_GETDEVICECERT"); + u8* destination = Memory::GetPointer(request.io_vectors[0].address); + + const EcWii& ec = EcWii::GetInstance(); + MakeNGCert(destination, ec.GetNGID(), ec.GetNGKeyID(), ec.GetNGPriv(), ec.GetNGSig()); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::Sign(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 2)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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); + + if (!GetTitleContext().active) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const EcWii& ec = EcWii::GetInstance(); + MakeAPSigAndCert(sig_out, ap_cert_out, GetTitleContext().tmd.GetTitleId(), data, data_size, + ec.GetNGPriv(), ec.GetNGID()); + + return GetDefaultReply(IPC_SUCCESS); +} +} // namespace Device +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/ES/TitleContents.cpp b/Source/Core/Core/IOS/ES/TitleContents.cpp new file mode 100644 index 0000000000..b49140ed1f --- /dev/null +++ b/Source/Core/Core/IOS/ES/TitleContents.cpp @@ -0,0 +1,201 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/IOS/ES/ES.h" + +#include +#include +#include + +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" +#include "Core/HW/Memmap.h" +#include "Core/IOS/ES/Formats.h" +#include "DiscIO/NANDContentLoader.h" + +namespace IOS +{ +namespace HLE +{ +namespace Device +{ +u32 ES::OpenTitleContent(u32 CFD, u64 TitleID, u16 Index) +{ + const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); + + if (!Loader.IsValid() || !Loader.GetTMD().IsValid() || !Loader.GetTicket().IsValid()) + { + WARN_LOG(IOS_ES, "ES: loader not valid for %" PRIx64, TitleID); + return 0xffffffff; + } + + const DiscIO::SNANDContent* pContent = Loader.GetContentByIndex(Index); + + if (pContent == nullptr) + { + return 0xffffffff; // TODO: what is the correct error value here? + } + + OpenedContent content; + content.m_position = 0; + content.m_content = pContent->m_metadata; + content.m_title_id = TitleID; + + pContent->m_Data->Open(); + + m_ContentAccessMap[CFD] = content; + return CFD; +} + +IPCCommandResult ES::OpenTitleContent(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(3, 0)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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) +{ + if (!request.HasNumberOfValidVectors(1, 0)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + u32 Index = Memory::Read_U32(request.in_vectors[0].address); + + if (!GetTitleContext().active) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + s32 CFD = OpenTitleContent(m_AccessIdentID++, GetTitleContext().tmd.GetTitleId(), Index); + INFO_LOG(IOS_ES, "IOCTL_ES_OPENCONTENT: Index %i -> got CFD %x", Index, CFD); + + return GetDefaultReply(CFD); +} + +IPCCommandResult ES::ReadContent(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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); + } + OpenedContent& rContent = itr->second; + + u8* pDest = Memory::GetPointer(Addr); + + if (rContent.m_position + Size > rContent.m_content.size) + { + Size = static_cast(rContent.m_content.size) - rContent.m_position; + } + + if (Size > 0) + { + if (pDest) + { + const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(rContent.m_title_id); + // ContentLoader should never be invalid; rContent has been created by it. + if (ContentLoader.IsValid() && ContentLoader.GetTicket().IsValid()) + { + const DiscIO::SNANDContent* pContent = + ContentLoader.GetContentByIndex(rContent.m_content.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_content.index); + + return GetDefaultReply(Size); +} + +IPCCommandResult ES::CloseContent(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 0)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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_title_id); + // 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_content.index); + pContent->m_Data->Close(); + } + + m_ContentAccessMap.erase(itr); + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::SeekContent(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(3, 0)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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); + } + OpenedContent& 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 = static_cast(rContent.m_content.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); +} +} // namespace Device +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/ES/TitleInformation.cpp b/Source/Core/Core/IOS/ES/TitleInformation.cpp new file mode 100644 index 0000000000..dea38aa386 --- /dev/null +++ b/Source/Core/Core/IOS/ES/TitleInformation.cpp @@ -0,0 +1,326 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/IOS/ES/ES.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "Common/NandPaths.h" +#include "Common/StringUtil.h" +#include "Core/HW/Memmap.h" +#include "Core/IOS/ES/Formats.h" +#include "DiscIO/NANDContentLoader.h" + +namespace IOS +{ +namespace HLE +{ +namespace Device +{ +static std::vector GetStoredContentsFromTMD(const IOS::ES::TMDReader& tmd) +{ + if (!tmd.IsValid()) + return {}; + + const DiscIO::CSharedContent shared{Common::FROM_SESSION_ROOT}; + const std::vector contents = tmd.GetContents(); + + std::vector stored_contents; + + std::copy_if(contents.begin(), contents.end(), std::back_inserter(stored_contents), + [&tmd, &shared](const auto& content) { + if (content.IsShared()) + { + const std::string path = shared.GetFilenameFromSHA1(content.sha1.data()); + return path != "unk" && File::Exists(path); + } + return File::Exists( + Common::GetTitleContentPath(tmd.GetTitleId(), Common::FROM_SESSION_ROOT) + + StringFromFormat("%08x.app", content.id)); + }); + + return stored_contents; +} + +// Used by the GetStoredContents ioctlvs. This assumes that the first output vector +// is used for the content count (u32). +IPCCommandResult ES::GetStoredContentsCount(const IOS::ES::TMDReader& tmd, + const IOCtlVRequest& request) +{ + if (request.io_vectors[0].size != sizeof(u32) || !tmd.IsValid()) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const u16 num_contents = static_cast(GetStoredContentsFromTMD(tmd).size()); + Memory::Write_U32(num_contents, request.io_vectors[0].address); + + INFO_LOG(IOS_ES, "GetStoredContentsCount (0x%x): %u content(s) for %016" PRIx64, request.request, + num_contents, tmd.GetTitleId()); + return GetDefaultReply(IPC_SUCCESS); +} + +// Used by the GetStoredContents ioctlvs. This assumes that the second input vector is used +// for the content count and the output vector is used to store a list of content IDs (u32s). +IPCCommandResult ES::GetStoredContents(const IOS::ES::TMDReader& tmd, const IOCtlVRequest& request) +{ + if (!tmd.IsValid()) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + if (request.in_vectors[1].size != sizeof(u32) || + request.io_vectors[0].size != Memory::Read_U32(request.in_vectors[1].address) * sizeof(u32)) + { + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + } + + const auto contents = GetStoredContentsFromTMD(tmd); + const u32 max_content_count = Memory::Read_U32(request.in_vectors[1].address); + for (u32 i = 0; i < std::min(static_cast(contents.size()), max_content_count); ++i) + Memory::Write_U32(contents[i].id, request.io_vectors[0].address + i * sizeof(u32)); + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetStoredContentsCount(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != sizeof(u64)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const u64 title_id = Memory::Read_U64(request.in_vectors[0].address); + const DiscIO::CNANDContentLoader& content_loader = AccessContentDevice(title_id); + if (!content_loader.IsValid()) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + return GetStoredContentsCount(content_loader.GetTMD(), request); +} + +IPCCommandResult ES::GetStoredContents(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(2, 1) || request.in_vectors[0].size != sizeof(u64)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const u64 title_id = Memory::Read_U64(request.in_vectors[0].address); + const DiscIO::CNANDContentLoader& content_loader = AccessContentDevice(title_id); + if (!content_loader.IsValid()) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + return GetStoredContents(content_loader.GetTMD(), request); +} + +IPCCommandResult ES::GetTMDStoredContentsCount(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + std::vector tmd_bytes(request.in_vectors[0].size); + Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[0].address, tmd_bytes.size()); + return GetStoredContentsCount(IOS::ES::TMDReader{std::move(tmd_bytes)}, request); +} + +IPCCommandResult ES::GetTMDStoredContents(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(2, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + std::vector tmd_bytes(request.in_vectors[0].size); + Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[0].address, tmd_bytes.size()); + return GetStoredContents(IOS::ES::TMDReader{std::move(tmd_bytes)}, request); +} + +static bool IsValidPartOfTitleID(const std::string& string) +{ + if (string.length() != 8) + return false; + return std::all_of(string.begin(), string.end(), + [](const auto character) { return std::isxdigit(character) != 0; }); +} + +// Returns a vector of title IDs. IOS does not check the TMD at all here. +static std::vector GetInstalledTitles() +{ + const std::string titles_dir = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/title"; + if (!File::IsDirectory(titles_dir)) + { + ERROR_LOG(IOS_ES, "/title is not a directory"); + return {}; + } + + std::vector title_ids; + + // The /title directory contains one directory per title type, and each of them contains + // a directory per title (where the name is the low 32 bits of the title ID in %08x format). + const auto entries = File::ScanDirectoryTree(titles_dir, true); + for (const File::FSTEntry& title_type : entries.children) + { + if (!title_type.isDirectory || !IsValidPartOfTitleID(title_type.virtualName)) + continue; + + if (title_type.children.empty()) + continue; + + for (const File::FSTEntry& title_identifier : title_type.children) + { + if (!title_identifier.isDirectory || !IsValidPartOfTitleID(title_identifier.virtualName)) + continue; + + const u32 type = std::stoul(title_type.virtualName, nullptr, 16); + const u32 identifier = std::stoul(title_identifier.virtualName, nullptr, 16); + title_ids.push_back(static_cast(type) << 32 | identifier); + } + } + + return title_ids; +} + +// Returns a vector of title IDs for which there is a ticket. +static std::vector GetTitlesWithTickets() +{ + const std::string titles_dir = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/ticket"; + if (!File::IsDirectory(titles_dir)) + { + ERROR_LOG(IOS_ES, "/ticket is not a directory"); + return {}; + } + + std::vector title_ids; + + // The /ticket directory contains one directory per title type, and each of them contains + // one ticket per title (where the name is the low 32 bits of the title ID in %08x format). + const auto entries = File::ScanDirectoryTree(titles_dir, true); + for (const File::FSTEntry& title_type : entries.children) + { + if (!title_type.isDirectory || !IsValidPartOfTitleID(title_type.virtualName)) + continue; + + if (title_type.children.empty()) + continue; + + for (const File::FSTEntry& ticket : title_type.children) + { + const std::string name_without_ext = ticket.virtualName.substr(0, 8); + if (ticket.isDirectory || !IsValidPartOfTitleID(name_without_ext) || + name_without_ext + ".tik" != ticket.virtualName) + { + continue; + } + + const u32 type = std::stoul(title_type.virtualName, nullptr, 16); + const u32 identifier = std::stoul(name_without_ext, nullptr, 16); + title_ids.push_back(static_cast(type) << 32 | identifier); + } + } + + return title_ids; +} + +IPCCommandResult ES::GetTitleCount(const std::vector& titles, const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(0, 1) || request.io_vectors[0].size != 4) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + Memory::Write_U32(static_cast(titles.size()), request.io_vectors[0].address); + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetTitles(const std::vector& titles, const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const size_t max_count = Memory::Read_U32(request.in_vectors[0].address); + for (size_t i = 0; i < std::min(max_count, titles.size()); i++) + { + Memory::Write_U64(titles[i], request.io_vectors[0].address + static_cast(i) * sizeof(u64)); + INFO_LOG(IOS_ES, " title %016" PRIx64, titles[i]); + } + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetTitleCount(const IOCtlVRequest& request) +{ + const std::vector titles = GetInstalledTitles(); + INFO_LOG(IOS_ES, "GetTitleCount: %zu titles", titles.size()); + return GetTitleCount(titles, request); +} + +IPCCommandResult ES::GetTitles(const IOCtlVRequest& request) +{ + return GetTitles(GetInstalledTitles(), request); +} + +IPCCommandResult ES::GetStoredTMDSize(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); + + if (!Loader.IsValid() || !Loader.GetTMD().IsValid()) + return GetDefaultReply(FS_ENOENT); + + const u32 tmd_size = static_cast(Loader.GetTMD().GetRawTMD().size()); + Memory::Write_U32(tmd_size, request.io_vectors[0].address); + + INFO_LOG(IOS_ES, "IOCTL_ES_GETSTOREDTMDSIZE: title: %08x/%08x (view size %i)", + (u32)(TitleID >> 32), (u32)TitleID, tmd_size); + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetStoredTMD(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(2, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + // TODO: actually use this param in when writing to the outbuffer :/ + const u32 MaxCount = Memory::Read_U32(request.in_vectors[1].address); + const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); + + if (!Loader.IsValid() || !Loader.GetTMD().IsValid()) + return GetDefaultReply(FS_ENOENT); + + const std::vector raw_tmd = Loader.GetTMD().GetRawTMD(); + if (raw_tmd.size() != request.io_vectors[0].size) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.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::GetOwnedTitleCount(const IOCtlVRequest& request) +{ + const std::vector titles = GetTitlesWithTickets(); + INFO_LOG(IOS_ES, "GetOwnedTitleCount: %zu titles", titles.size()); + return GetTitleCount(titles, request); +} + +IPCCommandResult ES::GetOwnedTitles(const IOCtlVRequest& request) +{ + return GetTitles(GetTitlesWithTickets(), request); +} + +IPCCommandResult ES::GetBoot2Version(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(0, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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); +} +} // namespace Device +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp new file mode 100644 index 0000000000..5a275fa954 --- /dev/null +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -0,0 +1,563 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/IOS/ES/ES.h" + +#include +#include +#include +#include + +#include +#include + +#include "Common/Align.h" +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "Common/NandPaths.h" +#include "Common/StringUtil.h" +#include "Core/HW/Memmap.h" +#include "Core/IOS/ES/Formats.h" +#include "Core/ec_wii.h" +#include "DiscIO/NANDContentLoader.h" + +namespace IOS +{ +namespace HLE +{ +namespace Device +{ +IPCCommandResult ES::AddTicket(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(3, 0)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + std::vector bytes(request.in_vectors[0].size); + Memory::CopyFromEmu(bytes.data(), request.in_vectors[0].address, request.in_vectors[0].size); + + IOS::ES::TicketReader ticket{std::move(bytes)}; + if (!ticket.IsValid()) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const u32 ticket_device_id = ticket.GetDeviceId(); + const u32 device_id = EcWii::GetInstance().GetNGID(); + if (ticket_device_id != 0) + { + if (device_id != ticket_device_id) + { + WARN_LOG(IOS_ES, "Device ID mismatch: ticket %08x, device %08x", ticket_device_id, device_id); + return GetDefaultReply(ES_DEVICE_ID_MISMATCH); + } + const s32 ret = ticket.Unpersonalise(); + if (ret < 0) + { + ERROR_LOG(IOS_ES, "AddTicket: Failed to unpersonalise ticket for %016" PRIx64 " (ret = %d)", + ticket.GetTitleId(), ret); + return GetDefaultReply(ret); + } + } + + if (!DiscIO::AddTicket(ticket)) + return GetDefaultReply(ES_WRITE_FAILURE); + + INFO_LOG(IOS_ES, "AddTicket: Imported ticket for title %016" PRIx64, ticket.GetTitleId()); + return GetDefaultReply(IPC_SUCCESS); +} + +static bool WriteImportTMD(const IOS::ES::TMDReader& tmd) +{ + const std::string tmd_path = Common::GetImportTitlePath(tmd.GetTitleId()) + "/content/title.tmd"; + File::CreateFullPath(tmd_path); + + File::IOFile file(tmd_path, "wb"); + return file.WriteBytes(tmd.GetRawTMD().data(), tmd.GetRawTMD().size()); +} + +static bool MoveImportTMDToTitleDirectory(const IOS::ES::TMDReader& tmd) +{ + const std::string src = Common::GetImportTitlePath(tmd.GetTitleId()) + "/content/title.tmd"; + const std::string dest = Common::GetTMDFileName(tmd.GetTitleId(), Common::FROM_SESSION_ROOT); + return File::RenameSync(src, dest); +} + +static std::string GetImportContentPath(const IOS::ES::TMDReader& tmd, u32 content_id) +{ + return Common::GetImportTitlePath(tmd.GetTitleId()) + + StringFromFormat("/content/%08x.app", content_id); +} + +IPCCommandResult ES::AddTMD(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 0)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + std::vector tmd(request.in_vectors[0].size); + Memory::CopyFromEmu(tmd.data(), request.in_vectors[0].address, request.in_vectors[0].size); + + // Ioctlv 0x2b writes the TMD to /tmp/title.tmd (for imports) and doesn't seem to write it + // to either /import or /title. So here we simply have to set the import TMD. + m_addtitle_tmd.SetBytes(std::move(tmd)); + if (!m_addtitle_tmd.IsValid()) + return GetDefaultReply(ES_INVALID_TMD); + + DiscIO::cUIDsys uid_sys{Common::FROM_CONFIGURED_ROOT}; + uid_sys.AddTitle(m_addtitle_tmd.GetTitleId()); + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::AddTitleStart(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(4, 0)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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); + } + + DiscIO::cUIDsys uid_sys{Common::FROM_CONFIGURED_ROOT}; + uid_sys.AddTitle(m_addtitle_tmd.GetTitleId()); + + // TODO: check and use the other vectors. + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::AddContentStart(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(2, 0)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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 (!m_addtitle_tmd.IsValid()) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + if (title_id != m_addtitle_tmd.GetTitleId()) + { + ERROR_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTSTART: title id %016" PRIx64 " != " + "TMD title id %016" PRIx64 ", 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) +{ + if (!request.HasNumberOfValidVectors(2, 0)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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); +} + +static bool CheckIfContentHashMatches(const std::vector& content, const IOS::ES::Content& info) +{ + std::array sha1; + mbedtls_sha1(content.data(), info.size, sha1.data()); + return sha1 == info.sha1; +} + +IPCCommandResult ES::AddContentFinish(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 0)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + if (m_addtitle_content_id == 0xFFFFFFFF) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + u32 content_fd = Memory::Read_U32(request.in_vectors[0].address); + INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTFINISH: content fd %08x", content_fd); + + if (!m_addtitle_tmd.IsValid()) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + // Try to find the title key from a pre-installed ticket. + IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(m_addtitle_tmd.GetTitleId()); + if (!ticket.IsValid()) + { + return GetDefaultReply(ES_NO_TICKET_INSTALLED); + } + + mbedtls_aes_context aes_ctx; + mbedtls_aes_setkey_dec(&aes_ctx, ticket.GetTitleKey().data(), 128); + + // The IV for title content decryption is the lower two bytes of the + // content index, zero extended. + IOS::ES::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()); + if (!CheckIfContentHashMatches(decrypted_data, content_info)) + { + ERROR_LOG(IOS_ES, "AddContentFinish: Hash for content %08x doesn't match", content_info.id); + return GetDefaultReply(ES_HASH_DOESNT_MATCH); + } + + // Just write all contents to the title import directory. AddTitleFinish will + // move the contents to the proper location. + const std::string tmp_path = GetImportContentPath(m_addtitle_tmd, m_addtitle_content_id); + File::CreateFullPath(tmp_path); + + File::IOFile fp(tmp_path, "wb"); + if (!fp.WriteBytes(decrypted_data.data(), content_info.size)) + { + ERROR_LOG(IOS_ES, "AddContentFinish: Failed to write to %s", tmp_path.c_str()); + return GetDefaultReply(ES_WRITE_FAILURE); + } + + m_addtitle_content_id = 0xFFFFFFFF; + return GetDefaultReply(IPC_SUCCESS); +} + +static void AbortImport(const u64 title_id, const std::vector& processed_paths) +{ + for (const auto& path : processed_paths) + File::Delete(path); + + const std::string import_dir = Common::GetImportTitlePath(title_id); + File::DeleteDirRecursively(import_dir); +} + +IPCCommandResult ES::AddTitleFinish(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(0, 0) || !m_addtitle_tmd.IsValid()) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + std::vector processed_paths; + + for (const auto& content_info : m_addtitle_tmd.GetContents()) + { + const std::string source = GetImportContentPath(m_addtitle_tmd, content_info.id); + + // Contents may not have been all imported. This is normal and this isn't an error condition. + if (!File::Exists(source)) + continue; + + std::string content_path; + if (content_info.IsShared()) + { + DiscIO::CSharedContent shared_content{Common::FROM_SESSION_ROOT}; + content_path = shared_content.AddSharedContent(content_info.sha1.data()); + } + else + { + content_path = + StringFromFormat("%s%08x.app", Common::GetTitleContentPath(m_addtitle_tmd.GetTitleId(), + Common::FROM_SESSION_ROOT) + .c_str(), + content_info.id); + } + + File::CreateFullPath(content_path); + if (!File::RenameSync(source, content_path)) + { + ERROR_LOG(IOS_ES, "AddTitleFinish: Failed to rename %s to %s", source.c_str(), + content_path.c_str()); + AbortImport(m_addtitle_tmd.GetTitleId(), processed_paths); + return GetDefaultReply(ES_WRITE_FAILURE); + } + + // Do not delete shared contents even if the import fails. This is because + // they can be used by several titles and it's not safe to delete them. + // + // The reason we delete private contents is to avoid having a title with half-complete + // contents, as it can cause issues with the system menu. On the other hand, leaving + // shared contents does not cause any issue. + if (!content_info.IsShared()) + processed_paths.push_back(content_path); + } + + if (!WriteImportTMD(m_addtitle_tmd) || !MoveImportTMDToTitleDirectory(m_addtitle_tmd)) + return GetDefaultReply(ES_WRITE_FAILURE); + + INFO_LOG(IOS_ES, "IOCTL_ES_ADDTITLEFINISH"); + m_addtitle_tmd.SetBytes({}); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::AddTitleCancel(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(0, 0) || !m_addtitle_tmd.IsValid()) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + AbortImport(m_addtitle_tmd.GetTitleId(), {}); + m_addtitle_tmd.SetBytes({}); + return GetDefaultReply(IPC_SUCCESS); +} + +static bool CanDeleteTitle(u64 title_id) +{ + // IOS only allows deleting non-system titles (or a system title higher than 00000001-00000101). + return static_cast(title_id >> 32) != 0x00000001 || static_cast(title_id) > 0x101; +} + +IPCCommandResult ES::DeleteTitle(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != 8) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const u64 title_id = Memory::Read_U64(request.in_vectors[0].address); + + if (!CanDeleteTitle(title_id)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const std::string title_dir = + StringFromFormat("%s/title/%08x/%08x/", RootUserPath(Common::FROM_SESSION_ROOT).c_str(), + static_cast(title_id >> 32), static_cast(title_id)); + if (!File::IsDirectory(title_dir) || + !DiscIO::CNANDContentManager::Access().RemoveTitle(title_id, Common::FROM_SESSION_ROOT)) + { + return GetDefaultReply(FS_ENOENT); + } + + if (!File::DeleteDirRecursively(title_dir)) + { + ERROR_LOG(IOS_ES, "DeleteTitle: Failed to delete title directory: %s", title_dir.c_str()); + return GetDefaultReply(FS_EACCESS); + } + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 0)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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) +{ + if (!request.HasNumberOfValidVectors(1, 0)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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::ExportTitleInit(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != 8) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + // No concurrent title import/export is allowed. + if (m_export_title_context.valid) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const auto& content_loader = AccessContentDevice(Memory::Read_U64(request.in_vectors[0].address)); + if (!content_loader.IsValid()) + return GetDefaultReply(FS_ENOENT); + if (!content_loader.GetTMD().IsValid()) + return GetDefaultReply(ES_INVALID_TMD); + + m_export_title_context.tmd = content_loader.GetTMD(); + + const auto ticket = DiscIO::FindSignedTicket(m_export_title_context.tmd.GetTitleId()); + if (!ticket.IsValid()) + return GetDefaultReply(ES_NO_TICKET_INSTALLED); + if (ticket.GetTitleId() != m_export_title_context.tmd.GetTitleId()) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + m_export_title_context.title_key = ticket.GetTitleKey(); + + const auto& raw_tmd = m_export_title_context.tmd.GetRawTMD(); + if (request.io_vectors[0].size != raw_tmd.size()) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size()); + + m_export_title_context.valid = true; + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::ExportContentBegin(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(2, 0) || request.in_vectors[0].size != 8 || + request.in_vectors[1].size != 4) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const u64 title_id = Memory::Read_U64(request.in_vectors[0].address); + const u32 content_id = Memory::Read_U32(request.in_vectors[1].address); + + if (!m_export_title_context.valid || m_export_title_context.tmd.GetTitleId() != title_id) + { + ERROR_LOG(IOS_ES, "Tried to use ExportContentBegin with an invalid title export context."); + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + } + + const auto& content_loader = AccessContentDevice(title_id); + if (!content_loader.IsValid()) + return GetDefaultReply(FS_ENOENT); + + const auto* content = content_loader.GetContentByID(content_id); + if (!content) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + OpenedContent entry; + entry.m_position = 0; + entry.m_content = content->m_metadata; + entry.m_title_id = title_id; + content->m_Data->Open(); + + u32 cid = 0; + while (m_export_title_context.contents.find(cid) != m_export_title_context.contents.end()) + cid++; + + 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; + + m_export_title_context.contents.emplace(cid, content_export); + // IOS returns a content ID which is passed to further content calls. + return GetDefaultReply(cid); +} + +IPCCommandResult ES::ExportContentData(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != 4 || + request.io_vectors[0].size == 0) + { + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + } + + const u32 content_id = Memory::Read_U32(request.in_vectors[0].address); + const u32 bytes_to_read = request.io_vectors[0].size; + + const auto iterator = m_export_title_context.contents.find(content_id); + if (!m_export_title_context.valid || iterator == m_export_title_context.contents.end() || + iterator->second.content.m_position >= iterator->second.content.m_content.size) + { + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + } + + auto& metadata = iterator->second.content; + + const auto& content_loader = AccessContentDevice(metadata.m_title_id); + const auto* content = content_loader.GetContentByID(metadata.m_content.id); + content->m_Data->Open(); + + const u32 length = + std::min(static_cast(metadata.m_content.size - metadata.m_position), bytes_to_read); + std::vector buffer(length); + + if (!content->m_Data->GetRange(metadata.m_position, length, buffer.data())) + { + ERROR_LOG(IOS_ES, "ExportContentData: ES_READ_LESS_DATA_THAN_EXPECTED"); + return GetDefaultReply(ES_READ_LESS_DATA_THAN_EXPECTED); + } + + // IOS aligns the buffer to 32 bytes. Since we also need to align it to 16 bytes, + // let's just follow IOS here. + buffer.resize(Common::AlignUp(buffer.size(), 32)); + std::vector output(buffer.size()); + + mbedtls_aes_context aes_ctx; + mbedtls_aes_setkey_enc(&aes_ctx, m_export_title_context.title_key.data(), 128); + const int ret = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_ENCRYPT, buffer.size(), + iterator->second.iv.data(), buffer.data(), output.data()); + if (ret != 0) + { + // XXX: proper error code when IOSC_Encrypt fails. + ERROR_LOG(IOS_ES, "ExportContentData: Failed to encrypt content."); + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + } + + Memory::CopyToEmu(request.io_vectors[0].address, output.data(), output.size()); + metadata.m_position += length; + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::ExportContentEnd(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != 4) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const u32 content_id = Memory::Read_U32(request.in_vectors[0].address); + + const auto iterator = m_export_title_context.contents.find(content_id); + if (!m_export_title_context.valid || iterator == m_export_title_context.contents.end() || + iterator->second.content.m_position != iterator->second.content.m_content.size) + { + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + } + + // 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(); + + m_export_title_context.contents.erase(iterator); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::ExportTitleDone(const IOCtlVRequest& request) +{ + if (!m_export_title_context.valid) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + m_export_title_context.valid = false; + return GetDefaultReply(IPC_SUCCESS); +} +} // namespace Device +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/ES/Views.cpp b/Source/Core/Core/IOS/ES/Views.cpp new file mode 100644 index 0000000000..3e18385078 --- /dev/null +++ b/Source/Core/Core/IOS/ES/Views.cpp @@ -0,0 +1,266 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/IOS/ES/ES.h" + +#include +#include +#include +#include + +#include "Common/Logging/Log.h" +#include "Common/NandPaths.h" +#include "Core/ConfigManager.h" +#include "Core/HW/Memmap.h" +#include "Core/IOS/ES/Formats.h" +#include "DiscIO/NANDContentLoader.h" + +namespace IOS +{ +namespace HLE +{ +namespace Device +{ +// HACK: Since we do not want to require users to install disc updates when launching +// Wii games from the game list (which is the inaccurate game boot path anyway), +// IOSes have to be faked for games which reload IOS to work properly. +// To minimize the effect of this hack, we should only do this for disc titles +// booted from the game list, though. +static bool ShouldReturnFakeViewsForIOSes(u64 title_id, const TitleContext& context) +{ + const bool ios = IsTitleType(title_id, IOS::ES::TitleType::System) && title_id != TITLEID_SYSMENU; + const bool disc_title = context.active && IOS::ES::IsDiscTitle(context.tmd.GetTitleId()); + return ios && SConfig::GetInstance().m_BootType == SConfig::BOOT_ISO && disc_title; +} + +IPCCommandResult ES::GetTicketViewCount(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + + const IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(TitleID); + u32 view_count = ticket.IsValid() ? ticket.GetNumberOfTickets() : 0; + + if (ShouldReturnFakeViewsForIOSes(TitleID, GetTitleContext())) + { + view_count = 1; + WARN_LOG(IOS_ES, "GetViewCount: Faking IOS title %016" PRIx64 " being present", TitleID); + } + + INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWCNT for titleID: %08x/%08x (View Count = %u)", + static_cast(TitleID >> 32), static_cast(TitleID), view_count); + + Memory::Write_U32(view_count, request.io_vectors[0].address); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetTicketViews(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(2, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + u32 maxViews = Memory::Read_U32(request.in_vectors[1].address); + + const IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(TitleID); + + if (ticket.IsValid()) + { + u32 number_of_views = std::min(maxViews, ticket.GetNumberOfTickets()); + for (u32 view = 0; view < number_of_views; ++view) + { + const std::vector ticket_view = ticket.GetRawTicketView(view); + Memory::CopyToEmu(request.io_vectors[0].address + view * sizeof(IOS::ES::TicketView), + ticket_view.data(), ticket_view.size()); + } + } + else if (ShouldReturnFakeViewsForIOSes(TitleID, GetTitleContext())) + { + Memory::Memset(request.io_vectors[0].address, 0, sizeof(IOS::ES::TicketView)); + WARN_LOG(IOS_ES, "GetViews: Faking IOS title %016" PRIx64 " being present", TitleID); + } + + INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWS for titleID: %08x/%08x (MaxViews = %i)", (u32)(TitleID >> 32), + (u32)TitleID, maxViews); + + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetTMDViewSize(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); + + const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); + + if (!Loader.IsValid()) + return GetDefaultReply(FS_ENOENT); + + const u32 view_size = static_cast(Loader.GetTMD().GetRawView().size()); + Memory::Write_U32(view_size, request.io_vectors[0].address); + + INFO_LOG(IOS_ES, "IOCTL_ES_GETTMDVIEWCNT: title: %08x/%08x (view size %i)", (u32)(TitleID >> 32), + (u32)TitleID, view_size); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetTMDViews(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(2, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + 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()) + return GetDefaultReply(FS_ENOENT); + + const std::vector raw_view = Loader.GetTMD().GetRawView(); + if (raw_view.size() != request.io_vectors[0].size) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + Memory::CopyToEmu(request.io_vectors[0].address, raw_view.data(), raw_view.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::DIGetTMDViewSize(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + // Sanity check the TMD size. + if (request.in_vectors[0].size >= 4 * 1024 * 1024) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + if (request.io_vectors[0].size != sizeof(u32)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const bool has_tmd = request.in_vectors[0].size != 0; + size_t tmd_view_size = 0; + + if (has_tmd) + { + std::vector tmd_bytes(request.in_vectors[0].size); + Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[0].address, tmd_bytes.size()); + const IOS::ES::TMDReader tmd{std::move(tmd_bytes)}; + + // Yes, this returns -1017, not ES_INVALID_TMD. + // IOS simply checks whether the TMD has all required content entries. + if (!tmd.IsValid()) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + tmd_view_size = tmd.GetRawView().size(); + } + else + { + // If no TMD was passed in and no title is active, IOS returns -1017. + if (!GetTitleContext().active) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + tmd_view_size = GetTitleContext().tmd.GetRawView().size(); + } + + Memory::Write_U32(static_cast(tmd_view_size), request.io_vectors[0].address); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::DIGetTMDView(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(2, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + // Sanity check the TMD size. + if (request.in_vectors[0].size >= 4 * 1024 * 1024) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + // Check whether the TMD view size is consistent. + if (request.in_vectors[1].size != sizeof(u32) || + Memory::Read_U32(request.in_vectors[1].address) != request.io_vectors[0].size) + { + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + } + + const bool has_tmd = request.in_vectors[0].size != 0; + std::vector tmd_view; + + if (has_tmd) + { + std::vector tmd_bytes(request.in_vectors[0].size); + Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[0].address, tmd_bytes.size()); + const IOS::ES::TMDReader tmd{std::move(tmd_bytes)}; + + if (!tmd.IsValid()) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + tmd_view = tmd.GetRawView(); + } + else + { + // If no TMD was passed in and no title is active, IOS returns -1017. + if (!GetTitleContext().active) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + tmd_view = GetTitleContext().tmd.GetRawView(); + } + + if (tmd_view.size() != request.io_vectors[0].size) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + Memory::CopyToEmu(request.io_vectors[0].address, tmd_view.data(), tmd_view.size()); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::DIGetTicketView(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 1) || + request.io_vectors[0].size != sizeof(IOS::ES::TicketView)) + { + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + } + + const bool has_ticket_vector = request.in_vectors[0].size == 0x2A4; + + // This ioctlv takes either a signed ticket or no ticket, in which case the ticket size must be 0. + if (!has_ticket_vector && request.in_vectors[0].size != 0) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + std::vector view; + + // If no ticket was passed in, IOS returns the ticket view for the current title. + // Of course, this returns -1017 if no title is active and no ticket is passed. + if (!has_ticket_vector) + { + if (!GetTitleContext().active) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + view = GetTitleContext().ticket.GetRawTicketView(0); + } + else + { + std::vector ticket_bytes(request.in_vectors[0].size); + Memory::CopyFromEmu(ticket_bytes.data(), request.in_vectors[0].address, ticket_bytes.size()); + const IOS::ES::TicketReader ticket{std::move(ticket_bytes)}; + + view = ticket.GetRawTicketView(0); + } + + Memory::CopyToEmu(request.io_vectors[0].address, view.data(), view.size()); + return GetDefaultReply(IPC_SUCCESS); +} + +} // namespace Device +} // namespace HLE +} // namespace IOS