diff --git a/Source/Core/Core/IOS/ES/Formats.cpp b/Source/Core/Core/IOS/ES/Formats.cpp index ad80683739..831488d2df 100644 --- a/Source/Core/Core/IOS/ES/Formats.cpp +++ b/Source/Core/Core/IOS/ES/Formats.cpp @@ -239,19 +239,7 @@ void TicketReader::SetBytes(std::vector&& bytes) bool TicketReader::IsValid() const { - // Too small for the signature type. - if (m_bytes.size() < sizeof(u32)) - return false; - - u32 ticket_offset = GetOffset(); - if (ticket_offset == 0) - return false; - - // Too small for the ticket itself. - if (m_bytes.size() < ticket_offset + sizeof(Ticket)) - return false; - - return true; + return m_bytes.size() % sizeof(Ticket) == 0; } void TicketReader::DoState(PointerWrap& p) @@ -259,23 +247,9 @@ void TicketReader::DoState(PointerWrap& p) p.Do(m_bytes); } -u32 TicketReader::GetNumberOfTickets() const +size_t TicketReader::GetNumberOfTickets() const { - return static_cast(m_bytes.size() / (GetOffset() + sizeof(Ticket))); -} - -u32 TicketReader::GetOffset() const -{ - u32 signature_type = Common::swap32(m_bytes.data()); - if (signature_type == 0x10000) // RSA4096 - return 576; - if (signature_type == 0x10001) // RSA2048 - return 320; - if (signature_type == 0x10002) // ECDSA - return 128; - - ERROR_LOG(COMMON, "Invalid ticket signature type: %08x", signature_type); - return 0; + return m_bytes.size() / sizeof(Ticket); } const std::vector& TicketReader::GetRawTicket() const @@ -286,7 +260,7 @@ const std::vector& TicketReader::GetRawTicket() const std::vector TicketReader::GetRawTicketView(u32 ticket_num) const { // A ticket view is composed of a version + part of a ticket starting from the ticket_id field. - const auto ticket_start = m_bytes.cbegin() + GetOffset() + sizeof(Ticket) * ticket_num; + 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). @@ -303,32 +277,47 @@ std::vector TicketReader::GetRawTicketView(u32 ticket_num) const u32 TicketReader::GetDeviceId() const { - return Common::swap32(m_bytes.data() + GetOffset() + offsetof(Ticket, device_id)); + return Common::swap32(m_bytes.data() + offsetof(Ticket, device_id)); } u64 TicketReader::GetTitleId() const { - return Common::swap64(m_bytes.data() + GetOffset() + offsetof(Ticket, title_id)); + return Common::swap64(m_bytes.data() + offsetof(Ticket, title_id)); } std::vector TicketReader::GetTitleKey() const { u8 iv[16] = {}; - std::copy_n(&m_bytes[GetOffset() + offsetof(Ticket, title_id)], sizeof(Ticket::title_id), iv); - auto common_key_handle = m_bytes.at(GetOffset() + offsetof(Ticket, common_key_index)) == 0 ? + std::copy_n(&m_bytes[offsetof(Ticket, title_id)], sizeof(Ticket::title_id), iv); + auto common_key_handle = m_bytes.at(offsetof(Ticket, common_key_index)) == 0 ? HLE::IOSC::HANDLE_COMMON_KEY : HLE::IOSC::HANDLE_NEW_COMMON_KEY; std::vector key(16); HLE::IOSC iosc; - iosc.Decrypt(common_key_handle, iv, &m_bytes[GetOffset() + offsetof(Ticket, title_key)], 16, - key.data(), HLE::PID_ES); + iosc.Decrypt(common_key_handle, iv, &m_bytes[offsetof(Ticket, title_key)], 16, key.data(), + HLE::PID_ES); return key; } +void TicketReader::DeleteTicket(u64 ticket_id_to_delete) +{ + std::vector new_ticket; + 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 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)); + } + + m_bytes = std::move(new_ticket); +} + s32 TicketReader::Unpersonalise() { - const auto ticket_begin = m_bytes.begin() + GetOffset(); + const auto ticket_begin = m_bytes.begin(); // IOS uses IOSC to compute an AES key from the peer public key and the device's private ECC key, // which is used the decrypt the title key. The IV is the ticket ID (8 bytes), zero extended. diff --git a/Source/Core/Core/IOS/ES/Formats.h b/Source/Core/Core/IOS/ES/Formats.h index 5f370c2cbd..6337aeaf32 100644 --- a/Source/Core/Core/IOS/ES/Formats.h +++ b/Source/Core/Core/IOS/ES/Formats.h @@ -97,8 +97,14 @@ struct TicketView }; static_assert(sizeof(TicketView) == 0xd8, "TicketView has the wrong size"); +// This structure is used for (signed) tickets. Technically, there are other types of tickets +// (RSA4096, ECDSA, ...). However, only RSA2048 tickets have ever been seen and these are also +// the only ticket type that is supported by the Wii's IOS. struct Ticket { + u32 signature_type; + u8 signature[256]; + u8 unused[60]; u8 signature_issuer[0x40]; u8 server_public_key[0x3c]; u8 version; @@ -118,7 +124,7 @@ struct Ticket u8 content_access_permissions[0x40]; TimeLimit time_limits[8]; }; -static_assert(sizeof(Ticket) == 356, "Ticket has the wrong size"); +static_assert(sizeof(Ticket) == 0x2A4, "Ticket has the wrong size"); #pragma pack(pop) class TMDReader final @@ -175,8 +181,7 @@ public: void DoState(PointerWrap& p); const std::vector& GetRawTicket() const; - u32 GetNumberOfTickets() const; - u32 GetOffset() const; + size_t GetNumberOfTickets() const; // Returns a "raw" ticket view, without byte swapping. Intended for use from ES. // Theoretically, a ticket file can contain one or more tickets. In practice, most (all?) @@ -188,6 +193,9 @@ public: u64 GetTitleId() const; std::vector GetTitleKey() const; + // Deletes a ticket with the given ticket ID from the internal buffer. + void DeleteTicket(u64 ticket_id); + // Decrypts the title key field for a "personalised" ticket -- one that is device-specific // and has a title key that must be decrypted first. s32 Unpersonalise(); diff --git a/Source/Core/Core/IOS/ES/TitleManagement.cpp b/Source/Core/Core/IOS/ES/TitleManagement.cpp index 93dfbee95c..cb0b6420f1 100644 --- a/Source/Core/Core/IOS/ES/TitleManagement.cpp +++ b/Source/Core/Core/IOS/ES/TitleManagement.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -335,15 +336,45 @@ IPCCommandResult ES::DeleteTitle(const IOCtlVRequest& request) IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request) { - if (!request.HasNumberOfValidVectors(1, 0)) + if (!request.HasNumberOfValidVectors(1, 0) || + request.in_vectors[0].size != sizeof(IOS::ES::TicketView)) + { + return GetDefaultReply(ES_EINVAL); + } + + const u64 title_id = + Memory::Read_U64(request.in_vectors[0].address + offsetof(IOS::ES::TicketView, title_id)); + + if (!CanDeleteTitle(title_id)) return GetDefaultReply(ES_EINVAL); - 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); + auto ticket = DiscIO::FindSignedTicket(title_id); + if (!ticket.IsValid()) + return GetDefaultReply(FS_ENOENT); - // Presumably return -1017 when delete fails - if (!File::Delete(Common::GetTicketFileName(TitleID, Common::FROM_SESSION_ROOT))) - return GetDefaultReply(ES_EINVAL); + const u64 ticket_id = + Memory::Read_U64(request.in_vectors[0].address + offsetof(IOS::ES::TicketView, ticket_id)); + ticket.DeleteTicket(ticket_id); + + const std::vector& new_ticket = ticket.GetRawTicket(); + const std::string ticket_path = Common::GetTicketFileName(title_id, Common::FROM_SESSION_ROOT); + { + File::IOFile ticket_file(ticket_path, "wb"); + if (!ticket_file || !ticket_file.WriteBytes(new_ticket.data(), new_ticket.size())) + return GetDefaultReply(ES_EIO); + } + + // Delete the ticket file if it is now empty. + if (new_ticket.empty()) + File::Delete(ticket_path); + + // Delete the ticket directory if it is now empty. + const std::string ticket_parent_dir = + Common::RootUserPath(Common::FROM_CONFIGURED_ROOT) + + StringFromFormat("/ticket/%08x", static_cast(title_id >> 32)); + const auto ticket_parent_dir_entries = File::ScanDirectoryTree(ticket_parent_dir, false); + if (ticket_parent_dir_entries.children.empty()) + File::DeleteDir(ticket_parent_dir); return GetDefaultReply(IPC_SUCCESS); } diff --git a/Source/Core/Core/IOS/ES/Views.cpp b/Source/Core/Core/IOS/ES/Views.cpp index 639ca81110..4fa0aa966b 100644 --- a/Source/Core/Core/IOS/ES/Views.cpp +++ b/Source/Core/Core/IOS/ES/Views.cpp @@ -48,7 +48,7 @@ IPCCommandResult ES::GetTicketViewCount(const IOCtlVRequest& request) 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; + u32 view_count = ticket.IsValid() ? static_cast(ticket.GetNumberOfTickets()) : 0; if (ShouldReturnFakeViewsForIOSes(TitleID, GetTitleContext())) { @@ -75,7 +75,7 @@ IPCCommandResult ES::GetTicketViews(const IOCtlVRequest& request) if (ticket.IsValid()) { - u32 number_of_views = std::min(maxViews, ticket.GetNumberOfTickets()); + u32 number_of_views = std::min(maxViews, static_cast(ticket.GetNumberOfTickets())); for (u32 view = 0; view < number_of_views; ++view) { const std::vector ticket_view = ticket.GetRawTicketView(view); @@ -235,7 +235,7 @@ IPCCommandResult ES::DIGetTicketView(const IOCtlVRequest& request) return GetDefaultReply(ES_EINVAL); } - const bool has_ticket_vector = request.in_vectors[0].size == 0x2A4; + const bool has_ticket_vector = request.in_vectors[0].size == sizeof(IOS::ES::Ticket); // 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)