Add a Verify tab to game properties
This commit is contained in:
parent
c885fed9da
commit
84cbd5150f
|
@ -140,6 +140,27 @@ public:
|
|||
|
||||
bool CreateTitleDirectories(u64 title_id, u16 group_id) 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,
|
||||
};
|
||||
// On success, if issuer_handle is non-null, the IOSC object for the issuer will be written to it.
|
||||
// The caller is responsible for using IOSC_DeleteObject.
|
||||
ReturnCode VerifyContainer(VerifyContainerType type, VerifyMode mode,
|
||||
const IOS::ES::SignedBlobReader& signed_blob,
|
||||
const std::vector<u8>& cert_chain, u32* issuer_handle = nullptr);
|
||||
ReturnCode VerifyContainer(VerifyContainerType type, VerifyMode mode,
|
||||
const IOS::ES::CertReader& certificate,
|
||||
const std::vector<u8>& cert_chain, u32 certificate_iosc_handle);
|
||||
|
||||
private:
|
||||
enum
|
||||
{
|
||||
|
@ -308,29 +329,9 @@ 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);
|
||||
// On success, if issuer_handle is non-null, the IOSC object for the issuer will be written to it.
|
||||
// The caller is responsible for using IOSC_DeleteObject.
|
||||
ReturnCode VerifyContainer(VerifyContainerType type, VerifyMode mode,
|
||||
const IOS::ES::SignedBlobReader& signed_blob,
|
||||
const std::vector<u8>& cert_chain, u32* issuer_handle = nullptr);
|
||||
ReturnCode VerifyContainer(VerifyContainerType type, VerifyMode mode,
|
||||
const IOS::ES::CertReader& certificate,
|
||||
const std::vector<u8>& cert_chain, u32 certificate_iosc_handle);
|
||||
|
||||
// Start a title import.
|
||||
bool InitImport(const IOS::ES::TMDReader& tmd);
|
||||
|
|
|
@ -441,6 +441,11 @@ u64 TicketReader::GetTitleId() const
|
|||
return Common::swap64(m_bytes.data() + offsetof(Ticket, title_id));
|
||||
}
|
||||
|
||||
u8 TicketReader::GetCommonKeyIndex() const
|
||||
{
|
||||
return m_bytes[offsetof(Ticket, common_key_index)];
|
||||
}
|
||||
|
||||
std::array<u8, 16> TicketReader::GetTitleKey(const HLE::IOSC& iosc) const
|
||||
{
|
||||
u8 iv[16] = {};
|
||||
|
|
|
@ -240,6 +240,7 @@ public:
|
|||
|
||||
u32 GetDeviceId() const;
|
||||
u64 GetTitleId() const;
|
||||
u8 GetCommonKeyIndex() const;
|
||||
// Get the decrypted title key.
|
||||
std::array<u8, 16> GetTitleKey(const HLE::IOSC& iosc) const;
|
||||
// Same as the above version, but guesses the console type depending on the issuer
|
||||
|
|
|
@ -16,6 +16,7 @@ add_library(discio
|
|||
Volume.cpp
|
||||
VolumeFileBlobReader.cpp
|
||||
VolumeGC.cpp
|
||||
VolumeVerifier.cpp
|
||||
VolumeWad.cpp
|
||||
VolumeWii.cpp
|
||||
WiiSaveBanner.cpp
|
||||
|
|
|
@ -29,6 +29,11 @@ std::string NameForPartitionType(u32 partition_type, bool include_prefix)
|
|||
return "UPDATE";
|
||||
case PARTITION_CHANNEL:
|
||||
return "CHANNEL";
|
||||
case PARTITION_INSTALL:
|
||||
// wit doesn't recognize the name "INSTALL", so we can't use it when naming partition folders
|
||||
if (!include_prefix)
|
||||
return "INSTALL";
|
||||
// [[fallthrough]]
|
||||
default:
|
||||
const std::string type_as_game_id{static_cast<char>((partition_type >> 24) & 0xFF),
|
||||
static_cast<char>((partition_type >> 16) & 0xFF),
|
||||
|
|
|
@ -17,7 +17,8 @@ class Volume;
|
|||
|
||||
constexpr u32 PARTITION_DATA = 0;
|
||||
constexpr u32 PARTITION_UPDATE = 1;
|
||||
constexpr u32 PARTITION_CHANNEL = 2;
|
||||
constexpr u32 PARTITION_CHANNEL = 2; // Mario Kart Wii, Wii Fit, Wii Fit Plus, Rabbids Go Home
|
||||
constexpr u32 PARTITION_INSTALL = 3; // Dragon Quest X only
|
||||
|
||||
std::string NameForPartitionType(u32 partition_type, bool include_prefix);
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
<ClCompile Include="Volume.cpp" />
|
||||
<ClCompile Include="VolumeFileBlobReader.cpp" />
|
||||
<ClCompile Include="VolumeGC.cpp" />
|
||||
<ClCompile Include="VolumeVerifier.cpp" />
|
||||
<ClCompile Include="VolumeWad.cpp" />
|
||||
<ClCompile Include="VolumeWii.cpp" />
|
||||
<ClCompile Include="WbfsBlob.cpp" />
|
||||
|
@ -75,6 +76,7 @@
|
|||
<ClInclude Include="Volume.h" />
|
||||
<ClInclude Include="VolumeFileBlobReader.h" />
|
||||
<ClInclude Include="VolumeGC.h" />
|
||||
<ClInclude Include="VolumeVerifier.h" />
|
||||
<ClInclude Include="VolumeWad.h" />
|
||||
<ClInclude Include="VolumeWii.h" />
|
||||
<ClInclude Include="WbfsBlob.h" />
|
||||
|
|
|
@ -84,6 +84,9 @@
|
|||
<ClCompile Include="WiiSaveBanner.cpp">
|
||||
<Filter>NAND</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="VolumeVerifier.cpp">
|
||||
<Filter>Volume</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="DiscScrubber.h">
|
||||
|
@ -149,6 +152,9 @@
|
|||
<ClInclude Include="WiiSaveBanner.h">
|
||||
<Filter>NAND</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="VolumeVerifier.h">
|
||||
<Filter>Volume</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="CMakeLists.txt" />
|
||||
|
|
|
@ -0,0 +1,689 @@
|
|||
// Copyright 2019 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DiscIO/VolumeVerifier.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "Common/Align.h"
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Swap.h"
|
||||
#include "Core/IOS/Device.h"
|
||||
#include "Core/IOS/ES/ES.h"
|
||||
#include "Core/IOS/ES/Formats.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/IOS/IOSC.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/DiscExtractor.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/Filesystem.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
#include "DiscIO/VolumeWii.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
constexpr u64 MINI_DVD_SIZE = 1459978240; // GameCube
|
||||
constexpr u64 SL_DVD_SIZE = 4699979776; // Wii retail
|
||||
constexpr u64 SL_DVD_R_SIZE = 4707319808; // Wii RVT-R
|
||||
constexpr u64 DL_DVD_SIZE = 8511160320; // Wii retail
|
||||
constexpr u64 DL_DVD_R_SIZE = 8543666176; // Wii RVT-R
|
||||
|
||||
constexpr u64 BLOCK_SIZE = 0x20000;
|
||||
|
||||
VolumeVerifier::VolumeVerifier(const Volume& volume)
|
||||
: m_volume(volume), m_started(false), m_done(false), m_progress(0),
|
||||
m_max_progress(volume.GetSize())
|
||||
{
|
||||
}
|
||||
|
||||
void VolumeVerifier::Start()
|
||||
{
|
||||
ASSERT(!m_started);
|
||||
m_started = true;
|
||||
|
||||
m_is_tgc = m_volume.GetBlobType() == BlobType::TGC;
|
||||
m_is_datel = IsDisc(m_volume.GetVolumeType()) &&
|
||||
!GetBootDOLOffset(m_volume, m_volume.GetGamePartition()).has_value();
|
||||
m_is_not_retail =
|
||||
(m_volume.GetVolumeType() == Platform::WiiDisc && !m_volume.IsEncryptedAndHashed()) ||
|
||||
IsDebugSigned();
|
||||
|
||||
CheckPartitions();
|
||||
if (m_volume.GetVolumeType() == Platform::WiiWAD)
|
||||
CheckCorrectlySigned(PARTITION_NONE, GetStringT("This title is not correctly signed."));
|
||||
CheckDiscSize();
|
||||
CheckMisc();
|
||||
}
|
||||
|
||||
void VolumeVerifier::CheckPartitions()
|
||||
{
|
||||
const std::vector<Partition> partitions = m_volume.GetPartitions();
|
||||
if (partitions.empty())
|
||||
{
|
||||
if (m_volume.GetVolumeType() != Platform::WiiWAD &&
|
||||
!m_volume.GetFileSystem(m_volume.GetGamePartition()))
|
||||
{
|
||||
AddProblem(Severity::High, GetStringT("The filesystem is invalid or could not be read."));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::optional<u32> partitions_in_first_table = m_volume.ReadSwapped<u32>(0x40000, PARTITION_NONE);
|
||||
if (partitions_in_first_table && *partitions_in_first_table > 8)
|
||||
{
|
||||
// Not sure if 8 actually is the limit, but there certainly aren't any discs
|
||||
// released that have as many partitions as 8 in the first partition table.
|
||||
// The only game that has that many partitions in total is Super Smash Bros. Brawl,
|
||||
// and that game places all partitions other than UPDATE and DATA in the second table.
|
||||
AddProblem(Severity::Low,
|
||||
GetStringT("There are too many partitions in the first partition table."));
|
||||
}
|
||||
|
||||
std::vector<u32> types;
|
||||
for (const Partition& partition : partitions)
|
||||
{
|
||||
const std::optional<u32> type = m_volume.GetPartitionType(partition);
|
||||
if (type)
|
||||
types.emplace_back(*type);
|
||||
}
|
||||
|
||||
if (std::find(types.cbegin(), types.cend(), PARTITION_UPDATE) == types.cend())
|
||||
AddProblem(Severity::Low, GetStringT("The update partition is missing."));
|
||||
|
||||
if (std::find(types.cbegin(), types.cend(), PARTITION_DATA) == types.cend())
|
||||
AddProblem(Severity::High, GetStringT("The data partition is missing."));
|
||||
|
||||
const bool has_channel_partition =
|
||||
std::find(types.cbegin(), types.cend(), PARTITION_CHANNEL) != types.cend();
|
||||
if (ShouldHaveChannelPartition() && !has_channel_partition)
|
||||
AddProblem(Severity::Medium, GetStringT("The channel partition is missing."));
|
||||
|
||||
const bool has_install_partition =
|
||||
std::find(types.cbegin(), types.cend(), PARTITION_INSTALL) != types.cend();
|
||||
if (ShouldHaveInstallPartition() && !has_install_partition)
|
||||
AddProblem(Severity::High, GetStringT("The install partition is missing."));
|
||||
|
||||
if (ShouldHaveMasterpiecePartitions() &&
|
||||
types.cend() ==
|
||||
std::find_if(types.cbegin(), types.cend(), [](u32 type) { return type >= 0xFF; }))
|
||||
{
|
||||
// i18n: This string is referring to a game mode in Super Smash Bros. Brawl called Masterpieces
|
||||
// where you play demos of NES/SNES/N64 games. Official translations:
|
||||
// 名作トライアル (Japanese), Masterpieces (English), Meisterstücke (German), Chefs-d'œuvre
|
||||
// (French), Clásicos (Spanish), Capolavori (Italian), 클래식 게임 체험판 (Korean).
|
||||
// If your language is not one of the languages above, consider leaving the string untranslated
|
||||
// so that people will recognize it as the name of the game mode.
|
||||
AddProblem(Severity::Medium, GetStringT("The Masterpiece partitions are missing."));
|
||||
}
|
||||
|
||||
for (const Partition& partition : partitions)
|
||||
{
|
||||
if (m_volume.GetPartitionType(partition) == PARTITION_UPDATE && partition.offset != 0x50000)
|
||||
{
|
||||
AddProblem(Severity::Low, GetStringT("The update partition is not at its normal position."));
|
||||
}
|
||||
|
||||
const u64 normal_data_offset = m_volume.IsEncryptedAndHashed() ? 0xF800000 : 0x838000;
|
||||
if (m_volume.GetPartitionType(partition) == PARTITION_DATA &&
|
||||
partition.offset != normal_data_offset && !has_channel_partition && !has_install_partition)
|
||||
{
|
||||
AddProblem(
|
||||
Severity::Low,
|
||||
GetStringT("The data partition is not at its normal position. This will affect the "
|
||||
"emulated loading times. When using NetPlay or sending input recordings to "
|
||||
"other people, you will experience desyncs if anyone is using a good dump."));
|
||||
}
|
||||
}
|
||||
|
||||
for (const Partition& partition : partitions)
|
||||
CheckPartition(partition);
|
||||
}
|
||||
|
||||
bool VolumeVerifier::CheckPartition(const Partition& partition)
|
||||
{
|
||||
std::optional<u32> type = m_volume.GetPartitionType(partition);
|
||||
if (!type)
|
||||
{
|
||||
// Not sure if this can happen in practice
|
||||
AddProblem(Severity::Medium, GetStringT("The type of a partition could not be read."));
|
||||
return false;
|
||||
}
|
||||
|
||||
Severity severity = Severity::Medium;
|
||||
if (*type == PARTITION_DATA || *type == PARTITION_INSTALL)
|
||||
severity = Severity::High;
|
||||
else if (*type == PARTITION_UPDATE)
|
||||
severity = Severity::Low;
|
||||
|
||||
std::string name = NameForPartitionType(*type, false);
|
||||
if (ShouldHaveMasterpiecePartitions() && *type > 0xFF)
|
||||
{
|
||||
// i18n: This string is referring to a game mode in Super Smash Bros. Brawl called Masterpieces
|
||||
// where you play demos of NES/SNES/N64 games. This string is referring to a specific such demo
|
||||
// rather than the game mode as a whole, so please use the singular form. Official translations:
|
||||
// 名作トライアル (Japanese), Masterpieces (English), Meisterstücke (German), Chefs-d'œuvre
|
||||
// (French), Clásicos (Spanish), Capolavori (Italian), 클래식 게임 체험판 (Korean).
|
||||
// If your language is not one of the languages above, consider leaving the string untranslated
|
||||
// so that people will recognize it as the name of the game mode.
|
||||
name = StringFromFormat(GetStringT("%s (Masterpiece)").c_str(), name.c_str());
|
||||
}
|
||||
|
||||
if (partition.offset % VolumeWii::BLOCK_TOTAL_SIZE != 0 ||
|
||||
m_volume.PartitionOffsetToRawOffset(0, partition) % VolumeWii::BLOCK_TOTAL_SIZE != 0)
|
||||
{
|
||||
AddProblem(Severity::Medium,
|
||||
StringFromFormat(GetStringT("The %s partition is not properly aligned.").c_str(),
|
||||
name.c_str()));
|
||||
}
|
||||
|
||||
CheckCorrectlySigned(
|
||||
partition, StringFromFormat(GetStringT("The %s partition is not correctly signed.").c_str(),
|
||||
name.c_str()));
|
||||
|
||||
bool invalid_disc_header = false;
|
||||
std::vector<u8> disc_header(0x80);
|
||||
constexpr u32 WII_MAGIC = 0x5D1C9EA3;
|
||||
if (!m_volume.Read(0, disc_header.size(), disc_header.data(), partition))
|
||||
{
|
||||
invalid_disc_header = true;
|
||||
}
|
||||
else if (Common::swap32(disc_header.data() + 0x18) != WII_MAGIC)
|
||||
{
|
||||
for (size_t i = 0; i < disc_header.size(); i += 4)
|
||||
{
|
||||
if (Common::swap32(disc_header.data() + i) != i)
|
||||
{
|
||||
invalid_disc_header = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The loop above ends without breaking for discs that legitimately lack updates.
|
||||
// No such discs have been released to end users. Most such discs are debug signed,
|
||||
// but there is apparently at least one that is retail signed, the Movie-Ch Install Disc.
|
||||
return false;
|
||||
}
|
||||
if (invalid_disc_header)
|
||||
{
|
||||
// This can happen when certain programs that create WBFS files scrub the entirety of
|
||||
// the Masterpiece partitions in Super Smash Bros. Brawl without removing them from
|
||||
// the partition table. https://bugs.dolphin-emu.org/issues/8733
|
||||
const std::string text = StringFromFormat(
|
||||
GetStringT("The %s partition does not seem to contain valid data.").c_str(), name.c_str());
|
||||
AddProblem(severity, text);
|
||||
return false;
|
||||
}
|
||||
|
||||
const DiscIO::FileSystem* filesystem = m_volume.GetFileSystem(partition);
|
||||
if (!filesystem)
|
||||
{
|
||||
const std::string text = StringFromFormat(
|
||||
GetStringT("The %s partition does not have a valid file system.").c_str(), name.c_str());
|
||||
AddProblem(severity, text);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == PARTITION_UPDATE)
|
||||
{
|
||||
std::unique_ptr<FileInfo> file_info = filesystem->FindFileInfo("_sys");
|
||||
bool has_correct_ios = false;
|
||||
if (file_info)
|
||||
{
|
||||
const IOS::ES::TMDReader& tmd = m_volume.GetTMD(m_volume.GetGamePartition());
|
||||
if (tmd.IsValid())
|
||||
{
|
||||
const std::string correct_ios = "IOS" + std::to_string(tmd.GetIOSId() & 0xFF) + "-";
|
||||
for (const FileInfo& f : *file_info)
|
||||
{
|
||||
if (StringBeginsWith(f.GetName(), 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,
|
||||
GetStringT("The update partition does not contain the IOS used by this title."));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VolumeVerifier::CheckCorrectlySigned(const Partition& partition, const std::string& error_text)
|
||||
{
|
||||
IOS::HLE::Kernel ios;
|
||||
const auto es = ios.GetES();
|
||||
const std::vector<u8> cert_chain = m_volume.GetCertificateChain(partition);
|
||||
|
||||
if (IOS::HLE::IPC_SUCCESS !=
|
||||
es->VerifyContainer(IOS::HLE::Device::ES::VerifyContainerType::Ticket,
|
||||
IOS::HLE::Device::ES::VerifyMode::DoNotUpdateCertStore,
|
||||
m_volume.GetTicket(partition), cert_chain) ||
|
||||
IOS::HLE::IPC_SUCCESS !=
|
||||
es->VerifyContainer(IOS::HLE::Device::ES::VerifyContainerType::TMD,
|
||||
IOS::HLE::Device::ES::VerifyMode::DoNotUpdateCertStore,
|
||||
m_volume.GetTMD(partition), cert_chain))
|
||||
{
|
||||
AddProblem(Severity::Low, error_text);
|
||||
}
|
||||
}
|
||||
|
||||
bool VolumeVerifier::IsDebugSigned() const
|
||||
{
|
||||
const IOS::ES::TicketReader& ticket = m_volume.GetTicket(m_volume.GetGamePartition());
|
||||
return ticket.IsValid() ? ticket.GetConsoleType() == IOS::HLE::IOSC::ConsoleType::RVT : false;
|
||||
}
|
||||
|
||||
bool VolumeVerifier::ShouldHaveChannelPartition() const
|
||||
{
|
||||
const std::unordered_set<std::string> channel_discs{
|
||||
"RFNE01", "RFNJ01", "RFNK01", "RFNP01", "RFNW01", "RFPE01", "RFPJ01", "RFPK01", "RFPP01",
|
||||
"RFPW01", "RGWE41", "RGWJ41", "RGWP41", "RGWX41", "RMCE01", "RMCJ01", "RMCK01", "RMCP01",
|
||||
};
|
||||
|
||||
return channel_discs.find(m_volume.GetGameID()) != channel_discs.end();
|
||||
}
|
||||
|
||||
bool VolumeVerifier::ShouldHaveInstallPartition() const
|
||||
{
|
||||
const std::unordered_set<std::string> dragon_quest_x{"S4MJGD", "S4SJGD", "S6TJGD", "SDQJGD"};
|
||||
return dragon_quest_x.find(m_volume.GetGameID()) != dragon_quest_x.end();
|
||||
}
|
||||
|
||||
bool VolumeVerifier::ShouldHaveMasterpiecePartitions() const
|
||||
{
|
||||
const std::unordered_set<std::string> ssbb{"RSBE01", "RSBJ01", "RSBK01", "RSBP01"};
|
||||
return ssbb.find(m_volume.GetGameID()) != ssbb.end();
|
||||
}
|
||||
|
||||
bool VolumeVerifier::ShouldBeDualLayer() const
|
||||
{
|
||||
// The Japanese versions of Xenoblade and The Last Story are single-layer
|
||||
// (unlike the other versions) and must not be added to this list.
|
||||
const std::unordered_set<std::string> dual_layer_discs{
|
||||
"R3ME01", "R3MP01", "R3OE01", "R3OJ01", "R3OP01", "RSBE01", "RSBJ01", "RSBK01", "RSBP01",
|
||||
"RXMJ8P", "S59E01", "S59JC8", "S59P01", "S5QJC8", "SK8X52", "SAKENS", "SAKPNS", "SK8V52",
|
||||
"SK8X52", "SLSEXJ", "SLSP01", "SQIE4Q", "SQIP4Q", "SQIY4Q", "SR5E41", "SR5P41", "SUOE41",
|
||||
"SUOP41", "SVXX52", "SVXY52", "SX4E01", "SX4P01", "SZ3EGT", "SZ3PGT",
|
||||
};
|
||||
|
||||
return dual_layer_discs.find(m_volume.GetGameID()) != dual_layer_discs.end();
|
||||
}
|
||||
|
||||
void VolumeVerifier::CheckDiscSize()
|
||||
{
|
||||
if (!IsDisc(m_volume.GetVolumeType()))
|
||||
return;
|
||||
|
||||
const u64 biggest_offset = GetBiggestUsedOffset();
|
||||
if (biggest_offset > m_volume.GetSize())
|
||||
{
|
||||
const bool second_layer_missing =
|
||||
biggest_offset > SL_DVD_SIZE && m_volume.GetSize() >= SL_DVD_SIZE;
|
||||
const std::string text =
|
||||
second_layer_missing ?
|
||||
GetStringT(
|
||||
"This disc image is too small and lacks some data. The problem is most likely that "
|
||||
"this is a dual-layer disc that has been dumped as a single-layer disc.") :
|
||||
GetStringT(
|
||||
"This disc image is too small and lacks some data. If your dumping program saved "
|
||||
"the disc image as several parts, you need to merge them into one file.");
|
||||
AddProblem(Severity::High, text);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShouldBeDualLayer() && biggest_offset <= SL_DVD_R_SIZE)
|
||||
{
|
||||
AddProblem(
|
||||
Severity::Medium,
|
||||
GetStringT("This game has been hacked to fit on a single-layer DVD. Some content such as "
|
||||
"pre-rendered videos, extra languages or entire game modes will be broken. "
|
||||
"This problem generally only exists in illegal copies of games."));
|
||||
}
|
||||
|
||||
if (!m_volume.IsSizeAccurate())
|
||||
{
|
||||
AddProblem(Severity::Low, GetStringT("The format that the disc image is saved in does not "
|
||||
"store the size of the disc image."));
|
||||
}
|
||||
else if (!m_is_tgc)
|
||||
{
|
||||
const Platform platform = m_volume.GetVolumeType();
|
||||
const u64 size = m_volume.GetSize();
|
||||
|
||||
const bool valid_gamecube = size == MINI_DVD_SIZE;
|
||||
const bool valid_retail_wii = size == SL_DVD_SIZE || size == DL_DVD_SIZE;
|
||||
const bool valid_debug_wii = size == SL_DVD_R_SIZE || size == DL_DVD_R_SIZE;
|
||||
|
||||
const bool debug = IsDebugSigned();
|
||||
if ((platform == Platform::GameCubeDisc && !valid_gamecube) ||
|
||||
(platform == Platform::WiiDisc && (debug ? !valid_debug_wii : !valid_retail_wii)))
|
||||
{
|
||||
if (debug && valid_retail_wii)
|
||||
{
|
||||
AddProblem(Severity::Low,
|
||||
GetStringT("This debug disc image has the size of a retail disc image."));
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool small =
|
||||
(m_volume.GetVolumeType() == Platform::GameCubeDisc && size < MINI_DVD_SIZE) ||
|
||||
(m_volume.GetVolumeType() == Platform::WiiDisc && size < SL_DVD_SIZE);
|
||||
|
||||
if (small)
|
||||
{
|
||||
AddProblem(Severity::Low,
|
||||
GetStringT("This disc image has an unusual size. This will likely make the "
|
||||
"emulated loading times longer. When using NetPlay or sending "
|
||||
"input recordings to other people, you will likely experience "
|
||||
"desyncs if anyone is using a good dump."));
|
||||
}
|
||||
else
|
||||
{
|
||||
AddProblem(Severity::Low, GetStringT("This disc image has an unusual size."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u64 VolumeVerifier::GetBiggestUsedOffset()
|
||||
{
|
||||
std::vector<Partition> partitions = m_volume.GetPartitions();
|
||||
if (partitions.empty())
|
||||
partitions.emplace_back(m_volume.GetGamePartition());
|
||||
|
||||
const u64 disc_header_size = m_volume.GetVolumeType() == Platform::GameCubeDisc ? 0x460 : 0x50000;
|
||||
u64 biggest_offset = disc_header_size;
|
||||
for (const Partition& partition : partitions)
|
||||
{
|
||||
if (partition != PARTITION_NONE)
|
||||
{
|
||||
const u64 offset = m_volume.PartitionOffsetToRawOffset(0x440, partition);
|
||||
biggest_offset = std::max(biggest_offset, offset);
|
||||
}
|
||||
|
||||
const std::optional<u64> dol_offset = GetBootDOLOffset(m_volume, partition);
|
||||
if (dol_offset)
|
||||
{
|
||||
const std::optional<u64> dol_size = GetBootDOLSize(m_volume, partition, *dol_offset);
|
||||
if (dol_size)
|
||||
{
|
||||
const u64 offset = m_volume.PartitionOffsetToRawOffset(*dol_offset + *dol_size, partition);
|
||||
biggest_offset = std::max(biggest_offset, offset);
|
||||
}
|
||||
}
|
||||
|
||||
const std::optional<u64> fst_offset = GetFSTOffset(m_volume, partition);
|
||||
const std::optional<u64> fst_size = GetFSTSize(m_volume, partition);
|
||||
if (fst_offset && fst_size)
|
||||
{
|
||||
const u64 offset = m_volume.PartitionOffsetToRawOffset(*fst_offset + *fst_size, partition);
|
||||
biggest_offset = std::max(biggest_offset, offset);
|
||||
}
|
||||
|
||||
const FileSystem* fs = m_volume.GetFileSystem(partition);
|
||||
if (fs)
|
||||
{
|
||||
const u64 offset =
|
||||
m_volume.PartitionOffsetToRawOffset(GetBiggestUsedOffset(fs->GetRoot()), partition);
|
||||
biggest_offset = std::max(biggest_offset, offset);
|
||||
}
|
||||
}
|
||||
return biggest_offset;
|
||||
}
|
||||
|
||||
u64 VolumeVerifier::GetBiggestUsedOffset(const FileInfo& file_info) const
|
||||
{
|
||||
if (file_info.IsDirectory())
|
||||
{
|
||||
u64 biggest_offset = 0;
|
||||
for (const FileInfo& f : file_info)
|
||||
biggest_offset = std::max(biggest_offset, GetBiggestUsedOffset(f));
|
||||
return biggest_offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
return file_info.GetOffset() + file_info.GetSize();
|
||||
}
|
||||
}
|
||||
|
||||
void VolumeVerifier::CheckMisc()
|
||||
{
|
||||
const std::string game_id_unencrypted = m_volume.GetGameID(PARTITION_NONE);
|
||||
const std::string game_id_encrypted = m_volume.GetGameID(m_volume.GetGamePartition());
|
||||
|
||||
if (game_id_unencrypted != game_id_encrypted)
|
||||
{
|
||||
bool inconsistent_game_id = true;
|
||||
if (game_id_encrypted == "RELSAB")
|
||||
{
|
||||
if (StringBeginsWith(game_id_unencrypted, "410"))
|
||||
{
|
||||
// This is the Wii Backup Disc (aka "pinkfish" disc),
|
||||
// which legitimately has an inconsistent game ID.
|
||||
inconsistent_game_id = false;
|
||||
}
|
||||
else if (StringBeginsWith(game_id_unencrypted, "010"))
|
||||
{
|
||||
// Hacked version of the Wii Backup Disc (aka "pinkfish" disc).
|
||||
std::string proper_game_id = game_id_unencrypted;
|
||||
proper_game_id[0] = '4';
|
||||
AddProblem(Severity::Low,
|
||||
StringFromFormat(GetStringT("The game ID is %s but should be %s.").c_str(),
|
||||
game_id_unencrypted.c_str(), proper_game_id.c_str()));
|
||||
inconsistent_game_id = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (inconsistent_game_id)
|
||||
{
|
||||
AddProblem(Severity::Low, GetStringT("The game ID is inconsistent."));
|
||||
}
|
||||
}
|
||||
|
||||
const Region region = m_volume.GetRegion();
|
||||
const Platform platform = m_volume.GetVolumeType();
|
||||
|
||||
if (game_id_encrypted.size() < 4)
|
||||
{
|
||||
AddProblem(Severity::Low, GetStringT("The game ID is unusually short."));
|
||||
}
|
||||
else
|
||||
{
|
||||
const char country_code = game_id_encrypted[3];
|
||||
if (CountryCodeToRegion(country_code, platform, region) != region)
|
||||
{
|
||||
AddProblem(
|
||||
Severity::Medium,
|
||||
GetStringT("The region code does not match the game ID. If this is because the "
|
||||
"region code has been modified, the game might run at the wrong speed, "
|
||||
"graphical elements might be offset, or the game might not run at all."));
|
||||
}
|
||||
}
|
||||
|
||||
const IOS::ES::TMDReader& tmd = m_volume.GetTMD(m_volume.GetGamePartition());
|
||||
if (tmd.IsValid())
|
||||
{
|
||||
const u64 ios_id = tmd.GetIOSId() & 0xFF;
|
||||
|
||||
// List of launch day Korean IOSes obtained from https://hackmii.com/2008/09/korean-wii/.
|
||||
// More IOSes were released later that were used in Korean games, but they're all over 40.
|
||||
// Also, old IOSes like IOS36 did eventually get released for Korean Wiis as part of system
|
||||
// updates, but there are likely no Korean games using them since those IOSes were old by then.
|
||||
if (region == Region::NTSC_K && ios_id < 40 && ios_id != 4 && ios_id != 9 && ios_id != 21 &&
|
||||
ios_id != 37)
|
||||
{
|
||||
// This is intended to catch pirated Korean games that have had the IOS slot set to 36
|
||||
// as a side effect of having to fakesign after changing the common key slot to 0.
|
||||
// (IOS36 was the last IOS to have the Trucha bug.) https://bugs.dolphin-emu.org/issues/10319
|
||||
AddProblem(Severity::High,
|
||||
// i18n: You may want to leave the term "ERROR #002" untranslated,
|
||||
// since the emulated software always displays it in English.
|
||||
GetStringT("This Korean title is set to use an IOS that typically isn't used on "
|
||||
"Korean consoles. This is likely to lead to ERROR #002."));
|
||||
}
|
||||
|
||||
if (ios_id >= 0x80)
|
||||
{
|
||||
// This is also intended to catch fakesigned pirated Korean games,
|
||||
// but this time with the IOS slot set to cIOS instead of IOS36.
|
||||
AddProblem(Severity::High, GetStringT("This title is set to use an invalid IOS."));
|
||||
}
|
||||
}
|
||||
|
||||
const IOS::ES::TicketReader& ticket = m_volume.GetTicket(m_volume.GetGamePartition());
|
||||
if (ticket.IsValid())
|
||||
{
|
||||
const u8 common_key = ticket.GetCommonKeyIndex();
|
||||
|
||||
if (common_key > 1)
|
||||
{
|
||||
// Many fakesigned WADs have the common key index set to a (random?) bogus value.
|
||||
// For WADs, Dolphin will detect this and use common key 0 instead, making this low severity.
|
||||
const Severity severity =
|
||||
m_volume.GetVolumeType() == Platform::WiiWAD ? Severity::Low : Severity::High;
|
||||
// i18n: This is "common" as in "shared", not the opposite of "uncommon"
|
||||
AddProblem(Severity::Low, GetStringT("This title is set to use an invalid common key."));
|
||||
}
|
||||
|
||||
if (common_key == 1 && region != Region::NTSC_K)
|
||||
{
|
||||
// Apparently a certain pirate WAD of Chronos Twins DX unluckily got an index of 1,
|
||||
// which Dolphin does not change to 0 because 1 is valid on Korean Wiis.
|
||||
// https://forums.dolphin-emu.org/Thread-wiiware-chronos-twins-dx
|
||||
AddProblem(Severity::High,
|
||||
// i18n: This is "common" as in "shared", not the opposite of "uncommon"
|
||||
GetStringT("This non-Korean title is set to use the Korean common key."));
|
||||
}
|
||||
}
|
||||
|
||||
if (IsDisc(m_volume.GetVolumeType()))
|
||||
{
|
||||
constexpr u32 NKIT_MAGIC = 0x4E4B4954; // "NKIT"
|
||||
if (m_volume.ReadSwapped<u32>(0x200, PARTITION_NONE) == NKIT_MAGIC)
|
||||
{
|
||||
AddProblem(Severity::Low,
|
||||
GetStringT("This disc image is in the NKit format. It is not a good dump in its "
|
||||
"current form, but it might become a good dump if converted back. "
|
||||
"The CRC32 of this file might match the CRC32 of a good dump even "
|
||||
"though the files are not identical."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VolumeVerifier::Process()
|
||||
{
|
||||
ASSERT(m_started);
|
||||
ASSERT(!m_done);
|
||||
|
||||
if (m_progress == m_max_progress)
|
||||
return;
|
||||
|
||||
m_progress += std::min(m_max_progress - m_progress, BLOCK_SIZE);
|
||||
}
|
||||
|
||||
u64 VolumeVerifier::GetBytesProcessed() const
|
||||
{
|
||||
return m_progress;
|
||||
}
|
||||
|
||||
u64 VolumeVerifier::GetTotalBytes() const
|
||||
{
|
||||
return m_max_progress;
|
||||
}
|
||||
|
||||
void VolumeVerifier::Finish()
|
||||
{
|
||||
if (m_done)
|
||||
return;
|
||||
m_done = true;
|
||||
|
||||
// Show the most serious problems at the top
|
||||
std::stable_sort(m_result.problems.begin(), m_result.problems.end(),
|
||||
[](const Problem& p1, const Problem& p2) { return p1.severity > p2.severity; });
|
||||
const Severity highest_severity =
|
||||
m_result.problems.empty() ? Severity::None : m_result.problems[0].severity;
|
||||
|
||||
if (m_is_datel)
|
||||
{
|
||||
m_result.summary_text = GetStringT("Dolphin is unable to verify unlicensed discs.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_is_tgc)
|
||||
{
|
||||
m_result.summary_text = GetStringT("Dolphin is unable to verify typical TGC files properly, "
|
||||
"since they are not dumps of actual discs.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (highest_severity)
|
||||
{
|
||||
case Severity::None:
|
||||
if (IsWii(m_volume.GetVolumeType()) && !m_is_not_retail)
|
||||
{
|
||||
m_result.summary_text =
|
||||
GetStringT("No problems were found. This does not guarantee that this is a good dump, "
|
||||
"but since Wii titles contain a lot of verification data, it does mean that "
|
||||
"there most likely are no problems that will affect emulation.");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_result.summary_text = GetStringT("No problems were found.");
|
||||
}
|
||||
break;
|
||||
case Severity::Low:
|
||||
m_result.summary_text = GetStringT("Problems with low severity were found. They will most "
|
||||
"likely not prevent the game from running.");
|
||||
break;
|
||||
case Severity::Medium:
|
||||
m_result.summary_text = GetStringT("Problems with medium severity were found. The whole game "
|
||||
"or certain parts of the game might not work correctly.");
|
||||
break;
|
||||
case Severity::High:
|
||||
m_result.summary_text = GetStringT(
|
||||
"Problems with high severity were found. The game will most likely not work at all.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_volume.GetVolumeType() == Platform::GameCubeDisc)
|
||||
{
|
||||
m_result.summary_text +=
|
||||
GetStringT("\n\nBecause GameCube disc images contain little verification data, "
|
||||
"there may be problems that Dolphin is unable to detect.");
|
||||
}
|
||||
else if (m_is_not_retail)
|
||||
{
|
||||
m_result.summary_text += GetStringT("\n\nBecause this title is not for retail Wii consoles, "
|
||||
"Dolphin cannot verify that it hasn't been tampered with.");
|
||||
}
|
||||
}
|
||||
|
||||
const VolumeVerifier::Result& VolumeVerifier::GetResult() const
|
||||
{
|
||||
return m_result;
|
||||
}
|
||||
|
||||
void VolumeVerifier::AddProblem(Severity severity, const std::string& text)
|
||||
{
|
||||
m_result.problems.emplace_back(Problem{severity, text});
|
||||
}
|
||||
|
||||
} // namespace DiscIO
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright 2019 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
|
||||
// To be used as follows:
|
||||
//
|
||||
// VolumeVerifier verifier(volume);
|
||||
// verifier.Start();
|
||||
// while (verifier.GetBytesProcessed() != verifier.GetTotalBytes())
|
||||
// verifier.Process();
|
||||
// verifier.Finish();
|
||||
// auto result = verifier.GetResult();
|
||||
//
|
||||
// Start, Process and Finish may take some time to run.
|
||||
//
|
||||
// GetResult() can be called before the processing is finished, but the result will be incomplete.
|
||||
|
||||
namespace IOS::ES
|
||||
{
|
||||
class SignedBlobReader;
|
||||
}
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
class FileInfo;
|
||||
|
||||
class VolumeVerifier final
|
||||
{
|
||||
public:
|
||||
enum class Severity
|
||||
{
|
||||
None, // Only used internally
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
};
|
||||
|
||||
struct Problem
|
||||
{
|
||||
Severity severity;
|
||||
std::string text;
|
||||
};
|
||||
|
||||
struct Result
|
||||
{
|
||||
std::string summary_text;
|
||||
std::vector<Problem> problems;
|
||||
};
|
||||
|
||||
VolumeVerifier(const Volume& volume);
|
||||
void Start();
|
||||
void Process();
|
||||
u64 GetBytesProcessed() const;
|
||||
u64 GetTotalBytes() const;
|
||||
void Finish();
|
||||
const Result& GetResult() const;
|
||||
|
||||
private:
|
||||
void CheckPartitions();
|
||||
bool CheckPartition(const Partition& partition); // Returns false if partition should be ignored
|
||||
void CheckCorrectlySigned(const Partition& partition, const std::string& error_text);
|
||||
bool IsDebugSigned() const;
|
||||
bool ShouldHaveChannelPartition() const;
|
||||
bool ShouldHaveInstallPartition() const;
|
||||
bool ShouldHaveMasterpiecePartitions() const;
|
||||
bool ShouldBeDualLayer() const;
|
||||
void CheckDiscSize();
|
||||
u64 GetBiggestUsedOffset();
|
||||
u64 GetBiggestUsedOffset(const FileInfo& file_info) const;
|
||||
void CheckMisc();
|
||||
|
||||
void AddProblem(Severity severity, const std::string& text);
|
||||
|
||||
const Volume& m_volume;
|
||||
Result m_result;
|
||||
bool m_is_tgc;
|
||||
bool m_is_datel;
|
||||
bool m_is_not_retail;
|
||||
|
||||
bool m_started;
|
||||
bool m_done;
|
||||
u64 m_progress;
|
||||
u64 m_max_progress;
|
||||
};
|
||||
|
||||
} // namespace DiscIO
|
|
@ -78,6 +78,7 @@ add_executable(dolphin-emu
|
|||
Config/PatchesWidget.cpp
|
||||
Config/PropertiesDialog.cpp
|
||||
Config/SettingsWindow.cpp
|
||||
Config/VerifyWidget.cpp
|
||||
Debugger/BreakpointWidget.cpp
|
||||
Debugger/CodeViewWidget.cpp
|
||||
Debugger/CodeWidget.cpp
|
||||
|
|
|
@ -40,8 +40,8 @@ enum class EntryType
|
|||
};
|
||||
Q_DECLARE_METATYPE(EntryType);
|
||||
|
||||
FilesystemWidget::FilesystemWidget(const UICommon::GameFile& game)
|
||||
: m_game(game), m_volume(DiscIO::CreateVolumeFromFilename(game.GetFilePath()))
|
||||
FilesystemWidget::FilesystemWidget(std::shared_ptr<DiscIO::Volume> volume)
|
||||
: m_volume(std::move(volume))
|
||||
{
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
#include <QIcon>
|
||||
#include <memory>
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
class QStandardItem;
|
||||
class QStandardItemModel;
|
||||
class QTreeView;
|
||||
|
@ -26,7 +24,7 @@ class FilesystemWidget final : public QWidget
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FilesystemWidget(const UICommon::GameFile& game);
|
||||
explicit FilesystemWidget(std::shared_ptr<DiscIO::Volume> volume);
|
||||
~FilesystemWidget() override;
|
||||
|
||||
private:
|
||||
|
@ -52,8 +50,7 @@ private:
|
|||
QStandardItemModel* m_tree_model;
|
||||
QTreeView* m_tree_view;
|
||||
|
||||
UICommon::GameFile m_game;
|
||||
std::unique_ptr<DiscIO::Volume> m_volume;
|
||||
std::shared_ptr<DiscIO::Volume> m_volume;
|
||||
|
||||
QIcon m_folder_icon;
|
||||
QIcon m_file_icon;
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QPushButton>
|
||||
#include <QTabWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
|
||||
#include "DolphinQt/Config/ARCodeWidget.h"
|
||||
#include "DolphinQt/Config/FilesystemWidget.h"
|
||||
|
@ -16,6 +19,7 @@
|
|||
#include "DolphinQt/Config/InfoWidget.h"
|
||||
#include "DolphinQt/Config/PatchesWidget.h"
|
||||
#include "DolphinQt/Config/PropertiesDialog.h"
|
||||
#include "DolphinQt/Config/VerifyWidget.h"
|
||||
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
|
@ -54,12 +58,23 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& ga
|
|||
tr("Gecko Codes"));
|
||||
tab_widget->addTab(GetWrappedWidget(info, this, padding_width, padding_height), tr("Info"));
|
||||
|
||||
if (game.GetPlatform() != DiscIO::Platform::ELFOrDOL)
|
||||
{
|
||||
std::shared_ptr<DiscIO::Volume> volume = DiscIO::CreateVolumeFromFilename(game.GetFilePath());
|
||||
if (volume)
|
||||
{
|
||||
VerifyWidget* verify = new VerifyWidget(volume);
|
||||
tab_widget->addTab(GetWrappedWidget(verify, this, padding_width, padding_height),
|
||||
tr("Verify"));
|
||||
|
||||
if (DiscIO::IsDisc(game.GetPlatform()))
|
||||
{
|
||||
FilesystemWidget* filesystem = new FilesystemWidget(game);
|
||||
FilesystemWidget* filesystem = new FilesystemWidget(volume);
|
||||
tab_widget->addTab(GetWrappedWidget(filesystem, this, padding_width, padding_height),
|
||||
tr("Filesystem"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layout->addWidget(tab_widget);
|
||||
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2019 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/VerifyWidget.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QProgressDialog>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "DiscIO/Volume.h"
|
||||
#include "DiscIO/VolumeVerifier.h"
|
||||
|
||||
VerifyWidget::VerifyWidget(std::shared_ptr<DiscIO::Volume> volume) : m_volume(std::move(volume))
|
||||
{
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
||||
layout->addWidget(m_problems);
|
||||
layout->addWidget(m_summary_text);
|
||||
layout->addWidget(m_verify_button);
|
||||
|
||||
layout->setStretchFactor(m_problems, 5);
|
||||
layout->setStretchFactor(m_summary_text, 2);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void VerifyWidget::CreateWidgets()
|
||||
{
|
||||
m_problems = new QTableWidget(0, 2, this);
|
||||
m_problems->setHorizontalHeaderLabels({tr("Problem"), tr("Severity")});
|
||||
m_problems->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
m_problems->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
|
||||
m_problems->horizontalHeader()->setHighlightSections(false);
|
||||
m_problems->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
m_problems->verticalHeader()->hide();
|
||||
|
||||
m_summary_text = new QTextEdit(this);
|
||||
m_summary_text->setReadOnly(true);
|
||||
|
||||
m_verify_button = new QPushButton(tr("Verify Integrity"), this);
|
||||
}
|
||||
|
||||
void VerifyWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_verify_button, &QPushButton::clicked, this, &VerifyWidget::Verify);
|
||||
}
|
||||
|
||||
void VerifyWidget::Verify()
|
||||
{
|
||||
DiscIO::VolumeVerifier verifier(*m_volume);
|
||||
|
||||
// We have to divide the number of processed bytes with something so it won't make ints overflow
|
||||
constexpr int DIVISOR = 0x100;
|
||||
|
||||
QProgressDialog* progress = new QProgressDialog(tr("Verifying"), tr("Cancel"), 0,
|
||||
verifier.GetTotalBytes() / DIVISOR, this);
|
||||
progress->setWindowTitle(tr("Verifying"));
|
||||
progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
progress->setMinimumDuration(500);
|
||||
progress->setWindowModality(Qt::WindowModal);
|
||||
|
||||
verifier.Start();
|
||||
while (verifier.GetBytesProcessed() != verifier.GetTotalBytes())
|
||||
{
|
||||
progress->setValue(verifier.GetBytesProcessed() / DIVISOR);
|
||||
if (progress->wasCanceled())
|
||||
return;
|
||||
|
||||
verifier.Process();
|
||||
}
|
||||
verifier.Finish();
|
||||
|
||||
DiscIO::VolumeVerifier::Result result = verifier.GetResult();
|
||||
progress->setValue(verifier.GetBytesProcessed() / DIVISOR);
|
||||
|
||||
m_summary_text->setText(QString::fromStdString(result.summary_text));
|
||||
|
||||
m_problems->setRowCount(static_cast<int>(result.problems.size()));
|
||||
for (int i = 0; i < m_problems->rowCount(); ++i)
|
||||
{
|
||||
const DiscIO::VolumeVerifier::Problem problem = result.problems[i];
|
||||
|
||||
QString severity;
|
||||
switch (problem.severity)
|
||||
{
|
||||
case DiscIO::VolumeVerifier::Severity::Low:
|
||||
severity = tr("Low");
|
||||
break;
|
||||
case DiscIO::VolumeVerifier::Severity::Medium:
|
||||
severity = tr("Medium");
|
||||
break;
|
||||
case DiscIO::VolumeVerifier::Severity::High:
|
||||
severity = tr("High");
|
||||
break;
|
||||
}
|
||||
|
||||
SetProblemCellText(i, 0, QString::fromStdString(problem.text));
|
||||
SetProblemCellText(i, 1, severity);
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyWidget::SetProblemCellText(int row, int column, QString text)
|
||||
{
|
||||
QLabel* label = new QLabel(text);
|
||||
label->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
label->setWordWrap(true);
|
||||
label->setMargin(4);
|
||||
m_problems->setCellWidget(row, column, label);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2019 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QTableWidget>
|
||||
#include <QTextEdit>
|
||||
#include <QWidget>
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
class Volume;
|
||||
}
|
||||
|
||||
class VerifyWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VerifyWidget(std::shared_ptr<DiscIO::Volume> volume);
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
|
||||
void Verify();
|
||||
void SetProblemCellText(int row, int column, QString text);
|
||||
|
||||
std::shared_ptr<DiscIO::Volume> m_volume;
|
||||
QTableWidget* m_problems;
|
||||
QTextEdit* m_summary_text;
|
||||
QPushButton* m_verify_button;
|
||||
};
|
|
@ -109,6 +109,7 @@
|
|||
<QtMoc Include="Config\PatchesWidget.h" />
|
||||
<QtMoc Include="Config\PropertiesDialog.h" />
|
||||
<QtMoc Include="Config\SettingsWindow.h" />
|
||||
<QtMoc Include="Config\VerifyWidget.h" />
|
||||
<QtMoc Include="DiscordHandler.h" />
|
||||
<QtMoc Include="DiscordJoinRequestDialog.h" />
|
||||
<QtMoc Include="FIFO\FIFOAnalyzer.h" />
|
||||
|
@ -267,6 +268,7 @@
|
|||
<ClCompile Include="$(QtMocOutPrefix)ToolBar.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)USBDeviceAddToWhitelistDialog.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)Updater.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)VerifyWidget.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)WatchWidget.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)WiiPane.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)WiiTASInputWindow.cpp" />
|
||||
|
@ -329,6 +331,7 @@
|
|||
<ClCompile Include="Config\PatchesWidget.cpp" />
|
||||
<ClCompile Include="Config\PropertiesDialog.cpp" />
|
||||
<ClCompile Include="Config\SettingsWindow.cpp" />
|
||||
<ClCompile Include="Config\VerifyWidget.cpp" />
|
||||
<ClCompile Include="Debugger\CodeViewWidget.cpp" />
|
||||
<ClCompile Include="Debugger\CodeWidget.cpp" />
|
||||
<ClCompile Include="Debugger\JITWidget.cpp" />
|
||||
|
|
Loading…
Reference in New Issue