Move the ticket code to ESFormats
This moves some parsing code for tickets and ticket views to ESFormats instead of duplicating it over DiscIO and Core.
This commit is contained in:
parent
a62711de55
commit
bf1f70db0a
|
@ -348,7 +348,7 @@ bool CBoot::EmulatedBS2_Wii()
|
|||
|
||||
std::vector<u8> tmd = DVDInterface::GetVolume().GetTMD();
|
||||
|
||||
IOS::HLE::TMDReader tmd_reader{std::move(tmd)};
|
||||
ES::TMDReader tmd_reader{std::move(tmd)};
|
||||
|
||||
if (!SetupWiiMemory(tmd_reader.GetIOSId()))
|
||||
return false;
|
||||
|
|
|
@ -2,35 +2,6 @@
|
|||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// =======================================================
|
||||
// File description
|
||||
// -------------
|
||||
/* Here we handle /dev/es requests. We have cases for these functions, the exact
|
||||
DevKitPro/libogc name is in parenthesis:
|
||||
|
||||
0x20 GetTitleID (ES_GetTitleID) (Input: none, Output: 8 bytes)
|
||||
0x1d GetDataDir (ES_GetDataDir) (Input: 8 bytes, Output: 30 bytes)
|
||||
|
||||
0x1b DiGetTicketView (Input: none, Output: 216 bytes)
|
||||
0x16 GetConsumption (Input: 8 bytes, Output: 0 bytes, 4 bytes) // there are two output buffers
|
||||
|
||||
0x12 GetNumTicketViews (ES_GetNumTicketViews) (Input: 8 bytes, Output: 4 bytes)
|
||||
0x14 GetTMDViewSize (ES_GetTMDViewSize) (Input: ?, Output: ?) // I don't get this anymore,
|
||||
it used to come after 0x12
|
||||
|
||||
but only the first two are correctly supported. For the other four we ignore any potential
|
||||
input and only write zero to the out buffer. However, most games only use first two,
|
||||
but some Nintendo developed games use the other ones to:
|
||||
|
||||
0x1b: Mario Galaxy, Mario Kart, SSBB
|
||||
0x16: Mario Galaxy, Mario Kart, SSBB
|
||||
0x12: Mario Kart
|
||||
0x14: Mario Kart: But only if we don't return a zeroed out buffer for the 0x12 question,
|
||||
and instead answer for example 1 will this question appear.
|
||||
|
||||
*/
|
||||
// =============
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
|
@ -347,7 +318,8 @@ IPCCommandResult ES::AddTicket(const IOCtlVRequest& request)
|
|||
INFO_LOG(IOS_ES, "IOCTL_ES_ADDTICKET");
|
||||
std::vector<u8> ticket(request.in_vectors[0].size);
|
||||
Memory::CopyFromEmu(ticket.data(), request.in_vectors[0].address, request.in_vectors[0].size);
|
||||
DiscIO::AddTicket(ticket);
|
||||
|
||||
DiscIO::AddTicket(::ES::TicketReader{std::move(ticket)});
|
||||
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
@ -441,18 +413,18 @@ IPCCommandResult ES::AddContentFinish(const IOCtlVRequest& request)
|
|||
INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTFINISH: content fd %08x", content_fd);
|
||||
|
||||
// Try to find the title key from a pre-installed ticket.
|
||||
std::vector<u8> ticket = DiscIO::FindSignedTicket(m_addtitle_tmd.GetTitleId());
|
||||
if (ticket.size() == 0)
|
||||
::ES::TicketReader ticket = DiscIO::FindSignedTicket(m_addtitle_tmd.GetTitleId());
|
||||
if (!ticket.IsValid())
|
||||
{
|
||||
return GetDefaultReply(ES_NO_TICKET_INSTALLED);
|
||||
}
|
||||
|
||||
mbedtls_aes_context aes_ctx;
|
||||
mbedtls_aes_setkey_dec(&aes_ctx, DiscIO::GetKeyFromTicket(ticket).data(), 128);
|
||||
mbedtls_aes_setkey_dec(&aes_ctx, ticket.GetTitleKey().data(), 128);
|
||||
|
||||
// The IV for title content decryption is the lower two bytes of the
|
||||
// content index, zero extended.
|
||||
TMDReader::Content content_info;
|
||||
::ES::TMDReader::Content content_info;
|
||||
if (!m_addtitle_tmd.FindContentById(m_addtitle_content_id, &content_info))
|
||||
{
|
||||
return GetDefaultReply(ES_INVALID_TMD);
|
||||
|
@ -777,46 +749,24 @@ IPCCommandResult ES::GetViewCount(const IOCtlVRequest& request)
|
|||
|
||||
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
|
||||
|
||||
u32 retVal = 0;
|
||||
const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID);
|
||||
u32 ViewCount =
|
||||
static_cast<u32>(Loader.GetTicket().size()) / DiscIO::CNANDContentLoader::TICKET_SIZE;
|
||||
|
||||
if (!ViewCount)
|
||||
size_t view_count = 0;
|
||||
if (TitleID >> 32 == 0x00000001 && TitleID != TITLEID_SYSMENU)
|
||||
{
|
||||
std::string TicketFilename = Common::GetTicketFileName(TitleID, Common::FROM_SESSION_ROOT);
|
||||
if (File::Exists(TicketFilename))
|
||||
{
|
||||
u32 FileSize = (u32)File::GetSize(TicketFilename);
|
||||
_dbg_assert_msg_(IOS_ES, (FileSize % DiscIO::CNANDContentLoader::TICKET_SIZE) == 0,
|
||||
"IOCTL_ES_GETVIEWCNT ticket file size seems to be wrong");
|
||||
|
||||
ViewCount = FileSize / DiscIO::CNANDContentLoader::TICKET_SIZE;
|
||||
_dbg_assert_msg_(IOS_ES, (ViewCount > 0) && (ViewCount <= 4),
|
||||
"IOCTL_ES_GETVIEWCNT ticket count seems to be wrong");
|
||||
}
|
||||
else if (TitleID >> 32 == 0x00000001)
|
||||
{
|
||||
// Fake a ticket view to make IOS reload work.
|
||||
ViewCount = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewCount = 0;
|
||||
if (TitleID == TITLEID_SYSMENU)
|
||||
{
|
||||
PanicAlertT("There must be a ticket for 00000001/00000002. Your NAND dump is probably "
|
||||
"incomplete.");
|
||||
}
|
||||
// retVal = ES_NO_TICKET_INSTALLED;
|
||||
}
|
||||
// Fake a ticket view to make IOS reload work.
|
||||
view_count = 1;
|
||||
}
|
||||
else if (Loader.IsValid() && Loader.GetTicket().IsValid())
|
||||
{
|
||||
view_count = Loader.GetTicket().GetNumberOfTickets();
|
||||
}
|
||||
|
||||
INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWCNT for titleID: %08x/%08x (View Count = %i)",
|
||||
(u32)(TitleID >> 32), (u32)TitleID, ViewCount);
|
||||
INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWCNT for titleID: %08x/%08x (View Count = %zu)",
|
||||
static_cast<u32>(TitleID >> 32), static_cast<u32>(TitleID), view_count);
|
||||
|
||||
Memory::Write_U32(ViewCount, request.io_vectors[0].address);
|
||||
return GetDefaultReply(retVal);
|
||||
Memory::Write_U32(static_cast<u32>(view_count), request.io_vectors[0].address);
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::GetViews(const IOCtlVRequest& request)
|
||||
|
@ -826,68 +776,37 @@ IPCCommandResult ES::GetViews(const IOCtlVRequest& request)
|
|||
|
||||
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
|
||||
u32 maxViews = Memory::Read_U32(request.in_vectors[1].address);
|
||||
u32 retVal = 0;
|
||||
|
||||
const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID);
|
||||
|
||||
const std::vector<u8>& ticket = Loader.GetTicket();
|
||||
|
||||
if (ticket.empty())
|
||||
if (TitleID >> 32 == 0x00000001 && TitleID != TITLEID_SYSMENU)
|
||||
{
|
||||
std::string TicketFilename = Common::GetTicketFileName(TitleID, Common::FROM_SESSION_ROOT);
|
||||
if (File::Exists(TicketFilename))
|
||||
{
|
||||
File::IOFile pFile(TicketFilename, "rb");
|
||||
if (pFile)
|
||||
{
|
||||
u8 FileTicket[DiscIO::CNANDContentLoader::TICKET_SIZE];
|
||||
for (unsigned int View = 0;
|
||||
View != maxViews &&
|
||||
pFile.ReadBytes(FileTicket, DiscIO::CNANDContentLoader::TICKET_SIZE);
|
||||
++View)
|
||||
{
|
||||
Memory::Write_U32(View, request.io_vectors[0].address + View * 0xD8);
|
||||
Memory::CopyToEmu(request.io_vectors[0].address + 4 + View * 0xD8, FileTicket + 0x1D0,
|
||||
212);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (TitleID >> 32 == 0x00000001)
|
||||
{
|
||||
// For IOS titles, the ticket view isn't normally parsed by either the
|
||||
// SDK or libogc, just passed to LaunchTitle, so this
|
||||
// shouldn't matter at all. Just fill out some fields just
|
||||
// to be on the safe side.
|
||||
u32 Address = request.io_vectors[0].address;
|
||||
Memory::Memset(Address, 0, 0xD8);
|
||||
Memory::Write_U64(TitleID, Address + 4 + (0x1dc - 0x1d0)); // title ID
|
||||
Memory::Write_U16(0xffff, Address + 4 + (0x1e4 - 0x1d0)); // unnnown
|
||||
Memory::Write_U32(0xff00, Address + 4 + (0x1ec - 0x1d0)); // access mask
|
||||
Memory::Memset(Address + 4 + (0x222 - 0x1d0), 0xff, 0x20); // content permissions
|
||||
}
|
||||
else
|
||||
{
|
||||
// retVal = ES_NO_TICKET_INSTALLED;
|
||||
PanicAlertT("IOCTL_ES_GETVIEWS: Tried to get data from an unknown ticket: %08x/%08x",
|
||||
(u32)(TitleID >> 32), (u32)TitleID);
|
||||
}
|
||||
// For IOS titles, the ticket view isn't normally parsed by either the
|
||||
// SDK or libogc, just passed to LaunchTitle, so this
|
||||
// shouldn't matter at all. Just fill out some fields just
|
||||
// to be on the safe side.
|
||||
u32 Address = request.io_vectors[0].address;
|
||||
Memory::Memset(Address, 0, 0xD8);
|
||||
Memory::Write_U64(TitleID, Address + 4 + (0x1dc - 0x1d0)); // title ID
|
||||
Memory::Write_U16(0xffff, Address + 4 + (0x1e4 - 0x1d0)); // unnnown
|
||||
Memory::Write_U32(0xff00, Address + 4 + (0x1ec - 0x1d0)); // access mask
|
||||
Memory::Memset(Address + 4 + (0x222 - 0x1d0), 0xff, 0x20); // content permissions
|
||||
}
|
||||
else
|
||||
else if (Loader.IsValid() && Loader.GetTicket().IsValid())
|
||||
{
|
||||
u32 view_count =
|
||||
static_cast<u32>(Loader.GetTicket().size()) / DiscIO::CNANDContentLoader::TICKET_SIZE;
|
||||
for (unsigned int view = 0; view != maxViews && view < view_count; ++view)
|
||||
u32 number_of_views = std::min(maxViews, Loader.GetTicket().GetNumberOfTickets());
|
||||
for (u32 view = 0; view < number_of_views; ++view)
|
||||
{
|
||||
Memory::Write_U32(view, request.io_vectors[0].address + view * 0xD8);
|
||||
Memory::CopyToEmu(request.io_vectors[0].address + 4 + view * 0xD8,
|
||||
&ticket[0x1D0 + (view * DiscIO::CNANDContentLoader::TICKET_SIZE)], 212);
|
||||
const std::vector<u8> ticket_view = Loader.GetTicket().GetRawTicketView(view);
|
||||
Memory::CopyToEmu(request.io_vectors[0].address + view * sizeof(::ES::TicketView),
|
||||
ticket_view.data(), ticket_view.size());
|
||||
}
|
||||
}
|
||||
|
||||
INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWS for titleID: %08x/%08x (MaxViews = %i)", (u32)(TitleID >> 32),
|
||||
(u32)TitleID, maxViews);
|
||||
|
||||
return GetDefaultReply(retVal);
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::GetTMDViewCount(const IOCtlVRequest& request)
|
||||
|
|
|
@ -203,7 +203,7 @@ private:
|
|||
u32 m_AccessIdentID = 0;
|
||||
|
||||
// For title installation (ioctls IOCTL_ES_ADDTITLE*).
|
||||
TMDReader m_addtitle_tmd;
|
||||
::ES::TMDReader m_addtitle_tmd;
|
||||
u32 m_addtitle_content_id = 0xFFFFFFFF;
|
||||
std::vector<u8> m_addtitle_content_buffer;
|
||||
};
|
||||
|
|
|
@ -5,17 +5,30 @@
|
|||
#include "Core/IOS/ES/Formats.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <mbedtls/aes.h>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace IOS
|
||||
namespace ES
|
||||
{
|
||||
namespace HLE
|
||||
std::vector<u8> AESDecode(const u8* key, u8* iv, const u8* src, u32 size)
|
||||
{
|
||||
mbedtls_aes_context aes_ctx;
|
||||
std::vector<u8> buffer(size);
|
||||
|
||||
mbedtls_aes_setkey_dec(&aes_ctx, key, 128);
|
||||
mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, size, iv, src, buffer.data());
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
TMDReader::TMDReader(const std::vector<u8>& bytes) : m_bytes(bytes)
|
||||
{
|
||||
}
|
||||
|
@ -103,5 +116,92 @@ void TMDReader::DoState(PointerWrap& p)
|
|||
{
|
||||
p.Do(m_bytes);
|
||||
}
|
||||
} // namespace HLE
|
||||
} // namespace IOS
|
||||
|
||||
TicketReader::TicketReader(const std::vector<u8>& bytes) : m_bytes(bytes)
|
||||
{
|
||||
}
|
||||
|
||||
TicketReader::TicketReader(std::vector<u8>&& bytes) : m_bytes(std::move(bytes))
|
||||
{
|
||||
}
|
||||
|
||||
void TicketReader::SetBytes(const std::vector<u8>& bytes)
|
||||
{
|
||||
m_bytes = bytes;
|
||||
}
|
||||
|
||||
void TicketReader::SetBytes(std::vector<u8>&& bytes)
|
||||
{
|
||||
m_bytes = std::move(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;
|
||||
}
|
||||
|
||||
u32 TicketReader::GetNumberOfTickets() const
|
||||
{
|
||||
return static_cast<u32>(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;
|
||||
}
|
||||
|
||||
const std::vector<u8>& TicketReader::GetRawTicket() const
|
||||
{
|
||||
return m_bytes;
|
||||
}
|
||||
|
||||
std::vector<u8> TicketReader::GetRawTicketView(u32 ticket_num) const
|
||||
{
|
||||
// A ticket view is composed of a view ID + part of a ticket starting from the ticket_id field.
|
||||
std::vector<u8> view{sizeof(TicketView)};
|
||||
|
||||
u32 view_id = Common::swap32(ticket_num);
|
||||
std::memcpy(view.data(), &view_id, sizeof(view_id));
|
||||
|
||||
const size_t ticket_start = (GetOffset() + sizeof(Ticket)) * ticket_num;
|
||||
const size_t view_start = ticket_start + offsetof(Ticket, ticket_id);
|
||||
std::memcpy(view.data() + sizeof(view_id), &m_bytes[view_start], sizeof(view) - sizeof(view_id));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
u64 TicketReader::GetTitleId() const
|
||||
{
|
||||
return Common::swap64(m_bytes.data() + GetOffset() + offsetof(Ticket, title_id));
|
||||
}
|
||||
|
||||
std::vector<u8> TicketReader::GetTitleKey() const
|
||||
{
|
||||
const u8 common_key[16] = {0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4,
|
||||
0x48, 0xd9, 0xc5, 0x45, 0x73, 0x81, 0xaa, 0xf7};
|
||||
u8 iv[16] = {};
|
||||
std::copy_n(&m_bytes[GetOffset() + offsetof(Ticket, title_id)], sizeof(Ticket::title_id), iv);
|
||||
return AESDecode(common_key, iv, &m_bytes[GetOffset() + offsetof(Ticket, title_key)], 16);
|
||||
}
|
||||
} // namespace ES
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace IOS
|
||||
{
|
||||
namespace HLE
|
||||
namespace ES
|
||||
{
|
||||
std::vector<u8> AESDecode(const u8* key, u8* iv, const u8* src, u32 size);
|
||||
|
||||
class TMDReader final
|
||||
{
|
||||
public:
|
||||
|
@ -49,5 +49,79 @@ public:
|
|||
private:
|
||||
std::vector<u8> m_bytes;
|
||||
};
|
||||
} // namespace HLE
|
||||
} // namespace IOS
|
||||
|
||||
#pragma pack(push, 4)
|
||||
struct TimeLimit
|
||||
{
|
||||
u32 enabled;
|
||||
u32 seconds;
|
||||
};
|
||||
|
||||
struct TicketView
|
||||
{
|
||||
u32 view;
|
||||
u64 ticket_id;
|
||||
u32 device_id;
|
||||
u64 title_id;
|
||||
u16 access_mask;
|
||||
u32 permitted_title_id;
|
||||
u32 permitted_title_mask;
|
||||
u8 title_export_allowed;
|
||||
u8 common_key_index;
|
||||
u8 unknown2[0x30];
|
||||
u8 content_access_permissions[0x40];
|
||||
TimeLimit time_limits[8];
|
||||
};
|
||||
static_assert(sizeof(TicketView) == 0xd8, "TicketView has the wrong size");
|
||||
|
||||
struct Ticket
|
||||
{
|
||||
u8 signature_issuer[0x40];
|
||||
u8 ecdh_key[0x3c];
|
||||
u8 unknown[0x03];
|
||||
u8 title_key[0x10];
|
||||
u64 ticket_id;
|
||||
u32 device_id;
|
||||
u64 title_id;
|
||||
u16 access_mask;
|
||||
u16 ticket_version;
|
||||
u32 permitted_title_id;
|
||||
u32 permitted_title_mask;
|
||||
u8 title_export_allowed;
|
||||
u8 common_key_index;
|
||||
u8 unknown2[0x30];
|
||||
u8 content_access_permissions[0x40];
|
||||
TimeLimit time_limits[8];
|
||||
};
|
||||
static_assert(sizeof(Ticket) == 356, "Ticket has the wrong size");
|
||||
#pragma pack(pop)
|
||||
|
||||
class TicketReader final
|
||||
{
|
||||
public:
|
||||
TicketReader() = default;
|
||||
explicit TicketReader(const std::vector<u8>& bytes);
|
||||
explicit TicketReader(std::vector<u8>&& bytes);
|
||||
|
||||
void SetBytes(const std::vector<u8>& bytes);
|
||||
void SetBytes(std::vector<u8>&& bytes);
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
const std::vector<u8>& GetRawTicket() const;
|
||||
u32 GetNumberOfTickets() const;
|
||||
u32 GetOffset() 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?)
|
||||
// official titles only have one ticket, but IOS *does* have code to handle ticket files with
|
||||
// more than just one ticket and generate ticket views for them, so we implement it too.
|
||||
std::vector<u8> GetRawTicketView(u32 ticket_num) const;
|
||||
|
||||
u64 GetTitleId() const;
|
||||
std::vector<u8> GetTitleKey() const;
|
||||
|
||||
private:
|
||||
std::vector<u8> m_bytes;
|
||||
};
|
||||
} // namespace ES
|
||||
|
|
|
@ -109,14 +109,14 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request)
|
|||
Memory::CopyFromEmu(tmd_bytes.data(), tmd_addr, tmd_size);
|
||||
m_tmd.SetBytes(std::move(tmd_bytes));
|
||||
|
||||
std::vector<u8> ticket = DiscIO::FindSignedTicket(m_tmd.GetTitleId());
|
||||
if (ticket.size() == 0)
|
||||
ES::TicketReader ticket = DiscIO::FindSignedTicket(m_tmd.GetTitleId());
|
||||
if (!ticket.IsValid())
|
||||
{
|
||||
return_error_code = -11028;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(m_aes_key, DiscIO::GetKeyFromTicket(ticket).data(), sizeof(m_aes_key));
|
||||
memcpy(m_aes_key, ticket.GetTitleKey().data(), sizeof(m_aes_key));
|
||||
mbedtls_aes_setkey_dec(&m_aes_ctx, m_aes_key, 128);
|
||||
|
||||
break;
|
||||
|
@ -134,7 +134,7 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request)
|
|||
|
||||
// Initializes the IV from the index of the content in the TMD contents.
|
||||
u32 content_id = Memory::Read_U32(request.buffer_in + 8);
|
||||
TMDReader::Content content_info;
|
||||
ES::TMDReader::Content content_info;
|
||||
if (!m_tmd.FindContentById(content_id, &content_info))
|
||||
{
|
||||
WARN_LOG(IOS, "%s: Content id %08x not found", ioctl_name, content_id);
|
||||
|
|
|
@ -50,7 +50,7 @@ private:
|
|||
u8 m_aes_key[0x10] = {};
|
||||
u8 m_aes_iv[0x10] = {};
|
||||
|
||||
TMDReader m_tmd;
|
||||
::ES::TMDReader m_tmd;
|
||||
std::string m_base_extract_path;
|
||||
|
||||
ARCUnpacker m_arc_unpacker;
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <mbedtls/aes.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
@ -29,50 +28,6 @@
|
|||
|
||||
namespace DiscIO
|
||||
{
|
||||
namespace
|
||||
{
|
||||
// Strips the signature part of a ticket, which has variable size based on
|
||||
// signature type. Returns a new vector which has only the ticket structure
|
||||
// itself.
|
||||
std::vector<u8> SignedTicketToTicket(const std::vector<u8>& signed_ticket)
|
||||
{
|
||||
u32 signature_type = Common::swap32(signed_ticket.data());
|
||||
u32 entry_offset;
|
||||
if (signature_type == 0x10000) // RSA4096
|
||||
{
|
||||
entry_offset = 576;
|
||||
}
|
||||
else if (signature_type == 0x10001) // RSA2048
|
||||
{
|
||||
entry_offset = 320;
|
||||
}
|
||||
else if (signature_type == 0x10002) // ECDSA
|
||||
{
|
||||
entry_offset = 128;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG(DISCIO, "Invalid ticket signature type: %08x", signature_type);
|
||||
return std::vector<u8>();
|
||||
}
|
||||
|
||||
std::vector<u8> ticket(signed_ticket.size() - entry_offset);
|
||||
std::copy(signed_ticket.begin() + entry_offset, signed_ticket.end(), ticket.begin());
|
||||
return ticket;
|
||||
}
|
||||
|
||||
std::vector<u8> AESDecode(const u8* key, u8* iv, const u8* src, u32 size)
|
||||
{
|
||||
mbedtls_aes_context aes_ctx;
|
||||
std::vector<u8> buffer(size);
|
||||
|
||||
mbedtls_aes_setkey_dec(&aes_ctx, key, 128);
|
||||
mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, size, iv, src, buffer.data());
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
CNANDContentData::~CNANDContentData() = default;
|
||||
|
||||
CSharedContent::CSharedContent(Common::FromWhichRoot root) : m_root(root)
|
||||
|
@ -222,13 +177,11 @@ bool CNANDContentLoader::Initialize(const std::string& name)
|
|||
WiiWAD wad(name);
|
||||
std::vector<u8> data_app;
|
||||
std::vector<u8> tmd;
|
||||
std::vector<u8> decrypted_title_key;
|
||||
|
||||
if (wad.IsValid())
|
||||
{
|
||||
m_IsWAD = true;
|
||||
m_Ticket = wad.GetTicket();
|
||||
decrypted_title_key = GetKeyFromTicket(m_Ticket);
|
||||
m_ticket = wad.GetTicket();
|
||||
tmd = wad.GetTMD();
|
||||
data_app = wad.GetDataApp();
|
||||
}
|
||||
|
@ -265,12 +218,14 @@ bool CNANDContentLoader::Initialize(const std::string& name)
|
|||
if (m_Country == 2) // SYSMENU
|
||||
m_Country = GetSysMenuRegion(m_TitleVersion);
|
||||
|
||||
InitializeContentEntries(tmd, decrypted_title_key, data_app);
|
||||
if (!m_IsWAD)
|
||||
m_ticket = FindSignedTicket(m_TitleID);
|
||||
|
||||
InitializeContentEntries(tmd, data_app);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNANDContentLoader::InitializeContentEntries(const std::vector<u8>& tmd,
|
||||
const std::vector<u8>& decrypted_title_key,
|
||||
const std::vector<u8>& data_app)
|
||||
{
|
||||
m_Content.resize(m_NumEntries);
|
||||
|
@ -305,8 +260,8 @@ void CNANDContentLoader::InitializeContentEntries(const std::vector<u8>& tmd,
|
|||
iv.fill(0);
|
||||
std::copy(&tmd[entry_offset + 0x01E8], &tmd[entry_offset + 0x01E8 + 2], iv.begin());
|
||||
|
||||
content.m_Data = std::make_unique<CNANDContentDataBuffer>(AESDecode(
|
||||
decrypted_title_key.data(), iv.data(), &data_app[data_app_offset], rounded_size));
|
||||
content.m_Data = std::make_unique<CNANDContentDataBuffer>(ES::AESDecode(
|
||||
m_ticket.GetTitleKey().data(), iv.data(), &data_app[data_app_offset], rounded_size));
|
||||
|
||||
data_app_offset += rounded_size;
|
||||
continue;
|
||||
|
@ -533,14 +488,14 @@ u64 CNANDContentManager::Install_WiiWAD(const std::string& filename)
|
|||
return title_id;
|
||||
}
|
||||
|
||||
bool AddTicket(const std::vector<u8>& signed_ticket)
|
||||
bool AddTicket(const ES::TicketReader& signed_ticket)
|
||||
{
|
||||
std::vector<u8> ticket = SignedTicketToTicket(signed_ticket);
|
||||
if (ticket.empty())
|
||||
if (!signed_ticket.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
u64 title_id = Common::swap64(ticket.data() + 0x9c);
|
||||
|
||||
u64 title_id = signed_ticket.GetTitleId();
|
||||
|
||||
std::string ticket_filename = Common::GetTicketFileName(title_id, Common::FROM_CONFIGURED_ROOT);
|
||||
File::CreateFullPath(ticket_filename);
|
||||
|
@ -549,46 +504,25 @@ bool AddTicket(const std::vector<u8>& signed_ticket)
|
|||
if (!ticket_file)
|
||||
return false;
|
||||
|
||||
return ticket_file.WriteBytes(signed_ticket.data(), signed_ticket.size());
|
||||
const std::vector<u8>& raw_ticket = signed_ticket.GetRawTicket();
|
||||
return ticket_file.WriteBytes(raw_ticket.data(), raw_ticket.size());
|
||||
}
|
||||
|
||||
std::vector<u8> FindSignedTicket(u64 title_id)
|
||||
ES::TicketReader FindSignedTicket(u64 title_id)
|
||||
{
|
||||
std::string ticket_filename = Common::GetTicketFileName(title_id, Common::FROM_CONFIGURED_ROOT);
|
||||
File::IOFile ticket_file(ticket_filename, "rb");
|
||||
if (!ticket_file)
|
||||
{
|
||||
return std::vector<u8>();
|
||||
return ES::TicketReader{};
|
||||
}
|
||||
|
||||
std::vector<u8> signed_ticket(ticket_file.GetSize());
|
||||
if (!ticket_file.ReadBytes(signed_ticket.data(), signed_ticket.size()))
|
||||
{
|
||||
return std::vector<u8>();
|
||||
return ES::TicketReader{};
|
||||
}
|
||||
|
||||
return signed_ticket;
|
||||
return ES::TicketReader{std::move(signed_ticket)};
|
||||
}
|
||||
|
||||
std::vector<u8> FindTicket(u64 title_id)
|
||||
{
|
||||
std::vector<u8> signed_ticket = FindSignedTicket(title_id);
|
||||
if (signed_ticket.empty())
|
||||
{
|
||||
return std::vector<u8>();
|
||||
}
|
||||
|
||||
return SignedTicketToTicket(signed_ticket);
|
||||
}
|
||||
|
||||
std::vector<u8> GetKeyFromTicket(const std::vector<u8>& signed_ticket)
|
||||
{
|
||||
const u8 common_key[16] = {0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4,
|
||||
0x48, 0xd9, 0xc5, 0x45, 0x73, 0x81, 0xaa, 0xf7};
|
||||
u8 iv[16] = {};
|
||||
|
||||
std::copy(&signed_ticket[0x01DC], &signed_ticket[0x01DC + 8], iv);
|
||||
return AESDecode(common_key, iv, &signed_ticket[0x01BF], 16);
|
||||
}
|
||||
|
||||
} // namespace end
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/NandPaths.h"
|
||||
#include "Core/IOS/ES/Formats.h"
|
||||
|
||||
namespace File
|
||||
{
|
||||
|
@ -22,10 +23,8 @@ namespace DiscIO
|
|||
{
|
||||
enum class Region;
|
||||
|
||||
bool AddTicket(const std::vector<u8>& signed_ticket);
|
||||
std::vector<u8> FindSignedTicket(u64 title_id);
|
||||
std::vector<u8> FindTicket(u64 title_id);
|
||||
std::vector<u8> GetKeyFromTicket(const std::vector<u8>& ticket);
|
||||
bool AddTicket(const ES::TicketReader& signed_ticket);
|
||||
ES::TicketReader FindSignedTicket(u64 title_id);
|
||||
|
||||
class CNANDContentData
|
||||
{
|
||||
|
@ -93,7 +92,7 @@ public:
|
|||
const SNANDContent* GetContentByIndex(int index) const;
|
||||
const u8* GetTMDView() const { return m_TMDView; }
|
||||
const u8* GetTMDHeader() const { return m_TMDHeader; }
|
||||
const std::vector<u8>& GetTicket() const { return m_Ticket; }
|
||||
const ES::TicketReader& GetTicket() const { return m_ticket; }
|
||||
const std::vector<SNANDContent>& GetContent() const { return m_Content; }
|
||||
u16 GetTitleVersion() const { return m_TitleVersion; }
|
||||
u16 GetNumEntries() const { return m_NumEntries; }
|
||||
|
@ -104,14 +103,11 @@ public:
|
|||
TMD_VIEW_SIZE = 0x58,
|
||||
TMD_HEADER_SIZE = 0x1E4,
|
||||
CONTENT_HEADER_SIZE = 0x24,
|
||||
TICKET_SIZE = 0x2A4
|
||||
};
|
||||
|
||||
private:
|
||||
bool Initialize(const std::string& name);
|
||||
void InitializeContentEntries(const std::vector<u8>& tmd,
|
||||
const std::vector<u8>& decrypted_title_key,
|
||||
const std::vector<u8>& data_app);
|
||||
void InitializeContentEntries(const std::vector<u8>& tmd, const std::vector<u8>& data_app);
|
||||
|
||||
bool m_Valid;
|
||||
bool m_IsWAD;
|
||||
|
@ -123,7 +119,7 @@ private:
|
|||
u16 m_TitleVersion;
|
||||
u8 m_TMDView[TMD_VIEW_SIZE];
|
||||
u8 m_TMDHeader[TMD_HEADER_SIZE];
|
||||
std::vector<u8> m_Ticket;
|
||||
ES::TicketReader m_ticket;
|
||||
u8 m_Country;
|
||||
|
||||
std::vector<SNANDContent> m_Content;
|
||||
|
|
|
@ -88,7 +88,7 @@ bool WiiWAD::ParseWAD(IBlobReader& reader)
|
|||
u32 offset = 0x40;
|
||||
m_certificate_chain = CreateWADEntry(reader, certificate_chain_size, offset);
|
||||
offset += Common::AlignUp(certificate_chain_size, 0x40);
|
||||
m_ticket = CreateWADEntry(reader, ticket_size, offset);
|
||||
m_ticket.SetBytes(CreateWADEntry(reader, ticket_size, offset));
|
||||
offset += Common::AlignUp(ticket_size, 0x40);
|
||||
m_tmd = CreateWADEntry(reader, tmd_size, offset);
|
||||
offset += Common::AlignUp(tmd_size, 0x40);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/IOS/ES/Formats.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
@ -22,7 +23,7 @@ public:
|
|||
|
||||
bool IsValid() const { return m_valid; }
|
||||
const std::vector<u8>& GetCertificateChain() const { return m_certificate_chain; }
|
||||
const std::vector<u8>& GetTicket() const { return m_ticket; }
|
||||
const ES::TicketReader& GetTicket() const { return m_ticket; }
|
||||
const std::vector<u8>& GetTMD() const { return m_tmd; }
|
||||
const std::vector<u8>& GetDataApp() const { return m_data_app; }
|
||||
const std::vector<u8>& GetFooter() const { return m_footer; }
|
||||
|
@ -32,7 +33,7 @@ private:
|
|||
bool m_valid;
|
||||
|
||||
std::vector<u8> m_certificate_chain;
|
||||
std::vector<u8> m_ticket;
|
||||
ES::TicketReader m_ticket;
|
||||
std::vector<u8> m_tmd;
|
||||
std::vector<u8> m_data_app;
|
||||
std::vector<u8> m_footer;
|
||||
|
|
|
@ -182,7 +182,7 @@ void InfoPanel::LoadISODetails()
|
|||
m_fst->SetValue(StrToWxStr(std::to_string(m_opened_iso->GetFSTSize())));
|
||||
if (m_ios_version)
|
||||
{
|
||||
IOS::HLE::TMDReader tmd{m_opened_iso->GetTMD()};
|
||||
ES::TMDReader tmd{m_opened_iso->GetTMD()};
|
||||
if (tmd.IsValid())
|
||||
m_ios_version->SetValue(StringFromFormat("IOS%u", static_cast<u32>(tmd.GetIOSId())));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue