Large diffs are not rendered by default.

@@ -21,7 +21,7 @@

// To be used as follows:
//
// VolumeVerifier verifier(volume);
// VolumeVerifier verifier(volume, redump_verification, hashes_to_calculate);
// verifier.Start();
// while (verifier.GetBytesProcessed() != verifier.GetTotalBytes())
// verifier.Process();
@@ -36,6 +36,72 @@ namespace DiscIO
{
class FileInfo;

template <typename T>
struct Hashes
{
T crc32;
T md5;
T sha1;
};

class RedumpVerifier final
{
public:
enum class Status
{
Unknown,
GoodDump,
BadDump,
Error,
};

struct Result
{
Status status = Status::Unknown;
std::string message;
};

void Start(const Volume& volume);
Result Finish(const Hashes<std::vector<u8>>& hashes);

private:
enum class DownloadStatus
{
NotAttempted,
Success,
Fail,
FailButOldCacheAvailable,
SystemNotAvailable,
};

struct DownloadState
{
std::mutex mutex;
DownloadStatus status = DownloadStatus::NotAttempted;
};

struct PotentialMatch
{
u64 size;
Hashes<std::vector<u8>> hashes;
};

static DownloadStatus DownloadDatfile(const std::string& system, DownloadStatus old_status);
static std::vector<u8> ReadDatfile(const std::string& system);
std::vector<PotentialMatch> ScanDatfile(const std::vector<u8>& data);

std::string m_game_id;
u16 m_revision;
u8 m_disc_number;
u64 m_size;

std::future<std::vector<PotentialMatch>> m_future;
Result m_result;

static DownloadState m_gc_download_state;
static DownloadState m_wii_download_state;
};

class VolumeVerifier final
{
public:
@@ -53,22 +119,15 @@ class VolumeVerifier final
std::string text;
};

template <typename T>
struct Hashes
{
T crc32;
T md5;
T sha1;
};

struct Result
{
Hashes<std::vector<u8>> hashes;
std::string summary_text;
std::vector<Problem> problems;
RedumpVerifier::Result redump;
};

VolumeVerifier(const Volume& volume, Hashes<bool> hashes_to_calculate);
VolumeVerifier(const Volume& volume, bool redump_verification, Hashes<bool> hashes_to_calculate);
~VolumeVerifier();

void Start();
@@ -111,6 +170,9 @@ class VolumeVerifier final
bool m_is_datel = false;
bool m_is_not_retail = false;

bool m_redump_verification;
RedumpVerifier m_redump_verifier;

Hashes<bool> m_hashes_to_calculate{};
bool m_calculating_any_hash = false;
unsigned long m_crc32_context = 0;
@@ -29,6 +29,7 @@ VerifyWidget::VerifyWidget(std::shared_ptr<DiscIO::Volume> volume) : m_volume(st
layout->addWidget(m_problems);
layout->addWidget(m_summary_text);
layout->addLayout(m_hash_layout);
layout->addLayout(m_redump_layout);
layout->addWidget(m_verify_button);

layout->setStretchFactor(m_problems, 5);
@@ -55,8 +56,21 @@ void VerifyWidget::CreateWidgets()
std::tie(m_md5_checkbox, m_md5_line_edit) = AddHashLine(m_hash_layout, tr("MD5:"));
std::tie(m_sha1_checkbox, m_sha1_line_edit) = AddHashLine(m_hash_layout, tr("SHA-1:"));

m_redump_layout = new QFormLayout;
if (DiscIO::IsDisc(m_volume->GetVolumeType()))
{
std::tie(m_redump_checkbox, m_redump_line_edit) =
AddHashLine(m_redump_layout, tr("Redump.org Status:"));
}
else
{
m_redump_checkbox = nullptr;
m_redump_line_edit = nullptr;
}

// Extend line edits to their maximum possible widths (needed on macOS)
m_hash_layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
m_redump_layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);

m_verify_button = new QPushButton(tr("Verify Integrity"), this);
}
@@ -80,6 +94,9 @@ std::pair<QCheckBox*, QLineEdit*> VerifyWidget::AddHashLine(QFormLayout* layout,
void VerifyWidget::ConnectWidgets()
{
connect(m_verify_button, &QPushButton::clicked, this, &VerifyWidget::Verify);

connect(m_md5_checkbox, &QCheckBox::stateChanged, this, &VerifyWidget::UpdateRedumpEnabled);
connect(m_sha1_checkbox, &QCheckBox::stateChanged, this, &VerifyWidget::UpdateRedumpEnabled);
}

static void SetHash(QLineEdit* line_edit, const std::vector<u8>& hash)
@@ -89,10 +106,25 @@ static void SetHash(QLineEdit* line_edit, const std::vector<u8>& hash)
line_edit->setText(QString::fromLatin1(byte_array.toHex()));
}

bool VerifyWidget::CanVerifyRedump() const
{
// We don't allow Redump verification with CRC32 only since generating a collision is too easy
return m_md5_checkbox->isChecked() || m_sha1_checkbox->isChecked();
}

void VerifyWidget::UpdateRedumpEnabled()
{
if (m_redump_checkbox)
m_redump_checkbox->setEnabled(CanVerifyRedump());
}

void VerifyWidget::Verify()
{
const bool redump_verification =
CanVerifyRedump() && m_redump_checkbox && m_redump_checkbox->isChecked();

DiscIO::VolumeVerifier verifier(
*m_volume,
*m_volume, redump_verification,
{m_crc32_checkbox->isChecked(), m_md5_checkbox->isChecked(), m_sha1_checkbox->isChecked()});

// We have to divide the number of processed bytes with something so it won't make ints overflow
@@ -147,6 +179,9 @@ void VerifyWidget::Verify()
SetHash(m_crc32_line_edit, result.hashes.crc32);
SetHash(m_md5_line_edit, result.hashes.md5);
SetHash(m_sha1_line_edit, result.hashes.sha1);

if (m_redump_line_edit)
m_redump_line_edit->setText(QString::fromStdString(result.redump.message));
}

void VerifyWidget::SetProblemCellText(int row, int column, QString text)
@@ -32,18 +32,23 @@ class VerifyWidget final : public QWidget
std::pair<QCheckBox*, QLineEdit*> AddHashLine(QFormLayout* layout, QString text);
void ConnectWidgets();

bool CanVerifyRedump() const;
void UpdateRedumpEnabled();
void Verify();
void SetProblemCellText(int row, int column, QString text);

std::shared_ptr<DiscIO::Volume> m_volume;
QTableWidget* m_problems;
QTextEdit* m_summary_text;
QFormLayout* m_hash_layout;
QFormLayout* m_redump_layout;
QCheckBox* m_crc32_checkbox;
QCheckBox* m_md5_checkbox;
QCheckBox* m_sha1_checkbox;
QCheckBox* m_redump_checkbox;
QLineEdit* m_crc32_line_edit;
QLineEdit* m_md5_line_edit;
QLineEdit* m_sha1_line_edit;
QLineEdit* m_redump_line_edit;
QPushButton* m_verify_button;
};
@@ -10,6 +10,7 @@

#include "Common/FileSearch.h"
#include "Common/FileUtil.h"
#include "Common/MinizipUtil.h"
#include "Common/ScopeGuard.h"
#include "Common/StringUtil.h"

@@ -20,35 +21,6 @@ namespace ResourcePack
{
constexpr char TEXTURE_PATH[] = "Load/Textures/";

// Since minzip doesn't provide a way to unzip a file of a length > 65535, we have to implement
// this ourselves
template <typename ContiguousContainer>
static bool ReadCurrentFileUnlimited(unzFile file, ContiguousContainer& destination)
{
const u32 MAX_BUFFER_SIZE = 65535;

if (unzOpenCurrentFile(file) != UNZ_OK)
return false;

Common::ScopeGuard guard{[&] { unzCloseCurrentFile(file); }};

auto bytes_to_go = static_cast<u32>(destination.size());
while (bytes_to_go > 0)
{
const int bytes_read = unzReadCurrentFile(file, &destination[destination.size() - bytes_to_go],
std::min(bytes_to_go, MAX_BUFFER_SIZE));

if (bytes_read < 0)
{
return false;
}

bytes_to_go -= static_cast<u32>(bytes_read);
}

return true;
}

ResourcePack::ResourcePack(const std::string& path) : m_path(path)
{
auto file = unzOpen(path.c_str());
@@ -72,7 +44,7 @@ ResourcePack::ResourcePack(const std::string& path) : m_path(path)
unzGetCurrentFileInfo(file, &manifest_info, nullptr, 0, nullptr, 0, nullptr, 0);

std::string manifest_contents(manifest_info.uncompressed_size, '\0');
if (!ReadCurrentFileUnlimited(file, manifest_contents))
if (!Common::ReadFileFromZip(file, &manifest_contents))
{
m_valid = false;
m_error = "Failed to read manifest.json";
@@ -96,7 +68,7 @@ ResourcePack::ResourcePack(const std::string& path) : m_path(path)

m_logo_data.resize(logo_info.uncompressed_size);

if (!ReadCurrentFileUnlimited(file, m_logo_data))
if (!Common::ReadFileFromZip(file, &m_logo_data))
{
m_valid = false;
m_error = "Failed to read logo.png";
@@ -208,7 +180,7 @@ bool ResourcePack::Install(const std::string& path)
unzGetCurrentFileInfo(file, &texture_info, nullptr, 0, nullptr, 0, nullptr, 0);

std::vector<char> data(texture_info.uncompressed_size);
if (!ReadCurrentFileUnlimited(file, data))
if (!Common::ReadFileFromZip(file, &data))
{
m_error = "Failed to read texture " + texture;
return false;