From 2fd9852ca89e4f07f6231ec3ddab5656f2b7856c Mon Sep 17 00:00:00 2001 From: Sketch <75850871+SketchMaster2001@users.noreply.github.com> Date: Mon, 31 Oct 2022 21:02:19 -0400 Subject: [PATCH] IOS/ES: Add support for V1Ticket --- Source/Core/Common/NandPaths.cpp | 6 +++ Source/Core/Common/NandPaths.h | 1 + Source/Core/Core/IOS/ES/ES.h | 7 +-- Source/Core/Core/IOS/ES/Formats.cpp | 50 ++++++++++++++++----- Source/Core/Core/IOS/ES/Formats.h | 17 ++++++- Source/Core/Core/IOS/ES/NandUtils.cpp | 23 +++++++--- Source/Core/Core/IOS/ES/TitleManagement.cpp | 7 ++- Source/Core/Core/IOS/ES/Views.cpp | 50 ++++++++------------- 8 files changed, 109 insertions(+), 52 deletions(-) diff --git a/Source/Core/Common/NandPaths.cpp b/Source/Core/Common/NandPaths.cpp index 8fc33200d7..00f5eb235a 100644 --- a/Source/Core/Common/NandPaths.cpp +++ b/Source/Core/Common/NandPaths.cpp @@ -41,6 +41,12 @@ std::string GetTicketFileName(u64 title_id, std::optional from) static_cast(title_id >> 32), static_cast(title_id)); } +std::string GetV1TicketFileName(u64 title_id, std::optional from) +{ + return fmt::format("{}/ticket/{:08x}/{:08x}.tv1", RootUserPath(from), + static_cast(title_id >> 32), static_cast(title_id)); +} + std::string GetTitlePath(u64 title_id, std::optional from) { return fmt::format("{}/title/{:08x}/{:08x}", RootUserPath(from), static_cast(title_id >> 32), diff --git a/Source/Core/Common/NandPaths.h b/Source/Core/Common/NandPaths.h index 707cb87c3c..01bd8aa3ed 100644 --- a/Source/Core/Common/NandPaths.h +++ b/Source/Core/Common/NandPaths.h @@ -26,6 +26,7 @@ std::string RootUserPath(FromWhichRoot from); std::string GetImportTitlePath(u64 title_id, std::optional from = {}); std::string GetTicketFileName(u64 title_id, std::optional from = {}); +std::string GetV1TicketFileName(u64 title_id, std::optional from = {}); std::string GetTitlePath(u64 title_id, std::optional from = {}); std::string GetTitleDataPath(u64 title_id, std::optional from = {}); std::string GetTitleContentPath(u64 title_id, std::optional from = {}); diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 2b7e79559f..5867452e03 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -91,7 +91,8 @@ public: ES::TMDReader FindImportTMD(u64 title_id, Ticks ticks = {}) const; ES::TMDReader FindInstalledTMD(u64 title_id, Ticks ticks = {}) const; - ES::TicketReader FindSignedTicket(u64 title_id) const; + ES::TicketReader FindSignedTicket(u64 title_id, + std::optional desired_version = std::nullopt) const; // Get installed titles (in /title) without checking for TMDs at all. std::vector GetInstalledTitles() const; @@ -157,8 +158,8 @@ public: const std::vector& certs); // Views - ReturnCode GetV0TicketFromView(const u8* ticket_view, u8* ticket) const; - ReturnCode GetTicketFromView(const u8* ticket_view, u8* ticket, u32* ticket_size) const; + ReturnCode GetTicketFromView(const u8* ticket_view, u8* ticket, u32* ticket_size, + std::optional desired_version) const; ReturnCode SetUpStreamKey(u32 uid, const u8* ticket_view, const ES::TMDReader& tmd, u32* handle); diff --git a/Source/Core/Core/IOS/ES/Formats.cpp b/Source/Core/Core/IOS/ES/Formats.cpp index f091cf1769..cccf5086b8 100644 --- a/Source/Core/Core/IOS/ES/Formats.cpp +++ b/Source/Core/Core/IOS/ES/Formats.cpp @@ -371,22 +371,49 @@ TicketReader::TicketReader(std::vector bytes) : SignedBlobReader(std::move(b bool TicketReader::IsValid() const { - return IsSignatureValid() && !m_bytes.empty() && m_bytes.size() % sizeof(Ticket) == 0; + if (!IsSignatureValid() || m_bytes.empty()) + return false; + + if (IsV1Ticket()) + return m_bytes.size() == GetTicketSize(); + + return m_bytes.size() % sizeof(Ticket) == 0; +} + +bool TicketReader::IsV1Ticket() const +{ + // Version can only be 0 or 1. + return GetVersion() == 1; } size_t TicketReader::GetNumberOfTickets() const { + if (IsV1Ticket()) + return 1; + return m_bytes.size() / sizeof(Ticket); } +u32 TicketReader::GetTicketSize() const +{ + if (IsV1Ticket()) + { + return Common::swap32(m_bytes.data() + sizeof(Ticket) + + offsetof(V1TicketHeader, v1_ticket_size)) + + sizeof(Ticket); + } + + return sizeof(Ticket); +} + std::vector TicketReader::GetRawTicket(u64 ticket_id_to_find) const { for (size_t i = 0; i < GetNumberOfTickets(); ++i) { - const auto ticket_begin = m_bytes.begin() + sizeof(ES::Ticket) * i; + const auto ticket_begin = m_bytes.begin() + GetTicketSize() * i; const u64 ticket_id = Common::swap64(&*ticket_begin + offsetof(ES::Ticket, ticket_id)); if (ticket_id == ticket_id_to_find) - return std::vector(ticket_begin, ticket_begin + sizeof(ES::Ticket)); + return {ticket_begin, ticket_begin + GetTicketSize()}; } return {}; } @@ -397,18 +424,21 @@ std::vector TicketReader::GetRawTicketView(u32 ticket_num) const const auto ticket_start = m_bytes.cbegin() + sizeof(Ticket) * ticket_num; const auto view_start = ticket_start + offsetof(Ticket, ticket_id); - // Copy the ticket version to the buffer (a single byte extended to 4). - std::vector view(sizeof(TicketView::version)); - const u32 version = Common::swap32(m_bytes.at(offsetof(Ticket, version))); - std::memcpy(view.data(), &version, sizeof(version)); + std::vector view(sizeof(u32)); + view[0] = GetVersion(); // Copy the rest of the ticket view structure from the ticket. - view.insert(view.end(), view_start, view_start + (sizeof(TicketView) - sizeof(version))); + view.insert(view.end(), view_start, view_start + (sizeof(TicketView) - sizeof(u32))); ASSERT(view.size() == sizeof(TicketView)); return view; } +u8 TicketReader::GetVersion() const +{ + return m_bytes[offsetof(Ticket, version)]; +} + u32 TicketReader::GetDeviceId() const { return Common::swap32(m_bytes.data() + offsetof(Ticket, device_id)); @@ -461,10 +491,10 @@ void TicketReader::DeleteTicket(u64 ticket_id_to_delete) const size_t num_tickets = GetNumberOfTickets(); for (size_t i = 0; i < num_tickets; ++i) { - const auto ticket_start = m_bytes.cbegin() + sizeof(Ticket) * i; + const auto ticket_start = m_bytes.cbegin() + GetTicketSize() * i; const u64 ticket_id = Common::swap64(&*ticket_start + offsetof(Ticket, ticket_id)); if (ticket_id != ticket_id_to_delete) - new_ticket.insert(new_ticket.end(), ticket_start, ticket_start + sizeof(Ticket)); + new_ticket.insert(new_ticket.end(), ticket_start, ticket_start + GetTicketSize()); } m_bytes = std::move(new_ticket); diff --git a/Source/Core/Core/IOS/ES/Formats.h b/Source/Core/Core/IOS/ES/Formats.h index d1c19ce75c..72f36beded 100644 --- a/Source/Core/Core/IOS/ES/Formats.h +++ b/Source/Core/Core/IOS/ES/Formats.h @@ -111,7 +111,7 @@ struct TimeLimit struct TicketView { - u32 version; + u8 version; u64 ticket_id; u32 device_id; u64 title_id; @@ -151,6 +151,18 @@ struct Ticket TimeLimit time_limits[8]; }; static_assert(sizeof(Ticket) == 0x2A4, "Ticket has the wrong size"); + +struct V1TicketHeader +{ + u16 version; + u16 header_size; + u32 v1_ticket_size; + u32 section_header_table_offset; + u16 number_of_section_headers; + u16 section_header_size; + u32 flags; +}; +static_assert(sizeof(V1TicketHeader) == 0x14, "V1TicketHeader has the wrong size"); #pragma pack(pop) constexpr u32 MAX_TMD_SIZE = 0x49e4; @@ -229,6 +241,7 @@ public: explicit TicketReader(std::vector bytes); bool IsValid() const; + bool IsV1Ticket() const; std::vector GetRawTicket(u64 ticket_id) const; size_t GetNumberOfTickets() const; @@ -239,7 +252,9 @@ public: // more than just one ticket and generate ticket views for them, so we implement it too. std::vector GetRawTicketView(u32 ticket_num) const; + u8 GetVersion() const; u32 GetDeviceId() const; + u32 GetTicketSize() const; u64 GetTitleId() const; u8 GetCommonKeyIndex() const; // Get the decrypted title key. diff --git a/Source/Core/Core/IOS/ES/NandUtils.cpp b/Source/Core/Core/IOS/ES/NandUtils.cpp index f598ab2f99..ffbbfe0889 100644 --- a/Source/Core/Core/IOS/ES/NandUtils.cpp +++ b/Source/Core/Core/IOS/ES/NandUtils.cpp @@ -49,12 +49,24 @@ ES::TMDReader ESDevice::FindInstalledTMD(u64 title_id, Ticks ticks) const return FindTMD(*m_ios.GetFSDevice(), Common::GetTMDFileName(title_id), ticks); } -ES::TicketReader ESDevice::FindSignedTicket(u64 title_id) const +ES::TicketReader ESDevice::FindSignedTicket(u64 title_id, std::optional desired_version) const { - const std::string path = Common::GetTicketFileName(title_id); - const auto ticket_file = m_ios.GetFS()->OpenFile(PID_KERNEL, PID_KERNEL, path, FS::Mode::Read); + std::string path = desired_version == 1 ? Common::GetV1TicketFileName(title_id) : + Common::GetTicketFileName(title_id); + auto ticket_file = m_ios.GetFS()->OpenFile(PID_KERNEL, PID_KERNEL, path, FS::Mode::Read); if (!ticket_file) - return {}; + { + if (desired_version) + // Desired ticket does not exist. + return {}; + + // Check if we are dealing with a v1 ticket + path = Common::GetV1TicketFileName(title_id); + ticket_file = m_ios.GetFS()->OpenFile(PID_KERNEL, PID_KERNEL, path, FS::Mode::Read); + + if (!ticket_file) + return {}; + } std::vector signed_ticket(ticket_file->GetStatus()->size); if (!ticket_file->Read(signed_ticket.data(), signed_ticket.size())) @@ -151,7 +163,8 @@ std::vector ESDevice::GetTitlesWithTickets() const const std::string name_without_ext = file_name.substr(0, 8); if (fs->ReadDirectory(PID_KERNEL, PID_KERNEL, fmt::format("/ticket/{}/{}", title_type, file_name)) || - !IsValidPartOfTitleID(name_without_ext) || name_without_ext + ".tik" != file_name) + !IsValidPartOfTitleID(name_without_ext) || name_without_ext + ".tik" != file_name || + name_without_ext + ".tv1" != file_name) { continue; } diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index df4ff8fd05..c3390abcee 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -25,8 +25,9 @@ namespace IOS::HLE static ReturnCode WriteTicket(FS::FileSystem* fs, const ES::TicketReader& ticket) { const u64 title_id = ticket.GetTitleId(); + const std::string path = ticket.IsV1Ticket() ? Common::GetV1TicketFileName(title_id) : + Common::GetTicketFileName(title_id); - const std::string path = Common::GetTicketFileName(title_id); constexpr FS::Modes ticket_modes{FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::None}; fs->CreateFullPath(PID_KERNEL, PID_KERNEL, path, 0, ticket_modes); const auto file = fs->CreateAndOpenFile(PID_KERNEL, PID_KERNEL, path, ticket_modes); @@ -561,7 +562,9 @@ ReturnCode ESDevice::DeleteTicket(const u8* ticket_view) ticket.DeleteTicket(ticket_id); const std::vector& new_ticket = ticket.GetBytes(); - const std::string ticket_path = Common::GetTicketFileName(title_id); + const std::string ticket_path = ticket.IsV1Ticket() ? Common::GetV1TicketFileName(title_id) : + Common::GetTicketFileName(title_id); + if (!new_ticket.empty()) { const auto file = fs->OpenFile(PID_KERNEL, PID_KERNEL, ticket_path, FS::Mode::ReadWrite); diff --git a/Source/Core/Core/IOS/ES/Views.cpp b/Source/Core/Core/IOS/ES/Views.cpp index cee14d3cd4..222bf87d43 100644 --- a/Source/Core/Core/IOS/ES/Views.cpp +++ b/Source/Core/Core/IOS/ES/Views.cpp @@ -98,17 +98,26 @@ IPCReply ESDevice::GetTicketViews(const IOCtlVRequest& request) return IPCReply(IPC_SUCCESS); } -ReturnCode ESDevice::GetV0TicketFromView(const u8* ticket_view, u8* ticket) const +ReturnCode ESDevice::GetTicketFromView(const u8* ticket_view, u8* ticket, u32* ticket_size, + std::optional desired_version) const { const u64 title_id = Common::swap64(&ticket_view[offsetof(ES::TicketView, title_id)]); const u64 ticket_id = Common::swap64(&ticket_view[offsetof(ES::TicketView, ticket_id)]); + const u8 view_version = ticket_view[offsetof(ES::TicketView, version)]; + const u8 version = desired_version.value_or(view_version); - const auto installed_ticket = FindSignedTicket(title_id); - // TODO: when we get std::optional, check for presence instead of validity. - // This is close enough, though. + const auto installed_ticket = FindSignedTicket(title_id, version); if (!installed_ticket.IsValid()) return ES_NO_TICKET; + // Handle GetTicketSizeFromView + if (ticket == nullptr) + { + *ticket_size = installed_ticket.GetTicketSize(); + return IPC_SUCCESS; + } + + // Handle GetTicketFromView or GetV0TicketFromView const std::vector ticket_bytes = installed_ticket.GetRawTicket(ticket_id); if (ticket_bytes.empty()) return ES_NO_TICKET; @@ -135,27 +144,6 @@ ReturnCode ESDevice::GetV0TicketFromView(const u8* ticket_view, u8* ticket) cons return IPC_SUCCESS; } -ReturnCode ESDevice::GetTicketFromView(const u8* ticket_view, u8* ticket, u32* ticket_size) const -{ - const u8 version = ticket_view[offsetof(ES::TicketView, version)]; - if (version == 1) - { - // Currently, we have no support for v1 tickets at all (unlike IOS), so we fake it - // and return that there is no ticket. - // TODO: implement GetV1TicketFromView when we gain v1 ticket support. - ERROR_LOG_FMT(IOS_ES, "GetV1TicketFromView: Unimplemented -- returning -1028"); - return ES_NO_TICKET; - } - if (ticket != nullptr) - { - if (*ticket_size >= sizeof(ES::Ticket)) - return GetV0TicketFromView(ticket_view, ticket); - return ES_EINVAL; - } - *ticket_size = sizeof(ES::Ticket); - return IPC_SUCCESS; -} - IPCReply ESDevice::GetV0TicketFromView(const IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(1, 1) || @@ -164,8 +152,8 @@ IPCReply ESDevice::GetV0TicketFromView(const IOCtlVRequest& request) { return IPCReply(ES_EINVAL); } - return IPCReply(GetV0TicketFromView(Memory::GetPointer(request.in_vectors[0].address), - Memory::GetPointer(request.io_vectors[0].address))); + return IPCReply(GetTicketFromView(Memory::GetPointer(request.in_vectors[0].address), + Memory::GetPointer(request.io_vectors[0].address), nullptr, 0)); } IPCReply ESDevice::GetTicketSizeFromView(const IOCtlVRequest& request) @@ -177,8 +165,8 @@ IPCReply ESDevice::GetTicketSizeFromView(const IOCtlVRequest& request) { return IPCReply(ES_EINVAL); } - const ReturnCode ret = - GetTicketFromView(Memory::GetPointer(request.in_vectors[0].address), nullptr, &ticket_size); + const ReturnCode ret = GetTicketFromView(Memory::GetPointer(request.in_vectors[0].address), + nullptr, &ticket_size, std::nullopt); Memory::Write_U32(ticket_size, request.io_vectors[0].address); return IPCReply(ret); } @@ -197,8 +185,8 @@ IPCReply ESDevice::GetTicketFromView(const IOCtlVRequest& request) return IPCReply(ES_EINVAL); return IPCReply(GetTicketFromView(Memory::GetPointer(request.in_vectors[0].address), - Memory::GetPointer(request.io_vectors[0].address), - &ticket_size)); + Memory::GetPointer(request.io_vectors[0].address), &ticket_size, + std::nullopt)); } IPCReply ESDevice::GetTMDViewSize(const IOCtlVRequest& request)