mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-02-14 00:09:24 +01:00
Merge pull request #5419 from leoetlino/import-code-reuse
Reuse the IOS code for WAD installation
This commit is contained in:
commit
ed5e98c3cc
@ -983,13 +983,10 @@ bool SConfig::AutoSetup(EBootBS2 _BootBS2)
|
||||
DiscIO::CNANDContentManager::Access().GetNANDLoader(m_strFilename);
|
||||
const IOS::ES::TMDReader& tmd = content_loader.GetTMD();
|
||||
|
||||
if (content_loader.GetContentByIndex(tmd.GetBootIndex()) == nullptr)
|
||||
if (!IOS::ES::IsChannel(tmd.GetTitleId()))
|
||||
{
|
||||
// WAD is valid yet cannot be booted. Install instead.
|
||||
u64 installed = DiscIO::CNANDContentManager::Access().Install_WiiWAD(m_strFilename);
|
||||
if (installed)
|
||||
SuccessAlertT("The WAD has been installed successfully");
|
||||
return false; // do not boot
|
||||
PanicAlertT("This WAD is not bootable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
SetRegion(tmd.GetRegion(), &set_region_dir);
|
||||
|
@ -383,21 +383,21 @@ IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request)
|
||||
switch (request.request)
|
||||
{
|
||||
case IOCTL_ES_ADDTICKET:
|
||||
return AddTicket(request);
|
||||
return ImportTicket(request);
|
||||
case IOCTL_ES_ADDTMD:
|
||||
return AddTMD(*context, request);
|
||||
return ImportTmd(*context, request);
|
||||
case IOCTL_ES_ADDTITLESTART:
|
||||
return AddTitleStart(*context, request);
|
||||
return ImportTitleInit(*context, request);
|
||||
case IOCTL_ES_ADDCONTENTSTART:
|
||||
return AddContentStart(*context, request);
|
||||
return ImportContentBegin(*context, request);
|
||||
case IOCTL_ES_ADDCONTENTDATA:
|
||||
return AddContentData(*context, request);
|
||||
return ImportContentData(*context, request);
|
||||
case IOCTL_ES_ADDCONTENTFINISH:
|
||||
return AddContentFinish(*context, request);
|
||||
return ImportContentEnd(*context, request);
|
||||
case IOCTL_ES_ADDTITLEFINISH:
|
||||
return AddTitleFinish(*context, request);
|
||||
return ImportTitleDone(*context, request);
|
||||
case IOCTL_ES_ADDTITLECANCEL:
|
||||
return AddTitleCancel(*context, request);
|
||||
return ImportTitleCancel(*context, request);
|
||||
case IOCTL_ES_GETDEVICEID:
|
||||
return GetConsoleID(request);
|
||||
case IOCTL_ES_OPENTITLECONTENT:
|
||||
|
@ -55,7 +55,65 @@ public:
|
||||
ReturnCode Close(u32 fd) override;
|
||||
IPCCommandResult IOCtlV(const IOCtlVRequest& request) override;
|
||||
|
||||
struct OpenedContent
|
||||
{
|
||||
u64 m_title_id;
|
||||
IOS::ES::Content m_content;
|
||||
u32 m_position;
|
||||
};
|
||||
|
||||
struct TitleImportContext
|
||||
{
|
||||
IOS::ES::TMDReader tmd;
|
||||
u32 content_id = 0xFFFFFFFF;
|
||||
std::vector<u8> content_buffer;
|
||||
};
|
||||
|
||||
// TODO: merge this with TitleImportContext. Also reuse the global content table.
|
||||
struct TitleExportContext
|
||||
{
|
||||
struct ExportContent
|
||||
{
|
||||
OpenedContent content;
|
||||
std::array<u8, 16> iv{};
|
||||
};
|
||||
|
||||
bool valid = false;
|
||||
IOS::ES::TMDReader tmd;
|
||||
std::vector<u8> title_key;
|
||||
std::map<u32, ExportContent> contents;
|
||||
};
|
||||
|
||||
struct Context
|
||||
{
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
u16 gid = 0;
|
||||
u32 uid = 0;
|
||||
TitleImportContext title_import;
|
||||
TitleExportContext title_export;
|
||||
bool active = false;
|
||||
// We use this to associate an IPC fd with an ES context.
|
||||
u32 ipc_fd = -1;
|
||||
};
|
||||
|
||||
// Title management
|
||||
ReturnCode ImportTicket(const std::vector<u8>& ticket_bytes);
|
||||
ReturnCode ImportTmd(Context& context, const std::vector<u8>& tmd_bytes);
|
||||
ReturnCode ImportTitleInit(Context& context, const std::vector<u8>& tmd_bytes);
|
||||
ReturnCode ImportContentBegin(Context& context, u64 title_id, u32 content_id);
|
||||
ReturnCode ImportContentData(Context& context, u32 content_fd, const u8* data, u32 data_size);
|
||||
ReturnCode ImportContentEnd(Context& context, u32 content_fd);
|
||||
ReturnCode ImportTitleDone(Context& context);
|
||||
ReturnCode ImportTitleCancel(Context& context);
|
||||
ReturnCode ExportTitleInit(Context& context, u64 title_id, u8* tmd, u32 tmd_size);
|
||||
ReturnCode ExportContentBegin(Context& context, u64 title_id, u32 content_id);
|
||||
ReturnCode ExportContentData(Context& context, u32 content_fd, u8* data, u32 data_size);
|
||||
ReturnCode ExportContentEnd(Context& context, u32 content_fd);
|
||||
ReturnCode ExportTitleDone(Context& context);
|
||||
ReturnCode DeleteTitle(u64 title_id);
|
||||
ReturnCode DeleteTitleContent(u64 title_id) const;
|
||||
ReturnCode DeleteTicket(const u8* ticket_view);
|
||||
|
||||
private:
|
||||
enum
|
||||
@ -131,80 +189,26 @@ private:
|
||||
IOCTL_ES_CHECKKOREAREGION = 0x45,
|
||||
};
|
||||
|
||||
struct OpenedContent
|
||||
{
|
||||
u64 m_title_id;
|
||||
IOS::ES::Content m_content;
|
||||
u32 m_position;
|
||||
};
|
||||
|
||||
struct ecc_cert_t
|
||||
{
|
||||
u32 sig_type;
|
||||
u8 sig[0x3c];
|
||||
u8 pad[0x40];
|
||||
u8 issuer[0x40];
|
||||
u32 key_type;
|
||||
u8 key_name[0x40];
|
||||
u32 ng_key_id;
|
||||
u8 ecc_pubkey[0x3c];
|
||||
u8 padding[0x3c];
|
||||
};
|
||||
|
||||
struct TitleImportContext
|
||||
{
|
||||
IOS::ES::TMDReader tmd;
|
||||
u32 content_id = 0xFFFFFFFF;
|
||||
std::vector<u8> content_buffer;
|
||||
};
|
||||
|
||||
// TODO: merge this with TitleImportContext. Also reuse the global content table.
|
||||
struct TitleExportContext
|
||||
{
|
||||
struct ExportContent
|
||||
{
|
||||
OpenedContent content;
|
||||
std::array<u8, 16> iv{};
|
||||
};
|
||||
|
||||
bool valid = false;
|
||||
IOS::ES::TMDReader tmd;
|
||||
std::vector<u8> title_key;
|
||||
std::map<u32, ExportContent> contents;
|
||||
};
|
||||
|
||||
struct Context
|
||||
{
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
u16 gid = 0;
|
||||
u32 uid = 0;
|
||||
TitleImportContext title_import;
|
||||
TitleExportContext title_export;
|
||||
bool active = false;
|
||||
// We use this to associate an IPC fd with an ES context.
|
||||
u32 ipc_fd = -1;
|
||||
};
|
||||
// ES can only have 3 contexts at one time.
|
||||
using ContextArray = std::array<Context, 3>;
|
||||
|
||||
// Title management
|
||||
IPCCommandResult AddTicket(const IOCtlVRequest& request);
|
||||
IPCCommandResult AddTMD(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult AddTitleStart(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult AddContentStart(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult AddContentData(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult AddContentFinish(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult AddTitleFinish(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult AddTitleCancel(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult ImportTicket(const IOCtlVRequest& request);
|
||||
IPCCommandResult ImportTmd(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult ImportTitleInit(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult ImportContentBegin(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult ImportContentData(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult ImportContentEnd(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult ImportTitleDone(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult ImportTitleCancel(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult ExportTitleInit(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult ExportContentBegin(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult ExportContentData(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult ExportContentEnd(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult ExportTitleDone(Context& context, const IOCtlVRequest& request);
|
||||
IPCCommandResult DeleteTitle(const IOCtlVRequest& request);
|
||||
IPCCommandResult DeleteTicket(const IOCtlVRequest& request);
|
||||
IPCCommandResult DeleteTitleContent(const IOCtlVRequest& request);
|
||||
IPCCommandResult DeleteTicket(const IOCtlVRequest& request);
|
||||
|
||||
// Device identity and encryption
|
||||
IPCCommandResult GetConsoleID(const IOCtlVRequest& request);
|
||||
|
@ -42,9 +42,13 @@ bool IsDiscTitle(u64 title_id)
|
||||
|
||||
bool IsChannel(u64 title_id)
|
||||
{
|
||||
if (title_id == TITLEID_SYSMENU)
|
||||
return true;
|
||||
|
||||
return IsTitleType(title_id, TitleType::Channel) ||
|
||||
IsTitleType(title_id, TitleType::SystemChannel) ||
|
||||
IsTitleType(title_id, TitleType::GameWithChannel);
|
||||
IsTitleType(title_id, TitleType::GameWithChannel) ||
|
||||
IsTitleType(title_id, TitleType::HiddenChannel);
|
||||
}
|
||||
|
||||
bool Content::IsShared() const
|
||||
|
@ -30,17 +30,11 @@ namespace HLE
|
||||
{
|
||||
namespace Device
|
||||
{
|
||||
IPCCommandResult ES::AddTicket(const IOCtlVRequest& request)
|
||||
ReturnCode ES::ImportTicket(const std::vector<u8>& ticket_bytes)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(3, 0))
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
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)};
|
||||
IOS::ES::TicketReader ticket{ticket_bytes};
|
||||
if (!ticket.IsValid())
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
return ES_EINVAL;
|
||||
|
||||
const u32 ticket_device_id = ticket.GetDeviceId();
|
||||
const u32 device_id = EcWii::GetInstance().GetNGID();
|
||||
@ -49,105 +43,123 @@ IPCCommandResult ES::AddTicket(const IOCtlVRequest& request)
|
||||
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);
|
||||
return ES_DEVICE_ID_MISMATCH;
|
||||
}
|
||||
const s32 ret = ticket.Unpersonalise();
|
||||
const ReturnCode ret = static_cast<ReturnCode>(ticket.Unpersonalise());
|
||||
if (ret < 0)
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "AddTicket: Failed to unpersonalise ticket for %016" PRIx64 " (ret = %d)",
|
||||
ERROR_LOG(IOS_ES, "ImportTicket: Failed to unpersonalise ticket for %016" PRIx64 " (%d)",
|
||||
ticket.GetTitleId(), ret);
|
||||
return GetDefaultReply(ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (!DiscIO::AddTicket(ticket))
|
||||
return GetDefaultReply(ES_EIO);
|
||||
return ES_EIO;
|
||||
|
||||
INFO_LOG(IOS_ES, "AddTicket: Imported ticket for title %016" PRIx64, ticket.GetTitleId());
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
INFO_LOG(IOS_ES, "ImportTicket: Imported ticket for title %016" PRIx64, ticket.GetTitleId());
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::AddTMD(Context& context, const IOCtlVRequest& request)
|
||||
IPCCommandResult ES::ImportTicket(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(3, 0))
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
std::vector<u8> bytes(request.in_vectors[0].size);
|
||||
Memory::CopyFromEmu(bytes.data(), request.in_vectors[0].address, request.in_vectors[0].size);
|
||||
return GetDefaultReply(ImportTicket(bytes));
|
||||
}
|
||||
|
||||
ReturnCode ES::ImportTmd(Context& context, const std::vector<u8>& tmd_bytes)
|
||||
{
|
||||
// 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.
|
||||
context.title_import.tmd.SetBytes(tmd_bytes);
|
||||
// TODO: validate TMDs and return the proper error code (-1027) if the signature type is invalid.
|
||||
if (!context.title_import.tmd.IsValid())
|
||||
return ES_EINVAL;
|
||||
|
||||
if (!IOS::ES::InitImport(context.title_import.tmd.GetTitleId()))
|
||||
return ES_EIO;
|
||||
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ImportTmd(Context& context, const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 0))
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
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.
|
||||
context.title_import.tmd.SetBytes(std::move(tmd));
|
||||
// TODO: validate TMDs and return the proper error code (-1027) if the signature type is invalid.
|
||||
if (!context.title_import.tmd.IsValid())
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
if (!IOS::ES::InitImport(context.title_import.tmd.GetTitleId()))
|
||||
return GetDefaultReply(FS_EIO);
|
||||
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
return GetDefaultReply(ImportTmd(context, tmd));
|
||||
}
|
||||
|
||||
IPCCommandResult ES::AddTitleStart(Context& context, const IOCtlVRequest& request)
|
||||
static void CleanUpStaleImport(const u64 title_id)
|
||||
{
|
||||
const auto import_tmd = IOS::ES::FindImportTMD(title_id);
|
||||
if (!import_tmd.IsValid())
|
||||
File::DeleteDirRecursively(Common::GetImportTitlePath(title_id) + "/content");
|
||||
else
|
||||
IOS::ES::FinishImport(import_tmd);
|
||||
}
|
||||
|
||||
ReturnCode ES::ImportTitleInit(Context& context, const std::vector<u8>& tmd_bytes)
|
||||
{
|
||||
INFO_LOG(IOS_ES, "ImportTitleInit");
|
||||
context.title_import.tmd.SetBytes(tmd_bytes);
|
||||
if (!context.title_import.tmd.IsValid())
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "Invalid TMD while adding title (size = %zd)", tmd_bytes.size());
|
||||
return ES_EINVAL;
|
||||
}
|
||||
|
||||
// Finish a previous import (if it exists).
|
||||
CleanUpStaleImport(context.title_import.tmd.GetTitleId());
|
||||
|
||||
if (!IOS::ES::InitImport(context.title_import.tmd.GetTitleId()))
|
||||
return ES_EIO;
|
||||
|
||||
// TODO: check and use the other vectors.
|
||||
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ImportTitleInit(Context& context, const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(4, 0))
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
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);
|
||||
|
||||
context.title_import.tmd.SetBytes(tmd);
|
||||
if (!context.title_import.tmd.IsValid())
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "Invalid TMD while adding title (size = %zd)", tmd.size());
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
}
|
||||
|
||||
// Finish a previous import (if it exists).
|
||||
const IOS::ES::TMDReader previous_tmd =
|
||||
IOS::ES::FindImportTMD(context.title_import.tmd.GetTitleId());
|
||||
if (previous_tmd.IsValid())
|
||||
FinishImport(previous_tmd);
|
||||
|
||||
if (!IOS::ES::InitImport(context.title_import.tmd.GetTitleId()))
|
||||
return GetDefaultReply(FS_EIO);
|
||||
|
||||
// TODO: check and use the other vectors.
|
||||
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
return GetDefaultReply(ImportTitleInit(context, tmd));
|
||||
}
|
||||
|
||||
IPCCommandResult ES::AddContentStart(Context& context, const IOCtlVRequest& request)
|
||||
ReturnCode ES::ImportContentBegin(Context& context, u64 title_id, u32 content_id)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(2, 0))
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
u64 title_id = Memory::Read_U64(request.in_vectors[0].address);
|
||||
u32 content_id = Memory::Read_U32(request.in_vectors[1].address);
|
||||
|
||||
if (context.title_import.content_id != 0xFFFFFFFF)
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "Trying to add content when we haven't finished adding "
|
||||
"another content. Unsupported.");
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
return ES_EINVAL;
|
||||
}
|
||||
context.title_import.content_id = content_id;
|
||||
|
||||
context.title_import.content_buffer.clear();
|
||||
|
||||
INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTSTART: title id %016" PRIx64 ", "
|
||||
"content id %08x",
|
||||
title_id, context.title_import.content_id);
|
||||
INFO_LOG(IOS_ES, "ImportContentBegin: title %016" PRIx64 ", content ID %08x", title_id,
|
||||
context.title_import.content_id);
|
||||
|
||||
if (!context.title_import.tmd.IsValid())
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
return ES_EINVAL;
|
||||
|
||||
if (title_id != context.title_import.tmd.GetTitleId())
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTSTART: title id %016" PRIx64 " != "
|
||||
ERROR_LOG(IOS_ES, "ImportContentBegin: title id %016" PRIx64 " != "
|
||||
"TMD title id %016" PRIx64 ", ignoring",
|
||||
title_id, context.title_import.tmd.GetTitleId());
|
||||
return ES_EINVAL;
|
||||
}
|
||||
|
||||
// We're supposed to return a "content file descriptor" here, which is
|
||||
@ -155,24 +167,36 @@ IPCCommandResult ES::AddContentStart(Context& context, const IOCtlVRequest& requ
|
||||
// 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);
|
||||
return static_cast<ReturnCode>(content_fd);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::AddContentData(Context& context, const IOCtlVRequest& request)
|
||||
IPCCommandResult ES::ImportContentBegin(Context& context, const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(2, 0))
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
u64 title_id = Memory::Read_U64(request.in_vectors[0].address);
|
||||
u32 content_id = Memory::Read_U32(request.in_vectors[1].address);
|
||||
return GetDefaultReply(ImportContentBegin(context, title_id, content_id));
|
||||
}
|
||||
|
||||
ReturnCode ES::ImportContentData(Context& context, u32 content_fd, const u8* data, u32 data_size)
|
||||
{
|
||||
INFO_LOG(IOS_ES, "ImportContentData: content fd %08x, size %d", content_fd, data_size);
|
||||
context.title_import.content_buffer.insert(context.title_import.content_buffer.end(), data,
|
||||
data + data_size);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ImportContentData(Context& context, const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(2, 0))
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
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;
|
||||
context.title_import.content_buffer.insert(context.title_import.content_buffer.end(), data_start,
|
||||
data_end);
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
return GetDefaultReply(
|
||||
ImportContentData(context, content_fd, data_start, request.in_vectors[1].size));
|
||||
}
|
||||
|
||||
static bool CheckIfContentHashMatches(const std::vector<u8>& content, const IOS::ES::Content& info)
|
||||
@ -187,34 +211,27 @@ static std::string GetImportContentPath(u64 title_id, u32 content_id)
|
||||
return Common::GetImportTitlePath(title_id) + StringFromFormat("/content/%08x.app", content_id);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::AddContentFinish(Context& context, const IOCtlVRequest& request)
|
||||
ReturnCode ES::ImportContentEnd(Context& context, u32 content_fd)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 0))
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
INFO_LOG(IOS_ES, "ImportContentEnd: content fd %08x", content_fd);
|
||||
|
||||
if (context.title_import.content_id == 0xFFFFFFFF)
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
u32 content_fd = Memory::Read_U32(request.in_vectors[0].address);
|
||||
INFO_LOG(IOS_ES, "IOCTL_ES_ADDCONTENTFINISH: content fd %08x", content_fd);
|
||||
return ES_EINVAL;
|
||||
|
||||
if (!context.title_import.tmd.IsValid())
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
return ES_EINVAL;
|
||||
|
||||
// Try to find the title key from a pre-installed ticket.
|
||||
IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(context.title_import.tmd.GetTitleId());
|
||||
if (!ticket.IsValid())
|
||||
{
|
||||
return GetDefaultReply(ES_NO_TICKET);
|
||||
}
|
||||
return ES_NO_TICKET;
|
||||
|
||||
// The IV for title content decryption is the lower two bytes of the
|
||||
// content index, zero extended.
|
||||
IOS::ES::Content content_info;
|
||||
if (!context.title_import.tmd.FindContentById(context.title_import.content_id, &content_info))
|
||||
{
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
}
|
||||
return ES_EINVAL;
|
||||
|
||||
u8 iv[16] = {0};
|
||||
iv[0] = (content_info.index >> 8) & 0xFF;
|
||||
iv[1] = content_info.index & 0xFF;
|
||||
@ -223,8 +240,8 @@ IPCCommandResult ES::AddContentFinish(Context& context, const IOCtlVRequest& req
|
||||
context.title_import.content_buffer.size());
|
||||
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_MISMATCH);
|
||||
ERROR_LOG(IOS_ES, "ImportContentEnd: Hash for content %08x doesn't match", content_info.id);
|
||||
return ES_HASH_MISMATCH;
|
||||
}
|
||||
|
||||
std::string content_path;
|
||||
@ -248,56 +265,72 @@ IPCCommandResult ES::AddContentFinish(Context& context, const IOCtlVRequest& req
|
||||
File::IOFile file(temp_path, "wb");
|
||||
if (!file.WriteBytes(decrypted_data.data(), content_info.size))
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "AddContentFinish: Failed to write to %s", temp_path.c_str());
|
||||
return GetDefaultReply(ES_EIO);
|
||||
ERROR_LOG(IOS_ES, "ImportContentEnd: Failed to write to %s", temp_path.c_str());
|
||||
return ES_EIO;
|
||||
}
|
||||
}
|
||||
|
||||
if (!File::Rename(temp_path, content_path))
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "AddContentFinish: Failed to move content to %s", content_path.c_str());
|
||||
return GetDefaultReply(ES_EIO);
|
||||
ERROR_LOG(IOS_ES, "ImportContentEnd: Failed to move content to %s", content_path.c_str());
|
||||
return ES_EIO;
|
||||
}
|
||||
|
||||
context.title_import.content_id = 0xFFFFFFFF;
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::AddTitleFinish(Context& context, const IOCtlVRequest& request)
|
||||
IPCCommandResult ES::ImportContentEnd(Context& context, const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(0, 0) || !context.title_import.tmd.IsValid())
|
||||
if (!request.HasNumberOfValidVectors(1, 0))
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
u32 content_fd = Memory::Read_U32(request.in_vectors[0].address);
|
||||
return GetDefaultReply(ImportContentEnd(context, content_fd));
|
||||
}
|
||||
|
||||
ReturnCode ES::ImportTitleDone(Context& context)
|
||||
{
|
||||
if (!context.title_import.tmd.IsValid())
|
||||
return ES_EINVAL;
|
||||
|
||||
if (!WriteImportTMD(context.title_import.tmd))
|
||||
return GetDefaultReply(ES_EIO);
|
||||
return ES_EIO;
|
||||
|
||||
if (!FinishImport(context.title_import.tmd))
|
||||
return GetDefaultReply(FS_EIO);
|
||||
return ES_EIO;
|
||||
|
||||
INFO_LOG(IOS_ES, "IOCTL_ES_ADDTITLEFINISH");
|
||||
INFO_LOG(IOS_ES, "ImportTitleDone: title %016" PRIx64, context.title_import.tmd.GetTitleId());
|
||||
context.title_import.tmd.SetBytes({});
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::AddTitleCancel(Context& context, const IOCtlVRequest& request)
|
||||
IPCCommandResult ES::ImportTitleDone(Context& context, const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(0, 0))
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
return GetDefaultReply(ImportTitleDone(context));
|
||||
}
|
||||
|
||||
ReturnCode ES::ImportTitleCancel(Context& context)
|
||||
{
|
||||
if (!context.title_import.tmd.IsValid())
|
||||
return ES_EINVAL;
|
||||
|
||||
CleanUpStaleImport(context.title_import.tmd.GetTitleId());
|
||||
|
||||
INFO_LOG(IOS_ES, "ImportTitleCancel: title %016" PRIx64, context.title_import.tmd.GetTitleId());
|
||||
context.title_import.tmd.SetBytes({});
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ImportTitleCancel(Context& context, const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(0, 0) || !context.title_import.tmd.IsValid())
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
const IOS::ES::TMDReader original_tmd =
|
||||
IOS::ES::FindInstalledTMD(context.title_import.tmd.GetTitleId());
|
||||
if (!original_tmd.IsValid())
|
||||
{
|
||||
// This should never happen unless someone messed with the installed TMD directly.
|
||||
// Still, let's check for this case and return an error instead of potentially crashing.
|
||||
return GetDefaultReply(FS_ENOENT);
|
||||
}
|
||||
|
||||
if (!FinishImport(original_tmd))
|
||||
return GetDefaultReply(FS_EIO);
|
||||
|
||||
context.title_import.tmd.SetBytes({});
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
return GetDefaultReply(ImportTitleCancel(context));
|
||||
}
|
||||
|
||||
static bool CanDeleteTitle(u64 title_id)
|
||||
@ -306,51 +339,47 @@ static bool CanDeleteTitle(u64 title_id)
|
||||
return static_cast<u32>(title_id >> 32) != 0x00000001 || static_cast<u32>(title_id) > 0x101;
|
||||
}
|
||||
|
||||
ReturnCode ES::DeleteTitle(u64 title_id)
|
||||
{
|
||||
if (!CanDeleteTitle(title_id))
|
||||
return ES_EINVAL;
|
||||
|
||||
const std::string title_dir = Common::GetTitlePath(title_id, Common::FROM_SESSION_ROOT);
|
||||
if (!File::IsDirectory(title_dir))
|
||||
return FS_ENOENT;
|
||||
|
||||
if (!File::DeleteDirRecursively(title_dir))
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "DeleteTitle: Failed to delete title directory: %s", title_dir.c_str());
|
||||
return FS_EACCESS;
|
||||
}
|
||||
// XXX: ugly, but until we drop CNANDContentManager everywhere, this is going to be needed.
|
||||
DiscIO::CNANDContentManager::Access().ClearCache();
|
||||
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::DeleteTitle(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != 8)
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
const u64 title_id = Memory::Read_U64(request.in_vectors[0].address);
|
||||
|
||||
if (!CanDeleteTitle(title_id))
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
const std::string title_dir = Common::GetTitlePath(title_id, Common::FROM_SESSION_ROOT);
|
||||
if (!File::IsDirectory(title_dir))
|
||||
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);
|
||||
}
|
||||
// XXX: ugly, but until we drop CNANDContentManager everywhere, this is going to be needed.
|
||||
DiscIO::CNANDContentManager::Access().ClearCache();
|
||||
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
return GetDefaultReply(DeleteTitle(title_id));
|
||||
}
|
||||
|
||||
IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request)
|
||||
ReturnCode ES::DeleteTicket(const u8* ticket_view)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 0) ||
|
||||
request.in_vectors[0].size != sizeof(IOS::ES::TicketView))
|
||||
{
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
}
|
||||
|
||||
const u64 title_id =
|
||||
Memory::Read_U64(request.in_vectors[0].address + offsetof(IOS::ES::TicketView, title_id));
|
||||
const u64 title_id = Common::swap64(ticket_view + offsetof(IOS::ES::TicketView, title_id));
|
||||
|
||||
if (!CanDeleteTitle(title_id))
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
return ES_EINVAL;
|
||||
|
||||
auto ticket = DiscIO::FindSignedTicket(title_id);
|
||||
if (!ticket.IsValid())
|
||||
return GetDefaultReply(FS_ENOENT);
|
||||
return FS_ENOENT;
|
||||
|
||||
const u64 ticket_id =
|
||||
Memory::Read_U64(request.in_vectors[0].address + offsetof(IOS::ES::TicketView, ticket_id));
|
||||
const u64 ticket_id = Common::swap64(ticket_view + offsetof(IOS::ES::TicketView, ticket_id));
|
||||
ticket.DeleteTicket(ticket_id);
|
||||
|
||||
const std::vector<u8>& new_ticket = ticket.GetRawTicket();
|
||||
@ -358,7 +387,7 @@ IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request)
|
||||
{
|
||||
File::IOFile ticket_file(ticket_path, "wb");
|
||||
if (!ticket_file || !ticket_file.WriteBytes(new_ticket.data(), new_ticket.size()))
|
||||
return GetDefaultReply(ES_EIO);
|
||||
return ES_EIO;
|
||||
}
|
||||
|
||||
// Delete the ticket file if it is now empty.
|
||||
@ -373,7 +402,17 @@ IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request)
|
||||
if (ticket_parent_dir_entries.children.empty())
|
||||
File::DeleteDir(ticket_parent_dir);
|
||||
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 0) ||
|
||||
request.in_vectors[0].size != sizeof(IOS::ES::TicketView))
|
||||
{
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
}
|
||||
return GetDefaultReply(DeleteTicket(Memory::GetPointer(request.in_vectors[0].address)));
|
||||
}
|
||||
|
||||
ReturnCode ES::DeleteTitleContent(u64 title_id) const
|
||||
@ -401,37 +440,82 @@ IPCCommandResult ES::DeleteTitleContent(const IOCtlVRequest& request)
|
||||
return GetDefaultReply(DeleteTitleContent(Memory::Read_U64(request.in_vectors[0].address)));
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ExportTitleInit(Context& context, const IOCtlVRequest& request)
|
||||
ReturnCode ES::ExportTitleInit(Context& context, u64 title_id, u8* tmd_bytes, u32 tmd_size)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != 8)
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
// No concurrent title import/export is allowed.
|
||||
if (context.title_export.valid)
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
return ES_EINVAL;
|
||||
|
||||
const auto tmd = IOS::ES::FindInstalledTMD(Memory::Read_U64(request.in_vectors[0].address));
|
||||
const auto tmd = IOS::ES::FindInstalledTMD(title_id);
|
||||
if (!tmd.IsValid())
|
||||
return GetDefaultReply(FS_ENOENT);
|
||||
return FS_ENOENT;
|
||||
|
||||
context.title_export.tmd = tmd;
|
||||
|
||||
const auto ticket = DiscIO::FindSignedTicket(context.title_export.tmd.GetTitleId());
|
||||
if (!ticket.IsValid())
|
||||
return GetDefaultReply(ES_NO_TICKET);
|
||||
return ES_NO_TICKET;
|
||||
if (ticket.GetTitleId() != context.title_export.tmd.GetTitleId())
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
return ES_EINVAL;
|
||||
|
||||
context.title_export.title_key = ticket.GetTitleKey();
|
||||
|
||||
const auto& raw_tmd = context.title_export.tmd.GetRawTMD();
|
||||
if (request.io_vectors[0].size != raw_tmd.size())
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
if (tmd_size != raw_tmd.size())
|
||||
return ES_EINVAL;
|
||||
|
||||
Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size());
|
||||
std::copy_n(raw_tmd.cbegin(), raw_tmd.size(), tmd_bytes);
|
||||
|
||||
context.title_export.valid = true;
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ExportTitleInit(Context& context, const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 1) || request.in_vectors[0].size != 8)
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
const u64 title_id = Memory::Read_U64(request.in_vectors[0].address);
|
||||
u8* tmd_bytes = Memory::GetPointer(request.io_vectors[0].address);
|
||||
const u32 tmd_size = request.io_vectors[0].size;
|
||||
|
||||
return GetDefaultReply(ExportTitleInit(context, title_id, tmd_bytes, tmd_size));
|
||||
}
|
||||
|
||||
ReturnCode ES::ExportContentBegin(Context& context, u64 title_id, u32 content_id)
|
||||
{
|
||||
if (!context.title_export.valid || context.title_export.tmd.GetTitleId() != title_id)
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "Tried to use ExportContentBegin with an invalid title export context.");
|
||||
return ES_EINVAL;
|
||||
}
|
||||
|
||||
const auto& content_loader = AccessContentDevice(title_id);
|
||||
if (!content_loader.IsValid())
|
||||
return FS_ENOENT;
|
||||
|
||||
const auto* content = content_loader.GetContentByID(content_id);
|
||||
if (!content)
|
||||
return ES_EINVAL;
|
||||
|
||||
OpenedContent entry;
|
||||
entry.m_position = 0;
|
||||
entry.m_content = content->m_metadata;
|
||||
entry.m_title_id = title_id;
|
||||
content->m_Data->Open();
|
||||
|
||||
u32 cfd = 0;
|
||||
while (context.title_export.contents.find(cfd) != context.title_export.contents.end())
|
||||
cfd++;
|
||||
|
||||
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;
|
||||
|
||||
context.title_export.contents.emplace(cfd, content_export);
|
||||
// IOS returns a content ID which is passed to further content calls.
|
||||
return static_cast<ReturnCode>(cfd);
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ExportContentBegin(Context& context, const IOCtlVRequest& request)
|
||||
@ -443,38 +527,44 @@ IPCCommandResult ES::ExportContentBegin(Context& context, const IOCtlVRequest& r
|
||||
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 (!context.title_export.valid || context.title_export.tmd.GetTitleId() != title_id)
|
||||
return GetDefaultReply(ExportContentBegin(context, title_id, content_id));
|
||||
}
|
||||
|
||||
ReturnCode ES::ExportContentData(Context& context, u32 content_fd, u8* data, u32 data_size)
|
||||
{
|
||||
const auto iterator = context.title_export.contents.find(content_fd);
|
||||
if (!context.title_export.valid || iterator == context.title_export.contents.end() ||
|
||||
iterator->second.content.m_position >= iterator->second.content.m_content.size)
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "Tried to use ExportContentBegin with an invalid title export context.");
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
return ES_EINVAL;
|
||||
}
|
||||
|
||||
const auto& content_loader = AccessContentDevice(title_id);
|
||||
if (!content_loader.IsValid())
|
||||
return GetDefaultReply(FS_ENOENT);
|
||||
auto& metadata = iterator->second.content;
|
||||
|
||||
const auto* content = content_loader.GetContentByID(content_id);
|
||||
if (!content)
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
OpenedContent entry;
|
||||
entry.m_position = 0;
|
||||
entry.m_content = content->m_metadata;
|
||||
entry.m_title_id = title_id;
|
||||
const auto& content_loader = AccessContentDevice(metadata.m_title_id);
|
||||
const auto* content = content_loader.GetContentByID(metadata.m_content.id);
|
||||
content->m_Data->Open();
|
||||
|
||||
u32 cid = 0;
|
||||
while (context.title_export.contents.find(cid) != context.title_export.contents.end())
|
||||
cid++;
|
||||
const u32 length =
|
||||
std::min(static_cast<u32>(metadata.m_content.size - metadata.m_position), data_size);
|
||||
std::vector<u8> buffer(length);
|
||||
|
||||
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;
|
||||
if (!content->m_Data->GetRange(metadata.m_position, length, buffer.data()))
|
||||
{
|
||||
ERROR_LOG(IOS_ES, "ExportContentData: ES_SHORT_READ");
|
||||
return ES_SHORT_READ;
|
||||
}
|
||||
|
||||
context.title_export.contents.emplace(cid, content_export);
|
||||
// IOS returns a content ID which is passed to further content calls.
|
||||
return GetDefaultReply(cid);
|
||||
// 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));
|
||||
|
||||
const std::vector<u8> output =
|
||||
Common::AES::Encrypt(context.title_export.title_key.data(), iterator->second.iv.data(),
|
||||
buffer.data(), buffer.size());
|
||||
std::copy_n(output.cbegin(), output.size(), data);
|
||||
metadata.m_position += length;
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ExportContentData(Context& context, const IOCtlVRequest& request)
|
||||
@ -485,57 +575,20 @@ IPCCommandResult ES::ExportContentData(Context& context, const IOCtlVRequest& re
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
}
|
||||
|
||||
const u32 content_id = Memory::Read_U32(request.in_vectors[0].address);
|
||||
const u32 content_fd = Memory::Read_U32(request.in_vectors[0].address);
|
||||
u8* data = Memory::GetPointer(request.io_vectors[0].address);
|
||||
const u32 bytes_to_read = request.io_vectors[0].size;
|
||||
|
||||
const auto iterator = context.title_export.contents.find(content_id);
|
||||
if (!context.title_export.valid || iterator == context.title_export.contents.end() ||
|
||||
iterator->second.content.m_position >= iterator->second.content.m_content.size)
|
||||
{
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
}
|
||||
|
||||
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_SHORT_READ");
|
||||
return GetDefaultReply(ES_SHORT_READ);
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
const std::vector<u8> output =
|
||||
Common::AES::Encrypt(context.title_export.title_key.data(), iterator->second.iv.data(),
|
||||
buffer.data(), buffer.size());
|
||||
|
||||
Memory::CopyToEmu(request.io_vectors[0].address, output.data(), output.size());
|
||||
metadata.m_position += length;
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
return GetDefaultReply(ExportContentData(context, content_fd, data, bytes_to_read));
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ExportContentEnd(Context& context, const IOCtlVRequest& request)
|
||||
ReturnCode ES::ExportContentEnd(Context& context, u32 content_fd)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != 4)
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
const u32 content_id = Memory::Read_U32(request.in_vectors[0].address);
|
||||
|
||||
const auto iterator = context.title_export.contents.find(content_id);
|
||||
const auto iterator = context.title_export.contents.find(content_fd);
|
||||
if (!context.title_export.valid || iterator == context.title_export.contents.end() ||
|
||||
iterator->second.content.m_position != iterator->second.content.m_content.size)
|
||||
{
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
return ES_EINVAL;
|
||||
}
|
||||
|
||||
// XXX: Check the content hash, as IOS does?
|
||||
@ -544,16 +597,30 @@ IPCCommandResult ES::ExportContentEnd(Context& context, const IOCtlVRequest& req
|
||||
content_loader.GetContentByID(iterator->second.content.m_content.id)->m_Data->Close();
|
||||
|
||||
context.title_export.contents.erase(iterator);
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ExportContentEnd(Context& context, const IOCtlVRequest& request)
|
||||
{
|
||||
if (!request.HasNumberOfValidVectors(1, 0) || request.in_vectors[0].size != 4)
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
const u32 content_fd = Memory::Read_U32(request.in_vectors[0].address);
|
||||
return GetDefaultReply(ExportContentEnd(context, content_fd));
|
||||
}
|
||||
|
||||
ReturnCode ES::ExportTitleDone(Context& context)
|
||||
{
|
||||
if (!context.title_export.valid)
|
||||
return ES_EINVAL;
|
||||
|
||||
context.title_export.valid = false;
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
IPCCommandResult ES::ExportTitleDone(Context& context, const IOCtlVRequest& request)
|
||||
{
|
||||
if (!context.title_export.valid)
|
||||
return GetDefaultReply(ES_EINVAL);
|
||||
|
||||
context.title_export.valid = false;
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
return GetDefaultReply(ExportTitleDone(context));
|
||||
}
|
||||
} // namespace Device
|
||||
} // namespace HLE
|
||||
|
@ -246,74 +246,6 @@ void CNANDContentManager::ClearCache()
|
||||
m_map.clear();
|
||||
}
|
||||
|
||||
u64 CNANDContentManager::Install_WiiWAD(const std::string& filename)
|
||||
{
|
||||
if (filename.find(".wad") == std::string::npos)
|
||||
return 0;
|
||||
const CNANDContentLoader& content_loader = GetNANDLoader(filename);
|
||||
if (content_loader.IsValid() == false)
|
||||
return 0;
|
||||
|
||||
const u64 title_id = content_loader.GetTMD().GetTitleId();
|
||||
|
||||
// copy WAD's TMD header and contents to content directory
|
||||
|
||||
std::string content_path(Common::GetTitleContentPath(title_id, Common::FROM_CONFIGURED_ROOT));
|
||||
std::string tmd_filename(Common::GetTMDFileName(title_id, Common::FROM_CONFIGURED_ROOT));
|
||||
File::CreateFullPath(tmd_filename);
|
||||
|
||||
File::IOFile tmd_file(tmd_filename, "wb");
|
||||
if (!tmd_file)
|
||||
{
|
||||
PanicAlertT("WAD installation failed: error creating %s", tmd_filename.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& raw_tmd = content_loader.GetTMD().GetRawTMD();
|
||||
tmd_file.WriteBytes(raw_tmd.data(), raw_tmd.size());
|
||||
|
||||
IOS::ES::SharedContentMap shared_content{Common::FromWhichRoot::FROM_CONFIGURED_ROOT};
|
||||
for (const auto& content : content_loader.GetContent())
|
||||
{
|
||||
std::string app_filename;
|
||||
if (content.m_metadata.IsShared())
|
||||
app_filename = shared_content.AddSharedContent(content.m_metadata.sha1);
|
||||
else
|
||||
app_filename = StringFromFormat("%s%08x.app", content_path.c_str(), content.m_metadata.id);
|
||||
|
||||
if (!File::Exists(app_filename))
|
||||
{
|
||||
File::CreateFullPath(app_filename);
|
||||
File::IOFile app_file(app_filename, "wb");
|
||||
if (!app_file)
|
||||
{
|
||||
PanicAlertT("WAD installation failed: error creating %s", app_filename.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
app_file.WriteBytes(content.m_Data->Get().data(), content.m_metadata.size);
|
||||
}
|
||||
else
|
||||
{
|
||||
INFO_LOG(DISCIO, "Content %s already exists.", app_filename.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Extract and copy WAD's ticket to ticket directory
|
||||
if (!AddTicket(content_loader.GetTicket()))
|
||||
{
|
||||
PanicAlertT("WAD installation failed: error creating ticket");
|
||||
return 0;
|
||||
}
|
||||
|
||||
IOS::ES::UIDSys uid_sys{Common::FromWhichRoot::FROM_CONFIGURED_ROOT};
|
||||
uid_sys.GetOrInsertUIDForTitle(title_id);
|
||||
|
||||
ClearCache();
|
||||
|
||||
return title_id;
|
||||
}
|
||||
|
||||
bool AddTicket(const IOS::ES::TicketReader& signed_ticket)
|
||||
{
|
||||
if (!signed_ticket.IsValid())
|
||||
|
@ -106,7 +106,6 @@ public:
|
||||
static CNANDContentManager instance;
|
||||
return instance;
|
||||
}
|
||||
u64 Install_WiiWAD(const std::string& fileName);
|
||||
|
||||
const CNANDContentLoader& GetNANDLoader(const std::string& content_path);
|
||||
const CNANDContentLoader& GetNANDLoader(u64 title_id, Common::FromWhichRoot from);
|
||||
|
@ -44,25 +44,24 @@ bool IsWiiWAD(const CBlobBigEndianReader& reader)
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
WiiWAD::WiiWAD(const std::string& name)
|
||||
WiiWAD::WiiWAD(const std::string& name) : m_reader(CreateBlobReader(name))
|
||||
{
|
||||
std::unique_ptr<IBlobReader> reader(CreateBlobReader(name));
|
||||
if (reader == nullptr || File::IsDirectory(name))
|
||||
if (m_reader == nullptr || File::IsDirectory(name))
|
||||
{
|
||||
m_valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_valid = ParseWAD(*reader);
|
||||
m_valid = ParseWAD();
|
||||
}
|
||||
|
||||
WiiWAD::~WiiWAD()
|
||||
{
|
||||
}
|
||||
|
||||
bool WiiWAD::ParseWAD(IBlobReader& reader)
|
||||
bool WiiWAD::ParseWAD()
|
||||
{
|
||||
CBlobBigEndianReader big_endian_reader(reader);
|
||||
CBlobBigEndianReader big_endian_reader(*m_reader);
|
||||
|
||||
if (!IsWiiWAD(big_endian_reader))
|
||||
return false;
|
||||
@ -86,18 +85,36 @@ bool WiiWAD::ParseWAD(IBlobReader& reader)
|
||||
_dbg_assert_msg_(BOOT, reserved == 0x00, "WiiWAD: Reserved must be 0x00");
|
||||
|
||||
u32 offset = 0x40;
|
||||
m_certificate_chain = CreateWADEntry(reader, certificate_chain_size, offset);
|
||||
m_certificate_chain = CreateWADEntry(*m_reader, certificate_chain_size, offset);
|
||||
offset += Common::AlignUp(certificate_chain_size, 0x40);
|
||||
m_ticket.SetBytes(CreateWADEntry(reader, ticket_size, offset));
|
||||
m_ticket.SetBytes(CreateWADEntry(*m_reader, ticket_size, offset));
|
||||
offset += Common::AlignUp(ticket_size, 0x40);
|
||||
m_tmd.SetBytes(CreateWADEntry(reader, tmd_size, offset));
|
||||
m_tmd.SetBytes(CreateWADEntry(*m_reader, tmd_size, offset));
|
||||
offset += Common::AlignUp(tmd_size, 0x40);
|
||||
m_data_app = CreateWADEntry(reader, data_app_size, offset);
|
||||
m_data_app_offset = offset;
|
||||
m_data_app = CreateWADEntry(*m_reader, data_app_size, offset);
|
||||
offset += Common::AlignUp(data_app_size, 0x40);
|
||||
m_footer = CreateWADEntry(reader, footer_size, offset);
|
||||
m_footer = CreateWADEntry(*m_reader, footer_size, offset);
|
||||
offset += Common::AlignUp(footer_size, 0x40);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<u8> WiiWAD::GetContent(u16 index) const
|
||||
{
|
||||
u64 offset = m_data_app_offset;
|
||||
for (const IOS::ES::Content& content : m_tmd.GetContents())
|
||||
{
|
||||
const u64 aligned_size = Common::AlignUp(content.size, 0x40);
|
||||
if (content.index == index)
|
||||
{
|
||||
std::vector<u8> data(aligned_size);
|
||||
if (!m_reader->Read(offset, aligned_size, data.data()))
|
||||
return {};
|
||||
return data;
|
||||
}
|
||||
offset += aligned_size;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
} // namespace DiscIO
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -27,11 +28,16 @@ public:
|
||||
const IOS::ES::TMDReader& GetTMD() const { return m_tmd; }
|
||||
const std::vector<u8>& GetDataApp() const { return m_data_app; }
|
||||
const std::vector<u8>& GetFooter() const { return m_footer; }
|
||||
std::vector<u8> GetContent(u16 index) const;
|
||||
|
||||
private:
|
||||
bool ParseWAD(IBlobReader& reader);
|
||||
bool ParseWAD();
|
||||
|
||||
bool m_valid;
|
||||
|
||||
std::unique_ptr<IBlobReader> m_reader;
|
||||
|
||||
u64 m_data_app_offset = 0;
|
||||
std::vector<u8> m_certificate_chain;
|
||||
IOS::ES::TicketReader m_ticket;
|
||||
IOS::ES::TMDReader m_tmd;
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "DolphinQt2/GameList/GameFile.h"
|
||||
#include "DolphinQt2/Resources.h"
|
||||
#include "DolphinQt2/Settings.h"
|
||||
#include "UICommon/WiiUtils.h"
|
||||
|
||||
static const int CACHE_VERSION = 13; // Last changed in PR #3261
|
||||
static const int DATASTREAM_VERSION = QDataStream::Qt_5_5;
|
||||
@ -63,6 +64,17 @@ GameFile::GameFile(const QString& path) : m_path(path)
|
||||
m_valid = true;
|
||||
}
|
||||
|
||||
bool GameFile::IsValid() const
|
||||
{
|
||||
if (!m_valid)
|
||||
return false;
|
||||
|
||||
if (m_platform == DiscIO::Platform::WII_WAD && !IOS::ES::IsChannel(m_title_id))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString GameFile::GetCacheFileName() const
|
||||
{
|
||||
QString folder = QString::fromStdString(File::GetUserPath(D_CACHE_IDX));
|
||||
@ -320,7 +332,7 @@ bool GameFile::Install()
|
||||
{
|
||||
_assert_(m_platform == DiscIO::Platform::WII_WAD);
|
||||
|
||||
return DiscIO::CNANDContentManager::Access().Install_WiiWAD(m_path.toStdString());
|
||||
return WiiUtils::InstallWAD(m_path.toStdString());
|
||||
}
|
||||
|
||||
bool GameFile::Uninstall()
|
||||
|
@ -27,7 +27,7 @@ class GameFile final
|
||||
public:
|
||||
explicit GameFile(const QString& path);
|
||||
|
||||
bool IsValid() const { return m_valid; }
|
||||
bool IsValid() const;
|
||||
// These will be properly initialized before we try to load the file.
|
||||
QString GetFilePath() const { return m_path; }
|
||||
QString GetFileName() const { return m_file_name; }
|
||||
|
@ -83,6 +83,8 @@
|
||||
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
#include "UICommon/WiiUtils.h"
|
||||
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
#include "VideoCommon/VideoBackendBase.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
@ -1209,11 +1211,8 @@ void CFrame::OnInstallWAD(wxCommandEvent& event)
|
||||
wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME |
|
||||
wxPD_REMAINING_TIME | wxPD_SMOOTH);
|
||||
|
||||
u64 titleID = DiscIO::CNANDContentManager::Access().Install_WiiWAD(fileName);
|
||||
if (titleID == TITLEID_SYSMENU)
|
||||
{
|
||||
if (WiiUtils::InstallWAD(fileName))
|
||||
UpdateLoadWiiMenuItem();
|
||||
}
|
||||
}
|
||||
|
||||
void CFrame::OnUninstallWAD(wxCommandEvent&)
|
||||
|
@ -28,6 +28,7 @@
|
||||
|
||||
#include "Core/Boot/Boot.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/IOS/ES/Formats.h"
|
||||
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
@ -177,6 +178,17 @@ GameListItem::~GameListItem()
|
||||
{
|
||||
}
|
||||
|
||||
bool GameListItem::IsValid() const
|
||||
{
|
||||
if (!m_Valid)
|
||||
return false;
|
||||
|
||||
if (m_Platform == DiscIO::Platform::WII_WAD && !IOS::ES::IsChannel(m_title_id))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameListItem::ReloadINI()
|
||||
{
|
||||
if (!IsValid())
|
||||
|
@ -35,7 +35,7 @@ public:
|
||||
// Reload settings after INI changes
|
||||
void ReloadINI();
|
||||
|
||||
bool IsValid() const { return m_Valid; }
|
||||
bool IsValid() const;
|
||||
const std::string& GetFileName() const { return m_FileName; }
|
||||
std::string GetName(DiscIO::Language language) const;
|
||||
std::string GetName() const;
|
||||
|
@ -3,6 +3,7 @@ set(SRCS
|
||||
Disassembler.cpp
|
||||
UICommon.cpp
|
||||
USBUtils.cpp
|
||||
WiiUtils.cpp
|
||||
)
|
||||
|
||||
if(USE_X11)
|
||||
|
@ -59,12 +59,14 @@
|
||||
<ClCompile Include="USBUtils.cpp">
|
||||
<DisableSpecificWarnings>4200;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WiiUtils.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="CommandLineParse.h" />
|
||||
<ClInclude Include="UICommon.h" />
|
||||
<ClInclude Include="Disassembler.h" />
|
||||
<ClInclude Include="USBUtils.h" />
|
||||
<ClInclude Include="WiiUtils.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(ExternalsDir)cpp-optparse\cpp-optparse.vcxproj">
|
||||
@ -74,4 +76,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
64
Source/Core/UICommon/WiiUtils.cpp
Normal file
64
Source/Core/UICommon/WiiUtils.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "UICommon/WiiUtils.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Core/IOS/ES/ES.h"
|
||||
#include "Core/IOS/ES/Formats.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "DiscIO/NANDContentLoader.h"
|
||||
#include "DiscIO/WiiWad.h"
|
||||
|
||||
namespace WiiUtils
|
||||
{
|
||||
bool InstallWAD(const std::string& wad_path)
|
||||
{
|
||||
const DiscIO::WiiWAD wad{wad_path};
|
||||
if (!wad.IsValid())
|
||||
{
|
||||
PanicAlertT("WAD installation failed: The selected file is not a valid WAD.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto tmd = wad.GetTMD();
|
||||
IOS::HLE::Kernel ios;
|
||||
const auto es = ios.GetES();
|
||||
|
||||
IOS::HLE::Device::ES::Context context;
|
||||
if (es->ImportTicket(wad.GetTicket().GetRawTicket()) < 0 ||
|
||||
es->ImportTitleInit(context, tmd.GetRawTMD()) < 0)
|
||||
{
|
||||
PanicAlertT("WAD installation failed: Could not initialise title import.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool contents_imported = [&]() {
|
||||
const u64 title_id = tmd.GetTitleId();
|
||||
for (const IOS::ES::Content& content : tmd.GetContents())
|
||||
{
|
||||
const std::vector<u8> data = wad.GetContent(content.index);
|
||||
|
||||
if (es->ImportContentBegin(context, title_id, content.id) < 0 ||
|
||||
es->ImportContentData(context, 0, data.data(), static_cast<u32>(data.size())) < 0 ||
|
||||
es->ImportContentEnd(context, 0) < 0)
|
||||
{
|
||||
PanicAlertT("WAD installation failed: Could not import content %08x.", content.id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}();
|
||||
|
||||
if ((contents_imported && es->ImportTitleDone(context) < 0) ||
|
||||
(!contents_imported && es->ImportTitleCancel(context) < 0))
|
||||
{
|
||||
PanicAlertT("WAD installation failed: Could not finalise title import.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DiscIO::CNANDContentManager::Access().ClearCache();
|
||||
return true;
|
||||
}
|
||||
}
|
14
Source/Core/UICommon/WiiUtils.h
Normal file
14
Source/Core/UICommon/WiiUtils.h
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
// Small utility functions for common Wii related tasks.
|
||||
|
||||
namespace WiiUtils
|
||||
{
|
||||
bool InstallWAD(const std::string& wad_path);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user