Merge pull request #5071 from leoetlino/reorganise-es
IOS/ES: Split the ES code
This commit is contained in:
commit
13d23c21b7
|
@ -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
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,563 @@
|
|||
// 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 <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <mbedtls/aes.h>
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#include "Common/Align.h"
|
||||
#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 "Core/ec_wii.h"
|
||||
#include "DiscIO/NANDContentLoader.h"
|
||||
|
||||
namespace IOS
|
||||
{
|
||||
namespace HLE
|
||||
{
|
||||
namespace Device
|
||||
{
|
||||
IPCCommandResult ES::AddTicket(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(3, 0))
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
std::vector<u8> bytes(request.in_vectors[0].size);
|
||||
Memory::CopyFromEmu(bytes.data(), request.in_vectors[0].address, request.in_vectors[0].size);
|
||||
|
||||
IOS::ES::TicketReader ticket{std::move(bytes)};
|
||||
if (!ticket.IsValid())
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
const u32 ticket_device_id = ticket.GetDeviceId();
|
||||
const u32 device_id = EcWii::GetInstance().GetNGID();
|
||||
if (ticket_device_id != 0)
|
||||
{
|
||||
if (device_id != ticket_device_id)
|
||||
{
|
||||
WARN_LOG(IOS_ES, "Device ID mismatch: ticket %08x, device %08x", ticket_device_id, device_id);
|
||||
return GetDefaultReply(ES_DEVICE_ID_MISMATCH);
|
||||
}
|
||||
const s32 ret = ticket.Unpersonalise();
|
||||
if (ret < 0)
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "AddTicket: Failed to unpersonalise ticket for %016" PRIx64 " (ret = %d)",
|
||||
ticket.GetTitleId(), ret);
|
||||
return GetDefaultReply(ret);
|
||||
}
|
||||
}
|
||||
|
||||
if (!DiscIO::AddTicket(ticket))
|
||||
return GetDefaultReply(ES_WRITE_FAILURE);
|
||||
|
||||
INFO_LOG(IOS_ES, "AddTicket: Imported ticket for title %016" PRIx64, ticket.GetTitleId());
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
static bool WriteImportTMD(const IOS::ES::TMDReader& tmd)
|
||||
{
|
||||
const std::string tmd_path = Common::GetImportTitlePath(tmd.GetTitleId()) + "/content/title.tmd";
|
||||
File::CreateFullPath(tmd_path);
|
||||
|
||||
File::IOFile file(tmd_path, "wb");
|
||||
return file.WriteBytes(tmd.GetRawTMD().data(), tmd.GetRawTMD().size());
|
||||
}
|
||||
|
||||
static bool MoveImportTMDToTitleDirectory(const IOS::ES::TMDReader& tmd)
|
||||
{
|
||||
const std::string src = Common::GetImportTitlePath(tmd.GetTitleId()) + "/content/title.tmd";
|
||||
const std::string dest = Common::GetTMDFileName(tmd.GetTitleId(), Common::FROM_SESSION_ROOT);
|
||||
return File::RenameSync(src, dest);
|
||||
}
|
||||
|
||||
static std::string GetImportContentPath(const IOS::ES::TMDReader& tmd, u32 content_id)
|
||||
{
|
||||
return Common::GetImportTitlePath(tmd.GetTitleId()) +
|
||||
StringFromFormat("/content/%08x.app", content_id);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::AddTMD(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 0))
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
std::vector<u8> tmd(request.in_vectors[0].size);
|
||||
Memory::CopyFromEmu(tmd.data(), request.in_vectors[0].address, request.in_vectors[0].size);
|
||||
|
||||
// Ioctlv 0x2b writes the TMD to /tmp/title.tmd (for imports) and doesn't seem to write it
|
||||
// to either /import or /title. So here we simply have to set the import TMD.
|
||||
m_addtitle_tmd.SetBytes(std::move(tmd));
|
||||
if (!m_addtitle_tmd.IsValid())
|
||||
return GetDefaultReply(ES_INVALID_TMD);
|
||||
|
||||
DiscIO::cUIDsys uid_sys{Common::FROM_CONFIGURED_ROOT};
|
||||
uid_sys.AddTitle(m_addtitle_tmd.GetTitleId());
|
||||
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::AddTitleStart(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(4, 0))
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
INFO_LOG(IOS_ES, "IOCTL_ES_ADDTITLESTART");
|
||||
std::vector<u8> tmd(request.in_vectors[0].size);
|
||||
Memory::CopyFromEmu(tmd.data(), request.in_vectors[0].address, request.in_vectors[0].size);
|
||||
|
||||
m_addtitle_tmd.SetBytes(tmd);
|
||||
if (!m_addtitle_tmd.IsValid())
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "Invalid TMD while adding title (size = %zd)", tmd.size());
|
||||
return GetDefaultReply(ES_INVALID_TMD);
|
||||
}
|
||||
|
||||
DiscIO::cUIDsys uid_sys{Common::FROM_CONFIGURED_ROOT};
|
||||
uid_sys.AddTitle(m_addtitle_tmd.GetTitleId());
|
||||
|
||||
// TODO: check and use the other vectors.
|
||||
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::AddContentStart(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(2, 0))
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
u64 title_id = Memory::Read_U64(request.in_vectors[0].address);
|
||||
u32 content_id = Memory::Read_U32(request.in_vectors[1].address);
|
||||
|
||||
if (m_addtitle_content_id != 0xFFFFFFFF)
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "Trying to add content when we haven't finished adding "
|
||||
"another content. Unsupported.");
|
||||
return GetDefaultReply(ES_WRITE_FAILURE);
|
||||
}
|
||||
m_addtitle_content_id = content_id;
|
||||
|
||||
m_addtitle_content_buffer.clear();
|
||||
|
||||
INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTSTART: title id %016" PRIx64 ", "
|
||||
"content id %08x",
|
||||
title_id, m_addtitle_content_id);
|
||||
|
||||
if (!m_addtitle_tmd.IsValid())
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
if (title_id != m_addtitle_tmd.GetTitleId())
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTSTART: title id %016" PRIx64 " != "
|
||||
"TMD title id %016" PRIx64 ", ignoring",
|
||||
title_id, m_addtitle_tmd.GetTitleId());
|
||||
}
|
||||
|
||||
// We're supposed to return a "content file descriptor" here, which is
|
||||
// passed to further AddContentData / AddContentFinish. But so far there is
|
||||
// no known content installer which performs content addition concurrently.
|
||||
// Instead we just log an error (see above) if this condition is detected.
|
||||
s32 content_fd = 0;
|
||||
return GetDefaultReply(content_fd);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::AddContentData(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(2, 0))
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
u32 content_fd = Memory::Read_U32(request.in_vectors[0].address);
|
||||
INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTDATA: content fd %08x, "
|
||||
"size %d",
|
||||
content_fd, request.in_vectors[1].size);
|
||||
|
||||
u8* data_start = Memory::GetPointer(request.in_vectors[1].address);
|
||||
u8* data_end = data_start + request.in_vectors[1].size;
|
||||
m_addtitle_content_buffer.insert(m_addtitle_content_buffer.end(), data_start, data_end);
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
static bool CheckIfContentHashMatches(const std::vector<u8>& content, const IOS::ES::Content& info)
|
||||
{
|
||||
std::array<u8, 20> sha1;
|
||||
mbedtls_sha1(content.data(), info.size, sha1.data());
|
||||
return sha1 == info.sha1;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::AddContentFinish(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 0))
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
if (m_addtitle_content_id == 0xFFFFFFFF)
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
u32 content_fd = Memory::Read_U32(request.in_vectors[0].address);
|
||||
INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTFINISH: content fd %08x", content_fd);
|
||||
|
||||
if (!m_addtitle_tmd.IsValid())
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
// Try to find the title key from a pre-installed ticket.
|
||||
IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(m_addtitle_tmd.GetTitleId());
|
||||
if (!ticket.IsValid())
|
||||
{
|
||||
return GetDefaultReply(ES_NO_TICKET_INSTALLED);
|
||||
}
|
||||
|
||||
mbedtls_aes_context aes_ctx;
|
||||
mbedtls_aes_setkey_dec(&aes_ctx, ticket.GetTitleKey().data(), 128);
|
||||
|
||||
// The IV for title content decryption is the lower two bytes of the
|
||||
// content index, zero extended.
|
||||
IOS::ES::Content content_info;
|
||||
if (!m_addtitle_tmd.FindContentById(m_addtitle_content_id, &content_info))
|
||||
{
|
||||
return GetDefaultReply(ES_INVALID_TMD);
|
||||
}
|
||||
u8 iv[16] = {0};
|
||||
iv[0] = (content_info.index >> 8) & 0xFF;
|
||||
iv[1] = content_info.index & 0xFF;
|
||||
std::vector<u8> decrypted_data(m_addtitle_content_buffer.size());
|
||||
mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, m_addtitle_content_buffer.size(), iv,
|
||||
m_addtitle_content_buffer.data(), decrypted_data.data());
|
||||
if (!CheckIfContentHashMatches(decrypted_data, content_info))
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "AddContentFinish: Hash for content %08x doesn't match", content_info.id);
|
||||
return GetDefaultReply(ES_HASH_DOESNT_MATCH);
|
||||
}
|
||||
|
||||
// Just write all contents to the title import directory. AddTitleFinish will
|
||||
// move the contents to the proper location.
|
||||
const std::string tmp_path = GetImportContentPath(m_addtitle_tmd, m_addtitle_content_id);
|
||||
File::CreateFullPath(tmp_path);
|
||||
|
||||
File::IOFile fp(tmp_path, "wb");
|
||||
if (!fp.WriteBytes(decrypted_data.data(), content_info.size))
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "AddContentFinish: Failed to write to %s", tmp_path.c_str());
|
||||
return GetDefaultReply(ES_WRITE_FAILURE);
|
||||
}
|
||||
|
||||
m_addtitle_content_id = 0xFFFFFFFF;
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
static void AbortImport(const u64 title_id, const std::vector<std::string>& processed_paths)
|
||||
{
|
||||
for (const auto& path : processed_paths)
|
||||
File::Delete(path);
|
||||
|
||||
const std::string import_dir = Common::GetImportTitlePath(title_id);
|
||||
File::DeleteDirRecursively(import_dir);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::AddTitleFinish(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(0, 0) || !m_addtitle_tmd.IsValid())
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
std::vector<std::string> processed_paths;
|
||||
|
||||
for (const auto& content_info : m_addtitle_tmd.GetContents())
|
||||
{
|
||||
const std::string source = GetImportContentPath(m_addtitle_tmd, content_info.id);
|
||||
|
||||
// Contents may not have been all imported. This is normal and this isn't an error condition.
|
||||
if (!File::Exists(source))
|
||||
continue;
|
||||
|
||||
std::string content_path;
|
||||
if (content_info.IsShared())
|
||||
{
|
||||
DiscIO::CSharedContent shared_content{Common::FROM_SESSION_ROOT};
|
||||
content_path = shared_content.AddSharedContent(content_info.sha1.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
content_path =
|
||||
StringFromFormat("%s%08x.app", Common::GetTitleContentPath(m_addtitle_tmd.GetTitleId(),
|
||||
Common::FROM_SESSION_ROOT)
|
||||
.c_str(),
|
||||
content_info.id);
|
||||
}
|
||||
|
||||
File::CreateFullPath(content_path);
|
||||
if (!File::RenameSync(source, content_path))
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "AddTitleFinish: Failed to rename %s to %s", source.c_str(),
|
||||
content_path.c_str());
|
||||
AbortImport(m_addtitle_tmd.GetTitleId(), processed_paths);
|
||||
return GetDefaultReply(ES_WRITE_FAILURE);
|
||||
}
|
||||
|
||||
// Do not delete shared contents even if the import fails. This is because
|
||||
// they can be used by several titles and it's not safe to delete them.
|
||||
//
|
||||
// The reason we delete private contents is to avoid having a title with half-complete
|
||||
// contents, as it can cause issues with the system menu. On the other hand, leaving
|
||||
// shared contents does not cause any issue.
|
||||
if (!content_info.IsShared())
|
||||
processed_paths.push_back(content_path);
|
||||
}
|
||||
|
||||
if (!WriteImportTMD(m_addtitle_tmd) || !MoveImportTMDToTitleDirectory(m_addtitle_tmd))
|
||||
return GetDefaultReply(ES_WRITE_FAILURE);
|
||||
|
||||
INFO_LOG(IOS_ES, "IOCTL_ES_ADDTITLEFINISH");
|
||||
m_addtitle_tmd.SetBytes({});
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::AddTitleCancel(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(0, 0) || !m_addtitle_tmd.IsValid())
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
AbortImport(m_addtitle_tmd.GetTitleId(), {});
|
||||
m_addtitle_tmd.SetBytes({});
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
static bool CanDeleteTitle(u64 title_id)
|
||||
{
|
||||
// IOS only allows deleting non-system titles (or a system title higher than 00000001-00000101).
|
||||
return static_cast<u32>(title_id >> 32) != 0x00000001 || static_cast<u32>(title_id) > 0x101;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::DeleteTitle(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != 8)
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
const u64 title_id = Memory::Read_U64(request.in_vectors[0].address);
|
||||
|
||||
if (!CanDeleteTitle(title_id))
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
const std::string title_dir =
|
||||
StringFromFormat("%s/title/%08x/%08x/", RootUserPath(Common::FROM_SESSION_ROOT).c_str(),
|
||||
static_cast<u32>(title_id >> 32), static_cast<u32>(title_id));
|
||||
if (!File::IsDirectory(title_dir) ||
|
||||
!DiscIO::CNANDContentManager::Access().RemoveTitle(title_id, Common::FROM_SESSION_ROOT))
|
||||
{
|
||||
return GetDefaultReply(FS_ENOENT);
|
||||
}
|
||||
|
||||
if (!File::DeleteDirRecursively(title_dir))
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "DeleteTitle: Failed to delete title directory: %s", title_dir.c_str());
|
||||
return GetDefaultReply(FS_EACCESS);
|
||||
}
|
||||
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 0))
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
|
||||
INFO_LOG(IOS_ES, "IOCTL_ES_DELETETICKET: title: %08x/%08x", (u32)(TitleID >> 32), (u32)TitleID);
|
||||
|
||||
// Presumably return -1017 when delete fails
|
||||
if (!File::Delete(Common::GetTicketFileName(TitleID, Common::FROM_SESSION_ROOT)))
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::DeleteTitleContent(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 0))
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address);
|
||||
INFO_LOG(IOS_ES, "IOCTL_ES_DELETETITLECONTENT: title: %08x/%08x", (u32)(TitleID >> 32),
|
||||
(u32)TitleID);
|
||||
|
||||
// Presumably return -1017 when title not installed TODO verify
|
||||
if (!DiscIO::CNANDContentManager::Access().RemoveTitle(TitleID, Common::FROM_SESSION_ROOT))
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ExportTitleInit(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != 8)
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
// No concurrent title import/export is allowed.
|
||||
if (m_export_title_context.valid)
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
const auto& content_loader = AccessContentDevice(Memory::Read_U64(request.in_vectors[0].address));
|
||||
if (!content_loader.IsValid())
|
||||
return GetDefaultReply(FS_ENOENT);
|
||||
if (!content_loader.GetTMD().IsValid())
|
||||
return GetDefaultReply(ES_INVALID_TMD);
|
||||
|
||||
m_export_title_context.tmd = content_loader.GetTMD();
|
||||
|
||||
const auto ticket = DiscIO::FindSignedTicket(m_export_title_context.tmd.GetTitleId());
|
||||
if (!ticket.IsValid())
|
||||
return GetDefaultReply(ES_NO_TICKET_INSTALLED);
|
||||
if (ticket.GetTitleId() != m_export_title_context.tmd.GetTitleId())
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
m_export_title_context.title_key = ticket.GetTitleKey();
|
||||
|
||||
const auto& raw_tmd = m_export_title_context.tmd.GetRawTMD();
|
||||
if (request.io_vectors[0].size != raw_tmd.size())
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size());
|
||||
|
||||
m_export_title_context.valid = true;
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ExportContentBegin(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(2, 0) || request.in_vectors[0].size != 8 ||
|
||||
request.in_vectors[1].size != 4)
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
const u64 title_id = Memory::Read_U64(request.in_vectors[0].address);
|
||||
const u32 content_id = Memory::Read_U32(request.in_vectors[1].address);
|
||||
|
||||
if (!m_export_title_context.valid || m_export_title_context.tmd.GetTitleId() != title_id)
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "Tried to use ExportContentBegin with an invalid title export context.");
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
}
|
||||
|
||||
const auto& content_loader = AccessContentDevice(title_id);
|
||||
if (!content_loader.IsValid())
|
||||
return GetDefaultReply(FS_ENOENT);
|
||||
|
||||
const auto* content = content_loader.GetContentByID(content_id);
|
||||
if (!content)
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
OpenedContent entry;
|
||||
entry.m_position = 0;
|
||||
entry.m_content = content->m_metadata;
|
||||
entry.m_title_id = title_id;
|
||||
content->m_Data->Open();
|
||||
|
||||
u32 cid = 0;
|
||||
while (m_export_title_context.contents.find(cid) != m_export_title_context.contents.end())
|
||||
cid++;
|
||||
|
||||
TitleExportContext::ExportContent content_export;
|
||||
content_export.content = std::move(entry);
|
||||
content_export.iv[0] = (content->m_metadata.index >> 8) & 0xFF;
|
||||
content_export.iv[1] = content->m_metadata.index & 0xFF;
|
||||
|
||||
m_export_title_context.contents.emplace(cid, content_export);
|
||||
// IOS returns a content ID which is passed to further content calls.
|
||||
return GetDefaultReply(cid);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ExportContentData(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != 4 ||
|
||||
request.io_vectors[0].size == 0)
|
||||
{
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
}
|
||||
|
||||
const u32 content_id = Memory::Read_U32(request.in_vectors[0].address);
|
||||
const u32 bytes_to_read = request.io_vectors[0].size;
|
||||
|
||||
const auto iterator = m_export_title_context.contents.find(content_id);
|
||||
if (!m_export_title_context.valid || iterator == m_export_title_context.contents.end() ||
|
||||
iterator->second.content.m_position >= iterator->second.content.m_content.size)
|
||||
{
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
}
|
||||
|
||||
auto& metadata = iterator->second.content;
|
||||
|
||||
const auto& content_loader = AccessContentDevice(metadata.m_title_id);
|
||||
const auto* content = content_loader.GetContentByID(metadata.m_content.id);
|
||||
content->m_Data->Open();
|
||||
|
||||
const u32 length =
|
||||
std::min(static_cast<u32>(metadata.m_content.size - metadata.m_position), bytes_to_read);
|
||||
std::vector<u8> buffer(length);
|
||||
|
||||
if (!content->m_Data->GetRange(metadata.m_position, length, buffer.data()))
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "ExportContentData: ES_READ_LESS_DATA_THAN_EXPECTED");
|
||||
return GetDefaultReply(ES_READ_LESS_DATA_THAN_EXPECTED);
|
||||
}
|
||||
|
||||
// IOS aligns the buffer to 32 bytes. Since we also need to align it to 16 bytes,
|
||||
// let's just follow IOS here.
|
||||
buffer.resize(Common::AlignUp(buffer.size(), 32));
|
||||
std::vector<u8> output(buffer.size());
|
||||
|
||||
mbedtls_aes_context aes_ctx;
|
||||
mbedtls_aes_setkey_enc(&aes_ctx, m_export_title_context.title_key.data(), 128);
|
||||
const int ret = mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_ENCRYPT, buffer.size(),
|
||||
iterator->second.iv.data(), buffer.data(), output.data());
|
||||
if (ret != 0)
|
||||
{
|
||||
// XXX: proper error code when IOSC_Encrypt fails.
|
||||
ERROR_LOG(IOS_ES, "ExportContentData: Failed to encrypt content.");
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
}
|
||||
|
||||
Memory::CopyToEmu(request.io_vectors[0].address, output.data(), output.size());
|
||||
metadata.m_position += length;
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ExportContentEnd(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != 4)
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
const u32 content_id = Memory::Read_U32(request.in_vectors[0].address);
|
||||
|
||||
const auto iterator = m_export_title_context.contents.find(content_id);
|
||||
if (!m_export_title_context.valid || iterator == m_export_title_context.contents.end() ||
|
||||
iterator->second.content.m_position != iterator->second.content.m_content.size)
|
||||
{
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
}
|
||||
|
||||
// XXX: Check the content hash, as IOS does?
|
||||
|
||||
const auto& content_loader = AccessContentDevice(iterator->second.content.m_title_id);
|
||||
content_loader.GetContentByID(iterator->second.content.m_content.id)->m_Data->Close();
|
||||
|
||||
m_export_title_context.contents.erase(iterator);
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ExportTitleDone(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!m_export_title_context.valid)
|
||||
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
|
||||
|
||||
m_export_title_context.valid = false;
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
} // namespace Device
|
||||
} // namespace HLE
|
||||
} // namespace IOS
|
|
@ -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
|
Loading…
Reference in New Issue