Merge pull request #5612 from leoetlino/verify-signatures

IOS/ES: Add signature verification
This commit is contained in:
Leo Lam 2017-06-16 16:36:26 +02:00 committed by GitHub
commit 37208d2874
8 changed files with 339 additions and 39 deletions

View file

@ -286,6 +286,7 @@ void SConfig::SaveCoreSettings(IniFile& ini)
core->Set("PerfMapDir", m_perfDir);
core->Set("EnableCustomRTC", bEnableCustomRTC);
core->Set("CustomRTCValue", m_customRTCValue);
core->Set("EnableSignatureChecks", m_enable_signature_checks);
}
void SConfig::SaveMovieSettings(IniFile& ini)
@ -608,6 +609,7 @@ void SConfig::LoadCoreSettings(IniFile& ini)
core->Get("EnableCustomRTC", &bEnableCustomRTC, false);
// Default to seconds between 1.1.1970 and 1.1.2000
core->Get("CustomRTCValue", &m_customRTCValue, 946684800);
core->Get("EnableSignatureChecks", &m_enable_signature_checks, true);
}
void SConfig::LoadMovieSettings(IniFile& ini)

View file

@ -168,6 +168,8 @@ struct SConfig : NonCopyable
std::set<std::pair<u16, u16>> m_usb_passthrough_devices;
bool IsUSBDeviceWhitelisted(std::pair<u16, u16> vid_pid) const;
bool m_enable_signature_checks = true;
// SYSCONF settings
int m_sensor_bar_position = 0x01;
int m_sensor_bar_sensitivity = 0x03;

View file

@ -19,38 +19,39 @@ namespace HLE
{
enum ReturnCode : s32
{
IPC_SUCCESS = 0, // Success
IPC_EACCES = -1, // Permission denied
IPC_EEXIST = -2, // File exists
IPC_EINVAL = -4, // Invalid argument or fd
IPC_EMAX = -5, // Too many file descriptors open
IPC_ENOENT = -6, // File not found
IPC_EQUEUEFULL = -8, // Queue full
IPC_EIO = -12, // ECC error
IPC_ENOMEM = -22, // Alloc failed during request
FS_EINVAL = -101, // Invalid path
FS_EACCESS = -102, // Permission denied
FS_ECORRUPT = -103, // Corrupted NAND
FS_EEXIST = -105, // File exists
FS_ENOENT = -106, // No such file or directory
FS_ENFILE = -107, // Too many fds open
FS_EFBIG = -108, // Max block count reached?
FS_EFDEXHAUSTED = -109, // Too many fds open
FS_ENAMELEN = -110, // Pathname is too long
FS_EFDOPEN = -111, // FD is already open
FS_EIO = -114, // ECC error
FS_ENOTEMPTY = -115, // Directory not empty
FS_EDIRDEPTH = -116, // Max directory depth exceeded
FS_EBUSY = -118, // Resource busy
ES_SHORT_READ = -1009, // Short read
ES_EIO = -1010, // Write failure
IPC_SUCCESS = 0, // Success
IPC_EACCES = -1, // Permission denied
IPC_EEXIST = -2, // File exists
IPC_EINVAL = -4, // Invalid argument or fd
IPC_EMAX = -5, // Too many file descriptors open
IPC_ENOENT = -6, // File not found
IPC_EQUEUEFULL = -8, // Queue full
IPC_EIO = -12, // ECC error
IPC_ENOMEM = -22, // Alloc failed during request
FS_EINVAL = -101, // Invalid path
FS_EACCESS = -102, // Permission denied
FS_ECORRUPT = -103, // Corrupted NAND
FS_EEXIST = -105, // File exists
FS_ENOENT = -106, // No such file or directory
FS_ENFILE = -107, // Too many fds open
FS_EFBIG = -108, // Max block count reached?
FS_EFDEXHAUSTED = -109, // Too many fds open
FS_ENAMELEN = -110, // Pathname is too long
FS_EFDOPEN = -111, // FD is already open
FS_EIO = -114, // ECC error
FS_ENOTEMPTY = -115, // Directory not empty
FS_EDIRDEPTH = -116, // Max directory depth exceeded
FS_EBUSY = -118, // Resource busy
ES_SHORT_READ = -1009, // Short read
ES_EIO = -1010, // Write failure
ES_INVALID_SIGNATURE_TYPE = -1012,
ES_FD_EXHAUSTED = -1016, // Max of 3 ES handles exceeded
ES_EINVAL = -1017, // Invalid argument
ES_DEVICE_ID_MISMATCH = -1018,
ES_HASH_MISMATCH = -1022, // Decrypted content hash doesn't match with the hash from the TMD
ES_ENOMEM = -1024, // Alloc failed during request
ES_EACCES = -1026, // Incorrect access rights (according to TMD)
ES_INVALID_TMD_SIGNATURE_TYPE = -1027,
ES_UNKNOWN_ISSUER = -1027,
ES_NO_TICKET = -1028,
ES_INVALID_TICKET = -1029,
IOSC_EACCES = -2000,

View file

@ -11,15 +11,20 @@
#include <utility>
#include <vector>
#include <mbedtls/sha1.h>
#include "Common/ChunkFile.h"
#include "Common/File.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/NandPaths.h"
#include "Common/ScopeGuard.h"
#include "Common/StringUtil.h"
#include "Core/ConfigManager.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/IOS/IOSC.h"
#include "DiscIO/NANDContentLoader.h"
namespace IOS
@ -35,8 +40,46 @@ static TitleContext s_title_context;
// Title to launch after IOS has been reset and reloaded (similar to /sys/launch.sys).
static u64 s_title_to_launch;
struct DirectoryToCreate
{
const char* path;
u32 attributes;
OpenMode owner_perm;
OpenMode group_perm;
OpenMode other_perm;
};
constexpr std::array<DirectoryToCreate, 9> s_directories_to_create = {{
{"/sys", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE},
{"/ticket", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE},
{"/title", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_READ},
{"/shared1", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE},
{"/shared2", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW},
{"/tmp", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW},
{"/import", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE},
{"/meta", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_RW},
{"/wfs", 0, OpenMode::IOS_OPEN_RW, OpenMode::IOS_OPEN_NONE, OpenMode::IOS_OPEN_NONE},
}};
ES::ES(Kernel& ios, const std::string& device_name) : Device(ios, device_name)
{
for (const auto& directory : s_directories_to_create)
{
const std::string path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + directory.path;
// Create the directory if it does not exist.
if (File::IsDirectory(path))
continue;
File::CreateFullPath(path);
if (File::CreateDir(path))
INFO_LOG(IOS_ES, "Created %s (at %s)", directory.path, path.c_str());
else
ERROR_LOG(IOS_ES, "Failed to create %s (at %s)", directory.path, path.c_str());
// TODO: Set permissions.
}
FinishAllStaleImports();
s_content_file = "";
@ -610,6 +653,9 @@ s32 ES::DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& tic
if (!File::Exists(tmd_path))
{
// XXX: We are supposed to verify the TMD and ticket here, but cannot because
// this may cause issues with custom/patched games.
File::IOFile tmd_file(tmd_path, "wb");
const std::vector<u8>& tmd_bytes = tmd.GetBytes();
if (!tmd_file.WriteBytes(tmd_bytes.data(), tmd_bytes.size()))
@ -692,6 +738,19 @@ ReturnCode ES::SetUpStreamKey(const u32 uid, const u8* ticket_view, const IOS::E
if (ticket_bytes.empty())
return ES_NO_TICKET;
std::vector<u8> cert_store;
ret = ReadCertStore(&cert_store);
if (ret != IPC_SUCCESS)
return ret;
ret = VerifyContainer(VerifyContainerType::TMD, VerifyMode::UpdateCertStore, tmd, cert_store);
if (ret != IPC_SUCCESS)
return ret;
ret = VerifyContainer(VerifyContainerType::Ticket, VerifyMode::UpdateCertStore, installed_ticket,
cert_store);
if (ret != IPC_SUCCESS)
return ret;
// Create the handle and return it.
std::array<u8, 16> iv{};
std::memcpy(iv.data(), &title_id, sizeof(title_id));
@ -760,6 +819,151 @@ bool ES::IsActiveTitlePermittedByTicket(const u8* ticket_view) const
Common::swap32(ticket_view + offsetof(IOS::ES::TicketView, permitted_title_id));
return title_identifier && (title_identifier & ~permitted_title_mask) == permitted_title_id;
}
bool ES::IsIssuerCorrect(VerifyContainerType type, const IOS::ES::CertReader& issuer_cert) const
{
switch (type)
{
case VerifyContainerType::TMD:
return issuer_cert.GetName().compare(0, 2, "CP") == 0;
case VerifyContainerType::Ticket:
return issuer_cert.GetName().compare(0, 2, "XS") == 0;
case VerifyContainerType::Device:
return issuer_cert.GetName().compare(0, 2, "MS") == 0;
default:
return false;
}
}
ReturnCode ES::ReadCertStore(std::vector<u8>* buffer) const
{
const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys";
File::IOFile store_file{store_path, "rb"};
if (!store_file)
return FS_ENOENT;
buffer->resize(store_file.GetSize());
if (!store_file.ReadBytes(buffer->data(), buffer->size()))
return ES_SHORT_READ;
return IPC_SUCCESS;
}
ReturnCode ES::WriteNewCertToStore(const IOS::ES::CertReader& cert)
{
const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys";
// The certificate store file may not exist, so we use a+b and not r+b here.
File::IOFile store_file{store_path, "a+b"};
if (!store_file)
return ES_EIO;
// Read the current store to determine if the new cert needs to be written.
const u64 file_size = store_file.GetSize();
if (file_size != 0)
{
std::vector<u8> certs_bytes(file_size);
if (!store_file.ReadBytes(certs_bytes.data(), certs_bytes.size()))
return ES_SHORT_READ;
const std::map<std::string, IOS::ES::CertReader> certs = IOS::ES::ParseCertChain(certs_bytes);
// The cert is already present in the store. Nothing to do.
if (certs.find(cert.GetName()) != certs.end())
return IPC_SUCCESS;
}
// Otherwise, write the new cert at the end of the store.
// When opening a file in read-write mode, a seek is required before a write.
store_file.Seek(0, SEEK_END);
if (!store_file.WriteBytes(cert.GetBytes().data(), cert.GetBytes().size()))
return ES_EIO;
return IPC_SUCCESS;
}
ReturnCode ES::VerifyContainer(VerifyContainerType type, VerifyMode mode,
const IOS::ES::SignedBlobReader& signed_blob,
const std::vector<u8>& cert_chain, u32 iosc_handle)
{
if (!SConfig::GetInstance().m_enable_signature_checks)
return IPC_SUCCESS;
if (!signed_blob.IsSignatureValid())
return ES_EINVAL;
// A blob should have exactly 3 parent issuers.
// Example for a ticket: "Root-CA00000001-XS00000003" => {"Root", "CA00000001", "XS00000003"}
const std::string issuer = signed_blob.GetIssuer();
const std::vector<std::string> parents = SplitString(issuer, '-');
if (parents.size() != 3)
return ES_EINVAL;
// Find the direct issuer and the CA certificates for the blob.
const std::map<std::string, IOS::ES::CertReader> certs = IOS::ES::ParseCertChain(cert_chain);
const auto issuer_cert_iterator = certs.find(parents[2]);
const auto ca_cert_iterator = certs.find(parents[1]);
if (issuer_cert_iterator == certs.end() || ca_cert_iterator == certs.end())
return ES_UNKNOWN_ISSUER;
const IOS::ES::CertReader& issuer_cert = issuer_cert_iterator->second;
const IOS::ES::CertReader& ca_cert = ca_cert_iterator->second;
// Some blobs can only be signed by specific certificates.
if (!IsIssuerCorrect(type, issuer_cert))
return ES_EINVAL;
// Verify the whole cert chain using IOSC.
// IOS assumes that the CA cert will always be signed by the root certificate,
// and that the issuer is signed by the CA.
IOSC& iosc = m_ios.GetIOSC();
IOSC::Handle handle;
// Create and initialise a handle for the CA cert and the issuer cert.
ReturnCode ret = iosc.CreateObject(&handle, IOSC::TYPE_PUBLIC_KEY, IOSC::SUBTYPE_RSA2048, PID_ES);
if (ret != IPC_SUCCESS)
return ret;
Common::ScopeGuard ca_guard{[&] { iosc.DeleteObject(handle, PID_ES); }};
ret = iosc.ImportCertificate(ca_cert.GetBytes().data(), IOSC::HANDLE_ROOT_KEY, handle, PID_ES);
if (ret != IPC_SUCCESS)
return ret;
IOSC::Handle issuer_handle;
const IOSC::ObjectSubType subtype =
type == VerifyContainerType::Device ? IOSC::SUBTYPE_ECC233 : IOSC::SUBTYPE_RSA2048;
ret = iosc.CreateObject(&issuer_handle, IOSC::TYPE_PUBLIC_KEY, subtype, PID_ES);
if (ret != IPC_SUCCESS)
return ret;
Common::ScopeGuard issuer_guard{[&] { iosc.DeleteObject(issuer_handle, PID_ES); }};
ret = iosc.ImportCertificate(issuer_cert.GetBytes().data(), handle, issuer_handle, PID_ES);
if (ret != IPC_SUCCESS)
return ret;
// Calculate the SHA1 of the signed blob.
const size_t skip = type == VerifyContainerType::Device ? offsetof(SignatureECC, issuer) :
offsetof(SignatureRSA2048, issuer);
std::array<u8, 20> sha1;
mbedtls_sha1(signed_blob.GetBytes().data() + skip, signed_blob.GetBytes().size() - skip,
sha1.data());
// Verify the signature.
const std::vector<u8> signature = signed_blob.GetSignatureData();
ret = iosc.VerifyPublicKeySign(sha1, issuer_handle, signature.data(), PID_ES);
if (ret != IPC_SUCCESS)
return ret;
if (mode == VerifyMode::UpdateCertStore)
{
ret = WriteNewCertToStore(issuer_cert);
if (ret != IPC_SUCCESS)
ERROR_LOG(IOS_ES, "VerifyContainer: Writing the issuer cert failed with return code %d", ret);
ret = WriteNewCertToStore(ca_cert);
if (ret != IPC_SUCCESS)
ERROR_LOG(IOS_ES, "VerifyContainer: Writing the CA cert failed with return code %d", ret);
}
// Import the signed blob to iosc_handle (if a handle was passed to us).
if (ret == IPC_SUCCESS && iosc_handle)
ret = iosc.ImportCertificate(signed_blob.GetBytes().data(), issuer_handle, iosc_handle, PID_ES);
return ret;
}
} // namespace Device
} // namespace HLE
} // namespace IOS

View file

@ -112,9 +112,10 @@ public:
std::vector<std::array<u8, 20>> GetSharedContents() const;
// Title management
ReturnCode ImportTicket(const std::vector<u8>& ticket_bytes);
ReturnCode ImportTicket(const std::vector<u8>& ticket_bytes, const std::vector<u8>& cert_chain);
ReturnCode ImportTmd(Context& context, const std::vector<u8>& tmd_bytes);
ReturnCode ImportTitleInit(Context& context, const std::vector<u8>& tmd_bytes);
ReturnCode ImportTitleInit(Context& context, const std::vector<u8>& tmd_bytes,
const std::vector<u8>& cert_chain);
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);
@ -306,6 +307,25 @@ private:
ReturnCode CheckStreamKeyPermissions(u32 uid, const u8* ticket_view,
const IOS::ES::TMDReader& tmd) const;
enum class VerifyContainerType
{
TMD,
Ticket,
Device,
};
enum class VerifyMode
{
// Whether or not new certificates should be added to the certificate store (/sys/cert.sys).
DoNotUpdateCertStore,
UpdateCertStore,
};
bool IsIssuerCorrect(VerifyContainerType type, const IOS::ES::CertReader& issuer_cert) const;
ReturnCode ReadCertStore(std::vector<u8>* buffer) const;
ReturnCode WriteNewCertToStore(const IOS::ES::CertReader& cert);
ReturnCode VerifyContainer(VerifyContainerType type, VerifyMode mode,
const IOS::ES::SignedBlobReader& signed_blob,
const std::vector<u8>& cert_chain, u32 iosc_handle = 0);
// Start a title import.
bool InitImport(u64 title_id);
// Clean up the import content directory and move it back to /title.

View file

@ -97,7 +97,21 @@ IPCCommandResult ES::GetTMDStoredContents(const IOCtlVRequest& request)
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);
const IOS::ES::TMDReader tmd{std::move(tmd_bytes)};
if (!tmd.IsValid())
return GetDefaultReply(ES_EINVAL);
std::vector<u8> cert_store;
ReturnCode ret = ReadCertStore(&cert_store);
if (ret != IPC_SUCCESS)
return GetDefaultReply(ret);
ret = VerifyContainer(VerifyContainerType::TMD, VerifyMode::UpdateCertStore, tmd, cert_store);
if (ret != IPC_SUCCESS)
return GetDefaultReply(ret);
return GetStoredContents(tmd, request);
}
IPCCommandResult ES::GetTitleCount(const std::vector<u64>& titles, const IOCtlVRequest& request)

View file

@ -37,7 +37,6 @@ static ReturnCode WriteTicket(const IOS::ES::TicketReader& ticket)
const std::string ticket_path = Common::GetTicketFileName(title_id, Common::FROM_SESSION_ROOT);
File::CreateFullPath(ticket_path);
File::IOFile ticket_file(ticket_path, "wb");
if (!ticket_file)
return ES_EIO;
@ -46,7 +45,7 @@ static ReturnCode WriteTicket(const IOS::ES::TicketReader& ticket)
return ticket_file.WriteBytes(raw_ticket.data(), raw_ticket.size()) ? IPC_SUCCESS : ES_EIO;
}
ReturnCode ES::ImportTicket(const std::vector<u8>& ticket_bytes)
ReturnCode ES::ImportTicket(const std::vector<u8>& ticket_bytes, const std::vector<u8>& cert_chain)
{
IOS::ES::TicketReader ticket{ticket_bytes};
if (!ticket.IsValid())
@ -70,6 +69,11 @@ ReturnCode ES::ImportTicket(const std::vector<u8>& ticket_bytes)
}
}
const ReturnCode verify_ret =
VerifyContainer(VerifyContainerType::Ticket, VerifyMode::UpdateCertStore, ticket, cert_chain);
if (verify_ret != IPC_SUCCESS)
return verify_ret;
const ReturnCode write_ret = WriteTicket(ticket);
if (write_ret != IPC_SUCCESS)
return write_ret;
@ -85,7 +89,9 @@ IPCCommandResult ES::ImportTicket(const IOCtlVRequest& request)
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));
std::vector<u8> cert_chain(request.in_vectors[1].size);
Memory::CopyFromEmu(bytes.data(), request.in_vectors[1].address, request.in_vectors[1].size);
return GetDefaultReply(ImportTicket(bytes, cert_chain));
}
ReturnCode ES::ImportTmd(Context& context, const std::vector<u8>& tmd_bytes)
@ -93,10 +99,23 @@ 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;
std::vector<u8> cert_store;
ReturnCode ret = ReadCertStore(&cert_store);
if (ret != IPC_SUCCESS)
return ret;
ret = VerifyContainer(VerifyContainerType::TMD, VerifyMode::UpdateCertStore,
context.title_import.tmd, cert_store);
if (ret != IPC_SUCCESS)
{
// Reset the import context so that further calls consider the state as invalid.
context.title_import.tmd.SetBytes({});
return ret;
}
if (!InitImport(context.title_import.tmd.GetTitleId()))
return ES_EIO;
@ -116,7 +135,8 @@ IPCCommandResult ES::ImportTmd(Context& context, const IOCtlVRequest& request)
return GetDefaultReply(ImportTmd(context, tmd));
}
ReturnCode ES::ImportTitleInit(Context& context, const std::vector<u8>& tmd_bytes)
ReturnCode ES::ImportTitleInit(Context& context, const std::vector<u8>& tmd_bytes,
const std::vector<u8>& cert_chain)
{
INFO_LOG(IOS_ES, "ImportTitleInit");
context.title_import.tmd.SetBytes(tmd_bytes);
@ -129,11 +149,34 @@ ReturnCode ES::ImportTitleInit(Context& context, const std::vector<u8>& tmd_byte
// Finish a previous import (if it exists).
FinishStaleImport(context.title_import.tmd.GetTitleId());
ReturnCode ret = VerifyContainer(VerifyContainerType::TMD, VerifyMode::UpdateCertStore,
context.title_import.tmd, cert_chain);
if (ret != IPC_SUCCESS)
{
context.title_import.tmd.SetBytes({});
return ret;
}
const auto ticket = DiscIO::FindSignedTicket(context.title_import.tmd.GetTitleId());
if (!ticket.IsValid())
return ES_NO_TICKET;
std::vector<u8> cert_store;
ret = ReadCertStore(&cert_store);
if (ret != IPC_SUCCESS)
return ret;
ret = VerifyContainer(VerifyContainerType::Ticket, VerifyMode::DoNotUpdateCertStore, ticket,
cert_store);
if (ret != IPC_SUCCESS)
{
context.title_import.tmd.SetBytes({});
return ret;
}
if (!InitImport(context.title_import.tmd.GetTitleId()))
return ES_EIO;
// TODO: check and use the other vectors.
return IPC_SUCCESS;
}
@ -147,7 +190,9 @@ IPCCommandResult ES::ImportTitleInit(Context& context, const IOCtlVRequest& requ
std::vector<u8> tmd(request.in_vectors[0].size);
Memory::CopyFromEmu(tmd.data(), request.in_vectors[0].address, request.in_vectors[0].size);
return GetDefaultReply(ImportTitleInit(context, tmd));
std::vector<u8> certs(request.in_vectors[1].size);
Memory::CopyFromEmu(certs.data(), request.in_vectors[1].address, request.in_vectors[1].size);
return GetDefaultReply(ImportTitleInit(context, tmd, certs));
}
ReturnCode ES::ImportContentBegin(Context& context, u64 title_id, u32 content_id)

View file

@ -5,6 +5,7 @@
#include "UICommon/WiiUtils.h"
#include "Common/CommonTypes.h"
#include "Common/MsgHandler.h"
#include "Core/ConfigManager.h"
#include "Core/IOS/ES/ES.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/IOS/IOS.h"
@ -27,12 +28,23 @@ bool InstallWAD(const std::string& wad_path)
const auto es = ios.GetES();
IOS::HLE::Device::ES::Context context;
if (es->ImportTicket(wad.GetTicket().GetBytes()) < 0 ||
es->ImportTitleInit(context, tmd.GetBytes()) < 0)
IOS::HLE::ReturnCode ret;
const bool checks_enabled = SConfig::GetInstance().m_enable_signature_checks;
while ((ret = es->ImportTicket(wad.GetTicket().GetBytes(), wad.GetCertificateChain())) < 0 ||
(ret = es->ImportTitleInit(context, tmd.GetBytes(), wad.GetCertificateChain())) < 0)
{
if (checks_enabled && ret == IOS::HLE::IOSC_FAIL_CHECKVALUE &&
AskYesNoT("This WAD has not been signed by Nintendo. Continue to import?"))
{
SConfig::GetInstance().m_enable_signature_checks = false;
continue;
}
SConfig::GetInstance().m_enable_signature_checks = checks_enabled;
PanicAlertT("WAD installation failed: Could not initialise title import.");
return false;
}
SConfig::GetInstance().m_enable_signature_checks = checks_enabled;
const bool contents_imported = [&]() {
const u64 title_id = tmd.GetTitleId();