From e92308fe7e00c1c4abe685d59d104d6375f69f31 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=C3=A9o=20Lam?= <leo@innovatetechnologi.es>
Date: Sat, 20 May 2017 14:12:38 +0200
Subject: [PATCH] IOS/ES: Implement ES_GetTicketFromView ioctlvs

This implements ioctlvs 0x40, 0x43, 0x44.
---
 Source/Core/Core/IOS/ES/ES.cpp      |  10 ++-
 Source/Core/Core/IOS/ES/ES.h        |  13 +++-
 Source/Core/Core/IOS/ES/Formats.cpp |  12 ++++
 Source/Core/Core/IOS/ES/Formats.h   |   1 +
 Source/Core/Core/IOS/ES/Views.cpp   | 104 ++++++++++++++++++++++++++++
 5 files changed, 134 insertions(+), 6 deletions(-)

diff --git a/Source/Core/Core/IOS/ES/ES.cpp b/Source/Core/Core/IOS/ES/ES.cpp
index cbd92a4b40..2ef00bf0ce 100644
--- a/Source/Core/Core/IOS/ES/ES.cpp
+++ b/Source/Core/Core/IOS/ES/ES.cpp
@@ -504,17 +504,21 @@ 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);
diff --git a/Source/Core/Core/IOS/ES/ES.h b/Source/Core/Core/IOS/ES/ES.h
index 9bc9418090..a8c472ac9d 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<u8, 20>& 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
   {
@@ -182,11 +186,11 @@ private:
     IOCTL_ES_UNKNOWN_3D = 0x3D,
     IOCTL_ES_UNKNOWN_3E = 0x3E,
     IOCTL_ES_UNKNOWN_3F = 0x3F,
-    IOCTL_ES_UNKNOWN_40 = 0x40,
+    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<u8>& TicketReader::GetRawTicket() const
   return m_bytes;
 }
 
+std::vector<u8> 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<u8>(ticket_begin, ticket_begin + sizeof(IOS::ES::Ticket));
+  }
+  return {};
+}
+
 std::vector<u8> 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<u8>& GetRawTicket() const;
+  std::vector<u8> 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 <algorithm>
 #include <cinttypes>
+#include <cstddef>
 #include <cstdio>
 #include <vector>
 
@@ -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<u8> 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<u32>(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))