dolphin/Source/Core/DolphinQt/Config/VerifyWidget.cpp
JosJuice 55ef1069f1 DolphinQt: Disable verify button when emulation is running
Verifying a Wii game creates an instance of IOS, and Dolphin
can't handle more than one instance of IOS at the same time.
Properly supporting it is probably more effort than it's worth.

Fixes https://bugs.dolphin-emu.org/issues/12494.
2021-04-28 21:58:07 +02:00

227 lines
7.3 KiB
C++

// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt/Config/VerifyWidget.h"
#include <future>
#include <memory>
#include <optional>
#include <tuple>
#include <vector>
#include <QByteArray>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QVBoxLayout>
#include "Common/CommonTypes.h"
#include "Core/Core.h"
#include "DiscIO/Volume.h"
#include "DiscIO/VolumeVerifier.h"
#include "DolphinQt/QtUtils/ParallelProgressDialog.h"
#include "DolphinQt/Settings.h"
VerifyWidget::VerifyWidget(std::shared_ptr<DiscIO::Volume> volume) : m_volume(std::move(volume))
{
QVBoxLayout* layout = new QVBoxLayout;
CreateWidgets();
ConnectWidgets();
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);
layout->setStretchFactor(m_summary_text, 2);
setLayout(layout);
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);
}
void VerifyWidget::CreateWidgets()
{
m_problems = new QTableWidget(0, 2, this);
m_problems->setTabKeyNavigation(false);
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_hash_layout = new QFormLayout;
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:"));
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);
}
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);
QHBoxLayout* hbox_layout = new QHBoxLayout;
hbox_layout->addWidget(line_edit);
hbox_layout->addWidget(checkbox);
layout->addRow(text, hbox_layout);
return std::pair(checkbox, line_edit);
}
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)
{
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()));
}
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, 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
constexpr int DIVISOR = 0x100;
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);
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;
});
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()));
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;
case DiscIO::VolumeVerifier::Severity::None:
break;
}
SetProblemCellText(i, 0, QString::fromStdString(problem.text));
SetProblemCellText(i, 1, severity);
}
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)
{
QLabel* label = new QLabel(text);
label->setTextInteractionFlags(Qt::TextSelectableByMouse);
label->setWordWrap(true);
label->setMargin(4);
m_problems->setCellWidget(row, column, label);
}