diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp index cbd92a4b40..6fcf884578 100644 --- a/Source/Core/Core/IOS/ES/ES.cpp +++ b/Source/Core/Core/IOS/ES/ES.cpp @@ -504,22 +504,26 @@ IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request) case IOCTL_ES_GETBOOT2VERSION: return GetBoot2Version(request); + case IOCTL_ES_GET_V0_TICKET_FROM_VIEW: + return GetV0TicketFromView(request); + case IOCTL_ES_GET_TICKET_SIZE_FROM_VIEW: + return GetTicketSizeFromView(request); + case IOCTL_ES_GET_TICKET_FROM_VIEW: + return GetTicketFromView(request); + case IOCTL_ES_VERIFYSIGN: case IOCTL_ES_UNKNOWN_3B: case IOCTL_ES_UNKNOWN_3C: case IOCTL_ES_UNKNOWN_3D: case IOCTL_ES_UNKNOWN_3E: - case IOCTL_ES_UNKNOWN_3F: - case IOCTL_ES_UNKNOWN_40: case IOCTL_ES_UNKNOWN_41: case IOCTL_ES_UNKNOWN_42: - case IOCTL_ES_UNKNOWN_43: - case IOCTL_ES_UNKNOWN_44: PanicAlert("IOS-ES: Unimplemented ioctlv 0x%x (%zu in vectors, %zu io vectors)", request.request, request.in_vectors.size(), request.io_vectors.size()); request.DumpUnknown(GetDeviceName(), LogTypes::IOS_ES, LogTypes::LERROR); return GetDefaultReply(IPC_EINVAL); + case IOCTL_ES_INVALID_3F: default: return GetDefaultReply(IPC_EINVAL); } diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h index 9bc9418090..a55297e760 100644 --- a/Source/Core/Core/IOS/ES/ES.h +++ b/Source/Core/Core/IOS/ES/ES.h @@ -116,6 +116,10 @@ public: ReturnCode DeleteTicket(const u8* ticket_view); ReturnCode DeleteSharedContent(const std::array& sha1) const; + // Views + ReturnCode GetV0TicketFromView(const u8* ticket_view, u8* ticket) const; + ReturnCode GetTicketFromView(const u8* ticket_view, u8* ticket, u32* ticket_size) const; + private: enum { @@ -181,12 +185,12 @@ private: IOCTL_ES_UNKNOWN_3C = 0x3C, IOCTL_ES_UNKNOWN_3D = 0x3D, IOCTL_ES_UNKNOWN_3E = 0x3E, - IOCTL_ES_UNKNOWN_3F = 0x3F, - IOCTL_ES_UNKNOWN_40 = 0x40, + IOCTL_ES_INVALID_3F = 0x3F, + IOCTL_ES_GET_V0_TICKET_FROM_VIEW = 0x40, IOCTL_ES_UNKNOWN_41 = 0x41, IOCTL_ES_UNKNOWN_42 = 0x42, - IOCTL_ES_UNKNOWN_43 = 0x43, - IOCTL_ES_UNKNOWN_44 = 0x44, + IOCTL_ES_GET_TICKET_SIZE_FROM_VIEW = 0x43, + IOCTL_ES_GET_TICKET_FROM_VIEW = 0x44, IOCTL_ES_CHECKKOREAREGION = 0x45, }; @@ -259,6 +263,9 @@ private: // Views for tickets and TMDs IPCCommandResult GetTicketViewCount(const IOCtlVRequest& request); IPCCommandResult GetTicketViews(const IOCtlVRequest& request); + IPCCommandResult GetV0TicketFromView(const IOCtlVRequest& request); + IPCCommandResult GetTicketSizeFromView(const IOCtlVRequest& request); + IPCCommandResult GetTicketFromView(const IOCtlVRequest& request); IPCCommandResult GetTMDViewSize(const IOCtlVRequest& request); IPCCommandResult GetTMDViews(const IOCtlVRequest& request); IPCCommandResult DIGetTicketView(const IOCtlVRequest& request); diff --git a/Source/Core/Core/IOS/ES/Formats.cpp b/Source/Core/Core/IOS/ES/Formats.cpp index 039dc7b6e3..8c8abb82c2 100644 --- a/Source/Core/Core/IOS/ES/Formats.cpp +++ b/Source/Core/Core/IOS/ES/Formats.cpp @@ -261,6 +261,18 @@ const std::vector& TicketReader::GetRawTicket() const return m_bytes; } +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(IOS::ES::Ticket) * i; + const u64 ticket_id = Common::swap64(&*ticket_begin + offsetof(IOS::ES::Ticket, ticket_id)); + if (ticket_id == ticket_id_to_find) + return std::vector(ticket_begin, ticket_begin + sizeof(IOS::ES::Ticket)); + } + return {}; +} + 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. diff --git a/Source/Core/Core/IOS/ES/Formats.h b/Source/Core/Core/IOS/ES/Formats.h index 182013558c..dcf25d863d 100644 --- a/Source/Core/Core/IOS/ES/Formats.h +++ b/Source/Core/Core/IOS/ES/Formats.h @@ -181,6 +181,7 @@ public: void DoState(PointerWrap& p); const std::vector& GetRawTicket() const; + std::vector GetRawTicket(u64 ticket_id) const; size_t GetNumberOfTickets() const; // Returns a "raw" ticket view, without byte swapping. Intended for use from ES. diff --git a/Source/Core/Core/IOS/ES/Views.cpp b/Source/Core/Core/IOS/ES/Views.cpp index bf8edaa5e4..6bdd730c20 100644 --- a/Source/Core/Core/IOS/ES/Views.cpp +++ b/Source/Core/Core/IOS/ES/Views.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -95,6 +96,109 @@ IPCCommandResult ES::GetTicketViews(const IOCtlVRequest& request) return GetDefaultReply(IPC_SUCCESS); } +ReturnCode ES::GetV0TicketFromView(const u8* ticket_view, u8* ticket) const +{ + const u64 title_id = Common::swap64(&ticket_view[offsetof(IOS::ES::TicketView, title_id)]); + const u64 ticket_id = Common::swap64(&ticket_view[offsetof(IOS::ES::TicketView, ticket_id)]); + + const auto installed_ticket = DiscIO::FindSignedTicket(title_id); + // TODO: when we get std::optional, check for presence instead of validity. + // This is close enough, though. + if (!installed_ticket.IsValid()) + return ES_NO_TICKET; + + const std::vector ticket_bytes = installed_ticket.GetRawTicket(ticket_id); + if (ticket_bytes.empty()) + return ES_NO_TICKET; + + if (!GetTitleContext().active) + return ES_EINVAL; + + // Check for permission to export the ticket. + const u32 title_identifier = static_cast(GetTitleContext().tmd.GetTitleId()); + const u32 permitted_title_mask = + Common::swap32(ticket_bytes.data() + offsetof(IOS::ES::Ticket, permitted_title_mask)); + const u32 permitted_title_id = + Common::swap32(ticket_bytes.data() + offsetof(IOS::ES::Ticket, permitted_title_id)); + const u8 title_export_allowed = ticket_bytes[offsetof(IOS::ES::Ticket, title_export_allowed)]; + + // This is the check present in IOS. The 5 does not correspond to any known constant, sadly. + if (!title_identifier || (title_identifier & ~permitted_title_mask) != permitted_title_id || + (title_export_allowed & 0xF) != 5) + { + return ES_EACCES; + } + + std::copy(ticket_bytes.begin(), ticket_bytes.end(), ticket); + return IPC_SUCCESS; +} + +ReturnCode ES::GetTicketFromView(const u8* ticket_view, u8* ticket, u32* ticket_size) const +{ + const u8 version = ticket_view[offsetof(IOS::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(IOS_ES, "GetV1TicketFromView: Unimplemented -- returning -1028"); + return ES_NO_TICKET; + } + if (ticket != nullptr) + { + if (*ticket_size >= sizeof(IOS::ES::Ticket)) + return GetV0TicketFromView(ticket_view, ticket); + return ES_EINVAL; + } + *ticket_size = sizeof(IOS::ES::Ticket); + return IPC_SUCCESS; +} + +IPCCommandResult ES::GetV0TicketFromView(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 1) || + request.in_vectors[0].size != sizeof(IOS::ES::TicketView) || + request.io_vectors[0].size != sizeof(IOS::ES::Ticket)) + { + return GetDefaultReply(ES_EINVAL); + } + return GetDefaultReply(GetV0TicketFromView(Memory::GetPointer(request.in_vectors[0].address), + Memory::GetPointer(request.io_vectors[0].address))); +} + +IPCCommandResult ES::GetTicketSizeFromView(const IOCtlVRequest& request) +{ + u32 ticket_size = 0; + if (!request.HasNumberOfValidVectors(1, 1) || + request.in_vectors[0].size != sizeof(IOS::ES::TicketView) || + request.io_vectors[0].size != sizeof(ticket_size)) + { + return GetDefaultReply(ES_EINVAL); + } + const ReturnCode ret = + GetTicketFromView(Memory::GetPointer(request.in_vectors[0].address), nullptr, &ticket_size); + Memory::Write_U32(ticket_size, request.io_vectors[0].address); + return GetDefaultReply(ret); +} + +IPCCommandResult ES::GetTicketFromView(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(2, 1) || + request.in_vectors[0].size != sizeof(IOS::ES::TicketView) || + request.in_vectors[1].size != sizeof(u32)) + { + return GetDefaultReply(ES_EINVAL); + } + + u32 ticket_size = Memory::Read_U32(request.in_vectors[1].address); + if (ticket_size != request.io_vectors[0].size) + return GetDefaultReply(ES_EINVAL); + + return GetDefaultReply(GetTicketFromView(Memory::GetPointer(request.in_vectors[0].address), + Memory::GetPointer(request.io_vectors[0].address), + &ticket_size)); +} + IPCCommandResult ES::GetTMDViewSize(const IOCtlVRequest& request) { if (!request.HasNumberOfValidVectors(1, 1))