Merge pull request #7922 from JosJuice/verify-disc

Add a Verify tab to game properties
This commit is contained in:
JosJuice
2019-04-11 16:39:49 +02:00
committed by GitHub
41 changed files with 1614 additions and 263 deletions

View File

@ -76,6 +76,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

View File

@ -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();
@ -225,8 +225,7 @@ void FilesystemWidget::ShowContextMenu(const QPoint&)
{
if (const std::optional<u32> partition_type = m_volume->GetPartitionType(p))
{
const std::string partition_name =
DiscIO::DirectoryNameForPartitionType(*partition_type);
const std::string partition_name = DiscIO::NameForPartitionType(*partition_type, true);
ExtractPartition(p, folder + QChar(u'/') + QString::fromStdString(partition_name));
}
}
@ -239,12 +238,6 @@ void FilesystemWidget::ShowContextMenu(const QPoint&)
if (!folder.isEmpty())
ExtractPartition(partition, folder);
});
if (m_volume->IsEncryptedAndHashed())
{
menu->addSeparator();
menu->addAction(tr("Check Partition Integrity"), this,
[this, partition] { CheckIntegrity(partition); });
}
break;
case EntryType::File:
menu->addAction(tr("Extract File..."), this, [this, partition, path] {
@ -328,35 +321,3 @@ void FilesystemWidget::ExtractFile(const DiscIO::Partition& partition, const QSt
else
ModalMessageBox::critical(this, tr("Error"), tr("Failed to extract file."));
}
void FilesystemWidget::CheckIntegrity(const DiscIO::Partition& partition)
{
QProgressDialog* dialog = new QProgressDialog(this);
std::future<bool> is_valid = std::async(
std::launch::async, [this, partition] { return m_volume->CheckIntegrity(partition); });
dialog->setLabelText(tr("Verifying integrity of partition..."));
dialog->setWindowFlags(dialog->windowFlags() & ~Qt::WindowContextHelpButtonHint);
dialog->setWindowTitle(tr("Verifying partition"));
dialog->setMinimum(0);
dialog->setMaximum(0);
dialog->show();
while (is_valid.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready)
QCoreApplication::processEvents();
dialog->close();
if (is_valid.get())
{
ModalMessageBox::information(this, tr("Success"),
tr("Integrity check completed. No errors have been found."));
}
else
{
ModalMessageBox::critical(this, tr("Error"),
tr("Integrity check for partition failed. The disc image is most "
"likely corrupted or has been patched incorrectly."));
}
}

View File

@ -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:
@ -45,15 +43,13 @@ private:
const QString& out);
void ExtractFile(const DiscIO::Partition& partition, const QString& path, const QString& out);
bool ExtractSystemData(const DiscIO::Partition& partition, const QString& out);
void CheckIntegrity(const DiscIO::Partition& partition);
DiscIO::Partition GetPartitionFromID(int id);
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;

View File

@ -78,7 +78,6 @@ QGroupBox* InfoWidget::CreateISODetails()
QLineEdit* maker =
CreateValueDisplay((game_maker.empty() ? UNKNOWN_NAME.toStdString() : game_maker) + " (" +
m_game.GetMakerID() + ")");
QWidget* checksum = CreateChecksumComputer();
layout->addRow(tr("Name:"), internal_name);
layout->addRow(tr("File:"), file_path);
@ -89,8 +88,6 @@ QGroupBox* InfoWidget::CreateISODetails()
if (!m_game.GetApploaderDate().empty())
layout->addRow(tr("Apploader Date:"), CreateValueDisplay(m_game.GetApploaderDate()));
layout->addRow(tr("MD5 Checksum:"), checksum);
group->setLayout(layout);
return group;
}
@ -198,53 +195,3 @@ void InfoWidget::ChangeLanguage()
if (m_description)
m_description->setText(QString::fromStdString(m_game.GetDescription(language)));
}
QWidget* InfoWidget::CreateChecksumComputer()
{
QWidget* widget = new QWidget();
QHBoxLayout* layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
m_checksum_result = new QLineEdit();
m_checksum_result->setReadOnly(true);
QPushButton* calculate = new QPushButton(tr("Compute"));
connect(calculate, &QPushButton::clicked, this, &InfoWidget::ComputeChecksum);
layout->addWidget(m_checksum_result);
layout->addWidget(calculate);
widget->setLayout(layout);
return widget;
}
void InfoWidget::ComputeChecksum()
{
QCryptographicHash hash(QCryptographicHash::Md5);
hash.reset();
std::unique_ptr<DiscIO::BlobReader> file(DiscIO::CreateBlobReader(m_game.GetFilePath()));
std::vector<u8> file_data(8 * 1080 * 1080); // read 1MB at a time
u64 game_size = file->GetDataSize();
u64 read_offset = 0;
// a maximum of 1000 is used instead of game_size because otherwise 8GB games overflow the int
// typed maximum parameter
QProgressDialog* progress =
new QProgressDialog(tr("Computing MD5 Checksum"), tr("Cancel"), 0, 1000, this);
progress->setWindowTitle(tr("Computing MD5 Checksum"));
progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint);
progress->setMinimumDuration(500);
progress->setWindowModality(Qt::WindowModal);
while (read_offset < game_size)
{
progress->setValue(static_cast<double>(read_offset) / static_cast<double>(game_size) * 1000);
if (progress->wasCanceled())
return;
u64 read_size = std::min<u64>(file_data.size(), game_size - read_offset);
file->Read(read_offset, read_size, file_data.data());
hash.addData(reinterpret_cast<char*>(file_data.data()), read_size);
read_offset += read_size;
}
m_checksum_result->setText(QString::fromUtf8(hash.result().toHex()));
Q_ASSERT(read_offset == game_size);
progress->setValue(1000);
}

View File

@ -23,7 +23,6 @@ public:
explicit InfoWidget(const UICommon::GameFile& game);
private:
void ComputeChecksum();
void ChangeLanguage();
void SaveBanner();
@ -31,12 +30,10 @@ private:
QGroupBox* CreateISODetails();
QLineEdit* CreateValueDisplay(const QString& value);
QLineEdit* CreateValueDisplay(const std::string& value = "");
QWidget* CreateChecksumComputer();
void CreateLanguageSelector();
QWidget* CreateBannerGraphic(const QPixmap& image);
UICommon::GameFile m_game;
QLineEdit* m_checksum_result;
QComboBox* m_language_selector;
QLineEdit* m_name = {};
QLineEdit* m_maker = {};

View File

@ -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,11 +58,22 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const UICommon::GameFile& ga
tr("Gecko Codes"));
tab_widget->addTab(GetWrappedWidget(info, this, padding_width, padding_height), tr("Info"));
if (DiscIO::IsDisc(game.GetPlatform()))
if (game.GetPlatform() != DiscIO::Platform::ELFOrDOL)
{
FilesystemWidget* filesystem = new FilesystemWidget(game);
tab_widget->addTab(GetWrappedWidget(filesystem, this, padding_width, padding_height),
tr("Filesystem"));
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(volume);
tab_widget->addTab(GetWrappedWidget(filesystem, this, padding_width, padding_height),
tr("Filesystem"));
}
}
}
layout->addWidget(tab_widget);

View File

@ -0,0 +1,156 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt/Config/VerifyWidget.h"
#include <memory>
#include <tuple>
#include <vector>
#include <QByteArray>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QProgressDialog>
#include <QVBoxLayout>
#include "Common/CommonTypes.h"
#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->addLayout(m_hash_layout);
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_hash_layout = new QFormLayout(this);
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_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(this);
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);
}
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()));
}
void VerifyWidget::Verify()
{
DiscIO::VolumeVerifier verifier(
*m_volume,
{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;
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);
}
SetHash(m_crc32_line_edit, result.hashes.crc32);
SetHash(m_md5_line_edit, result.hashes.md5);
SetHash(m_sha1_line_edit, result.hashes.sha1);
}
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);
}

View File

@ -0,0 +1,49 @@
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#include <utility>
#include <QCheckBox>
#include <QFormLayout>
#include <QLineEdit>
#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();
std::pair<QCheckBox*, QLineEdit*> AddHashLine(QFormLayout* layout, QString text);
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;
QFormLayout* m_hash_layout;
QCheckBox* m_crc32_checkbox;
QCheckBox* m_md5_checkbox;
QCheckBox* m_sha1_checkbox;
QLineEdit* m_crc32_line_edit;
QLineEdit* m_md5_line_edit;
QLineEdit* m_sha1_line_edit;
QPushButton* m_verify_button;
};

View File

@ -108,6 +108,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" />
@ -269,6 +270,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" />