2019-03-21 22:04:56 +00:00
|
|
|
// Copyright 2019 Dolphin Emulator Project
|
2021-07-05 01:22:19 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2019-03-21 22:04:56 +00:00
|
|
|
|
|
|
|
#include "DolphinQt/Config/VerifyWidget.h"
|
|
|
|
|
2020-03-29 22:28:16 +00:00
|
|
|
#include <future>
|
2019-03-21 22:04:56 +00:00
|
|
|
#include <memory>
|
2020-03-29 22:28:16 +00:00
|
|
|
#include <optional>
|
2019-03-30 11:45:00 +00:00
|
|
|
#include <tuple>
|
|
|
|
#include <vector>
|
2019-03-21 22:04:56 +00:00
|
|
|
|
2019-03-30 11:45:00 +00:00
|
|
|
#include <QByteArray>
|
|
|
|
#include <QHBoxLayout>
|
2019-03-21 22:04:56 +00:00
|
|
|
#include <QHeaderView>
|
|
|
|
#include <QLabel>
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
|
2019-03-30 11:45:00 +00:00
|
|
|
#include "Common/CommonTypes.h"
|
2021-04-28 19:58:07 +00:00
|
|
|
#include "Core/Core.h"
|
2019-03-21 22:04:56 +00:00
|
|
|
#include "DiscIO/Volume.h"
|
|
|
|
#include "DiscIO/VolumeVerifier.h"
|
2020-03-29 22:28:16 +00:00
|
|
|
#include "DolphinQt/QtUtils/ParallelProgressDialog.h"
|
2021-04-28 19:58:07 +00:00
|
|
|
#include "DolphinQt/Settings.h"
|
2019-03-21 22:04:56 +00:00
|
|
|
|
|
|
|
VerifyWidget::VerifyWidget(std::shared_ptr<DiscIO::Volume> volume) : m_volume(std::move(volume))
|
|
|
|
{
|
2019-04-21 18:28:55 +00:00
|
|
|
QVBoxLayout* layout = new QVBoxLayout;
|
2019-03-21 22:04:56 +00:00
|
|
|
|
|
|
|
CreateWidgets();
|
|
|
|
ConnectWidgets();
|
|
|
|
|
|
|
|
layout->addWidget(m_problems);
|
|
|
|
layout->addWidget(m_summary_text);
|
2019-03-30 11:45:00 +00:00
|
|
|
layout->addLayout(m_hash_layout);
|
2019-08-23 11:20:09 +00:00
|
|
|
layout->addLayout(m_redump_layout);
|
2019-03-21 22:04:56 +00:00
|
|
|
layout->addWidget(m_verify_button);
|
|
|
|
|
|
|
|
layout->setStretchFactor(m_problems, 5);
|
|
|
|
layout->setStretchFactor(m_summary_text, 2);
|
|
|
|
|
|
|
|
setLayout(layout);
|
2021-04-28 19:58:07 +00:00
|
|
|
|
|
|
|
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
|
|
|
&VerifyWidget::OnEmulationStateChanged);
|
|
|
|
|
|
|
|
OnEmulationStateChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void VerifyWidget::OnEmulationStateChanged()
|
|
|
|
{
|
|
|
|
const bool running = Core::GetState() != Core::State::Uninitialized;
|
|
|
|
|
|
|
|
// Verifying a Wii game while emulation is running doesn't work correctly
|
|
|
|
// due to verification of a Wii game creating an instance of IOS
|
|
|
|
m_verify_button->setEnabled(!running);
|
2019-03-21 22:04:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void VerifyWidget::CreateWidgets()
|
|
|
|
{
|
|
|
|
m_problems = new QTableWidget(0, 2, this);
|
2020-02-07 02:34:27 +00:00
|
|
|
m_problems->setTabKeyNavigation(false);
|
2019-03-21 22:04:56 +00:00
|
|
|
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);
|
|
|
|
|
2019-04-21 18:28:55 +00:00
|
|
|
m_hash_layout = new QFormLayout;
|
2019-03-30 11:45:00 +00:00
|
|
|
std::tie(m_crc32_checkbox, m_crc32_line_edit) = AddHashLine(m_hash_layout, tr("CRC32:"));
|
|
|
|
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:"));
|
|
|
|
|
2019-08-23 11:20:09 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-04-16 22:38:31 +00:00
|
|
|
// Extend line edits to their maximum possible widths (needed on macOS)
|
|
|
|
m_hash_layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
2019-08-23 11:20:09 +00:00
|
|
|
m_redump_layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
2019-04-16 22:38:31 +00:00
|
|
|
|
2019-03-21 22:04:56 +00:00
|
|
|
m_verify_button = new QPushButton(tr("Verify Integrity"), this);
|
|
|
|
}
|
|
|
|
|
2019-03-30 11:45:00 +00:00
|
|
|
std::pair<QCheckBox*, QLineEdit*> VerifyWidget::AddHashLine(QFormLayout* layout, QString text)
|
|
|
|
{
|
|
|
|
QLineEdit* line_edit = new QLineEdit(this);
|
|
|
|
line_edit->setReadOnly(true);
|
|
|
|
QCheckBox* checkbox = new QCheckBox(tr("Calculate"), this);
|
|
|
|
checkbox->setChecked(true);
|
|
|
|
|
2019-04-21 18:28:55 +00:00
|
|
|
QHBoxLayout* hbox_layout = new QHBoxLayout;
|
2019-03-30 11:45:00 +00:00
|
|
|
hbox_layout->addWidget(line_edit);
|
|
|
|
hbox_layout->addWidget(checkbox);
|
|
|
|
|
|
|
|
layout->addRow(text, hbox_layout);
|
|
|
|
|
|
|
|
return std::pair(checkbox, line_edit);
|
|
|
|
}
|
|
|
|
|
2019-03-21 22:04:56 +00:00
|
|
|
void VerifyWidget::ConnectWidgets()
|
|
|
|
{
|
|
|
|
connect(m_verify_button, &QPushButton::clicked, this, &VerifyWidget::Verify);
|
2019-08-23 11:20:09 +00:00
|
|
|
|
|
|
|
connect(m_md5_checkbox, &QCheckBox::stateChanged, this, &VerifyWidget::UpdateRedumpEnabled);
|
|
|
|
connect(m_sha1_checkbox, &QCheckBox::stateChanged, this, &VerifyWidget::UpdateRedumpEnabled);
|
2019-03-21 22:04:56 +00:00
|
|
|
}
|
|
|
|
|
2019-03-30 11:45:00 +00:00
|
|
|
static void SetHash(QLineEdit* line_edit, const std::vector<u8>& hash)
|
|
|
|
{
|
|
|
|
const QByteArray byte_array = QByteArray::fromRawData(reinterpret_cast<const char*>(hash.data()),
|
|
|
|
static_cast<int>(hash.size()));
|
|
|
|
line_edit->setText(QString::fromLatin1(byte_array.toHex()));
|
|
|
|
}
|
|
|
|
|
2019-08-23 11:20:09 +00:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2019-03-21 22:04:56 +00:00
|
|
|
void VerifyWidget::Verify()
|
|
|
|
{
|
2019-08-23 11:20:09 +00:00
|
|
|
const bool redump_verification =
|
|
|
|
CanVerifyRedump() && m_redump_checkbox && m_redump_checkbox->isChecked();
|
|
|
|
|
2019-03-30 11:45:00 +00:00
|
|
|
DiscIO::VolumeVerifier verifier(
|
2019-08-23 11:20:09 +00:00
|
|
|
*m_volume, redump_verification,
|
2019-03-30 11:45:00 +00:00
|
|
|
{m_crc32_checkbox->isChecked(), m_md5_checkbox->isChecked(), m_sha1_checkbox->isChecked()});
|
2019-03-21 22:04:56 +00:00
|
|
|
|
|
|
|
// We have to divide the number of processed bytes with something so it won't make ints overflow
|
|
|
|
constexpr int DIVISOR = 0x100;
|
|
|
|
|
2020-03-29 22:28:16 +00:00
|
|
|
ParallelProgressDialog progress(tr("Verifying"), tr("Cancel"), 0,
|
|
|
|
static_cast<int>(verifier.GetTotalBytes() / DIVISOR), this);
|
|
|
|
progress.GetRaw()->setWindowTitle(tr("Verifying"));
|
|
|
|
progress.GetRaw()->setMinimumDuration(500);
|
|
|
|
progress.GetRaw()->setWindowModality(Qt::WindowModal);
|
|
|
|
|
2020-06-25 11:11:29 +00:00
|
|
|
auto future =
|
|
|
|
std::async(std::launch::async,
|
|
|
|
[&verifier, &progress]() -> std::optional<DiscIO::VolumeVerifier::Result> {
|
|
|
|
progress.SetValue(0);
|
|
|
|
verifier.Start();
|
|
|
|
while (verifier.GetBytesProcessed() != verifier.GetTotalBytes())
|
|
|
|
{
|
|
|
|
progress.SetValue(static_cast<int>(verifier.GetBytesProcessed() / DIVISOR));
|
|
|
|
if (progress.WasCanceled())
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
verifier.Process();
|
|
|
|
}
|
|
|
|
verifier.Finish();
|
|
|
|
|
|
|
|
const DiscIO::VolumeVerifier::Result result = verifier.GetResult();
|
|
|
|
progress.Reset();
|
|
|
|
|
|
|
|
return result;
|
|
|
|
});
|
2020-03-29 22:28:16 +00:00
|
|
|
progress.GetRaw()->exec();
|
|
|
|
|
|
|
|
std::optional<DiscIO::VolumeVerifier::Result> result = future.get();
|
|
|
|
if (!result)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_summary_text->setText(QString::fromStdString(result->summary_text));
|
|
|
|
|
|
|
|
m_problems->setRowCount(static_cast<int>(result->problems.size()));
|
2019-03-21 22:04:56 +00:00
|
|
|
for (int i = 0; i < m_problems->rowCount(); ++i)
|
|
|
|
{
|
2020-03-29 22:28:16 +00:00
|
|
|
const DiscIO::VolumeVerifier::Problem problem = result->problems[i];
|
2019-03-21 22:04:56 +00:00
|
|
|
|
|
|
|
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;
|
2019-09-30 21:30:53 +00:00
|
|
|
case DiscIO::VolumeVerifier::Severity::None:
|
|
|
|
break;
|
2019-03-21 22:04:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SetProblemCellText(i, 0, QString::fromStdString(problem.text));
|
|
|
|
SetProblemCellText(i, 1, severity);
|
|
|
|
}
|
2019-03-30 11:45:00 +00:00
|
|
|
|
2020-03-29 22:28:16 +00:00
|
|
|
SetHash(m_crc32_line_edit, result->hashes.crc32);
|
|
|
|
SetHash(m_md5_line_edit, result->hashes.md5);
|
|
|
|
SetHash(m_sha1_line_edit, result->hashes.sha1);
|
2019-08-23 11:20:09 +00:00
|
|
|
|
|
|
|
if (m_redump_line_edit)
|
2020-03-29 22:28:16 +00:00
|
|
|
m_redump_line_edit->setText(QString::fromStdString(result->redump.message));
|
2019-03-21 22:04:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|