Merge pull request #5020 from leoetlino/es-safer-import

IOS/ES: Implement ES_AddTitleCancel (and slightly safer import process)
This commit is contained in:
Matthew Parlane 2017-03-12 16:14:17 +13:00 committed by GitHub
commit b1bd231421
4 changed files with 121 additions and 29 deletions

View File

@ -23,6 +23,13 @@ std::string RootUserPath(FromWhichRoot from)
return File::GetUserPath(idx);
}
std::string GetImportTitlePath(u64 title_id, FromWhichRoot from)
{
return RootUserPath(from) + StringFromFormat("/import/%08x/%08x",
static_cast<u32>(title_id >> 32),
static_cast<u32>(title_id));
}
std::string GetTicketFileName(u64 _titleID, FromWhichRoot from)
{
return StringFromFormat("%s/ticket/%08x/%08x.tik", RootUserPath(from).c_str(),

View File

@ -22,6 +22,10 @@ enum FromWhichRoot
};
std::string RootUserPath(FromWhichRoot from);
// Returns /import/%08x/%08x. Intended for use by ES.
std::string GetImportTitlePath(u64 title_id, FromWhichRoot from = FROM_SESSION_ROOT);
std::string GetTicketFileName(u64 _titleID, FromWhichRoot from);
std::string GetTMDFileName(u64 _titleID, FromWhichRoot from);
std::string GetTitleDataPath(u64 _titleID, FromWhichRoot from);

View File

@ -86,6 +86,10 @@ ES::ES(u32 device_id, const std::string& device_name) : Device(device_id, device
void ES::Init()
{
const std::string import_dir = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/import";
File::DeleteDirRecursively(import_dir);
File::CreateDir(import_dir);
s_content_file = "";
s_title_context = TitleContext{};
@ -322,6 +326,8 @@ IPCCommandResult ES::IOCtlV(const IOCtlVRequest& request)
return AddContentFinish(request);
case IOCTL_ES_ADDTITLEFINISH:
return AddTitleFinish(request);
case IOCTL_ES_ADDTITLECANCEL:
return AddTitleCancel(request);
case IOCTL_ES_GETDEVICEID:
return ESGetDeviceID(request);
case IOCTL_ES_OPENTITLECONTENT:
@ -459,33 +465,44 @@ IPCCommandResult ES::AddTicket(const IOCtlVRequest& request)
return GetDefaultReply(IPC_SUCCESS);
}
// TODO: write this to /tmp (or /import?) first, as title imports can be cancelled.
static bool WriteTMD(const IOS::ES::TMDReader& tmd)
static bool WriteImportTMD(const IOS::ES::TMDReader& tmd)
{
const std::string tmd_path = Common::GetTMDFileName(tmd.GetTitleId(), Common::FROM_SESSION_ROOT);
const std::string tmd_path = Common::GetImportTitlePath(tmd.GetTitleId()) + "/content/title.tmd";
File::CreateFullPath(tmd_path);
File::IOFile fp(tmd_path, "wb");
return fp.WriteBytes(tmd.GetRawTMD().data(), tmd.GetRawTMD().size());
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)
{
// This may appear to be very similar to AddTitleStart, but AddTitleStart takes
// three additional vectors and may do some additional processing -- so let's keep these separate.
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);
m_addtitle_tmd.SetBytes(tmd);
// 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);
if (!WriteTMD(m_addtitle_tmd))
return GetDefaultReply(ES_WRITE_FAILURE);
DiscIO::cUIDsys uid_sys{Common::FROM_CONFIGURED_ROOT};
uid_sys.AddTitle(m_addtitle_tmd.GetTitleId());
return GetDefaultReply(IPC_SUCCESS);
}
@ -506,12 +523,11 @@ IPCCommandResult ES::AddTitleStart(const IOCtlVRequest& request)
return GetDefaultReply(ES_INVALID_TMD);
}
if (!WriteTMD(m_addtitle_tmd))
return GetDefaultReply(ES_WRITE_FAILURE);
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);
}
@ -583,6 +599,9 @@ 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);
@ -618,6 +637,46 @@ IPCCommandResult ES::AddContentFinish(const IOCtlVRequest& request)
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())
{
@ -626,25 +685,46 @@ IPCCommandResult ES::AddContentFinish(const IOCtlVRequest& request)
}
else
{
content_path = StringFromFormat(
"%s%08x.app",
Common::GetTitleContentPath(m_addtitle_tmd.GetTitleId(), Common::FROM_SESSION_ROOT).c_str(),
m_addtitle_content_id);
content_path =
StringFromFormat("%s%08x.app", Common::GetTitleContentPath(m_addtitle_tmd.GetTitleId(),
Common::FROM_SESSION_ROOT)
.c_str(),
content_info.id);
}
File::IOFile fp(content_path, "wb");
fp.WriteBytes(decrypted_data.data(), content_info.size);
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);
}
m_addtitle_content_id = 0xFFFFFFFF;
// 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::AddTitleFinish(const IOCtlVRequest& request)
IPCCommandResult ES::AddTitleCancel(const IOCtlVRequest& request)
{
if (!request.HasNumberOfValidVectors(0, 0) || !m_addtitle_tmd.IsValid())
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
INFO_LOG(IOS_ES, "IOCTL_ES_ADDTITLEFINISH");
AbortImport(m_addtitle_tmd.GetTitleId(), {});
m_addtitle_tmd.SetBytes({});
return GetDefaultReply(IPC_SUCCESS);
}

View File

@ -157,6 +157,7 @@ private:
IPCCommandResult AddContentData(const IOCtlVRequest& request);
IPCCommandResult AddContentFinish(const IOCtlVRequest& request);
IPCCommandResult AddTitleFinish(const IOCtlVRequest& request);
IPCCommandResult AddTitleCancel(const IOCtlVRequest& request);
IPCCommandResult ESGetDeviceID(const IOCtlVRequest& request);
IPCCommandResult OpenTitleContent(const IOCtlVRequest& request);
IPCCommandResult OpenContent(const IOCtlVRequest& request);