VolumeVerifier: Add extra validity checks for ticket and TMD

This fixes VolumeVerifier potentially calling TMDReader::GetIOSId for
invalid TMDs.

VolumeVerifier also has a call to TMDReader::GetContent that doesn't
check if the TMD is valid. In practice, this can't get called with an
invalid TMD because the previous commit made it so GetContentOffsets
returns an empty vector if the TMD is invalid, but I've added a check
inside TMDReader::GetContent just to be on the safe side.

I also made VolumeVerifier show a specifically worded problem if the
ticket or TMD is invalid. Before, invalid TMDs in Wii discs and WADs
and invalid tickets in WADs would show a more generic problem.
This commit is contained in:
JosJuice 2026-04-20 20:05:19 +02:00
parent 97fff931fd
commit a992245aa2
2 changed files with 66 additions and 35 deletions

View File

@ -322,7 +322,7 @@ u16 TMDReader::GetNumContents() const
bool TMDReader::GetContent(u16 index, Content* content) const
{
if (index >= GetNumContents())
if (!IsValid() || index >= GetNumContents())
{
return false;
}

View File

@ -646,44 +646,65 @@ bool VolumeVerifier::CheckPartition(const Partition& partition)
if (type == PARTITION_UPDATE)
{
const IOS::ES::TMDReader& tmd = m_volume.GetTMD(m_volume.GetGamePartition());
// IOS9 is the only IOS which can be assumed to exist in a working state on any Wii
// regardless of what updates have been installed. At least Mario Party 8
// (RM8E01, revision 2) uses IOS9 without having it in its update partition.
const u64 ios_ver = tmd.GetIOSId() & 0xFF;
bool has_correct_ios = tmd.IsValid() && ios_ver == 9;
if (!has_correct_ios && tmd.IsValid())
if (tmd.IsValid())
{
std::unique_ptr<FileInfo> file_info = filesystem->FindFileInfo("_sys");
if (file_info)
const u64 ios_ver = tmd.GetIOSId() & 0xFF;
bool has_correct_ios = false;
if (ios_ver == 9)
{
const std::string ios_ver_str = std::to_string(ios_ver);
const std::string correct_ios =
IsDebugSigned() ? ("firmware.64." + ios_ver_str + ".") : ("ios" + ios_ver_str + "-");
for (const FileInfo& f : *file_info)
// IOS9 is the only IOS which can be assumed to exist in a working state on any Wii
// regardless of what updates have been installed. At least Mario Party 8
// (RM8E01, revision 2) uses IOS9 without having it in its update partition.
has_correct_ios = true;
}
else
{
std::unique_ptr<FileInfo> file_info = filesystem->FindFileInfo("_sys");
if (file_info)
{
std::string file_name = f.GetName();
Common::ToLower(&file_name);
if (file_name.starts_with(correct_ios))
const std::string ios_ver_str = std::to_string(ios_ver);
const std::string correct_ios =
IsDebugSigned() ? ("firmware.64." + ios_ver_str + ".") : ("ios" + ios_ver_str + "-");
for (const FileInfo& f : *file_info)
{
has_correct_ios = true;
break;
std::string file_name = f.GetName();
Common::ToLower(&file_name);
if (file_name.starts_with(correct_ios))
{
has_correct_ios = true;
break;
}
}
}
}
}
if (!has_correct_ios)
{
// This is reached for hacked dumps where the update partition has been replaced with
// a very old update partition so that no updates will be installed.
AddProblem(
Severity::Low,
Common::GetStringT("The update partition does not contain the IOS used by this title."));
if (!has_correct_ios)
{
// This is reached for hacked dumps where the update partition has been replaced with
// a very old update partition so that no updates will be installed.
AddProblem(Severity::Low,
Common::GetStringT(
"The update partition does not contain the IOS used by this title."));
}
}
}
const IOS::ES::TicketReader& ticket = m_volume.GetTicket(partition);
if (!ticket.IsValid())
{
AddProblem(severity,
// i18n: "Ticket" here is a kind of digital authorization to use a certain title
// (e.g. a game)
Common::FmtFormatT("The {0} partition does not have a valid ticket.", name));
}
const IOS::ES::TMDReader& tmd = m_volume.GetTMD(partition);
if (!tmd.IsValid())
{
AddProblem(severity, Common::FmtFormatT("The {0} partition does not have a valid TMD.", name));
}
return true;
}
@ -981,18 +1002,28 @@ void VolumeVerifier::CheckMisc()
auto& es = ios.GetESCore();
const std::vector<u8>& cert_chain = m_volume.GetCertificateChain(PARTITION_NONE);
if (IOS::HLE::IPC_SUCCESS !=
es.VerifyContainer(IOS::HLE::ESCore::VerifyContainerType::Ticket,
IOS::HLE::ESCore::VerifyMode::DoNotUpdateCertStore, m_ticket,
cert_chain))
if (!m_ticket.IsValid())
{
// i18n: "Ticket" here is a kind of digital authorization to use a certain title (e.g. a game)
AddProblem(Severity::High, Common::GetStringT("The ticket is invalid."));
}
else if (IOS::HLE::IPC_SUCCESS !=
es.VerifyContainer(IOS::HLE::ESCore::VerifyContainerType::Ticket,
IOS::HLE::ESCore::VerifyMode::DoNotUpdateCertStore, m_ticket,
cert_chain))
{
// i18n: "Ticket" here is a kind of digital authorization to use a certain title (e.g. a game)
AddProblem(Severity::Low, Common::GetStringT("The ticket is not correctly signed."));
}
if (IOS::HLE::IPC_SUCCESS !=
es.VerifyContainer(IOS::HLE::ESCore::VerifyContainerType::TMD,
IOS::HLE::ESCore::VerifyMode::DoNotUpdateCertStore, tmd, cert_chain))
if (!tmd.IsValid())
{
AddProblem(Severity::High, Common::GetStringT("The TMD is invalid."));
}
else if (IOS::HLE::IPC_SUCCESS !=
es.VerifyContainer(IOS::HLE::ESCore::VerifyContainerType::TMD,
IOS::HLE::ESCore::VerifyMode::DoNotUpdateCertStore, tmd,
cert_chain))
{
AddProblem(
Severity::Medium,