From 35995e98d7e30259c625da06a06aac3a6dc256ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Wed, 1 Mar 2017 23:29:32 +0100 Subject: [PATCH 1/4] IOS/ES: Fix GetTitles implementation IOS determines installed titles by looking at /title, not uid.sys, which is more like a history of installed titles. And it does not care at all about the installed TMD (or even if it is present at all). --- Source/Core/Core/IOS/ES/ES.cpp | 80 ++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 2e8ec78de9..d3f162cc1d 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -59,7 +59,6 @@ struct TitleContext // Shared across all ES instances. static std::string s_content_file; -static std::vector s_title_ids; static TitleContext s_title_context; // Title to launch after IOS has been reset and reloaded (similar to /sys/launch.sys). @@ -97,14 +96,6 @@ void ES::Init() s_content_file = ""; s_title_context = TitleContext{}; - s_title_ids.clear(); - DiscIO::cUIDsys uid_sys{Common::FromWhichRoot::FROM_SESSION_ROOT}; - uid_sys.GetTitleIDs(s_title_ids); - - // uncomment if ES_GetOwnedTitlesCount / ES_GetOwnedTitles is implemented - // s_title_idsOwned.clear(); - // DiscIO::cUIDsys::AccessInstance().GetTitleIDs(s_title_idsOwned, true); - if (s_title_to_launch != 0) { NOTICE_LOG(IOS, "Re-launching title after IOS reload."); @@ -261,7 +252,6 @@ void ES::DoState(PointerWrap& p) Device::DoState(p); p.Do(s_content_file); p.Do(m_AccessIdentID); - p.Do(s_title_ids); s_title_context.DoState(p); m_addtitle_tmd.DoState(p); @@ -892,14 +882,61 @@ IPCCommandResult ES::SetUID(const IOCtlVRequest& request) 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; +} + IPCCommandResult ES::GetTitleCount(const IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(0, 1) || request.io_vectors[0].size != 4) return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - Memory::Write_U32((u32)s_title_ids.size(), request.io_vectors[0].address); + const std::vector titles = GetInstalledTitles(); - INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLECNT: Number of Titles %zu", s_title_ids.size()); + Memory::Write_U32(static_cast(titles.size()), request.io_vectors[0].address); + + INFO_LOG(IOS_ES, "GetTitleCount: %zu titles", titles.size()); return GetDefaultReply(IPC_SUCCESS); } @@ -909,19 +946,14 @@ IPCCommandResult ES::GetTitles(const IOCtlVRequest& request) if (!request.HasNumberOfValidVectors(1, 1)) return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - u32 MaxCount = Memory::Read_U32(request.in_vectors[0].address); - u32 Count = 0; - for (int i = 0; i < (int)s_title_ids.size(); i++) - { - Memory::Write_U64(s_title_ids[i], request.io_vectors[0].address + i * 8); - INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLES: %08x/%08x", (u32)(s_title_ids[i] >> 32), - (u32)s_title_ids[i]); - Count++; - if (Count >= MaxCount) - break; - } + const std::vector titles = GetInstalledTitles(); - INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLES: Number of titles returned %i", Count); + 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) * 8); + INFO_LOG(IOS_ES, " title %016" PRIx64, titles[i]); + } return GetDefaultReply(IPC_SUCCESS); } From b1ffbef5ce48b3410174c4858d305524b8835b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Wed, 1 Mar 2017 23:58:38 +0100 Subject: [PATCH 2/4] IOS/ES: Implement ES_GetOwnedTitles --- Source/Core/Core/IOS/ES/ES.cpp | 72 ++++++++++++++++++++++-- Source/Core/Core/IOS/ES/ES.h | 5 +- Source/Core/DiscIO/NANDContentLoader.cpp | 2 +- 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index d3f162cc1d..75eb42f58e 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -383,10 +383,16 @@ IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request) return GetTitleID(request); case IOCTL_ES_SETUID: return SetUID(request); + + case IOCTL_ES_GETOWNEDTITLECNT: + return GetOwnedTitleCount(request); + case IOCTL_ES_GETOWNEDTITLES: + return GetOwnedTitles(request); case IOCTL_ES_GETTITLECNT: return GetTitleCount(request); case IOCTL_ES_GETTITLES: return GetTitles(request); + case IOCTL_ES_GETVIEWCNT: return GetViewCount(request); case IOCTL_ES_GETVIEWS: @@ -435,8 +441,6 @@ IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request) return GetBoot2Version(request); case IOCTL_ES_DIGETTICKETVIEW: return DIGetTicketView(request); - case IOCTL_ES_GETOWNEDTITLECNT: - return GetOwnedTitleCount(request); default: request.DumpUnknown(GetDeviceName(), LogTypes::IOS); break; @@ -927,6 +931,47 @@ static std::vector GetInstalledTitles() 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 IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(0, 1) || request.io_vectors[0].size != 4) @@ -1529,9 +1574,26 @@ IPCCommandResult ES::GetOwnedTitleCount(const IOCtlVRequest& request) if (!request.HasNumberOfValidVectors(0, 1)) return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - INFO_LOG(IOS_ES, "IOCTL_ES_GETOWNEDTITLECNT"); - // TODO: unimplemented. - Memory::Write_U32(0, request.io_vectors[0].address); + const std::vector titles = GetTitlesWithTickets(); + Memory::Write_U32(static_cast(titles.size()), request.io_vectors[0].address); + + INFO_LOG(IOS_ES, "GetOwnedTitleCount: %zu titles", titles.size()); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult ES::GetOwnedTitles(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 1)) + return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + + const std::vector titles = GetTitlesWithTickets(); + + 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) * 8); + INFO_LOG(IOS_ES, " title %016" PRIx64, titles[i]); + } return GetDefaultReply(IPC_SUCCESS); } diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 8adb54e8e9..e7bbc76867 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -167,8 +167,12 @@ private: IPCCommandResult GetTitleDirectory(const IOCtlVRequest& request); IPCCommandResult GetTitleID(const IOCtlVRequest& request); IPCCommandResult SetUID(const IOCtlVRequest& request); + + IPCCommandResult GetOwnedTitleCount(const IOCtlVRequest& request); + IPCCommandResult GetOwnedTitles(const IOCtlVRequest& request); IPCCommandResult GetTitleCount(const IOCtlVRequest& request); IPCCommandResult GetTitles(const IOCtlVRequest& request); + IPCCommandResult GetViewCount(const IOCtlVRequest& request); IPCCommandResult GetViews(const IOCtlVRequest& request); IPCCommandResult GetTMDViewSize(const IOCtlVRequest& request); @@ -195,7 +199,6 @@ private: IPCCommandResult Sign(const IOCtlVRequest& request); IPCCommandResult GetBoot2Version(const IOCtlVRequest& request); IPCCommandResult DIGetTicketView(const IOCtlVRequest& request); - IPCCommandResult GetOwnedTitleCount(const IOCtlVRequest& request); static bool LaunchIOS(u64 ios_title_id); static bool LaunchPPCTitle(u64 title_id, bool skip_reload); diff --git a/Source/Core/DiscIO/NANDContentLoader.cpp b/Source/Core/DiscIO/NANDContentLoader.cpp index 914e23191e..c08e5bbef8 100644 --- a/Source/Core/DiscIO/NANDContentLoader.cpp +++ b/Source/Core/DiscIO/NANDContentLoader.cpp @@ -158,7 +158,7 @@ CNANDContentLoader::~CNANDContentLoader() bool CNANDContentLoader::IsValid() const { - return m_Valid && m_tmd.IsValid(); + return m_Valid; } const SNANDContent* CNANDContentLoader::GetContentByID(u32 id) const From 1525396ecf891f0171a740561a260fe7b14fe234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Thu, 2 Mar 2017 21:19:41 +0100 Subject: [PATCH 3/4] IOS/ES: Properly handle missing TMD in GetStoredTMD When the TMD doesn't exist on the NAND, IOS returns -106. This commit also changes IsValid() to not check for the TMD validity, since this is not always something we want. (IOS can have different error codes when the TMD is missing, or even worse, simply assume that the TMD is valid.) --- Source/Core/Core/IOS/ES/ES.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index 75eb42f58e..e6ffe68715 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -231,6 +231,9 @@ bool ES::LaunchPPCTitle(u64 title_id, bool skip_reload) return false; } + if (!content_loader.GetTMD().IsValid() || !content_loader.GetTicket().IsValid()) + return false; + // Before launching a title, IOS first reads the TMD and reloads into the specified IOS version, // even when that version is already running. After it has reloaded, ES_Launch will be called // again with the reload skipped, and the PPC will be bootstrapped then. @@ -310,7 +313,7 @@ u32 ES::OpenTitleContent(u32 CFD, u64 TitleID, u16 Index) { const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); - if (!Loader.IsValid() || !Loader.GetTicket().IsValid()) + if (!Loader.IsValid() || !Loader.GetTMD().IsValid() || !Loader.GetTicket().IsValid()) { WARN_LOG(IOS_ES, "ES: loader not valid for %" PRIx64, TitleID); return 0xffffffff; @@ -660,7 +663,7 @@ IPCCommandResult ES::GetTitleContentsCount(const IOCtlVRequest& request) u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); const DiscIO::CNANDContentLoader& nand_content = AccessContentDevice(TitleID); - if (!nand_content.IsValid()) + if (!nand_content.IsValid() || !nand_content.GetTMD().IsValid()) return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); const u16 num_contents = nand_content.GetTMD().GetNumContents(); @@ -684,7 +687,7 @@ IPCCommandResult ES::GetTitleContents(const IOCtlVRequest& request) u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); const DiscIO::CNANDContentLoader& rNANDContent = AccessContentDevice(TitleID); - if (!rNANDContent.IsValid()) + if (!rNANDContent.IsValid() || !rNANDContent.GetTMD().IsValid()) return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); for (const auto& content : rNANDContent.GetTMD().GetContents()) @@ -1182,10 +1185,10 @@ IPCCommandResult ES::GetStoredTMDSize(const IOCtlVRequest& request) u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); - u32 tmd_size = 0; - if (Loader.IsValid()) - tmd_size = static_cast(Loader.GetTMD().GetRawTMD().size()); + 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)", @@ -1204,17 +1207,14 @@ IPCCommandResult ES::GetStoredTMD(const IOCtlVRequest& request) const u32 MaxCount = Memory::Read_U32(request.in_vectors[1].address); const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); - INFO_LOG(IOS_ES, "IOCTL_ES_GETSTOREDTMD: title: %08x/%08x buffer size: %i", - (u32)(TitleID >> 32), (u32)TitleID, MaxCount); + if (!Loader.IsValid() || !Loader.GetTMD().IsValid()) + return GetDefaultReply(FS_ENOENT); - if (Loader.IsValid()) - { - const std::vector raw_tmd = Loader.GetTMD().GetRawTMD(); - if (raw_tmd.size() != request.io_vectors[0].size) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); + 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()); - } + 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); From 52e8486b7cf2cf363cc8842a25c49accb9a80957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 3 Mar 2017 08:38:28 +0100 Subject: [PATCH 4/4] IOS/ES: Refactor GetTitles into a utility function --- Source/Core/Core/IOS/ES/ES.cpp | 48 ++++++++++++++-------------------- Source/Core/Core/IOS/ES/ES.h | 2 ++ 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index e6ffe68715..9c2ae3a7ea 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -911,7 +911,7 @@ static std::vector GetInstalledTitles() // 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); + const auto entries = File::ScanDirectoryTree(titles_dir, true); for (const File::FSTEntry& title_type : entries.children) { if (!title_type.isDirectory || !IsValidPartOfTitleID(title_type.virtualName)) @@ -948,7 +948,7 @@ static std::vector GetTitlesWithTickets() // 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); + const auto entries = File::ScanDirectoryTree(titles_dir, true); for (const File::FSTEntry& title_type : entries.children) { if (!title_type.isDirectory || !IsValidPartOfTitleID(title_type.virtualName)) @@ -975,36 +975,42 @@ static std::vector GetTitlesWithTickets() return title_ids; } -IPCCommandResult ES::GetTitleCount(const IOCtlVRequest& request) +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); - const std::vector titles = GetInstalledTitles(); - Memory::Write_U32(static_cast(titles.size()), request.io_vectors[0].address); - INFO_LOG(IOS_ES, "GetTitleCount: %zu titles", titles.size()); - return GetDefaultReply(IPC_SUCCESS); } -IPCCommandResult ES::GetTitles(const IOCtlVRequest& request) +IPCCommandResult ES::GetTitles(const std::vector& titles, const IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(1, 1)) return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - const std::vector titles = GetInstalledTitles(); - 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) * 8); + 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::GetViewCount(const IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(1, 1)) @@ -1571,30 +1577,14 @@ IPCCommandResult ES::DIGetTicketView(const IOCtlVRequest& request) IPCCommandResult ES::GetOwnedTitleCount(const IOCtlVRequest& request) { - if (!request.HasNumberOfValidVectors(0, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - const std::vector titles = GetTitlesWithTickets(); - Memory::Write_U32(static_cast(titles.size()), request.io_vectors[0].address); - INFO_LOG(IOS_ES, "GetOwnedTitleCount: %zu titles", titles.size()); - return GetDefaultReply(IPC_SUCCESS); + return GetTitleCount(titles, request); } IPCCommandResult ES::GetOwnedTitles(const IOCtlVRequest& request) { - if (!request.HasNumberOfValidVectors(1, 1)) - return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); - - const std::vector titles = GetTitlesWithTickets(); - - 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) * 8); - INFO_LOG(IOS_ES, " title %016" PRIx64, titles[i]); - } - return GetDefaultReply(IPC_SUCCESS); + return GetTitles(GetTitlesWithTickets(), request); } const DiscIO::CNANDContentLoader& ES::AccessContentDevice(u64 title_id) diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index e7bbc76867..49278c4d88 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -168,6 +168,8 @@ private: IPCCommandResult GetTitleID(const IOCtlVRequest& request); IPCCommandResult SetUID(const IOCtlVRequest& request); + 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);