Merge pull request #4896 from leoetlino/esformats

Use ESFormats for tickets, TMDs and views
This commit is contained in:
Matthew Parlane
2017-02-27 16:15:05 +13:00
committed by GitHub
31 changed files with 579 additions and 530 deletions
+1
View File
@@ -33,6 +33,7 @@ set(SRCS Analytics.cpp
x64ABI.cpp
x64Emitter.cpp
MD5.cpp
Crypto/AES.cpp
Crypto/bn.cpp
Crypto/ec.cpp
Logging/LogManager.cpp)
+2
View File
@@ -143,6 +143,7 @@
<ClInclude Include="x64ABI.h" />
<ClInclude Include="x64Emitter.h" />
<ClInclude Include="x64Reg.h" />
<ClInclude Include="Crypto\AES.h" />
<ClInclude Include="Crypto\bn.h" />
<ClInclude Include="Crypto\ec.h" />
<ClInclude Include="Logging\ConsoleListener.h" />
@@ -195,6 +196,7 @@
<ClCompile Include="x64CPUDetect.cpp" />
<ClCompile Include="x64Emitter.cpp" />
<ClCompile Include="x64FPURoundMode.cpp" />
<ClCompile Include="Crypto\AES.cpp" />
<ClCompile Include="Crypto\bn.cpp" />
<ClCompile Include="Crypto\ec.cpp" />
<ClCompile Include="Logging\LogManager.cpp" />
@@ -79,6 +79,9 @@
<ClInclude Include="Logging\LogManager.h">
<Filter>Logging</Filter>
</ClInclude>
<ClInclude Include="Crypto\AES.h">
<Filter>Crypto</Filter>
</ClInclude>
<ClInclude Include="Crypto\ec.h">
<Filter>Crypto</Filter>
</ClInclude>
@@ -266,6 +269,9 @@
<ClCompile Include="x64CPUDetect.cpp" />
<ClCompile Include="x64Emitter.cpp" />
<ClCompile Include="x64FPURoundMode.cpp" />
<ClCompile Include="Crypto\AES.cpp">
<Filter>Crypto</Filter>
</ClCompile>
<ClCompile Include="Crypto\bn.cpp">
<Filter>Crypto</Filter>
</ClCompile>
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <mbedtls/aes.h>
#include "Common/Crypto/AES.h"
namespace Common
{
namespace AES
{
std::vector<u8> Decrypt(const u8* key, u8* iv, const u8* src, size_t 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;
}
} // namespace AES
} // namespace Common
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <cstddef>
#include <vector>
#include "Common/CommonTypes.h"
namespace Common
{
namespace AES
{
std::vector<u8> Decrypt(const u8* key, u8* iv, const u8* src, size_t size);
} // namespace AES
} // namespace Common
+2 -6
View File
@@ -102,7 +102,7 @@ bool CBoot::FindMapFile(std::string* existing_map_file, std::string* writable_ma
DiscIO::CNANDContentManager::Access().GetNANDLoader(_StartupPara.m_strFilename);
if (Loader.IsValid())
{
u64 TitleID = Loader.GetTitleID();
u64 TitleID = Loader.GetTMD().GetTitleId();
title_id_str = StringFromFormat("%08X_%08X", (u32)(TitleID >> 32) & 0xFFFFFFFF,
(u32)TitleID & 0xFFFFFFFF);
}
@@ -278,11 +278,7 @@ bool CBoot::BootUp()
PanicAlertT("Warning - starting ISO in wrong console mode!");
}
std::vector<u8> tmd_buffer = pVolume.GetTMD();
if (!tmd_buffer.empty())
{
IOS::HLE::ES_DIVerify(tmd_buffer);
}
IOS::HLE::ES_DIVerify(pVolume.GetTMD());
_StartupPara.bWii = pVolume.GetVolumeType() == DiscIO::Platform::WII_DISC;
+2 -4
View File
@@ -346,11 +346,9 @@ bool CBoot::EmulatedBS2_Wii()
PowerPC::ppcState.gpr[1] = 0x816ffff0; // StackPointer
std::vector<u8> tmd = DVDInterface::GetVolume().GetTMD();
IOS::ES::TMDReader tmd = DVDInterface::GetVolume().GetTMD();
IOS::HLE::TMDReader tmd_reader{std::move(tmd)};
if (!SetupWiiMemory(tmd_reader.GetIOSId()))
if (!SetupWiiMemory(tmd.GetIOSId()))
return false;
// Execute the apploader
+3 -4
View File
@@ -78,7 +78,7 @@ bool CBoot::Boot_WiiWAD(const std::string& _pFilename)
if (!ContentLoader.IsValid())
return false;
u64 titleID = ContentLoader.GetTitleID();
u64 titleID = ContentLoader.GetTMD().GetTitleId();
// create data directory
File::CreateFullPath(Common::GetTitleDataPath(titleID, Common::FROM_SESSION_ROOT));
@@ -86,12 +86,11 @@ bool CBoot::Boot_WiiWAD(const std::string& _pFilename)
IOS::HLE::CreateVirtualFATFilesystem();
// setup Wii memory
u64 ios_title_id = 0x0000000100000000ULL | ContentLoader.GetIosVersion();
if (!SetupWiiMemory(ios_title_id))
if (!SetupWiiMemory(ContentLoader.GetTMD().GetIOSId()))
return false;
// DOL
const DiscIO::SNANDContent* pContent =
ContentLoader.GetContentByIndex(ContentLoader.GetBootIndex());
ContentLoader.GetContentByIndex(ContentLoader.GetTMD().GetBootIndex());
if (pContent == nullptr)
return false;
+2 -2
View File
@@ -922,7 +922,7 @@ bool SConfig::AutoSetup(EBootBS2 _BootBS2)
const DiscIO::CNANDContentLoader& ContentLoader =
DiscIO::CNANDContentManager::Access().GetNANDLoader(m_strFilename);
if (ContentLoader.GetContentByIndex(ContentLoader.GetBootIndex()) == nullptr)
if (ContentLoader.GetContentByIndex(ContentLoader.GetTMD().GetBootIndex()) == nullptr)
{
// WAD is valid yet cannot be booted. Install instead.
u64 installed = DiscIO::CNANDContentManager::Access().Install_WiiWAD(m_strFilename);
@@ -931,7 +931,7 @@ bool SConfig::AutoSetup(EBootBS2 _BootBS2)
return false; // do not boot
}
SetRegion(ContentLoader.GetRegion(), &set_region_dir);
SetRegion(ContentLoader.GetTMD().GetRegion(), &set_region_dir);
bWii = true;
m_BootType = BOOT_WII_NAND;
+5 -3
View File
@@ -14,6 +14,7 @@
#include "Core/HW/DVDInterface.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/DI/DI.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/IOS/IPC.h"
#include "DiscIO/Volume.h"
@@ -105,9 +106,10 @@ IPCCommandResult DI::IOCtlV(const IOCtlVRequest& request)
INFO_LOG(IOS_DI, "DVDLowOpenPartition: partition_offset 0x%016" PRIx64, partition_offset);
// Read TMD to the buffer
std::vector<u8> tmd_buffer = DVDInterface::GetVolume().GetTMD();
Memory::CopyToEmu(request.io_vectors[0].address, tmd_buffer.data(), tmd_buffer.size());
ES_DIVerify(tmd_buffer);
const ES::TMDReader tmd = DVDInterface::GetVolume().GetTMD();
const std::vector<u8> raw_tmd = tmd.GetRawTMD();
Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size());
ES_DIVerify(tmd);
return_value = 1;
break;
+85 -212
View File
@@ -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>
@@ -115,7 +86,7 @@ void ES::OpenInternal()
// check for cd ...
if (contentLoader.IsValid())
{
m_TitleID = contentLoader.GetTitleID();
m_TitleID = contentLoader.GetTMD().GetTitleId();
m_TitleIDs.clear();
DiscIO::cUIDsys uid_sys{Common::FromWhichRoot::FROM_SESSION_ROOT};
@@ -215,7 +186,7 @@ u32 ES::OpenTitleContent(u32 CFD, u64 TitleID, u16 Index)
{
const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID);
if (!Loader.IsValid())
if (!Loader.IsValid() || !Loader.GetTicket().IsValid())
{
WARN_LOG(IOS_ES, "ES: loader not valid for %" PRIx64, TitleID);
return 0xffffffff;
@@ -230,8 +201,8 @@ u32 ES::OpenTitleContent(u32 CFD, u64 TitleID, u16 Index)
SContentAccess Access;
Access.m_Position = 0;
Access.m_Index = pContent->m_Index;
Access.m_Size = pContent->m_Size;
Access.m_Index = pContent->m_metadata.index;
Access.m_Size = static_cast<u32>(pContent->m_metadata.size);
Access.m_TitleID = TitleID;
pContent->m_Data->Open();
@@ -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(IOS::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)
IOS::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;
IOS::ES::Content content_info;
if (!m_addtitle_tmd.FindContentById(m_addtitle_content_id, &content_info))
{
return GetDefaultReply(ES_INVALID_TMD);
@@ -499,28 +471,21 @@ IPCCommandResult ES::GetTitleContentsCount(const IOCtlVRequest& request)
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
const DiscIO::CNANDContentLoader& rNANDContent = AccessContentDevice(TitleID);
u16 NumberOfPrivateContent = 0;
s32 return_value = IPC_SUCCESS;
if (rNANDContent.IsValid()) // Not sure if dolphin will ever fail this check
{
NumberOfPrivateContent = rNANDContent.GetNumEntries();
const DiscIO::CNANDContentLoader& nand_content = AccessContentDevice(TitleID);
if (!nand_content.IsValid())
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
if ((u32)(TitleID >> 32) == 0x00010000)
Memory::Write_U32(0, request.io_vectors[0].address);
else
Memory::Write_U32(NumberOfPrivateContent, request.io_vectors[0].address);
}
const u16 num_contents = nand_content.GetTMD().GetNumContents();
if ((u32)(TitleID >> 32) == 0x00010000)
Memory::Write_U32(0, request.io_vectors[0].address);
else
{
return_value = static_cast<s32>(rNANDContent.GetContentSize());
}
Memory::Write_U32(num_contents, request.io_vectors[0].address);
INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLECONTENTSCNT: TitleID: %08x/%08x content count %i",
(u32)(TitleID >> 32), (u32)TitleID,
rNANDContent.IsValid() ? NumberOfPrivateContent : (u32)rNANDContent.GetContentSize());
(u32)(TitleID >> 32), (u32)TitleID, num_contents);
return GetDefaultReply(return_value);
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::GetTitleContents(const IOCtlVRequest& request)
@@ -533,25 +498,17 @@ IPCCommandResult ES::GetTitleContents(const IOCtlVRequest& request)
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
const DiscIO::CNANDContentLoader& rNANDContent = AccessContentDevice(TitleID);
s32 return_value = IPC_SUCCESS;
if (rNANDContent.IsValid()) // Not sure if dolphin will ever fail this check
if (!rNANDContent.IsValid())
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
for (const auto& content : rNANDContent.GetTMD().GetContents())
{
for (u16 i = 0; i < rNANDContent.GetNumEntries(); i++)
{
Memory::Write_U32(rNANDContent.GetContentByIndex(i)->m_ContentID,
request.io_vectors[0].address + i * 4);
INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLECONTENTS: Index %d: %08x", i,
rNANDContent.GetContentByIndex(i)->m_ContentID);
}
}
else
{
return_value = static_cast<s32>(rNANDContent.GetContentSize());
ERROR_LOG(IOS_ES, "IOCTL_ES_GETTITLECONTENTS: Unable to open content %zu",
rNANDContent.GetContentSize());
const u16 index = content.index;
Memory::Write_U32(content.id, request.io_vectors[0].address + index * 4);
INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLECONTENTS: Index %d: %08x", index, content.id);
}
return GetDefaultReply(return_value);
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::OpenTitleContent(const IOCtlVRequest& request)
@@ -611,7 +568,7 @@ IPCCommandResult ES::ReadContent(const IOCtlVRequest& request)
{
const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(rContent.m_TitleID);
// ContentLoader should never be invalid; rContent has been created by it.
if (ContentLoader.IsValid())
if (ContentLoader.IsValid() && ContentLoader.GetTicket().IsValid())
{
const DiscIO::SNANDContent* pContent = ContentLoader.GetContentByIndex(rContent.m_Index);
if (!pContent->m_Data->GetRange(rContent.m_Position, Size, pDest))
@@ -777,46 +734,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,70 +761,40 @@ 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(IOS::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);
}
// TODO: rename this to GetTMDViewSize. There is only one TMD, so the name doesn't make sense.
IPCCommandResult ES::GetTMDViewCount(const IOCtlVRequest& request)
{
_dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 1, "IOCTL_ES_GETTMDVIEWCNT no in buffer");
@@ -899,18 +804,13 @@ IPCCommandResult ES::GetTMDViewCount(const IOCtlVRequest& request)
const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID);
u32 TMDViewCnt = 0;
u32 view_size = 0;
if (Loader.IsValid())
{
TMDViewCnt += DiscIO::CNANDContentLoader::TMD_VIEW_SIZE;
TMDViewCnt += 2; // title version
TMDViewCnt += 2; // num entries
TMDViewCnt += (u32)Loader.GetContentSize() * (4 + 2 + 2 + 8); // content id, index, type, size
}
Memory::Write_U32(TMDViewCnt, request.io_vectors[0].address);
view_size = static_cast<u32>(Loader.GetTMD().GetRawView().size());
Memory::Write_U32(view_size, request.io_vectors[0].address);
INFO_LOG(IOS_ES, "IOCTL_ES_GETTMDVIEWCNT: title: %08x/%08x (view size %i)", (u32)(TitleID >> 32),
(u32)TitleID, TMDViewCnt);
(u32)TitleID, view_size);
return GetDefaultReply(IPC_SUCCESS);
}
@@ -929,30 +829,11 @@ IPCCommandResult ES::GetTMDViews(const IOCtlVRequest& request)
if (Loader.IsValid())
{
u32 Address = request.io_vectors[0].address;
const std::vector<u8> raw_view = Loader.GetTMD().GetRawView();
if (raw_view.size() != request.io_vectors[0].size)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
Memory::CopyToEmu(Address, Loader.GetTMDView(), DiscIO::CNANDContentLoader::TMD_VIEW_SIZE);
Address += DiscIO::CNANDContentLoader::TMD_VIEW_SIZE;
Memory::Write_U16(Loader.GetTitleVersion(), Address);
Address += 2;
Memory::Write_U16(Loader.GetNumEntries(), Address);
Address += 2;
const std::vector<DiscIO::SNANDContent>& rContent = Loader.GetContent();
for (size_t i = 0; i < Loader.GetContentSize(); i++)
{
Memory::Write_U32(rContent[i].m_ContentID, Address);
Address += 4;
Memory::Write_U16(rContent[i].m_Index, Address);
Address += 2;
Memory::Write_U16(rContent[i].m_Type, Address);
Address += 2;
Memory::Write_U64(rContent[i].m_Size, Address);
Address += 8;
}
_dbg_assert_(IOS_ES, (Address - request.io_vectors[0].address) == request.io_vectors[0].size);
Memory::CopyToEmu(request.io_vectors[0].address, raw_view.data(), raw_view.size());
}
INFO_LOG(IOS_ES, "IOCTL_ES_GETTMDVIEWS: title: %08x/%08x (buffer size: %i)", (u32)(TitleID >> 32),
@@ -1004,18 +885,15 @@ IPCCommandResult ES::GetStoredTMDSize(const IOCtlVRequest& request)
const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID);
_dbg_assert_(IOS_ES, Loader.IsValid());
u32 TMDCnt = 0;
u32 tmd_size = 0;
if (Loader.IsValid())
{
TMDCnt += DiscIO::CNANDContentLoader::TMD_HEADER_SIZE;
TMDCnt += (u32)Loader.GetContentSize() * DiscIO::CNANDContentLoader::CONTENT_HEADER_SIZE;
}
tmd_size = static_cast<u32>(Loader.GetTMD().GetRawTMD().size());
if (request.io_vectors.size())
Memory::Write_U32(TMDCnt, request.io_vectors[0].address);
Memory::Write_U32(tmd_size, request.io_vectors[0].address);
INFO_LOG(IOS_ES, "IOCTL_ES_GETSTOREDTMDSIZE: title: %08x/%08x (view size %i)",
(u32)(TitleID >> 32), (u32)TitleID, TMDCnt);
(u32)(TitleID >> 32), (u32)TitleID, tmd_size);
return GetDefaultReply(IPC_SUCCESS);
}
@@ -1043,20 +921,11 @@ IPCCommandResult ES::GetStoredTMD(const IOCtlVRequest& request)
if (Loader.IsValid() && request.io_vectors.size())
{
u32 Address = request.io_vectors[0].address;
const std::vector<u8> raw_tmd = Loader.GetTMD().GetRawTMD();
if (raw_tmd.size() != request.io_vectors[0].size)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
Memory::CopyToEmu(Address, Loader.GetTMDHeader(), DiscIO::CNANDContentLoader::TMD_HEADER_SIZE);
Address += DiscIO::CNANDContentLoader::TMD_HEADER_SIZE;
const std::vector<DiscIO::SNANDContent>& rContent = Loader.GetContent();
for (size_t i = 0; i < Loader.GetContentSize(); i++)
{
Memory::CopyToEmu(Address, rContent[i].m_Header,
DiscIO::CNANDContentLoader::CONTENT_HEADER_SIZE);
Address += DiscIO::CNANDContentLoader::CONTENT_HEADER_SIZE;
}
_dbg_assert_(IOS_ES, (Address - request.io_vectors[0].address) == request.io_vectors[0].size);
Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size());
}
INFO_LOG(IOS_ES, "IOCTL_ES_GETSTOREDTMD: title: %08x/%08x (buffer size: %i)",
@@ -1131,9 +1000,9 @@ IPCCommandResult ES::Launch(const IOCtlVRequest& request)
const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(TitleID);
if (ContentLoader.IsValid())
{
ios_to_load = 0x0000000100000000ULL | ContentLoader.GetIosVersion();
ios_to_load = ContentLoader.GetTMD().GetIOSId();
u32 bootInd = ContentLoader.GetBootIndex();
u32 bootInd = ContentLoader.GetTMD().GetBootIndex();
const DiscIO::SNANDContent* pContent = ContentLoader.GetContentByIndex(bootInd);
if (pContent)
{
@@ -1275,10 +1144,13 @@ const DiscIO::CNANDContentLoader& ES::AccessContentDevice(u64 title_id)
return DiscIO::CNANDContentManager::Access().GetNANDLoader(title_id, Common::FROM_SESSION_ROOT);
}
u32 ES::ES_DIVerify(const std::vector<u8>& tmd)
u32 ES::ES_DIVerify(const IOS::ES::TMDReader& tmd)
{
if (!tmd.IsValid())
return -1;
u64 title_id = 0xDEADBEEFDEADBEEFull;
u64 tmd_title_id = Common::swap64(&tmd[0x18C]);
u64 tmd_title_id = tmd.GetTitleId();
DVDInterface::GetVolume().GetTitleID(&title_id);
if (title_id != tmd_title_id)
@@ -1292,7 +1164,8 @@ u32 ES::ES_DIVerify(const std::vector<u8>& tmd)
if (!File::Exists(tmd_path))
{
File::IOFile tmd_file(tmd_path, "wb");
if (!tmd_file.WriteBytes(tmd.data(), tmd.size()))
const std::vector<u8>& tmd_bytes = tmd.GetRawTMD();
if (!tmd_file.WriteBytes(tmd_bytes.data(), tmd_bytes.size()))
ERROR_LOG(IOS_ES, "DIVerify failed to write disc TMD to NAND.");
}
DiscIO::cUIDsys uid_sys{Common::FromWhichRoot::FROM_SESSION_ROOT};
+2 -2
View File
@@ -46,7 +46,7 @@ public:
void Close() override;
IPCCommandResult IOCtlV(const IOCtlVRequest& request) override;
static u32 ES_DIVerify(const std::vector<u8>& tmd);
static u32 ES_DIVerify(const IOS::ES::TMDReader& tmd);
// This should only be cleared on power reset
static std::string m_ContentFile;
@@ -203,7 +203,7 @@ private:
u32 m_AccessIdentID = 0;
// For title installation (ioctls IOCTL_ES_ADDTITLE*).
TMDReader m_addtitle_tmd;
IOS::ES::TMDReader m_addtitle_tmd;
u32 m_addtitle_content_id = 0xFFFFFFFF;
std::vector<u8> m_addtitle_content_buffer;
};
+165 -13
View File
@@ -5,17 +5,22 @@
#include "Core/IOS/ES/Formats.h"
#include <algorithm>
#include <cstddef>
#include <cstring>
#include <utility>
#include <vector>
#include "Common/ChunkFile.h"
#include "Common/CommonFuncs.h"
#include "Common/CommonTypes.h"
#include "Common/Crypto/AES.h"
namespace IOS
{
namespace HLE
namespace ES
{
constexpr size_t CONTENT_VIEW_SIZE = 0x10;
TMDReader::TMDReader(const std::vector<u8>& bytes) : m_bytes(bytes)
{
}
@@ -36,13 +41,13 @@ void TMDReader::SetBytes(std::vector<u8>&& bytes)
bool TMDReader::IsValid() const
{
if (m_bytes.size() < 0x1E4)
if (m_bytes.size() < sizeof(TMDHeader))
{
// TMD is too small to contain its base fields.
return false;
}
if (m_bytes.size() < 0x1E4 + GetNumContents() * 36u)
if (m_bytes.size() < sizeof(TMDHeader) + GetNumContents() * sizeof(Content))
{
// TMD is too small to contain all its expected content entries.
return false;
@@ -51,19 +56,69 @@ bool TMDReader::IsValid() const
return true;
}
const std::vector<u8>& TMDReader::GetRawTMD() const
{
return m_bytes;
}
std::vector<u8> TMDReader::GetRawHeader() const
{
return std::vector<u8>(m_bytes.begin(), m_bytes.begin() + sizeof(TMDHeader));
}
std::vector<u8> TMDReader::GetRawView() const
{
// Base fields
std::vector<u8> view(m_bytes.cbegin() + offsetof(TMDHeader, tmd_version),
m_bytes.cbegin() + offsetof(TMDHeader, access_rights));
const auto version = m_bytes.cbegin() + offsetof(TMDHeader, title_version);
view.insert(view.end(), version, version + sizeof(TMDHeader::title_version));
const auto num_contents = m_bytes.cbegin() + offsetof(TMDHeader, num_contents);
view.insert(view.end(), num_contents, num_contents + sizeof(TMDHeader::num_contents));
// Content views (same as Content, but without the hash)
for (size_t i = 0; i < GetNumContents(); ++i)
{
const auto content_iterator = m_bytes.cbegin() + sizeof(TMDHeader) + i * sizeof(Content);
view.insert(view.end(), content_iterator, content_iterator + CONTENT_VIEW_SIZE);
}
return view;
}
u16 TMDReader::GetBootIndex() const
{
return Common::swap16(m_bytes.data() + offsetof(TMDHeader, boot_index));
}
u64 TMDReader::GetIOSId() const
{
return Common::swap64(m_bytes.data() + 0x184);
return Common::swap64(m_bytes.data() + offsetof(TMDHeader, ios_id));
}
DiscIO::Region TMDReader::GetRegion() const
{
if (GetTitleId() == 0x0000000100000002)
return DiscIO::RegionSwitchWii(DiscIO::GetSysMenuRegion(GetTitleVersion()));
return DiscIO::RegionSwitchWii(static_cast<u8>(GetTitleId() & 0xff));
}
u64 TMDReader::GetTitleId() const
{
return Common::swap64(m_bytes.data() + 0x18C);
return Common::swap64(m_bytes.data() + offsetof(TMDHeader, title_id));
}
u16 TMDReader::GetTitleVersion() const
{
return Common::swap16(m_bytes.data() + offsetof(TMDHeader, title_version));
}
u16 TMDReader::GetNumContents() const
{
return Common::swap16(m_bytes.data() + 0x1DE);
return Common::swap16(m_bytes.data() + offsetof(TMDHeader, num_contents));
}
bool TMDReader::GetContent(u16 index, Content* content) const
@@ -73,16 +128,24 @@ bool TMDReader::GetContent(u16 index, Content* content) const
return false;
}
const u8* content_base = m_bytes.data() + 0x1E4 + index * 36;
content->id = Common::swap32(content_base);
content->index = Common::swap16(content_base + 4);
content->type = Common::swap16(content_base + 6);
content->size = Common::swap64(content_base + 8);
std::copy(content_base + 16, content_base + 36, content->sha1.begin());
const u8* content_base = m_bytes.data() + sizeof(TMDHeader) + index * sizeof(Content);
content->id = Common::swap32(content_base + offsetof(Content, id));
content->index = Common::swap16(content_base + offsetof(Content, index));
content->type = Common::swap16(content_base + offsetof(Content, type));
content->size = Common::swap64(content_base + offsetof(Content, size));
std::copy_n(content_base + offsetof(Content, sha1), content->sha1.size(), content->sha1.begin());
return true;
}
std::vector<Content> TMDReader::GetContents() const
{
std::vector<Content> contents(GetNumContents());
for (size_t i = 0; i < contents.size(); ++i)
GetContent(static_cast<u16>(i), &contents[i]);
return contents;
}
bool TMDReader::FindContentById(u32 id, Content* content) const
{
for (u16 index = 0; index < GetNumContents(); ++index)
@@ -103,5 +166,94 @@ void TMDReader::DoState(PointerWrap& p)
{
p.Do(m_bytes);
}
} // namespace HLE
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 Common::AES::Decrypt(common_key, iv, &m_bytes[GetOffset() + offsetof(Ticket, title_key)],
16);
}
} // namespace ES
} // namespace IOS
+124 -10
View File
@@ -12,11 +12,95 @@
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "DiscIO/Enums.h"
namespace IOS
{
namespace HLE
namespace ES
{
#pragma pack(push, 4)
struct TMDHeader
{
u32 signature_type;
u8 rsa_2048_signature[256];
u8 fill[60];
u8 issuer[64];
u8 tmd_version;
u8 ca_crl_version;
u8 signer_crl_version;
u64 ios_id;
u64 title_id;
u32 title_type;
u16 group_id;
u16 zero;
u16 region;
u8 ratings[16];
u8 reserved[12];
u8 ipc_mask[12];
u8 reserved2[18];
u32 access_rights;
u16 title_version;
u16 num_contents;
u16 boot_index;
u16 fill2;
};
static_assert(sizeof(TMDHeader) == 0x1e4, "TMDHeader has the wrong size");
struct Content
{
u32 id;
u16 index;
u16 type;
u64 size;
std::array<u8, 20> sha1;
};
static_assert(sizeof(Content) == 36, "Content has the wrong size");
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 TMDReader final
{
public:
@@ -29,19 +113,20 @@ public:
bool IsValid() const;
// Returns the TMD or parts of it without any kind of parsing. Intended for use by ES.
const std::vector<u8>& GetRawTMD() const;
std::vector<u8> GetRawHeader() const;
std::vector<u8> GetRawView() const;
u16 GetBootIndex() const;
u64 GetIOSId() const;
DiscIO::Region GetRegion() const;
u64 GetTitleId() const;
u16 GetTitleVersion() const;
struct Content
{
u32 id;
u16 index;
u16 type;
u64 size;
std::array<u8, 20> sha1;
};
u16 GetNumContents() const;
bool GetContent(u16 index, Content* content) const;
std::vector<Content> GetContents() const;
bool FindContentById(u32 id, Content* content) const;
void DoState(PointerWrap& p);
@@ -49,5 +134,34 @@ public:
private:
std::vector<u8> m_bytes;
};
} // namespace HLE
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
} // namespace IOS
+1 -1
View File
@@ -658,7 +658,7 @@ void SetDefaultContentFile(const std::string& file_name)
es->LoadWAD(file_name);
}
void ES_DIVerify(const std::vector<u8>& tmd)
void ES_DIVerify(const ES::TMDReader& tmd)
{
Device::ES::ES_DIVerify(tmd);
}
+6 -1
View File
@@ -16,6 +16,11 @@ class PointerWrap;
namespace IOS
{
namespace ES
{
class TMDReader;
}
namespace HLE
{
namespace Device
@@ -61,7 +66,7 @@ void DoState(PointerWrap& p);
// Set default content file
void SetDefaultContentFile(const std::string& file_name);
void ES_DIVerify(const std::vector<u8>& tmd);
void ES_DIVerify(const ES::TMDReader& tmd);
void SDIO_EventNotify();
+2 -1
View File
@@ -23,6 +23,7 @@
#include "Core/HW/DVDThread.h"
#include "Core/HW/Memmap.h"
#include "Core/HW/SystemTimers.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/IOS/MIOS.h"
#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/PowerPC/PowerPC.h"
@@ -97,7 +98,7 @@ static std::vector<u8> GetMIOSBinary()
if (!loader.IsValid())
return {};
const auto* content = loader.GetContentByIndex(loader.GetBootIndex());
const auto* content = loader.GetContentByIndex(loader.GetTMD().GetBootIndex());
if (!content)
return {};
+4 -4
View File
@@ -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::Content content_info;
if (!m_tmd.FindContentById(content_id, &content_info))
{
WARN_LOG(IOS, "%s: Content id %08x not found", ioctl_name, content_id);
+1 -1
View File
@@ -50,7 +50,7 @@ private:
u8 m_aes_key[0x10] = {};
u8 m_aes_iv[0x10] = {};
TMDReader m_tmd;
IOS::ES::TMDReader m_tmd;
std::string m_base_extract_path;
ARCUnpacker m_arc_unpacker;
+71 -165
View File
@@ -4,12 +4,12 @@
#include <algorithm>
#include <array>
#include <cinttypes>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <functional>
#include <map>
#include <mbedtls/aes.h>
#include <string>
#include <utility>
#include <vector>
@@ -17,6 +17,7 @@
#include "Common/Align.h"
#include "Common/CommonFuncs.h"
#include "Common/CommonTypes.h"
#include "Common/Crypto/AES.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
@@ -29,50 +30,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)
@@ -191,7 +148,6 @@ bool CNANDContentDataBuffer::GetRange(u32 start, u32 size, u8* buffer)
}
CNANDContentLoader::CNANDContentLoader(const std::string& content_name)
: m_Valid(false), m_IsWAD(false), m_TitleID(-1), m_IosVersion(0x09), m_BootIndex(-1)
{
m_Valid = Initialize(content_name);
}
@@ -200,11 +156,16 @@ CNANDContentLoader::~CNANDContentLoader()
{
}
bool CNANDContentLoader::IsValid() const
{
return m_Valid && m_tmd.IsValid();
}
const SNANDContent* CNANDContentLoader::GetContentByIndex(int index) const
{
for (auto& Content : m_Content)
{
if (Content.m_Index == index)
if (Content.m_metadata.index == index)
{
return &Content;
}
@@ -221,15 +182,12 @@ 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);
tmd = wad.GetTMD();
m_ticket = wad.GetTicket();
m_tmd = wad.GetTMD();
data_app = wad.GetDataApp();
}
else
@@ -248,92 +206,64 @@ bool CNANDContentLoader::Initialize(const std::string& name)
return false;
}
tmd.resize(static_cast<size_t>(File::GetSize(tmd_filename)));
tmd_file.ReadBytes(tmd.data(), tmd.size());
std::vector<u8> bytes(File::GetSize(tmd_filename));
tmd_file.ReadBytes(bytes.data(), bytes.size());
m_tmd.SetBytes(std::move(bytes));
m_ticket = FindSignedTicket(m_tmd.GetTitleId());
}
std::copy(&tmd[0], &tmd[TMD_HEADER_SIZE], m_TMDHeader);
std::copy(&tmd[0x180], &tmd[0x180 + TMD_VIEW_SIZE], m_TMDView);
m_TitleVersion = Common::swap16(&tmd[0x01DC]);
m_NumEntries = Common::swap16(&tmd[0x01DE]);
m_BootIndex = Common::swap16(&tmd[0x01E0]);
m_TitleID = Common::swap64(&tmd[0x018C]);
m_IosVersion = Common::swap16(&tmd[0x018A]);
m_Country = static_cast<u8>(m_TitleID & 0xFF);
if (m_Country == 2) // SYSMENU
m_Country = GetSysMenuRegion(m_TitleVersion);
InitializeContentEntries(tmd, decrypted_title_key, data_app);
InitializeContentEntries(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)
void CNANDContentLoader::InitializeContentEntries(const std::vector<u8>& data_app)
{
m_Content.resize(m_NumEntries);
if (!m_ticket.IsValid())
{
ERROR_LOG(IOS_ES, "No valid ticket for title %016" PRIx64, m_tmd.GetTitleId());
return;
}
const std::vector<IOS::ES::Content> contents = m_tmd.GetContents();
m_Content.resize(contents.size());
std::array<u8, 16> iv;
u32 data_app_offset = 0;
const std::vector<u8> title_key = m_ticket.GetTitleKey();
CSharedContent shared_content{Common::FromWhichRoot::FROM_SESSION_ROOT};
for (u32 i = 0; i < m_NumEntries; i++)
for (size_t i = 0; i < contents.size(); ++i)
{
const u32 entry_offset = 0x24 * i;
SNANDContent& content = m_Content[i];
content.m_ContentID = Common::swap32(&tmd[entry_offset + 0x01E4]);
content.m_Index = Common::swap16(&tmd[entry_offset + 0x01E8]);
content.m_Type = Common::swap16(&tmd[entry_offset + 0x01EA]);
content.m_Size = static_cast<u32>(Common::swap64(&tmd[entry_offset + 0x01EC]));
const auto header_begin = std::next(tmd.begin(), entry_offset + 0x01E4);
const auto header_end = std::next(header_begin, ArraySize(content.m_Header));
std::copy(header_begin, header_end, content.m_Header);
const auto hash_begin = std::next(tmd.begin(), entry_offset + 0x01F4);
const auto hash_end = std::next(hash_begin, ArraySize(content.m_SHA1Hash));
std::copy(hash_begin, hash_end, content.m_SHA1Hash);
const auto& content = contents.at(i);
if (m_IsWAD)
{
u32 rounded_size = Common::AlignUp(content.m_Size, 0x40);
// The content index is used as IV (2 bytes); the remaining 14 bytes are zeroes.
std::array<u8, 16> iv{};
iv[0] = static_cast<u8>(content.index >> 8) & 0xFF;
iv[1] = static_cast<u8>(content.index) & 0xFF;
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));
u32 rounded_size = Common::AlignUp(static_cast<u32>(content.size), 0x40);
m_Content[i].m_Data = std::make_unique<CNANDContentDataBuffer>(Common::AES::Decrypt(
title_key.data(), iv.data(), &data_app[data_app_offset], rounded_size));
data_app_offset += rounded_size;
continue;
}
else
{
std::string filename;
if (content.type & 0x8000) // shared app
filename = shared_content.GetFilenameFromSHA1(content.sha1.data());
else
filename = StringFromFormat("%s/%08x.app", m_Path.c_str(), content.id);
m_Content[i].m_Data = std::make_unique<CNANDContentDataFile>(filename);
}
std::string filename;
if (content.m_Type & 0x8000) // shared app
filename = shared_content.GetFilenameFromSHA1(content.m_SHA1Hash);
else
filename = StringFromFormat("%s/%08x.app", m_Path.c_str(), content.m_ContentID);
content.m_Data = std::make_unique<CNANDContentDataFile>(filename);
// Be graceful about incorrect TMDs.
if (File::Exists(filename))
content.m_Size = static_cast<u32>(File::GetSize(filename));
m_Content[i].m_metadata = std::move(content);
}
}
DiscIO::Region CNANDContentLoader::GetRegion() const
{
if (!IsValid())
return DiscIO::Region::UNKNOWN_REGION;
return RegionSwitchWii(m_Country);
}
CNANDContentManager::~CNANDContentManager()
{
}
@@ -372,18 +302,18 @@ void CNANDContentManager::ClearCache()
void CNANDContentLoader::RemoveTitle() const
{
INFO_LOG(DISCIO, "RemoveTitle %08x/%08x", (u32)(m_TitleID >> 32), (u32)m_TitleID);
const u64 title_id = m_tmd.GetTitleId();
INFO_LOG(DISCIO, "RemoveTitle %08x/%08x", (u32)(title_id >> 32), (u32)title_id);
if (IsValid())
{
// remove TMD?
for (u32 i = 0; i < m_NumEntries; i++)
for (const auto& content : m_Content)
{
if (!(m_Content[i].m_Type & 0x8000)) // skip shared apps
if (!(content.m_metadata.type & 0x8000)) // skip shared apps
{
std::string filename =
StringFromFormat("%s/%08x.app", m_Path.c_str(), m_Content[i].m_ContentID);
INFO_LOG(DISCIO, "Delete %s", filename.c_str());
File::Delete(filename);
std::string path = StringFromFormat("%s/%08x.app", m_Path.c_str(), content.m_metadata.id);
INFO_LOG(DISCIO, "Delete %s", path.c_str());
File::Delete(path);
}
}
CNANDContentManager::Access().ClearCache(); // deletes 'this'
@@ -470,7 +400,7 @@ u64 CNANDContentManager::Install_WiiWAD(const std::string& filename)
if (content_loader.IsValid() == false)
return 0;
u64 title_id = content_loader.GetTitleID();
const u64 title_id = content_loader.GetTMD().GetTitleId();
// copy WAD's TMD header and contents to content directory
@@ -485,20 +415,17 @@ u64 CNANDContentManager::Install_WiiWAD(const std::string& filename)
return 0;
}
tmd_file.WriteBytes(content_loader.GetTMDHeader(), CNANDContentLoader::TMD_HEADER_SIZE);
const auto& raw_tmd = content_loader.GetTMD().GetRawTMD();
tmd_file.WriteBytes(raw_tmd.data(), raw_tmd.size());
CSharedContent shared_content{Common::FromWhichRoot::FROM_CONFIGURED_ROOT};
for (u32 i = 0; i < content_loader.GetContentSize(); i++)
for (const auto& content : content_loader.GetContent())
{
const SNANDContent& content = content_loader.GetContent()[i];
tmd_file.WriteBytes(content.m_Header, CNANDContentLoader::CONTENT_HEADER_SIZE);
std::string app_filename;
if (content.m_Type & 0x8000) // shared
app_filename = shared_content.AddSharedContent(content.m_SHA1Hash);
if (content.m_metadata.type & 0x8000) // shared
app_filename = shared_content.AddSharedContent(content.m_metadata.sha1.data());
else
app_filename = StringFromFormat("%s%08x.app", content_path.c_str(), content.m_ContentID);
app_filename = StringFromFormat("%s%08x.app", content_path.c_str(), content.m_metadata.id);
if (!File::Exists(app_filename))
{
@@ -510,7 +437,7 @@ u64 CNANDContentManager::Install_WiiWAD(const std::string& filename)
return 0;
}
app_file.WriteBytes(content.m_Data->Get().data(), content.m_Size);
app_file.WriteBytes(content.m_Data->Get().data(), content.m_metadata.size);
}
else
{
@@ -533,14 +460,14 @@ u64 CNANDContentManager::Install_WiiWAD(const std::string& filename)
return title_id;
}
bool AddTicket(const std::vector<u8>& signed_ticket)
bool AddTicket(const IOS::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 +476,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)
IOS::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 IOS::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 IOS::ES::TicketReader{};
}
return signed_ticket;
return IOS::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

Some files were not shown because too many files have changed in this diff Show More