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
commit 48aeb5bf4b
31 changed files with 581 additions and 532 deletions

View File

@ -33,6 +33,7 @@ set(SRCS Analytics.cpp
x64ABI.cpp x64ABI.cpp
x64Emitter.cpp x64Emitter.cpp
MD5.cpp MD5.cpp
Crypto/AES.cpp
Crypto/bn.cpp Crypto/bn.cpp
Crypto/ec.cpp Crypto/ec.cpp
Logging/LogManager.cpp) Logging/LogManager.cpp)

View File

@ -143,6 +143,7 @@
<ClInclude Include="x64ABI.h" /> <ClInclude Include="x64ABI.h" />
<ClInclude Include="x64Emitter.h" /> <ClInclude Include="x64Emitter.h" />
<ClInclude Include="x64Reg.h" /> <ClInclude Include="x64Reg.h" />
<ClInclude Include="Crypto\AES.h" />
<ClInclude Include="Crypto\bn.h" /> <ClInclude Include="Crypto\bn.h" />
<ClInclude Include="Crypto\ec.h" /> <ClInclude Include="Crypto\ec.h" />
<ClInclude Include="Logging\ConsoleListener.h" /> <ClInclude Include="Logging\ConsoleListener.h" />
@ -195,6 +196,7 @@
<ClCompile Include="x64CPUDetect.cpp" /> <ClCompile Include="x64CPUDetect.cpp" />
<ClCompile Include="x64Emitter.cpp" /> <ClCompile Include="x64Emitter.cpp" />
<ClCompile Include="x64FPURoundMode.cpp" /> <ClCompile Include="x64FPURoundMode.cpp" />
<ClCompile Include="Crypto\AES.cpp" />
<ClCompile Include="Crypto\bn.cpp" /> <ClCompile Include="Crypto\bn.cpp" />
<ClCompile Include="Crypto\ec.cpp" /> <ClCompile Include="Crypto\ec.cpp" />
<ClCompile Include="Logging\LogManager.cpp" /> <ClCompile Include="Logging\LogManager.cpp" />

View File

@ -79,6 +79,9 @@
<ClInclude Include="Logging\LogManager.h"> <ClInclude Include="Logging\LogManager.h">
<Filter>Logging</Filter> <Filter>Logging</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="Crypto\AES.h">
<Filter>Crypto</Filter>
</ClInclude>
<ClInclude Include="Crypto\ec.h"> <ClInclude Include="Crypto\ec.h">
<Filter>Crypto</Filter> <Filter>Crypto</Filter>
</ClInclude> </ClInclude>
@ -266,6 +269,9 @@
<ClCompile Include="x64CPUDetect.cpp" /> <ClCompile Include="x64CPUDetect.cpp" />
<ClCompile Include="x64Emitter.cpp" /> <ClCompile Include="x64Emitter.cpp" />
<ClCompile Include="x64FPURoundMode.cpp" /> <ClCompile Include="x64FPURoundMode.cpp" />
<ClCompile Include="Crypto\AES.cpp">
<Filter>Crypto</Filter>
</ClCompile>
<ClCompile Include="Crypto\bn.cpp"> <ClCompile Include="Crypto\bn.cpp">
<Filter>Crypto</Filter> <Filter>Crypto</Filter>
</ClCompile> </ClCompile>

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

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

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); DiscIO::CNANDContentManager::Access().GetNANDLoader(_StartupPara.m_strFilename);
if (Loader.IsValid()) if (Loader.IsValid())
{ {
u64 TitleID = Loader.GetTitleID(); u64 TitleID = Loader.GetTMD().GetTitleId();
title_id_str = StringFromFormat("%08X_%08X", (u32)(TitleID >> 32) & 0xFFFFFFFF, title_id_str = StringFromFormat("%08X_%08X", (u32)(TitleID >> 32) & 0xFFFFFFFF,
(u32)TitleID & 0xFFFFFFFF); (u32)TitleID & 0xFFFFFFFF);
} }
@ -278,11 +278,7 @@ bool CBoot::BootUp()
PanicAlertT("Warning - starting ISO in wrong console mode!"); PanicAlertT("Warning - starting ISO in wrong console mode!");
} }
std::vector<u8> tmd_buffer = pVolume.GetTMD(); IOS::HLE::ES_DIVerify(pVolume.GetTMD());
if (!tmd_buffer.empty())
{
IOS::HLE::ES_DIVerify(tmd_buffer);
}
_StartupPara.bWii = pVolume.GetVolumeType() == DiscIO::Platform::WII_DISC; _StartupPara.bWii = pVolume.GetVolumeType() == DiscIO::Platform::WII_DISC;

View File

@ -346,11 +346,9 @@ bool CBoot::EmulatedBS2_Wii()
PowerPC::ppcState.gpr[1] = 0x816ffff0; // StackPointer 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.GetIOSId()))
if (!SetupWiiMemory(tmd_reader.GetIOSId()))
return false; return false;
// Execute the apploader // Execute the apploader

View File

@ -78,7 +78,7 @@ bool CBoot::Boot_WiiWAD(const std::string& _pFilename)
if (!ContentLoader.IsValid()) if (!ContentLoader.IsValid())
return false; return false;
u64 titleID = ContentLoader.GetTitleID(); u64 titleID = ContentLoader.GetTMD().GetTitleId();
// create data directory // create data directory
File::CreateFullPath(Common::GetTitleDataPath(titleID, Common::FROM_SESSION_ROOT)); File::CreateFullPath(Common::GetTitleDataPath(titleID, Common::FROM_SESSION_ROOT));
@ -86,12 +86,11 @@ bool CBoot::Boot_WiiWAD(const std::string& _pFilename)
IOS::HLE::CreateVirtualFATFilesystem(); IOS::HLE::CreateVirtualFATFilesystem();
// setup Wii memory // setup Wii memory
u64 ios_title_id = 0x0000000100000000ULL | ContentLoader.GetIosVersion(); if (!SetupWiiMemory(ContentLoader.GetTMD().GetIOSId()))
if (!SetupWiiMemory(ios_title_id))
return false; return false;
// DOL // DOL
const DiscIO::SNANDContent* pContent = const DiscIO::SNANDContent* pContent =
ContentLoader.GetContentByIndex(ContentLoader.GetBootIndex()); ContentLoader.GetContentByIndex(ContentLoader.GetTMD().GetBootIndex());
if (pContent == nullptr) if (pContent == nullptr)
return false; return false;

View File

@ -922,7 +922,7 @@ bool SConfig::AutoSetup(EBootBS2 _BootBS2)
const DiscIO::CNANDContentLoader& ContentLoader = const DiscIO::CNANDContentLoader& ContentLoader =
DiscIO::CNANDContentManager::Access().GetNANDLoader(m_strFilename); 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. // WAD is valid yet cannot be booted. Install instead.
u64 installed = DiscIO::CNANDContentManager::Access().Install_WiiWAD(m_strFilename); u64 installed = DiscIO::CNANDContentManager::Access().Install_WiiWAD(m_strFilename);
@ -931,7 +931,7 @@ bool SConfig::AutoSetup(EBootBS2 _BootBS2)
return false; // do not boot return false; // do not boot
} }
SetRegion(ContentLoader.GetRegion(), &set_region_dir); SetRegion(ContentLoader.GetTMD().GetRegion(), &set_region_dir);
bWii = true; bWii = true;
m_BootType = BOOT_WII_NAND; m_BootType = BOOT_WII_NAND;

View File

@ -14,6 +14,7 @@
#include "Core/HW/DVDInterface.h" #include "Core/HW/DVDInterface.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/IOS/DI/DI.h" #include "Core/IOS/DI/DI.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/IOS/IPC.h" #include "Core/IOS/IPC.h"
#include "DiscIO/Volume.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); INFO_LOG(IOS_DI, "DVDLowOpenPartition: partition_offset 0x%016" PRIx64, partition_offset);
// Read TMD to the buffer // Read TMD to the buffer
std::vector<u8> tmd_buffer = DVDInterface::GetVolume().GetTMD(); const ES::TMDReader tmd = DVDInterface::GetVolume().GetTMD();
Memory::CopyToEmu(request.io_vectors[0].address, tmd_buffer.data(), tmd_buffer.size()); const std::vector<u8> raw_tmd = tmd.GetRawTMD();
ES_DIVerify(tmd_buffer); Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size());
ES_DIVerify(tmd);
return_value = 1; return_value = 1;
break; break;

View File

@ -2,35 +2,6 @@
// Licensed under GPLv2+ // Licensed under GPLv2+
// Refer to the license.txt file included. // 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 <algorithm>
#include <cinttypes> #include <cinttypes>
#include <cstdio> #include <cstdio>
@ -115,7 +86,7 @@ void ES::OpenInternal()
// check for cd ... // check for cd ...
if (contentLoader.IsValid()) if (contentLoader.IsValid())
{ {
m_TitleID = contentLoader.GetTitleID(); m_TitleID = contentLoader.GetTMD().GetTitleId();
m_TitleIDs.clear(); m_TitleIDs.clear();
DiscIO::cUIDsys uid_sys{Common::FromWhichRoot::FROM_SESSION_ROOT}; 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); 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); WARN_LOG(IOS_ES, "ES: loader not valid for %" PRIx64, TitleID);
return 0xffffffff; return 0xffffffff;
@ -230,8 +201,8 @@ u32 ES::OpenTitleContent(u32 CFD, u64 TitleID, u16 Index)
SContentAccess Access; SContentAccess Access;
Access.m_Position = 0; Access.m_Position = 0;
Access.m_Index = pContent->m_Index; Access.m_Index = pContent->m_metadata.index;
Access.m_Size = pContent->m_Size; Access.m_Size = static_cast<u32>(pContent->m_metadata.size);
Access.m_TitleID = TitleID; Access.m_TitleID = TitleID;
pContent->m_Data->Open(); pContent->m_Data->Open();
@ -347,7 +318,8 @@ IPCCommandResult ES::AddTicket(const IOCtlVRequest& request)
INFO_LOG(IOS_ES, "IOCTL_ES_ADDTICKET"); INFO_LOG(IOS_ES, "IOCTL_ES_ADDTICKET");
std::vector<u8> ticket(request.in_vectors[0].size); std::vector<u8> ticket(request.in_vectors[0].size);
Memory::CopyFromEmu(ticket.data(), request.in_vectors[0].address, 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); 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); INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTFINISH: content fd %08x", content_fd);
// Try to find the title key from a pre-installed ticket. // Try to find the title key from a pre-installed ticket.
std::vector<u8> ticket = DiscIO::FindSignedTicket(m_addtitle_tmd.GetTitleId()); IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(m_addtitle_tmd.GetTitleId());
if (ticket.size() == 0) if (!ticket.IsValid())
{ {
return GetDefaultReply(ES_NO_TICKET_INSTALLED); return GetDefaultReply(ES_NO_TICKET_INSTALLED);
} }
mbedtls_aes_context aes_ctx; 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 // The IV for title content decryption is the lower two bytes of the
// content index, zero extended. // content index, zero extended.
TMDReader::Content content_info; IOS::ES::Content content_info;
if (!m_addtitle_tmd.FindContentById(m_addtitle_content_id, &content_info)) if (!m_addtitle_tmd.FindContentById(m_addtitle_content_id, &content_info))
{ {
return GetDefaultReply(ES_INVALID_TMD); 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); u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
const DiscIO::CNANDContentLoader& rNANDContent = AccessContentDevice(TitleID); const DiscIO::CNANDContentLoader& nand_content = AccessContentDevice(TitleID);
u16 NumberOfPrivateContent = 0; if (!nand_content.IsValid())
s32 return_value = IPC_SUCCESS; return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
if (rNANDContent.IsValid()) // Not sure if dolphin will ever fail this check
{ const u16 num_contents = nand_content.GetTMD().GetNumContents();
NumberOfPrivateContent = rNANDContent.GetNumEntries();
if ((u32)(TitleID >> 32) == 0x00010000) if ((u32)(TitleID >> 32) == 0x00010000)
Memory::Write_U32(0, request.io_vectors[0].address); Memory::Write_U32(0, request.io_vectors[0].address);
else else
Memory::Write_U32(NumberOfPrivateContent, request.io_vectors[0].address); Memory::Write_U32(num_contents, request.io_vectors[0].address);
}
else
{
return_value = static_cast<s32>(rNANDContent.GetContentSize());
}
INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLECONTENTSCNT: TitleID: %08x/%08x content count %i", INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLECONTENTSCNT: TitleID: %08x/%08x content count %i",
(u32)(TitleID >> 32), (u32)TitleID, (u32)(TitleID >> 32), (u32)TitleID, num_contents);
rNANDContent.IsValid() ? NumberOfPrivateContent : (u32)rNANDContent.GetContentSize());
return GetDefaultReply(return_value); return GetDefaultReply(IPC_SUCCESS);
} }
IPCCommandResult ES::GetTitleContents(const IOCtlVRequest& request) 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); u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
const DiscIO::CNANDContentLoader& rNANDContent = AccessContentDevice(TitleID); const DiscIO::CNANDContentLoader& rNANDContent = AccessContentDevice(TitleID);
s32 return_value = IPC_SUCCESS; if (!rNANDContent.IsValid())
if (rNANDContent.IsValid()) // Not sure if dolphin will ever fail this check return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
for (const auto& content : rNANDContent.GetTMD().GetContents())
{ {
for (u16 i = 0; i < rNANDContent.GetNumEntries(); i++) const u16 index = content.index;
{ Memory::Write_U32(content.id, request.io_vectors[0].address + index * 4);
Memory::Write_U32(rNANDContent.GetContentByIndex(i)->m_ContentID, INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLECONTENTS: Index %d: %08x", index, content.id);
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());
} }
return GetDefaultReply(return_value); return GetDefaultReply(IPC_SUCCESS);
} }
IPCCommandResult ES::OpenTitleContent(const IOCtlVRequest& request) IPCCommandResult ES::OpenTitleContent(const IOCtlVRequest& request)
@ -611,7 +568,7 @@ IPCCommandResult ES::ReadContent(const IOCtlVRequest& request)
{ {
const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(rContent.m_TitleID); const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(rContent.m_TitleID);
// ContentLoader should never be invalid; rContent has been created by it. // 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); const DiscIO::SNANDContent* pContent = ContentLoader.GetContentByIndex(rContent.m_Index);
if (!pContent->m_Data->GetRange(rContent.m_Position, Size, pDest)) 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); u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
u32 retVal = 0;
const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); 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. // Fake a ticket view to make IOS reload work.
ViewCount = 1; view_count = 1;
} }
else else if (Loader.IsValid() && Loader.GetTicket().IsValid())
{ {
ViewCount = 0; view_count = Loader.GetTicket().GetNumberOfTickets();
if (TitleID == TITLEID_SYSMENU)
{
PanicAlertT("There must be a ticket for 00000001/00000002. Your NAND dump is probably "
"incomplete.");
}
// retVal = ES_NO_TICKET_INSTALLED;
}
} }
INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWCNT for titleID: %08x/%08x (View Count = %i)", INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWCNT for titleID: %08x/%08x (View Count = %zu)",
(u32)(TitleID >> 32), (u32)TitleID, ViewCount); static_cast<u32>(TitleID >> 32), static_cast<u32>(TitleID), view_count);
Memory::Write_U32(ViewCount, request.io_vectors[0].address); Memory::Write_U32(static_cast<u32>(view_count), request.io_vectors[0].address);
return GetDefaultReply(retVal); return GetDefaultReply(IPC_SUCCESS);
} }
IPCCommandResult ES::GetViews(const IOCtlVRequest& request) IPCCommandResult ES::GetViews(const IOCtlVRequest& request)
@ -826,33 +761,10 @@ IPCCommandResult ES::GetViews(const IOCtlVRequest& request)
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
u32 maxViews = Memory::Read_U32(request.in_vectors[1].address); u32 maxViews = Memory::Read_U32(request.in_vectors[1].address);
u32 retVal = 0;
const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID); const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID);
const std::vector<u8>& ticket = Loader.GetTicket(); if (TitleID >> 32 == 0x00000001 && TitleID != TITLEID_SYSMENU)
if (ticket.empty())
{
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 // For IOS titles, the ticket view isn't normally parsed by either the
// SDK or libogc, just passed to LaunchTitle, so this // SDK or libogc, just passed to LaunchTitle, so this
@ -865,31 +777,24 @@ IPCCommandResult ES::GetViews(const IOCtlVRequest& request)
Memory::Write_U32(0xff00, Address + 4 + (0x1ec - 0x1d0)); // access mask Memory::Write_U32(0xff00, Address + 4 + (0x1ec - 0x1d0)); // access mask
Memory::Memset(Address + 4 + (0x222 - 0x1d0), 0xff, 0x20); // content permissions Memory::Memset(Address + 4 + (0x222 - 0x1d0), 0xff, 0x20); // content permissions
} }
else else if (Loader.IsValid() && Loader.GetTicket().IsValid())
{ {
// retVal = ES_NO_TICKET_INSTALLED; u32 number_of_views = std::min(maxViews, Loader.GetTicket().GetNumberOfTickets());
PanicAlertT("IOCTL_ES_GETVIEWS: Tried to get data from an unknown ticket: %08x/%08x", for (u32 view = 0; view < number_of_views; ++view)
(u32)(TitleID >> 32), (u32)TitleID);
}
}
else
{ {
u32 view_count = const std::vector<u8> ticket_view = Loader.GetTicket().GetRawTicketView(view);
static_cast<u32>(Loader.GetTicket().size()) / DiscIO::CNANDContentLoader::TICKET_SIZE; Memory::CopyToEmu(request.io_vectors[0].address + view * sizeof(IOS::ES::TicketView),
for (unsigned int view = 0; view != maxViews && view < view_count; ++view) ticket_view.data(), ticket_view.size());
{
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);
} }
} }
INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWS for titleID: %08x/%08x (MaxViews = %i)", (u32)(TitleID >> 32), INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWS for titleID: %08x/%08x (MaxViews = %i)", (u32)(TitleID >> 32),
(u32)TitleID, maxViews); (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) IPCCommandResult ES::GetTMDViewCount(const IOCtlVRequest& request)
{ {
_dbg_assert_msg_(IOS_ES, request.in_vectors.size() == 1, "IOCTL_ES_GETTMDVIEWCNT no in buffer"); _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); const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID);
u32 TMDViewCnt = 0; u32 view_size = 0;
if (Loader.IsValid()) if (Loader.IsValid())
{ view_size = static_cast<u32>(Loader.GetTMD().GetRawView().size());
TMDViewCnt += DiscIO::CNANDContentLoader::TMD_VIEW_SIZE; Memory::Write_U32(view_size, request.io_vectors[0].address);
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);
INFO_LOG(IOS_ES, "IOCTL_ES_GETTMDVIEWCNT: title: %08x/%08x (view size %i)", (u32)(TitleID >> 32), 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); return GetDefaultReply(IPC_SUCCESS);
} }
@ -929,30 +829,11 @@ IPCCommandResult ES::GetTMDViews(const IOCtlVRequest& request)
if (Loader.IsValid()) 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); Memory::CopyToEmu(request.io_vectors[0].address, raw_view.data(), raw_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);
} }
INFO_LOG(IOS_ES, "IOCTL_ES_GETTMDVIEWS: title: %08x/%08x (buffer size: %i)", (u32)(TitleID >> 32), 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); const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID);
_dbg_assert_(IOS_ES, Loader.IsValid()); _dbg_assert_(IOS_ES, Loader.IsValid());
u32 TMDCnt = 0; u32 tmd_size = 0;
if (Loader.IsValid()) if (Loader.IsValid())
{ tmd_size = static_cast<u32>(Loader.GetTMD().GetRawTMD().size());
TMDCnt += DiscIO::CNANDContentLoader::TMD_HEADER_SIZE;
TMDCnt += (u32)Loader.GetContentSize() * DiscIO::CNANDContentLoader::CONTENT_HEADER_SIZE;
}
if (request.io_vectors.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)", 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); return GetDefaultReply(IPC_SUCCESS);
} }
@ -1043,20 +921,11 @@ IPCCommandResult ES::GetStoredTMD(const IOCtlVRequest& request)
if (Loader.IsValid() && request.io_vectors.size()) 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); Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.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);
} }
INFO_LOG(IOS_ES, "IOCTL_ES_GETSTOREDTMD: title: %08x/%08x (buffer size: %i)", 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); const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(TitleID);
if (ContentLoader.IsValid()) 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); const DiscIO::SNANDContent* pContent = ContentLoader.GetContentByIndex(bootInd);
if (pContent) 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); 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 title_id = 0xDEADBEEFDEADBEEFull;
u64 tmd_title_id = Common::swap64(&tmd[0x18C]); u64 tmd_title_id = tmd.GetTitleId();
DVDInterface::GetVolume().GetTitleID(&title_id); DVDInterface::GetVolume().GetTitleID(&title_id);
if (title_id != tmd_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)) if (!File::Exists(tmd_path))
{ {
File::IOFile tmd_file(tmd_path, "wb"); 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."); ERROR_LOG(IOS_ES, "DIVerify failed to write disc TMD to NAND.");
} }
DiscIO::cUIDsys uid_sys{Common::FromWhichRoot::FROM_SESSION_ROOT}; DiscIO::cUIDsys uid_sys{Common::FromWhichRoot::FROM_SESSION_ROOT};

View File

@ -46,7 +46,7 @@ public:
void Close() override; void Close() override;
IPCCommandResult IOCtlV(const IOCtlVRequest& request) 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 // This should only be cleared on power reset
static std::string m_ContentFile; static std::string m_ContentFile;
@ -203,7 +203,7 @@ private:
u32 m_AccessIdentID = 0; u32 m_AccessIdentID = 0;
// For title installation (ioctls IOCTL_ES_ADDTITLE*). // For title installation (ioctls IOCTL_ES_ADDTITLE*).
TMDReader m_addtitle_tmd; IOS::ES::TMDReader m_addtitle_tmd;
u32 m_addtitle_content_id = 0xFFFFFFFF; u32 m_addtitle_content_id = 0xFFFFFFFF;
std::vector<u8> m_addtitle_content_buffer; std::vector<u8> m_addtitle_content_buffer;
}; };

View File

@ -5,17 +5,22 @@
#include "Core/IOS/ES/Formats.h" #include "Core/IOS/ES/Formats.h"
#include <algorithm> #include <algorithm>
#include <cstddef>
#include <cstring>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "Common/ChunkFile.h" #include "Common/ChunkFile.h"
#include "Common/CommonFuncs.h" #include "Common/CommonFuncs.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Crypto/AES.h"
namespace IOS namespace IOS
{ {
namespace HLE namespace ES
{ {
constexpr size_t CONTENT_VIEW_SIZE = 0x10;
TMDReader::TMDReader(const std::vector<u8>& bytes) : m_bytes(bytes) 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 bool TMDReader::IsValid() const
{ {
if (m_bytes.size() < 0x1E4) if (m_bytes.size() < sizeof(TMDHeader))
{ {
// TMD is too small to contain its base fields. // TMD is too small to contain its base fields.
return false; 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. // TMD is too small to contain all its expected content entries.
return false; return false;
@ -51,19 +56,69 @@ bool TMDReader::IsValid() const
return true; 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 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 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 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 bool TMDReader::GetContent(u16 index, Content* content) const
@ -73,16 +128,24 @@ bool TMDReader::GetContent(u16 index, Content* content) const
return false; return false;
} }
const u8* content_base = m_bytes.data() + 0x1E4 + index * 36; const u8* content_base = m_bytes.data() + sizeof(TMDHeader) + index * sizeof(Content);
content->id = Common::swap32(content_base); content->id = Common::swap32(content_base + offsetof(Content, id));
content->index = Common::swap16(content_base + 4); content->index = Common::swap16(content_base + offsetof(Content, index));
content->type = Common::swap16(content_base + 6); content->type = Common::swap16(content_base + offsetof(Content, type));
content->size = Common::swap64(content_base + 8); content->size = Common::swap64(content_base + offsetof(Content, size));
std::copy(content_base + 16, content_base + 36, content->sha1.begin()); std::copy_n(content_base + offsetof(Content, sha1), content->sha1.size(), content->sha1.begin());
return true; 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 bool TMDReader::FindContentById(u32 id, Content* content) const
{ {
for (u16 index = 0; index < GetNumContents(); ++index) for (u16 index = 0; index < GetNumContents(); ++index)
@ -103,5 +166,94 @@ void TMDReader::DoState(PointerWrap& p)
{ {
p.Do(m_bytes); 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 } // namespace IOS

View File

@ -12,11 +12,95 @@
#include "Common/ChunkFile.h" #include "Common/ChunkFile.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "DiscIO/Enums.h"
namespace IOS 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 class TMDReader final
{ {
public: public:
@ -29,19 +113,20 @@ public:
bool IsValid() const; bool IsValid() const;
u64 GetIOSId() const; // Returns the TMD or parts of it without any kind of parsing. Intended for use by ES.
u64 GetTitleId() const; 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; u16 GetNumContents() const;
bool GetContent(u16 index, Content* content) const; bool GetContent(u16 index, Content* content) const;
std::vector<Content> GetContents() const;
bool FindContentById(u32 id, Content* content) const; bool FindContentById(u32 id, Content* content) const;
void DoState(PointerWrap& p); void DoState(PointerWrap& p);
@ -49,5 +134,34 @@ public:
private: private:
std::vector<u8> m_bytes; 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 } // namespace IOS

View File

@ -658,7 +658,7 @@ void SetDefaultContentFile(const std::string& file_name)
es->LoadWAD(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); Device::ES::ES_DIVerify(tmd);
} }

View File

@ -16,6 +16,11 @@ class PointerWrap;
namespace IOS namespace IOS
{ {
namespace ES
{
class TMDReader;
}
namespace HLE namespace HLE
{ {
namespace Device namespace Device
@ -61,7 +66,7 @@ void DoState(PointerWrap& p);
// Set default content file // Set default content file
void SetDefaultContentFile(const std::string& file_name); 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(); void SDIO_EventNotify();

View File

@ -23,6 +23,7 @@
#include "Core/HW/DVDThread.h" #include "Core/HW/DVDThread.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/HW/SystemTimers.h" #include "Core/HW/SystemTimers.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/IOS/MIOS.h" #include "Core/IOS/MIOS.h"
#include "Core/PowerPC/PPCSymbolDB.h" #include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/PowerPC/PowerPC.h" #include "Core/PowerPC/PowerPC.h"
@ -97,7 +98,7 @@ static std::vector<u8> GetMIOSBinary()
if (!loader.IsValid()) if (!loader.IsValid())
return {}; return {};
const auto* content = loader.GetContentByIndex(loader.GetBootIndex()); const auto* content = loader.GetContentByIndex(loader.GetTMD().GetBootIndex());
if (!content) if (!content)
return {}; return {};

View File

@ -109,14 +109,14 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request)
Memory::CopyFromEmu(tmd_bytes.data(), tmd_addr, tmd_size); Memory::CopyFromEmu(tmd_bytes.data(), tmd_addr, tmd_size);
m_tmd.SetBytes(std::move(tmd_bytes)); m_tmd.SetBytes(std::move(tmd_bytes));
std::vector<u8> ticket = DiscIO::FindSignedTicket(m_tmd.GetTitleId()); ES::TicketReader ticket = DiscIO::FindSignedTicket(m_tmd.GetTitleId());
if (ticket.size() == 0) if (!ticket.IsValid())
{ {
return_error_code = -11028; return_error_code = -11028;
break; 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); mbedtls_aes_setkey_dec(&m_aes_ctx, m_aes_key, 128);
break; break;
@ -134,7 +134,7 @@ IPCCommandResult WFSI::IOCtl(const IOCtlRequest& request)
// Initializes the IV from the index of the content in the TMD contents. // Initializes the IV from the index of the content in the TMD contents.
u32 content_id = Memory::Read_U32(request.buffer_in + 8); 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)) if (!m_tmd.FindContentById(content_id, &content_info))
{ {
WARN_LOG(IOS, "%s: Content id %08x not found", ioctl_name, content_id); WARN_LOG(IOS, "%s: Content id %08x not found", ioctl_name, content_id);

View File

@ -50,7 +50,7 @@ private:
u8 m_aes_key[0x10] = {}; u8 m_aes_key[0x10] = {};
u8 m_aes_iv[0x10] = {}; u8 m_aes_iv[0x10] = {};
TMDReader m_tmd; IOS::ES::TMDReader m_tmd;
std::string m_base_extract_path; std::string m_base_extract_path;
ARCUnpacker m_arc_unpacker; ARCUnpacker m_arc_unpacker;

View File

@ -4,12 +4,12 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cinttypes>
#include <cstddef> #include <cstddef>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <functional> #include <functional>
#include <map> #include <map>
#include <mbedtls/aes.h>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -17,6 +17,7 @@
#include "Common/Align.h" #include "Common/Align.h"
#include "Common/CommonFuncs.h" #include "Common/CommonFuncs.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Crypto/AES.h"
#include "Common/FileUtil.h" #include "Common/FileUtil.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/MsgHandler.h" #include "Common/MsgHandler.h"
@ -29,50 +30,6 @@
namespace DiscIO 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; CNANDContentData::~CNANDContentData() = default;
CSharedContent::CSharedContent(Common::FromWhichRoot root) : m_root(root) 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) 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); 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 const SNANDContent* CNANDContentLoader::GetContentByIndex(int index) const
{ {
for (auto& Content : m_Content) for (auto& Content : m_Content)
{ {
if (Content.m_Index == index) if (Content.m_metadata.index == index)
{ {
return &Content; return &Content;
} }
@ -221,15 +182,12 @@ bool CNANDContentLoader::Initialize(const std::string& name)
WiiWAD wad(name); WiiWAD wad(name);
std::vector<u8> data_app; std::vector<u8> data_app;
std::vector<u8> tmd;
std::vector<u8> decrypted_title_key;
if (wad.IsValid()) if (wad.IsValid())
{ {
m_IsWAD = true; m_IsWAD = true;
m_Ticket = wad.GetTicket(); m_ticket = wad.GetTicket();
decrypted_title_key = GetKeyFromTicket(m_Ticket); m_tmd = wad.GetTMD();
tmd = wad.GetTMD();
data_app = wad.GetDataApp(); data_app = wad.GetDataApp();
} }
else else
@ -248,90 +206,62 @@ bool CNANDContentLoader::Initialize(const std::string& name)
return false; return false;
} }
tmd.resize(static_cast<size_t>(File::GetSize(tmd_filename))); std::vector<u8> bytes(File::GetSize(tmd_filename));
tmd_file.ReadBytes(tmd.data(), tmd.size()); 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); InitializeContentEntries(data_app);
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);
return true; return true;
} }
void CNANDContentLoader::InitializeContentEntries(const std::vector<u8>& tmd, void CNANDContentLoader::InitializeContentEntries(const std::vector<u8>& data_app)
const std::vector<u8>& decrypted_title_key,
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; u32 data_app_offset = 0;
const std::vector<u8> title_key = m_ticket.GetTitleKey();
CSharedContent shared_content{Common::FromWhichRoot::FROM_SESSION_ROOT}; 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; const auto& content = contents.at(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);
if (m_IsWAD) 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); u32 rounded_size = Common::AlignUp(static_cast<u32>(content.size), 0x40);
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));
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; data_app_offset += rounded_size;
continue;
} }
std::string filename;
if (content.m_Type & 0x8000) // shared app
filename = shared_content.GetFilenameFromSHA1(content.m_SHA1Hash);
else else
filename = StringFromFormat("%s/%08x.app", m_Path.c_str(), content.m_ContentID); {
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);
content.m_Data = std::make_unique<CNANDContentDataFile>(filename); m_Content[i].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));
} }
}
DiscIO::Region CNANDContentLoader::GetRegion() const m_Content[i].m_metadata = std::move(content);
{ }
if (!IsValid())
return DiscIO::Region::UNKNOWN_REGION;
return RegionSwitchWii(m_Country);
} }
CNANDContentManager::~CNANDContentManager() CNANDContentManager::~CNANDContentManager()
@ -372,18 +302,18 @@ void CNANDContentManager::ClearCache()
void CNANDContentLoader::RemoveTitle() const 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()) if (IsValid())
{ {
// remove TMD? // 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 = std::string path = StringFromFormat("%s/%08x.app", m_Path.c_str(), content.m_metadata.id);
StringFromFormat("%s/%08x.app", m_Path.c_str(), m_Content[i].m_ContentID); INFO_LOG(DISCIO, "Delete %s", path.c_str());
INFO_LOG(DISCIO, "Delete %s", filename.c_str()); File::Delete(path);
File::Delete(filename);
} }
} }
CNANDContentManager::Access().ClearCache(); // deletes 'this' CNANDContentManager::Access().ClearCache(); // deletes 'this'
@ -470,7 +400,7 @@ u64 CNANDContentManager::Install_WiiWAD(const std::string& filename)
if (content_loader.IsValid() == false) if (content_loader.IsValid() == false)
return 0; 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 // copy WAD's TMD header and contents to content directory
@ -485,20 +415,17 @@ u64 CNANDContentManager::Install_WiiWAD(const std::string& filename)
return 0; 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}; 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; std::string app_filename;
if (content.m_Type & 0x8000) // shared if (content.m_metadata.type & 0x8000) // shared
app_filename = shared_content.AddSharedContent(content.m_SHA1Hash); app_filename = shared_content.AddSharedContent(content.m_metadata.sha1.data());
else 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)) if (!File::Exists(app_filename))
{ {
@ -510,7 +437,7 @@ u64 CNANDContentManager::Install_WiiWAD(const std::string& filename)
return 0; 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 else
{ {
@ -533,14 +460,14 @@ u64 CNANDContentManager::Install_WiiWAD(const std::string& filename)
return title_id; 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 (!signed_ticket.IsValid())
if (ticket.empty())
{ {
return false; 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); std::string ticket_filename = Common::GetTicketFileName(title_id, Common::FROM_CONFIGURED_ROOT);
File::CreateFullPath(ticket_filename); File::CreateFullPath(ticket_filename);
@ -549,46 +476,25 @@ bool AddTicket(const std::vector<u8>& signed_ticket)
if (!ticket_file) if (!ticket_file)
return false; 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); std::string ticket_filename = Common::GetTicketFileName(title_id, Common::FROM_CONFIGURED_ROOT);
File::IOFile ticket_file(ticket_filename, "rb"); File::IOFile ticket_file(ticket_filename, "rb");
if (!ticket_file) if (!ticket_file)
{ {
return std::vector<u8>(); return IOS::ES::TicketReader{};
} }
std::vector<u8> signed_ticket(ticket_file.GetSize()); std::vector<u8> signed_ticket(ticket_file.GetSize());
if (!ticket_file.ReadBytes(signed_ticket.data(), signed_ticket.size())) 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 } // namespace end

View File

@ -12,6 +12,7 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/NandPaths.h" #include "Common/NandPaths.h"
#include "Core/IOS/ES/Formats.h"
namespace File namespace File
{ {
@ -22,10 +23,8 @@ namespace DiscIO
{ {
enum class Region; enum class Region;
bool AddTicket(const std::vector<u8>& signed_ticket); bool AddTicket(const IOS::ES::TicketReader& signed_ticket);
std::vector<u8> FindSignedTicket(u64 title_id); IOS::ES::TicketReader FindSignedTicket(u64 title_id);
std::vector<u8> FindTicket(u64 title_id);
std::vector<u8> GetKeyFromTicket(const std::vector<u8>& ticket);
class CNANDContentData class CNANDContentData
{ {
@ -67,13 +66,7 @@ private:
struct SNANDContent struct SNANDContent
{ {
u32 m_ContentID; IOS::ES::Content m_metadata;
u16 m_Index;
u16 m_Type;
u32 m_Size;
u8 m_SHA1Hash[20];
u8 m_Header[36]; // all of the above
std::unique_ptr<CNANDContentData> m_Data; std::unique_ptr<CNANDContentData> m_Data;
}; };
@ -84,47 +77,21 @@ public:
explicit CNANDContentLoader(const std::string& content_name); explicit CNANDContentLoader(const std::string& content_name);
~CNANDContentLoader(); ~CNANDContentLoader();
bool IsValid() const { return m_Valid; } bool IsValid() const;
void RemoveTitle() const; void RemoveTitle() const;
u64 GetTitleID() const { return m_TitleID; }
u16 GetIosVersion() const { return m_IosVersion; }
u32 GetBootIndex() const { return m_BootIndex; }
size_t GetContentSize() const { return m_Content.size(); }
const SNANDContent* GetContentByIndex(int index) const; const SNANDContent* GetContentByIndex(int index) const;
const u8* GetTMDView() const { return m_TMDView; } const IOS::ES::TMDReader& GetTMD() const { return m_tmd; }
const u8* GetTMDHeader() const { return m_TMDHeader; } const IOS::ES::TicketReader& GetTicket() const { return m_ticket; }
const std::vector<u8>& GetTicket() const { return m_Ticket; }
const std::vector<SNANDContent>& GetContent() const { return m_Content; } const std::vector<SNANDContent>& GetContent() const { return m_Content; }
u16 GetTitleVersion() const { return m_TitleVersion; }
u16 GetNumEntries() const { return m_NumEntries; }
DiscIO::Region GetRegion() const;
u8 GetCountryChar() const { return m_Country; }
enum
{
TMD_VIEW_SIZE = 0x58,
TMD_HEADER_SIZE = 0x1E4,
CONTENT_HEADER_SIZE = 0x24,
TICKET_SIZE = 0x2A4
};
private: private:
bool Initialize(const std::string& name); bool Initialize(const std::string& name);
void InitializeContentEntries(const std::vector<u8>& tmd, void InitializeContentEntries(const std::vector<u8>& data_app);
const std::vector<u8>& decrypted_title_key,
const std::vector<u8>& data_app);
bool m_Valid; bool m_Valid = false;
bool m_IsWAD; bool m_IsWAD = false;
std::string m_Path; std::string m_Path;
u64 m_TitleID; IOS::ES::TMDReader m_tmd;
u16 m_IosVersion; IOS::ES::TicketReader m_ticket;
u32 m_BootIndex;
u16 m_NumEntries;
u16 m_TitleVersion;
u8 m_TMDView[TMD_VIEW_SIZE];
u8 m_TMDHeader[TMD_HEADER_SIZE];
std::vector<u8> m_Ticket;
u8 m_Country;
std::vector<SNANDContent> m_Content; std::vector<SNANDContent> m_Content;
}; };

View File

@ -12,6 +12,7 @@
#include "Common/CommonFuncs.h" #include "Common/CommonFuncs.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
#include "Core/IOS/ES/Formats.h"
#include "DiscIO/Enums.h" #include "DiscIO/Enums.h"
namespace DiscIO namespace DiscIO
@ -36,7 +37,7 @@ public:
} }
virtual bool GetTitleID(u64*) const { return false; } virtual bool GetTitleID(u64*) const { return false; }
virtual std::vector<u8> GetTMD() const { return {}; } virtual IOS::ES::TMDReader GetTMD() const { return {}; }
virtual u64 PartitionOffsetToRawOffset(u64 offset) const { return offset; } virtual u64 PartitionOffsetToRawOffset(u64 offset) const { return offset; }
virtual std::string GetGameID() const = 0; virtual std::string GetGameID() const = 0;
virtual std::string GetMakerID() const = 0; virtual std::string GetMakerID() const = 0;

View File

@ -36,6 +36,16 @@ CVolumeWAD::CVolumeWAD(std::unique_ptr<IBlobReader> reader) : m_reader(std::move
Common::AlignUp(m_tick_size, 0x40); Common::AlignUp(m_tick_size, 0x40);
m_opening_bnr_offset = m_opening_bnr_offset =
m_tmd_offset + Common::AlignUp(m_tmd_size, 0x40) + Common::AlignUp(m_data_size, 0x40); m_tmd_offset + Common::AlignUp(m_tmd_size, 0x40) + Common::AlignUp(m_data_size, 0x40);
if (m_tmd_size > 1024 * 1024 * 4)
{
ERROR_LOG(DISCIO, "TMD is too large: %u bytes", m_tmd_size);
return;
}
std::vector<u8> tmd_buffer(m_tmd_size);
Read(m_tmd_offset, m_tmd_size, tmd_buffer.data(), false);
m_tmd.SetBytes(std::move(tmd_buffer));
} }
CVolumeWAD::~CVolumeWAD() CVolumeWAD::~CVolumeWAD()
@ -55,40 +65,26 @@ bool CVolumeWAD::Read(u64 offset, u64 length, u8* buffer, bool decrypt) const
Region CVolumeWAD::GetRegion() const Region CVolumeWAD::GetRegion() const
{ {
u8 country_code; if (!m_tmd.IsValid())
if (!Read(m_tmd_offset + 0x0193, 1, &country_code))
return Region::UNKNOWN_REGION; return Region::UNKNOWN_REGION;
return m_tmd.GetRegion();
return RegionSwitchWii(country_code);
} }
Country CVolumeWAD::GetCountry() const Country CVolumeWAD::GetCountry() const
{ {
// read the last digit of the titleID in the ticket if (!m_tmd.IsValid())
u8 country_code;
if (!Read(m_tmd_offset + 0x0193, 1, &country_code))
return Country::COUNTRY_UNKNOWN; return Country::COUNTRY_UNKNOWN;
u8 country_code = static_cast<u8>(m_tmd.GetTitleId() & 0xff);
if (country_code == 2) // SYSMENU if (country_code == 2) // SYSMENU
{ country_code = GetSysMenuRegion(m_tmd.GetTitleVersion());
u16 title_version = 0;
Read(m_tmd_offset + 0x01dc, 2, (u8*)&title_version);
country_code = GetSysMenuRegion(Common::swap16(title_version));
}
return CountrySwitch(country_code); return CountrySwitch(country_code);
} }
std::vector<u8> CVolumeWAD::GetTMD() const IOS::ES::TMDReader CVolumeWAD::GetTMD() const
{ {
if (m_tmd_size > 1024 * 1024 * 4) return m_tmd;
{
ERROR_LOG(DISCIO, "TMD is too large: %u bytes", m_tmd_size);
return {};
}
std::vector<u8> buffer(m_tmd_size);
Read(m_tmd_offset, m_tmd_size, buffer.data(), false);
return buffer;
} }
std::string CVolumeWAD::GetGameID() const std::string CVolumeWAD::GetGameID() const
@ -125,11 +121,10 @@ bool CVolumeWAD::GetTitleID(u64* buffer) const
u16 CVolumeWAD::GetRevision() const u16 CVolumeWAD::GetRevision() const
{ {
u16 revision; if (!m_tmd.IsValid())
if (!m_reader->Read(m_tmd_offset + 0x1dc, 2, (u8*)&revision))
return 0; return 0;
return Common::swap16(revision); return m_tmd.GetTitleVersion();
} }
Platform CVolumeWAD::GetVolumeType() const Platform CVolumeWAD::GetVolumeType() const

View File

@ -10,6 +10,7 @@
#include <vector> #include <vector>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/IOS/ES/Formats.h"
#include "DiscIO/Volume.h" #include "DiscIO/Volume.h"
// --- this volume type is used for Wad files --- // --- this volume type is used for Wad files ---
@ -32,7 +33,7 @@ public:
~CVolumeWAD(); ~CVolumeWAD();
bool Read(u64 offset, u64 length, u8* buffer, bool decrypt = false) const override; bool Read(u64 offset, u64 length, u8* buffer, bool decrypt = false) const override;
bool GetTitleID(u64* buffer) const override; bool GetTitleID(u64* buffer) const override;
std::vector<u8> GetTMD() const override; IOS::ES::TMDReader GetTMD() const override;
std::string GetGameID() const override; std::string GetGameID() const override;
std::string GetMakerID() const override; std::string GetMakerID() const override;
u16 GetRevision() const override; u16 GetRevision() const override;
@ -51,6 +52,7 @@ public:
private: private:
std::unique_ptr<IBlobReader> m_reader; std::unique_ptr<IBlobReader> m_reader;
IOS::ES::TMDReader m_tmd;
u32 m_offset = 0; u32 m_offset = 0;
u32 m_tmd_offset = 0; u32 m_tmd_offset = 0;
u32 m_opening_bnr_offset = 0; u32 m_opening_bnr_offset = 0;

View File

@ -114,7 +114,7 @@ bool CVolumeWiiCrypted::GetTitleID(u64* buffer) const
return true; return true;
} }
std::vector<u8> CVolumeWiiCrypted::GetTMD() const IOS::ES::TMDReader CVolumeWiiCrypted::GetTMD() const
{ {
u32 tmd_size; u32 tmd_size;
u32 tmd_address; u32 tmd_address;
@ -137,7 +137,7 @@ std::vector<u8> CVolumeWiiCrypted::GetTMD() const
std::vector<u8> buffer(tmd_size); std::vector<u8> buffer(tmd_size);
Read(m_VolumeOffset + tmd_address, tmd_size, buffer.data(), false); Read(m_VolumeOffset + tmd_address, tmd_size, buffer.data(), false);
return buffer; return IOS::ES::TMDReader{std::move(buffer)};
} }
u64 CVolumeWiiCrypted::PartitionOffsetToRawOffset(u64 offset) const u64 CVolumeWiiCrypted::PartitionOffsetToRawOffset(u64 offset) const

View File

@ -11,6 +11,7 @@
#include <vector> #include <vector>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/IOS/ES/Formats.h"
#include "DiscIO/Volume.h" #include "DiscIO/Volume.h"
// --- this volume type is used for encrypted Wii images --- // --- this volume type is used for encrypted Wii images ---
@ -32,7 +33,7 @@ public:
~CVolumeWiiCrypted(); ~CVolumeWiiCrypted();
bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const override; bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const override;
bool GetTitleID(u64* buffer) const override; bool GetTitleID(u64* buffer) const override;
std::vector<u8> GetTMD() const override; IOS::ES::TMDReader GetTMD() const override;
u64 PartitionOffsetToRawOffset(u64 offset) const override; u64 PartitionOffsetToRawOffset(u64 offset) const override;
std::string GetGameID() const override; std::string GetGameID() const override;
std::string GetMakerID() const override; std::string GetMakerID() const override;

View File

@ -88,9 +88,9 @@ bool WiiWAD::ParseWAD(IBlobReader& reader)
u32 offset = 0x40; u32 offset = 0x40;
m_certificate_chain = CreateWADEntry(reader, certificate_chain_size, offset); m_certificate_chain = CreateWADEntry(reader, certificate_chain_size, offset);
offset += Common::AlignUp(certificate_chain_size, 0x40); 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); offset += Common::AlignUp(ticket_size, 0x40);
m_tmd = CreateWADEntry(reader, tmd_size, offset); m_tmd.SetBytes(CreateWADEntry(reader, tmd_size, offset));
offset += Common::AlignUp(tmd_size, 0x40); offset += Common::AlignUp(tmd_size, 0x40);
m_data_app = CreateWADEntry(reader, data_app_size, offset); m_data_app = CreateWADEntry(reader, data_app_size, offset);
offset += Common::AlignUp(data_app_size, 0x40); offset += Common::AlignUp(data_app_size, 0x40);

View File

@ -8,6 +8,7 @@
#include <vector> #include <vector>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/IOS/ES/Formats.h"
namespace DiscIO namespace DiscIO
{ {
@ -22,8 +23,8 @@ public:
bool IsValid() const { return m_valid; } bool IsValid() const { return m_valid; }
const std::vector<u8>& GetCertificateChain() const { return m_certificate_chain; } const std::vector<u8>& GetCertificateChain() const { return m_certificate_chain; }
const std::vector<u8>& GetTicket() const { return m_ticket; } const IOS::ES::TicketReader& GetTicket() const { return m_ticket; }
const std::vector<u8>& GetTMD() const { return m_tmd; } const IOS::ES::TMDReader& GetTMD() const { return m_tmd; }
const std::vector<u8>& GetDataApp() const { return m_data_app; } const std::vector<u8>& GetDataApp() const { return m_data_app; }
const std::vector<u8>& GetFooter() const { return m_footer; } const std::vector<u8>& GetFooter() const { return m_footer; }
private: private:
@ -32,8 +33,8 @@ private:
bool m_valid; bool m_valid;
std::vector<u8> m_certificate_chain; std::vector<u8> m_certificate_chain;
std::vector<u8> m_ticket; IOS::ES::TicketReader m_ticket;
std::vector<u8> m_tmd; IOS::ES::TMDReader m_tmd;
std::vector<u8> m_data_app; std::vector<u8> m_data_app;
std::vector<u8> m_footer; std::vector<u8> m_footer;
}; };

View File

@ -1225,23 +1225,7 @@ void CFrame::OnInstallWAD(wxCommandEvent& event)
void CFrame::UpdateLoadWiiMenuItem() const void CFrame::UpdateLoadWiiMenuItem() const
{ {
auto* const menu_item = GetMenuBar()->FindItem(IDM_LOAD_WII_MENU); GetMenuBar()->Refresh(true, nullptr);
const auto& sys_menu_loader = DiscIO::CNANDContentManager::Access().GetNANDLoader(
TITLEID_SYSMENU, Common::FROM_CONFIGURED_ROOT);
if (sys_menu_loader.IsValid())
{
const int version = sys_menu_loader.GetTitleVersion();
const char region = sys_menu_loader.GetCountryChar();
menu_item->Enable();
menu_item->SetItemLabel(wxString::Format(_("Load Wii System Menu %d%c"), version, region));
}
else
{
menu_item->Enable(false);
menu_item->SetItemLabel(_("Load Wii System Menu"));
}
} }
void CFrame::OnFifoPlayer(wxCommandEvent& WXUNUSED(event)) void CFrame::OnFifoPlayer(wxCommandEvent& WXUNUSED(event))

View File

@ -182,7 +182,7 @@ void InfoPanel::LoadISODetails()
m_fst->SetValue(StrToWxStr(std::to_string(m_opened_iso->GetFSTSize()))); m_fst->SetValue(StrToWxStr(std::to_string(m_opened_iso->GetFSTSize())));
if (m_ios_version) if (m_ios_version)
{ {
IOS::HLE::TMDReader tmd{m_opened_iso->GetTMD()}; const IOS::ES::TMDReader tmd = m_opened_iso->GetTMD();
if (tmd.IsValid()) if (tmd.IsValid())
m_ios_version->SetValue(StringFromFormat("IOS%u", static_cast<u32>(tmd.GetIOSId()))); m_ios_version->SetValue(StringFromFormat("IOS%u", static_cast<u32>(tmd.GetIOSId())));
} }
@ -223,7 +223,7 @@ wxStaticBoxSizer* InfoPanel::CreateISODetailsSizer()
{_("Apploader Date:"), m_date}, {_("Apploader Date:"), m_date},
{_("FST Size:"), m_fst}, {_("FST Size:"), m_fst},
}}; }};
if (!m_opened_iso->GetTMD().empty()) if (m_opened_iso->GetTMD().IsValid())
controls.emplace_back(_("IOS Version:"), m_ios_version); controls.emplace_back(_("IOS Version:"), m_ios_version);
const int space_10 = FromDIP(10); const int space_10 = FromDIP(10);

View File

@ -12,6 +12,7 @@
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/PowerPC/PowerPC.h" #include "Core/PowerPC/PowerPC.h"
#include "Core/State.h" #include "Core/State.h"
#include "DiscIO/Enums.h"
#include "DiscIO/NANDContentLoader.h" #include "DiscIO/NANDContentLoader.h"
#include "DolphinWX/Globals.h" #include "DolphinWX/Globals.h"
#include "DolphinWX/WxUtils.h" #include "DolphinWX/WxUtils.h"
@ -546,8 +547,8 @@ void MainMenuBar::RefreshWiiSystemMenuLabel() const
if (sys_menu_loader.IsValid()) if (sys_menu_loader.IsValid())
{ {
const auto sys_menu_version = sys_menu_loader.GetTitleVersion(); const u16 sys_menu_version = sys_menu_loader.GetTMD().GetTitleVersion();
const auto sys_menu_region = sys_menu_loader.GetCountryChar(); const char sys_menu_region = DiscIO::GetSysMenuRegion(sys_menu_version);
item->Enable(); item->Enable();
item->SetItemLabel( item->SetItemLabel(
wxString::Format(_("Load Wii System Menu %u%c"), sys_menu_version, sys_menu_region)); wxString::Format(_("Load Wii System Menu %u%c"), sys_menu_version, sys_menu_region));