IOS/ES: Split the ES code

ES.cpp was becoming pretty huge. This commit splits the ES code into
several files:

* Main ES (launch, UID, current title directory and title ID, etc.)
* Device identity and encryption (ID and cert, keys, encrypt/decrypt)
* Title management (imports, exports, deletions)
* Title contents (open/close/read/seek)
* Title information (titles, stored contents, TMDs)
* Views (for tickets and TMDs)
This commit is contained in:
Léo Lam
2017-03-13 20:01:08 +01:00
parent 8035270aa8
commit 204703ae0d
10 changed files with 1631 additions and 1473 deletions
+5
View File
@@ -149,6 +149,11 @@ set(SRCS
IOS/DI/DI.cpp
IOS/ES/ES.cpp
IOS/ES/Formats.cpp
IOS/ES/Identity.cpp
IOS/ES/TitleContents.cpp
IOS/ES/TitleInformation.cpp
IOS/ES/TitleManagement.cpp
IOS/ES/Views.cpp
IOS/FS/FileIO.cpp
IOS/FS/FS.cpp
IOS/Network/ICMPLin.cpp
+5
View File
@@ -176,6 +176,11 @@
<ClCompile Include="IOS\DI\DI.cpp" />
<ClCompile Include="IOS\ES\ES.cpp" />
<ClCompile Include="IOS\ES\Formats.cpp" />
<ClCompile Include="IOS\ES\Identity.cpp" />
<ClCompile Include="IOS\ES\TitleContents.cpp" />
<ClCompile Include="IOS\ES\TitleInformation.cpp" />
<ClCompile Include="IOS\ES\TitleManagement.cpp" />
<ClCompile Include="IOS\ES\Views.cpp" />
<ClCompile Include="IOS\FS\FileIO.cpp" />
<ClCompile Include="IOS\FS\FS.cpp" />
<ClCompile Include="IOS\Network\ICMPLin.cpp" />
+15
View File
@@ -756,6 +756,21 @@
<ClCompile Include="IOS\ES\Formats.cpp">
<Filter>IOS\ES</Filter>
</ClCompile>
<ClCompile Include="IOS\ES\Identity.cpp">
<Filter>IOS\ES</Filter>
</ClCompile>
<ClCompile Include="IOS\ES\TitleContents.cpp">
<Filter>IOS\ES</Filter>
</ClCompile>
<ClCompile Include="IOS\ES\TitleInformation.cpp">
<Filter>IOS\ES</Filter>
</ClCompile>
<ClCompile Include="IOS\ES\TitleManagement.cpp">
<Filter>IOS\ES</Filter>
</ClCompile>
<ClCompile Include="IOS\ES\Views.cpp">
<Filter>IOS\ES</Filter>
</ClCompile>
<ClCompile Include="IOS\FS\FS.cpp">
<Filter>IOS\FS</Filter>
</ClCompile>
File diff suppressed because it is too large Load Diff
+50 -35
View File
@@ -6,7 +6,6 @@
#include <array>
#include <map>
#include <memory>
#include <string>
#include <vector>
@@ -20,7 +19,6 @@ class PointerWrap;
namespace DiscIO
{
class CNANDContentLoader;
struct SNANDContent;
}
namespace IOS
@@ -29,7 +27,20 @@ namespace HLE
{
namespace Device
{
class ES : public Device
struct TitleContext
{
void Clear();
void DoState(PointerWrap& p);
void Update(const DiscIO::CNANDContentLoader& content_loader);
void Update(const IOS::ES::TMDReader& tmd_, const IOS::ES::TicketReader& ticket_);
IOS::ES::TicketReader ticket;
IOS::ES::TMDReader tmd;
bool active = false;
bool first_change = true;
};
class ES final : public Device
{
public:
ES(u32 device_id, const std::string& device_name);
@@ -150,6 +161,7 @@ private:
u8 padding[0x3c];
};
// Title management
IPCCommandResult AddTicket(const IOCtlVRequest& request);
IPCCommandResult AddTMD(const IOCtlVRequest& request);
IPCCommandResult AddTitleStart(const IOCtlVRequest& request);
@@ -158,23 +170,46 @@ private:
IPCCommandResult AddContentFinish(const IOCtlVRequest& request);
IPCCommandResult AddTitleFinish(const IOCtlVRequest& request);
IPCCommandResult AddTitleCancel(const IOCtlVRequest& request);
IPCCommandResult ESGetDeviceID(const IOCtlVRequest& request);
IPCCommandResult ExportTitleInit(const IOCtlVRequest& request);
IPCCommandResult ExportContentBegin(const IOCtlVRequest& request);
IPCCommandResult ExportContentData(const IOCtlVRequest& request);
IPCCommandResult ExportContentEnd(const IOCtlVRequest& request);
IPCCommandResult ExportTitleDone(const IOCtlVRequest& request);
IPCCommandResult DeleteTitle(const IOCtlVRequest& request);
IPCCommandResult DeleteTicket(const IOCtlVRequest& request);
IPCCommandResult DeleteTitleContent(const IOCtlVRequest& request);
// Device identity and encryption
IPCCommandResult GetConsoleID(const IOCtlVRequest& request);
IPCCommandResult GetDeviceCertificate(const IOCtlVRequest& request);
IPCCommandResult CheckKoreaRegion(const IOCtlVRequest& request);
IPCCommandResult Sign(const IOCtlVRequest& request);
IPCCommandResult Encrypt(const IOCtlVRequest& request);
IPCCommandResult Decrypt(const IOCtlVRequest& request);
// Misc
IPCCommandResult SetUID(const IOCtlVRequest& request);
IPCCommandResult GetTitleDirectory(const IOCtlVRequest& request);
IPCCommandResult GetTitleID(const IOCtlVRequest& request);
IPCCommandResult GetConsumption(const IOCtlVRequest& request);
IPCCommandResult Launch(const IOCtlVRequest& request);
IPCCommandResult LaunchBC(const IOCtlVRequest& request);
// Title contents
IPCCommandResult OpenTitleContent(const IOCtlVRequest& request);
IPCCommandResult OpenContent(const IOCtlVRequest& request);
IPCCommandResult ReadContent(const IOCtlVRequest& request);
IPCCommandResult CloseContent(const IOCtlVRequest& request);
IPCCommandResult SeekContent(const IOCtlVRequest& request);
IPCCommandResult GetTitleDirectory(const IOCtlVRequest& request);
IPCCommandResult GetTitleID(const IOCtlVRequest& request);
IPCCommandResult SetUID(const IOCtlVRequest& request);
// Title information
IPCCommandResult GetTitleCount(const std::vector<u64>& titles, const IOCtlVRequest& request);
IPCCommandResult GetTitles(const std::vector<u64>& titles, const IOCtlVRequest& request);
IPCCommandResult GetOwnedTitleCount(const IOCtlVRequest& request);
IPCCommandResult GetOwnedTitles(const IOCtlVRequest& request);
IPCCommandResult GetTitleCount(const IOCtlVRequest& request);
IPCCommandResult GetTitles(const IOCtlVRequest& request);
IPCCommandResult GetBoot2Version(const IOCtlVRequest& request);
IPCCommandResult GetStoredContentsCount(const IOS::ES::TMDReader& tmd,
const IOCtlVRequest& request);
IPCCommandResult GetStoredContents(const IOS::ES::TMDReader& tmd, const IOCtlVRequest& request);
@@ -182,41 +217,21 @@ private:
IPCCommandResult GetStoredContents(const IOCtlVRequest& request);
IPCCommandResult GetTMDStoredContentsCount(const IOCtlVRequest& request);
IPCCommandResult GetTMDStoredContents(const IOCtlVRequest& request);
IPCCommandResult GetStoredTMDSize(const IOCtlVRequest& request);
IPCCommandResult GetStoredTMD(const IOCtlVRequest& request);
IPCCommandResult GetViewCount(const IOCtlVRequest& request);
IPCCommandResult GetViews(const IOCtlVRequest& request);
IPCCommandResult DIGetTicketView(const IOCtlVRequest& request);
// Views for tickets and TMDs
IPCCommandResult GetTicketViewCount(const IOCtlVRequest& request);
IPCCommandResult GetTicketViews(const IOCtlVRequest& request);
IPCCommandResult GetTMDViewSize(const IOCtlVRequest& request);
IPCCommandResult GetTMDViews(const IOCtlVRequest& request);
IPCCommandResult DIGetTicketView(const IOCtlVRequest& request);
IPCCommandResult DIGetTMDViewSize(const IOCtlVRequest& request);
IPCCommandResult DIGetTMDView(const IOCtlVRequest& request);
IPCCommandResult GetConsumption(const IOCtlVRequest& request);
IPCCommandResult DeleteTitle(const IOCtlVRequest& request);
IPCCommandResult DeleteTicket(const IOCtlVRequest& request);
IPCCommandResult DeleteTitleContent(const IOCtlVRequest& request);
IPCCommandResult GetStoredTMDSize(const IOCtlVRequest& request);
IPCCommandResult GetStoredTMD(const IOCtlVRequest& request);
IPCCommandResult Encrypt(const IOCtlVRequest& request);
IPCCommandResult Decrypt(const IOCtlVRequest& request);
IPCCommandResult Launch(const IOCtlVRequest& request);
IPCCommandResult LaunchBC(const IOCtlVRequest& request);
IPCCommandResult ExportTitleInit(const IOCtlVRequest& request);
IPCCommandResult ExportContentBegin(const IOCtlVRequest& request);
IPCCommandResult ExportContentData(const IOCtlVRequest& request);
IPCCommandResult ExportContentEnd(const IOCtlVRequest& request);
IPCCommandResult ExportTitleDone(const IOCtlVRequest& request);
IPCCommandResult CheckKoreaRegion(const IOCtlVRequest& request);
IPCCommandResult GetDeviceCertificate(const IOCtlVRequest& request);
IPCCommandResult Sign(const IOCtlVRequest& request);
IPCCommandResult GetBoot2Version(const IOCtlVRequest& request);
static bool LaunchIOS(u64 ios_title_id);
static bool LaunchPPCTitle(u64 title_id, bool skip_reload);
static TitleContext& GetTitleContext();
static const DiscIO::CNANDContentLoader& AccessContentDevice(u64 title_id);
+155
View File
@@ -0,0 +1,155 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Core/IOS/ES/ES.h"
#include <cstring>
#include <vector>
#include <mbedtls/aes.h>
#include "Common/Assert.h"
#include "Common/Logging/Log.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/ec_wii.h"
namespace IOS
{
namespace HLE
{
namespace Device
{
constexpr u8 s_key_sd[0x10] = {0xab, 0x01, 0xb9, 0xd8, 0xe1, 0x62, 0x2b, 0x08,
0xaf, 0xba, 0xd8, 0x4d, 0xbf, 0xc2, 0xa5, 0x5d};
constexpr u8 s_key_ecc[0x1e] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
constexpr u8 s_key_empty[0x10] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// default key table
constexpr const u8* s_key_table[11] = {
s_key_ecc, // ECC Private Key
s_key_empty, // Console ID
s_key_empty, // NAND AES Key
s_key_empty, // NAND HMAC
s_key_empty, // Common Key
s_key_empty, // PRNG seed
s_key_sd, // SD Key
s_key_empty, // Unknown
s_key_empty, // Unknown
s_key_empty, // Unknown
s_key_empty, // Unknown
};
void ES::DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv, u8* output)
{
mbedtls_aes_context AES_ctx;
mbedtls_aes_setkey_dec(&AES_ctx, s_key_table[key_index], 128);
memcpy(new_iv, iv, 16);
mbedtls_aes_crypt_cbc(&AES_ctx, MBEDTLS_AES_DECRYPT, size, new_iv, input, output);
}
IPCCommandResult ES::GetConsoleID(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(0, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
const EcWii& ec = EcWii::GetInstance();
INFO_LOG(IOS_ES, "IOCTL_ES_GETDEVICEID %08X", ec.GetNGID());
Memory::Write_U32(ec.GetNGID(), request.io_vectors[0].address);
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::Encrypt(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(3, 2))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
u32 keyIndex = Memory::Read_U32(request.in_vectors[0].address);
u8* IV = Memory::GetPointer(request.in_vectors[1].address);
u8* source = Memory::GetPointer(request.in_vectors[2].address);
u32 size = request.in_vectors[2].size;
u8* newIV = Memory::GetPointer(request.io_vectors[0].address);
u8* destination = Memory::GetPointer(request.io_vectors[1].address);
mbedtls_aes_context AES_ctx;
mbedtls_aes_setkey_enc(&AES_ctx, s_key_table[keyIndex], 128);
memcpy(newIV, IV, 16);
mbedtls_aes_crypt_cbc(&AES_ctx, MBEDTLS_AES_ENCRYPT, size, newIV, source, destination);
_dbg_assert_msg_(IOS_ES, keyIndex == 6,
"IOCTL_ES_ENCRYPT: Key type is not SD, data will be crap");
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::Decrypt(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(3, 2))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
u32 keyIndex = Memory::Read_U32(request.in_vectors[0].address);
u8* IV = Memory::GetPointer(request.in_vectors[1].address);
u8* source = Memory::GetPointer(request.in_vectors[2].address);
u32 size = request.in_vectors[2].size;
u8* newIV = Memory::GetPointer(request.io_vectors[0].address);
u8* destination = Memory::GetPointer(request.io_vectors[1].address);
DecryptContent(keyIndex, IV, source, size, newIV, destination);
_dbg_assert_msg_(IOS_ES, keyIndex == 6,
"IOCTL_ES_DECRYPT: Key type is not SD, data will be crap");
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::CheckKoreaRegion(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(0, 0))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
// note by DacoTaco : name is unknown, I just tried to name it SOMETHING.
// IOS70 has this to let system menu 4.2 check if the console is region changed. it returns
// -1017
// if the IOS didn't find the Korean keys and 0 if it does. 0 leads to a error 003
INFO_LOG(IOS_ES, "IOCTL_ES_CHECKKOREAREGION: Title checked for Korean keys.");
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
}
IPCCommandResult ES::GetDeviceCertificate(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(0, 1) || request.io_vectors[0].size != 0x180)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
INFO_LOG(IOS_ES, "IOCTL_ES_GETDEVICECERT");
u8* destination = Memory::GetPointer(request.io_vectors[0].address);
const EcWii& ec = EcWii::GetInstance();
MakeNGCert(destination, ec.GetNGID(), ec.GetNGKeyID(), ec.GetNGPriv(), ec.GetNGSig());
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::Sign(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(1, 2))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
INFO_LOG(IOS_ES, "IOCTL_ES_SIGN");
u8* ap_cert_out = Memory::GetPointer(request.io_vectors[1].address);
u8* data = Memory::GetPointer(request.in_vectors[0].address);
u32 data_size = request.in_vectors[0].size;
u8* sig_out = Memory::GetPointer(request.io_vectors[0].address);
if (!GetTitleContext().active)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
const EcWii& ec = EcWii::GetInstance();
MakeAPSigAndCert(sig_out, ap_cert_out, GetTitleContext().tmd.GetTitleId(), data, data_size,
ec.GetNGPriv(), ec.GetNGID());
return GetDefaultReply(IPC_SUCCESS);
}
} // namespace Device
} // namespace HLE
} // namespace IOS
+201
View File
@@ -0,0 +1,201 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Core/IOS/ES/ES.h"
#include <cinttypes>
#include <utility>
#include <vector>
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/ES/Formats.h"
#include "DiscIO/NANDContentLoader.h"
namespace IOS
{
namespace HLE
{
namespace Device
{
u32 ES::OpenTitleContent(u32 CFD, u64 TitleID, u16 Index)
{
const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID);
if (!Loader.IsValid() || !Loader.GetTMD().IsValid() || !Loader.GetTicket().IsValid())
{
WARN_LOG(IOS_ES, "ES: loader not valid for %" PRIx64, TitleID);
return 0xffffffff;
}
const DiscIO::SNANDContent* pContent = Loader.GetContentByIndex(Index);
if (pContent == nullptr)
{
return 0xffffffff; // TODO: what is the correct error value here?
}
OpenedContent content;
content.m_position = 0;
content.m_content = pContent->m_metadata;
content.m_title_id = TitleID;
pContent->m_Data->Open();
m_ContentAccessMap[CFD] = content;
return CFD;
}
IPCCommandResult ES::OpenTitleContent(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(3, 0))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
u32 Index = Memory::Read_U32(request.in_vectors[2].address);
s32 CFD = OpenTitleContent(m_AccessIdentID++, TitleID, Index);
INFO_LOG(IOS_ES, "IOCTL_ES_OPENTITLECONTENT: TitleID: %08x/%08x Index %i -> got CFD %x",
(u32)(TitleID >> 32), (u32)TitleID, Index, CFD);
return GetDefaultReply(CFD);
}
IPCCommandResult ES::OpenContent(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(1, 0))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
u32 Index = Memory::Read_U32(request.in_vectors[0].address);
if (!GetTitleContext().active)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
s32 CFD = OpenTitleContent(m_AccessIdentID++, GetTitleContext().tmd.GetTitleId(), Index);
INFO_LOG(IOS_ES, "IOCTL_ES_OPENCONTENT: Index %i -> got CFD %x", Index, CFD);
return GetDefaultReply(CFD);
}
IPCCommandResult ES::ReadContent(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(1, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
u32 CFD = Memory::Read_U32(request.in_vectors[0].address);
u32 Size = request.io_vectors[0].size;
u32 Addr = request.io_vectors[0].address;
auto itr = m_ContentAccessMap.find(CFD);
if (itr == m_ContentAccessMap.end())
{
return GetDefaultReply(-1);
}
OpenedContent& rContent = itr->second;
u8* pDest = Memory::GetPointer(Addr);
if (rContent.m_position + Size > rContent.m_content.size)
{
Size = static_cast<u32>(rContent.m_content.size) - rContent.m_position;
}
if (Size > 0)
{
if (pDest)
{
const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(rContent.m_title_id);
// ContentLoader should never be invalid; rContent has been created by it.
if (ContentLoader.IsValid() && ContentLoader.GetTicket().IsValid())
{
const DiscIO::SNANDContent* pContent =
ContentLoader.GetContentByIndex(rContent.m_content.index);
if (!pContent->m_Data->GetRange(rContent.m_position, Size, pDest))
ERROR_LOG(IOS_ES, "ES: failed to read %u bytes from %u!", Size, rContent.m_position);
}
rContent.m_position += Size;
}
else
{
PanicAlert("IOCTL_ES_READCONTENT - bad destination");
}
}
DEBUG_LOG(IOS_ES,
"IOCTL_ES_READCONTENT: CFD %x, Address 0x%x, Size %i -> stream pos %i (Index %i)", CFD,
Addr, Size, rContent.m_position, rContent.m_content.index);
return GetDefaultReply(Size);
}
IPCCommandResult ES::CloseContent(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(1, 0))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
u32 CFD = Memory::Read_U32(request.in_vectors[0].address);
INFO_LOG(IOS_ES, "IOCTL_ES_CLOSECONTENT: CFD %x", CFD);
auto itr = m_ContentAccessMap.find(CFD);
if (itr == m_ContentAccessMap.end())
{
return GetDefaultReply(-1);
}
const DiscIO::CNANDContentLoader& ContentLoader = AccessContentDevice(itr->second.m_title_id);
// ContentLoader should never be invalid; we shouldn't be here if ES_OPENCONTENT failed before.
if (ContentLoader.IsValid())
{
const DiscIO::SNANDContent* pContent =
ContentLoader.GetContentByIndex(itr->second.m_content.index);
pContent->m_Data->Close();
}
m_ContentAccessMap.erase(itr);
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::SeekContent(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(3, 0))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
u32 CFD = Memory::Read_U32(request.in_vectors[0].address);
u32 Addr = Memory::Read_U32(request.in_vectors[1].address);
u32 Mode = Memory::Read_U32(request.in_vectors[2].address);
auto itr = m_ContentAccessMap.find(CFD);
if (itr == m_ContentAccessMap.end())
{
return GetDefaultReply(-1);
}
OpenedContent& rContent = itr->second;
switch (Mode)
{
case 0: // SET
rContent.m_position = Addr;
break;
case 1: // CUR
rContent.m_position += Addr;
break;
case 2: // END
rContent.m_position = static_cast<u32>(rContent.m_content.size) + Addr;
break;
}
DEBUG_LOG(IOS_ES, "IOCTL_ES_SEEKCONTENT: CFD %x, Address 0x%x, Mode %i -> Pos %i", CFD, Addr,
Mode, rContent.m_position);
return GetDefaultReply(rContent.m_position);
}
} // namespace Device
} // namespace HLE
} // namespace IOS
@@ -0,0 +1,326 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Core/IOS/ES/ES.h"
#include <algorithm>
#include <cctype>
#include <cinttypes>
#include <cstdio>
#include <iterator>
#include <string>
#include <vector>
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/NandPaths.h"
#include "Common/StringUtil.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/ES/Formats.h"
#include "DiscIO/NANDContentLoader.h"
namespace IOS
{
namespace HLE
{
namespace Device
{
static std::vector<IOS::ES::Content> GetStoredContentsFromTMD(const IOS::ES::TMDReader& tmd)
{
if (!tmd.IsValid())
return {};
const DiscIO::CSharedContent shared{Common::FROM_SESSION_ROOT};
const std::vector<IOS::ES::Content> contents = tmd.GetContents();
std::vector<IOS::ES::Content> stored_contents;
std::copy_if(contents.begin(), contents.end(), std::back_inserter(stored_contents),
[&tmd, &shared](const auto& content) {
if (content.IsShared())
{
const std::string path = shared.GetFilenameFromSHA1(content.sha1.data());
return path != "unk" && File::Exists(path);
}
return File::Exists(
Common::GetTitleContentPath(tmd.GetTitleId(), Common::FROM_SESSION_ROOT) +
StringFromFormat("%08x.app", content.id));
});
return stored_contents;
}
// Used by the GetStoredContents ioctlvs. This assumes that the first output vector
// is used for the content count (u32).
IPCCommandResult ES::GetStoredContentsCount(const IOS::ES::TMDReader& tmd,
const IOCtlVRequest& request)
{
if (request.io_vectors[0].size != sizeof(u32) || !tmd.IsValid())
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
const u16 num_contents = static_cast<u16>(GetStoredContentsFromTMD(tmd).size());
Memory::Write_U32(num_contents, request.io_vectors[0].address);
INFO_LOG(IOS_ES, "GetStoredContentsCount (0x%x): %u content(s) for %016" PRIx64, request.request,
num_contents, tmd.GetTitleId());
return GetDefaultReply(IPC_SUCCESS);
}
// Used by the GetStoredContents ioctlvs. This assumes that the second input vector is used
// for the content count and the output vector is used to store a list of content IDs (u32s).
IPCCommandResult ES::GetStoredContents(const IOS::ES::TMDReader& tmd, const IOCtlVRequest& request)
{
if (!tmd.IsValid())
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
if (request.in_vectors[1].size != sizeof(u32) ||
request.io_vectors[0].size != Memory::Read_U32(request.in_vectors[1].address) * sizeof(u32))
{
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
}
const auto contents = GetStoredContentsFromTMD(tmd);
const u32 max_content_count = Memory::Read_U32(request.in_vectors[1].address);
for (u32 i = 0; i < std::min(static_cast<u32>(contents.size()), max_content_count); ++i)
Memory::Write_U32(contents[i].id, request.io_vectors[0].address + i * sizeof(u32));
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::GetStoredContentsCount(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != sizeof(u64))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
const u64 title_id = Memory::Read_U64(request.in_vectors[0].address);
const DiscIO::CNANDContentLoader& content_loader = AccessContentDevice(title_id);
if (!content_loader.IsValid())
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
return GetStoredContentsCount(content_loader.GetTMD(), request);
}
IPCCommandResult ES::GetStoredContents(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(2, 1) || request.in_vectors[0].size != sizeof(u64))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
const u64 title_id = Memory::Read_U64(request.in_vectors[0].address);
const DiscIO::CNANDContentLoader& content_loader = AccessContentDevice(title_id);
if (!content_loader.IsValid())
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
return GetStoredContents(content_loader.GetTMD(), request);
}
IPCCommandResult ES::GetTMDStoredContentsCount(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(1, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
std::vector<u8> tmd_bytes(request.in_vectors[0].size);
Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[0].address, tmd_bytes.size());
return GetStoredContentsCount(IOS::ES::TMDReader{std::move(tmd_bytes)}, request);
}
IPCCommandResult ES::GetTMDStoredContents(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(2, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
std::vector<u8> tmd_bytes(request.in_vectors[0].size);
Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[0].address, tmd_bytes.size());
return GetStoredContents(IOS::ES::TMDReader{std::move(tmd_bytes)}, request);
}
static bool IsValidPartOfTitleID(const std::string& string)
{
if (string.length() != 8)
return false;
return std::all_of(string.begin(), string.end(),
[](const auto character) { return std::isxdigit(character) != 0; });
}
// Returns a vector of title IDs. IOS does not check the TMD at all here.
static std::vector<u64> GetInstalledTitles()
{
const std::string titles_dir = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/title";
if (!File::IsDirectory(titles_dir))
{
ERROR_LOG(IOS_ES, "/title is not a directory");
return {};
}
std::vector<u64> title_ids;
// The /title directory contains one directory per title type, and each of them contains
// a directory per title (where the name is the low 32 bits of the title ID in %08x format).
const auto entries = File::ScanDirectoryTree(titles_dir, true);
for (const File::FSTEntry& title_type : entries.children)
{
if (!title_type.isDirectory || !IsValidPartOfTitleID(title_type.virtualName))
continue;
if (title_type.children.empty())
continue;
for (const File::FSTEntry& title_identifier : title_type.children)
{
if (!title_identifier.isDirectory || !IsValidPartOfTitleID(title_identifier.virtualName))
continue;
const u32 type = std::stoul(title_type.virtualName, nullptr, 16);
const u32 identifier = std::stoul(title_identifier.virtualName, nullptr, 16);
title_ids.push_back(static_cast<u64>(type) << 32 | identifier);
}
}
return title_ids;
}
// Returns a vector of title IDs for which there is a ticket.
static std::vector<u64> GetTitlesWithTickets()
{
const std::string titles_dir = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/ticket";
if (!File::IsDirectory(titles_dir))
{
ERROR_LOG(IOS_ES, "/ticket is not a directory");
return {};
}
std::vector<u64> title_ids;
// The /ticket directory contains one directory per title type, and each of them contains
// one ticket per title (where the name is the low 32 bits of the title ID in %08x format).
const auto entries = File::ScanDirectoryTree(titles_dir, true);
for (const File::FSTEntry& title_type : entries.children)
{
if (!title_type.isDirectory || !IsValidPartOfTitleID(title_type.virtualName))
continue;
if (title_type.children.empty())
continue;
for (const File::FSTEntry& ticket : title_type.children)
{
const std::string name_without_ext = ticket.virtualName.substr(0, 8);
if (ticket.isDirectory || !IsValidPartOfTitleID(name_without_ext) ||
name_without_ext + ".tik" != ticket.virtualName)
{
continue;
}
const u32 type = std::stoul(title_type.virtualName, nullptr, 16);
const u32 identifier = std::stoul(name_without_ext, nullptr, 16);
title_ids.push_back(static_cast<u64>(type) << 32 | identifier);
}
}
return title_ids;
}
IPCCommandResult ES::GetTitleCount(const std::vector<u64>& titles, const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(0, 1) || request.io_vectors[0].size != 4)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
Memory::Write_U32(static_cast<u32>(titles.size()), request.io_vectors[0].address);
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::GetTitles(const std::vector<u64>& titles, const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(1, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
const size_t max_count = Memory::Read_U32(request.in_vectors[0].address);
for (size_t i = 0; i < std::min(max_count, titles.size()); i++)
{
Memory::Write_U64(titles[i], request.io_vectors[0].address + static_cast<u32>(i) * sizeof(u64));
INFO_LOG(IOS_ES, " title %016" PRIx64, titles[i]);
}
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::GetTitleCount(const IOCtlVRequest& request)
{
const std::vector<u64> titles = GetInstalledTitles();
INFO_LOG(IOS_ES, "GetTitleCount: %zu titles", titles.size());
return GetTitleCount(titles, request);
}
IPCCommandResult ES::GetTitles(const IOCtlVRequest& request)
{
return GetTitles(GetInstalledTitles(), request);
}
IPCCommandResult ES::GetStoredTMDSize(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(1, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID);
if (!Loader.IsValid() || !Loader.GetTMD().IsValid())
return GetDefaultReply(FS_ENOENT);
const u32 tmd_size = static_cast<u32>(Loader.GetTMD().GetRawTMD().size());
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, tmd_size);
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::GetStoredTMD(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(2, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
// TODO: actually use this param in when writing to the outbuffer :/
const u32 MaxCount = Memory::Read_U32(request.in_vectors[1].address);
const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID);
if (!Loader.IsValid() || !Loader.GetTMD().IsValid())
return GetDefaultReply(FS_ENOENT);
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(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size());
INFO_LOG(IOS_ES, "IOCTL_ES_GETSTOREDTMD: title: %08x/%08x (buffer size: %i)",
(u32)(TitleID >> 32), (u32)TitleID, MaxCount);
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::GetOwnedTitleCount(const IOCtlVRequest& request)
{
const std::vector<u64> titles = GetTitlesWithTickets();
INFO_LOG(IOS_ES, "GetOwnedTitleCount: %zu titles", titles.size());
return GetTitleCount(titles, request);
}
IPCCommandResult ES::GetOwnedTitles(const IOCtlVRequest& request)
{
return GetTitles(GetTitlesWithTickets(), request);
}
IPCCommandResult ES::GetBoot2Version(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(0, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
INFO_LOG(IOS_ES, "IOCTL_ES_GETBOOT2VERSION");
// as of 26/02/2012, this was latest bootmii version
Memory::Write_U32(4, request.io_vectors[0].address);
return GetDefaultReply(IPC_SUCCESS);
}
} // namespace Device
} // namespace HLE
} // namespace IOS
File diff suppressed because it is too large Load Diff
+266
View File
@@ -0,0 +1,266 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Core/IOS/ES/ES.h"
#include <algorithm>
#include <cinttypes>
#include <cstdio>
#include <vector>
#include "Common/Logging/Log.h"
#include "Common/NandPaths.h"
#include "Core/ConfigManager.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/ES/Formats.h"
#include "DiscIO/NANDContentLoader.h"
namespace IOS
{
namespace HLE
{
namespace Device
{
// HACK: Since we do not want to require users to install disc updates when launching
// Wii games from the game list (which is the inaccurate game boot path anyway),
// IOSes have to be faked for games which reload IOS to work properly.
// To minimize the effect of this hack, we should only do this for disc titles
// booted from the game list, though.
static bool ShouldReturnFakeViewsForIOSes(u64 title_id, const TitleContext& context)
{
const bool ios = IsTitleType(title_id, IOS::ES::TitleType::System) && title_id != TITLEID_SYSMENU;
const bool disc_title = context.active && IOS::ES::IsDiscTitle(context.tmd.GetTitleId());
return ios && SConfig::GetInstance().m_BootType == SConfig::BOOT_ISO && disc_title;
}
IPCCommandResult ES::GetTicketViewCount(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(1, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
const IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(TitleID);
u32 view_count = ticket.IsValid() ? ticket.GetNumberOfTickets() : 0;
if (ShouldReturnFakeViewsForIOSes(TitleID, GetTitleContext()))
{
view_count = 1;
WARN_LOG(IOS_ES, "GetViewCount: Faking IOS title %016" PRIx64 " being present", TitleID);
}
INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWCNT for titleID: %08x/%08x (View Count = %u)",
static_cast<u32>(TitleID >> 32), static_cast<u32>(TitleID), view_count);
Memory::Write_U32(view_count, request.io_vectors[0].address);
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::GetTicketViews(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(2, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
u32 maxViews = Memory::Read_U32(request.in_vectors[1].address);
const IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(TitleID);
if (ticket.IsValid())
{
u32 number_of_views = std::min(maxViews, ticket.GetNumberOfTickets());
for (u32 view = 0; view < number_of_views; ++view)
{
const std::vector<u8> ticket_view = ticket.GetRawTicketView(view);
Memory::CopyToEmu(request.io_vectors[0].address + view * sizeof(IOS::ES::TicketView),
ticket_view.data(), ticket_view.size());
}
}
else if (ShouldReturnFakeViewsForIOSes(TitleID, GetTitleContext()))
{
Memory::Memset(request.io_vectors[0].address, 0, sizeof(IOS::ES::TicketView));
WARN_LOG(IOS_ES, "GetViews: Faking IOS title %016" PRIx64 " being present", TitleID);
}
INFO_LOG(IOS_ES, "IOCTL_ES_GETVIEWS for titleID: %08x/%08x (MaxViews = %i)", (u32)(TitleID >> 32),
(u32)TitleID, maxViews);
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::GetTMDViewSize(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(1, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID);
if (!Loader.IsValid())
return GetDefaultReply(FS_ENOENT);
const u32 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, view_size);
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::GetTMDViews(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(2, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
u32 MaxCount = Memory::Read_U32(request.in_vectors[1].address);
const DiscIO::CNANDContentLoader& Loader = AccessContentDevice(TitleID);
INFO_LOG(IOS_ES, "IOCTL_ES_GETTMDVIEWCNT: title: %08x/%08x buffer size: %i",
(u32)(TitleID >> 32), (u32)TitleID, MaxCount);
if (!Loader.IsValid())
return GetDefaultReply(FS_ENOENT);
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(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),
(u32)TitleID, MaxCount);
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::DIGetTMDViewSize(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(1, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
// Sanity check the TMD size.
if (request.in_vectors[0].size >= 4 * 1024 * 1024)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
if (request.io_vectors[0].size != sizeof(u32))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
const bool has_tmd = request.in_vectors[0].size != 0;
size_t tmd_view_size = 0;
if (has_tmd)
{
std::vector<u8> tmd_bytes(request.in_vectors[0].size);
Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[0].address, tmd_bytes.size());
const IOS::ES::TMDReader tmd{std::move(tmd_bytes)};
// Yes, this returns -1017, not ES_INVALID_TMD.
// IOS simply checks whether the TMD has all required content entries.
if (!tmd.IsValid())
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
tmd_view_size = tmd.GetRawView().size();
}
else
{
// If no TMD was passed in and no title is active, IOS returns -1017.
if (!GetTitleContext().active)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
tmd_view_size = GetTitleContext().tmd.GetRawView().size();
}
Memory::Write_U32(static_cast<u32>(tmd_view_size), request.io_vectors[0].address);
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::DIGetTMDView(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(2, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
// Sanity check the TMD size.
if (request.in_vectors[0].size >= 4 * 1024 * 1024)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
// Check whether the TMD view size is consistent.
if (request.in_vectors[1].size != sizeof(u32) ||
Memory::Read_U32(request.in_vectors[1].address) != request.io_vectors[0].size)
{
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
}
const bool has_tmd = request.in_vectors[0].size != 0;
std::vector<u8> tmd_view;
if (has_tmd)
{
std::vector<u8> tmd_bytes(request.in_vectors[0].size);
Memory::CopyFromEmu(tmd_bytes.data(), request.in_vectors[0].address, tmd_bytes.size());
const IOS::ES::TMDReader tmd{std::move(tmd_bytes)};
if (!tmd.IsValid())
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
tmd_view = tmd.GetRawView();
}
else
{
// If no TMD was passed in and no title is active, IOS returns -1017.
if (!GetTitleContext().active)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
tmd_view = GetTitleContext().tmd.GetRawView();
}
if (tmd_view.size() != request.io_vectors[0].size)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
Memory::CopyToEmu(request.io_vectors[0].address, tmd_view.data(), tmd_view.size());
return GetDefaultReply(IPC_SUCCESS);
}
IPCCommandResult ES::DIGetTicketView(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(1, 1) ||
request.io_vectors[0].size != sizeof(IOS::ES::TicketView))
{
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
}
const bool has_ticket_vector = request.in_vectors[0].size == 0x2A4;
// This ioctlv takes either a signed ticket or no ticket, in which case the ticket size must be 0.
if (!has_ticket_vector && request.in_vectors[0].size != 0)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
std::vector<u8> view;
// If no ticket was passed in, IOS returns the ticket view for the current title.
// Of course, this returns -1017 if no title is active and no ticket is passed.
if (!has_ticket_vector)
{
if (!GetTitleContext().active)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
view = GetTitleContext().ticket.GetRawTicketView(0);
}
else
{
std::vector<u8> ticket_bytes(request.in_vectors[0].size);
Memory::CopyFromEmu(ticket_bytes.data(), request.in_vectors[0].address, ticket_bytes.size());
const IOS::ES::TicketReader ticket{std::move(ticket_bytes)};
view = ticket.GetRawTicketView(0);
}
Memory::CopyToEmu(request.io_vectors[0].address, view.data(), view.size());
return GetDefaultReply(IPC_SUCCESS);
}
} // namespace Device
} // namespace HLE
} // namespace IOS