IOS/ES: Add support for V1Ticket
This commit is contained in:
parent
d3718b1b81
commit
2fd9852ca8
|
@ -41,6 +41,12 @@ std::string GetTicketFileName(u64 title_id, std::optional<FromWhichRoot> from)
|
|||
static_cast<u32>(title_id >> 32), static_cast<u32>(title_id));
|
||||
}
|
||||
|
||||
std::string GetV1TicketFileName(u64 title_id, std::optional<FromWhichRoot> from)
|
||||
{
|
||||
return fmt::format("{}/ticket/{:08x}/{:08x}.tv1", RootUserPath(from),
|
||||
static_cast<u32>(title_id >> 32), static_cast<u32>(title_id));
|
||||
}
|
||||
|
||||
std::string GetTitlePath(u64 title_id, std::optional<FromWhichRoot> from)
|
||||
{
|
||||
return fmt::format("{}/title/{:08x}/{:08x}", RootUserPath(from), static_cast<u32>(title_id >> 32),
|
||||
|
|
|
@ -26,6 +26,7 @@ std::string RootUserPath(FromWhichRoot from);
|
|||
std::string GetImportTitlePath(u64 title_id, std::optional<FromWhichRoot> from = {});
|
||||
|
||||
std::string GetTicketFileName(u64 title_id, std::optional<FromWhichRoot> from = {});
|
||||
std::string GetV1TicketFileName(u64 title_id, std::optional<FromWhichRoot> from = {});
|
||||
std::string GetTitlePath(u64 title_id, std::optional<FromWhichRoot> from = {});
|
||||
std::string GetTitleDataPath(u64 title_id, std::optional<FromWhichRoot> from = {});
|
||||
std::string GetTitleContentPath(u64 title_id, std::optional<FromWhichRoot> from = {});
|
||||
|
|
|
@ -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<u8> desired_version = std::nullopt) const;
|
||||
|
||||
// Get installed titles (in /title) without checking for TMDs at all.
|
||||
std::vector<u64> GetInstalledTitles() const;
|
||||
|
@ -157,8 +158,8 @@ public:
|
|||
const std::vector<u8>& 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<u8> desired_version) const;
|
||||
|
||||
ReturnCode SetUpStreamKey(u32 uid, const u8* ticket_view, const ES::TMDReader& tmd, u32* handle);
|
||||
|
||||
|
|
|
@ -371,22 +371,49 @@ TicketReader::TicketReader(std::vector<u8> 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<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(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<u8>(ticket_begin, ticket_begin + sizeof(ES::Ticket));
|
||||
return {ticket_begin, ticket_begin + GetTicketSize()};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
@ -397,18 +424,21 @@ std::vector<u8> 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<u8> view(sizeof(TicketView::version));
|
||||
const u32 version = Common::swap32(m_bytes.at(offsetof(Ticket, version)));
|
||||
std::memcpy(view.data(), &version, sizeof(version));
|
||||
std::vector<u8> 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);
|
||||
|
|
|
@ -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<u8> bytes);
|
||||
|
||||
bool IsValid() const;
|
||||
bool IsV1Ticket() const;
|
||||
|
||||
std::vector<u8> 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<u8> 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.
|
||||
|
|
|
@ -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<u8> 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<u8> signed_ticket(ticket_file->GetStatus()->size);
|
||||
if (!ticket_file->Read(signed_ticket.data(), signed_ticket.size()))
|
||||
|
@ -151,7 +163,8 @@ std::vector<u64> 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;
|
||||
}
|
||||
|
|
|
@ -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<u8>& 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);
|
||||
|
|
|
@ -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<u8> 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<u8> 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)
|
||||
|
|
Loading…
Reference in New Issue