Skip to content

Commit d1519aa

Browse files
committed
Qt: Use QtAsyncTaskWithProgress for dump verification
1 parent 3f882cf commit d1519aa

File tree

4 files changed

+129
-105
lines changed

4 files changed

+129
-105
lines changed

src/duckstation-qt/gamesummarywidget.cpp

Lines changed: 114 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -445,146 +445,158 @@ void GameSummaryWidget::onComputeHashClicked()
445445
return;
446446
}
447447

448-
Error error;
449-
std::unique_ptr<CDImage> image = CDImage::Open(m_path.c_str(), false, &error);
450-
if (!image)
451-
{
452-
QtUtils::MessageBoxCritical(QtUtils::GetRootWidget(this), tr("Image Open Failed"),
453-
QString::fromStdString(error.GetDescription()));
454-
return;
455-
}
448+
m_ui.computeHashes->setEnabled(false);
449+
450+
QtAsyncTaskWithProgress::create(this, TRANSLATE_SV("GameSummaryWidget", "Verifying Image"), {}, true, 1, 0, 0.0f,
451+
[this, path = m_path](ProgressCallback* progress) {
452+
Error error;
453+
CDImageHasher::TrackHashes track_hashes;
454+
const bool result = computeImageHash(path, track_hashes, progress, &error);
455+
const bool cancelled = (!result && progress->IsCancelled());
456+
return
457+
[this, track_hashes = std::move(track_hashes), error = std::move(error), result,
458+
cancelled]() { processHashResults(track_hashes, result, cancelled, error); };
459+
});
460+
}
456461

457-
QtModalProgressCallback progress_callback(this);
458-
progress_callback.SetCancellable(true);
459-
progress_callback.SetProgressRange(image->GetTrackCount());
460-
progress_callback.MakeVisible();
462+
bool GameSummaryWidget::computeImageHash(const std::string& path, CDImageHasher::TrackHashes& track_hashes,
463+
ProgressCallback* const progress, Error* const error) const
464+
{
465+
std::unique_ptr<CDImage> image = CDImage::Open(m_path.c_str(), false, error);
466+
if (!image)
467+
return false;
461468

462-
std::vector<CDImageHasher::Hash> track_hashes;
463469
track_hashes.reserve(image->GetTrackCount());
470+
progress->SetProgressRange(image->GetTrackCount());
464471

465-
// Calculate hashes
466-
bool calculate_hash_success = true;
467-
for (u8 track = 1; track <= image->GetTrackCount(); track++)
472+
for (u32 track = 0; track < image->GetTrackCount(); track++)
468473
{
469-
progress_callback.SetProgressValue(track - 1);
470-
progress_callback.PushState();
474+
progress->SetProgressValue(track);
475+
progress->PushState();
471476

472477
CDImageHasher::Hash hash;
473-
if (!CDImageHasher::GetTrackHash(image.get(), track, &hash, &progress_callback, &error))
478+
if (!CDImageHasher::GetTrackHash(image.get(), static_cast<u8>(track + 1), &hash, progress, error))
474479
{
475-
progress_callback.PopState();
476-
477-
if (progress_callback.IsCancelled())
478-
return;
479-
480-
QtUtils::MessageBoxCritical(QtUtils::GetRootWidget(this), tr("Hash Calculation Failed"),
481-
QString::fromStdString(error.GetDescription()));
482-
calculate_hash_success = false;
483-
break;
480+
progress->PopState();
481+
return false;
484482
}
483+
485484
track_hashes.emplace_back(hash);
485+
progress->PopState();
486+
}
486487

487-
QTreeWidgetItem* const row = m_ui.tracks->topLevelItem(track - 1);
488-
row->setText(4, QString::fromStdString(CDImageHasher::HashToString(hash)));
488+
return true;
489+
}
490+
491+
void GameSummaryWidget::processHashResults(const CDImageHasher::TrackHashes& track_hashes, bool result, bool cancelled,
492+
const Error& error)
493+
{
494+
m_ui.computeHashes->setEnabled(true);
495+
496+
if (!result)
497+
{
498+
if (!cancelled)
499+
{
500+
QtUtils::AsyncMessageBox(this, QMessageBox::Critical, tr("Hash Calculation Failed"),
501+
QString::fromStdString(error.GetDescription()));
502+
}
489503

490-
progress_callback.PopState();
504+
return;
491505
}
492506

493507
// Verify hashes against gamedb
494-
std::vector<bool> verification_results(image->GetTrackCount(), false);
495-
if (calculate_hash_success)
508+
std::vector<bool> verification_results(track_hashes.size(), false);
509+
510+
std::string found_revision;
511+
std::string found_serial;
512+
m_redump_search_keyword = CDImageHasher::HashToString(track_hashes.front());
513+
514+
// Verification strategy used:
515+
// 1. First, find all matches for the data track
516+
// If none are found, fail verification for all tracks
517+
// 2. For each data track match, try to match all audio tracks
518+
// If all match, assume this revision. Else, try other revisions,
519+
// and accept the one with the most matches.
520+
const GameDatabase::TrackHashesMap& hashes_map = GameDatabase::GetTrackHashesMap();
521+
522+
auto data_track_matches = hashes_map.equal_range(track_hashes[0]);
523+
if (data_track_matches.first != data_track_matches.second)
496524
{
497-
std::string found_revision;
498-
std::string found_serial;
499-
m_redump_search_keyword = CDImageHasher::HashToString(track_hashes.front());
500-
501-
progress_callback.SetStatusText(TRANSLATE("GameSummaryWidget", "Verifying hashes..."));
502-
progress_callback.SetProgressValue(image->GetTrackCount());
503-
504-
// Verification strategy used:
505-
// 1. First, find all matches for the data track
506-
// If none are found, fail verification for all tracks
507-
// 2. For each data track match, try to match all audio tracks
508-
// If all match, assume this revision. Else, try other revisions,
509-
// and accept the one with the most matches.
510-
const GameDatabase::TrackHashesMap& hashes_map = GameDatabase::GetTrackHashesMap();
511-
512-
auto data_track_matches = hashes_map.equal_range(track_hashes[0]);
513-
if (data_track_matches.first != data_track_matches.second)
525+
auto best_data_match = data_track_matches.second;
526+
for (auto iter = data_track_matches.first; iter != data_track_matches.second; ++iter)
514527
{
515-
auto best_data_match = data_track_matches.second;
516-
for (auto iter = data_track_matches.first; iter != data_track_matches.second; ++iter)
517-
{
518-
std::vector<bool> current_verification_results(image->GetTrackCount(), false);
519-
const auto& data_track_attribs = iter->second;
520-
current_verification_results[0] = true; // Data track already matched
528+
std::vector<bool> current_verification_results(track_hashes.size(), false);
529+
const auto& data_track_attribs = iter->second;
530+
current_verification_results[0] = true; // Data track already matched
521531

522-
for (auto audio_tracks_iter = std::next(track_hashes.begin()); audio_tracks_iter != track_hashes.end();
523-
++audio_tracks_iter)
532+
for (auto audio_tracks_iter = std::next(track_hashes.begin()); audio_tracks_iter != track_hashes.end();
533+
++audio_tracks_iter)
534+
{
535+
auto audio_track_matches = hashes_map.equal_range(*audio_tracks_iter);
536+
for (auto audio_iter = audio_track_matches.first; audio_iter != audio_track_matches.second; ++audio_iter)
524537
{
525-
auto audio_track_matches = hashes_map.equal_range(*audio_tracks_iter);
526-
for (auto audio_iter = audio_track_matches.first; audio_iter != audio_track_matches.second; ++audio_iter)
538+
// If audio track comes from the same revision and code as the data track, "pass" it
539+
if (audio_iter->second == data_track_attribs)
527540
{
528-
// If audio track comes from the same revision and code as the data track, "pass" it
529-
if (audio_iter->second == data_track_attribs)
530-
{
531-
current_verification_results[std::distance(track_hashes.begin(), audio_tracks_iter)] = true;
532-
break;
533-
}
541+
current_verification_results[std::distance(track_hashes.begin(), audio_tracks_iter)] = true;
542+
break;
534543
}
535544
}
545+
}
536546

537-
const auto old_matches_count = std::count(verification_results.begin(), verification_results.end(), true);
538-
const auto new_matches_count =
539-
std::count(current_verification_results.begin(), current_verification_results.end(), true);
547+
const auto old_matches_count = std::count(verification_results.begin(), verification_results.end(), true);
548+
const auto new_matches_count =
549+
std::count(current_verification_results.begin(), current_verification_results.end(), true);
540550

541-
if (new_matches_count > old_matches_count)
551+
if (new_matches_count > old_matches_count)
552+
{
553+
best_data_match = iter;
554+
verification_results = current_verification_results;
555+
// If all elements got matched, early out
556+
if (new_matches_count >= static_cast<ptrdiff_t>(verification_results.size()))
542557
{
543-
best_data_match = iter;
544-
verification_results = current_verification_results;
545-
// If all elements got matched, early out
546-
if (new_matches_count >= static_cast<ptrdiff_t>(verification_results.size()))
547-
{
548-
break;
549-
}
558+
break;
550559
}
551560
}
552-
553-
found_revision = best_data_match->second.revision_str;
554-
found_serial = best_data_match->second.serial;
555561
}
556562

557-
QString text;
563+
found_revision = best_data_match->second.revision_str;
564+
found_serial = best_data_match->second.serial;
565+
}
558566

559-
if (!found_revision.empty())
560-
text = tr("Revision: %1").arg(found_revision.empty() ? tr("N/A") : QString::fromStdString(found_revision));
567+
QString text;
561568

562-
if (found_serial != m_dialog->getGameSerial())
569+
if (!found_revision.empty())
570+
text = tr("Revision: %1").arg(found_revision.empty() ? tr("N/A") : QString::fromStdString(found_revision));
571+
572+
if (found_serial != m_dialog->getGameSerial())
573+
{
574+
if (found_serial.empty())
563575
{
564-
if (found_serial.empty())
565-
{
566-
text = tr("No known dump found that matches this hash.");
567-
}
576+
text = tr("No known dump found that matches this hash.");
577+
}
578+
else
579+
{
580+
const QString mismatch_str = tr("Serial Mismatch: %1 vs %2")
581+
.arg(QString::fromStdString(found_serial))
582+
.arg(QString::fromStdString(m_dialog->getGameSerial()));
583+
if (!text.isEmpty())
584+
text = QStringLiteral("%1 | %2").arg(mismatch_str).arg(text);
568585
else
569-
{
570-
const QString mismatch_str = tr("Serial Mismatch: %1 vs %2")
571-
.arg(QString::fromStdString(found_serial))
572-
.arg(QString::fromStdString(m_dialog->getGameSerial()));
573-
if (!text.isEmpty())
574-
text = QStringLiteral("%1 | %2").arg(mismatch_str).arg(text);
575-
else
576-
text = mismatch_str;
577-
}
586+
text = mismatch_str;
578587
}
579-
580-
setRevisionText(text);
581588
}
582589

583-
for (u8 track = 0; track < image->GetTrackCount(); track++)
590+
setRevisionText(text);
591+
592+
// update in ui
593+
for (size_t i = 0; i < track_hashes.size(); i++)
584594
{
585-
QTreeWidgetItem* const row = m_ui.tracks->topLevelItem(track);
595+
QTreeWidgetItem* const row = m_ui.tracks->topLevelItem(static_cast<int>(i));
596+
row->setText(4, QString::fromStdString(CDImageHasher::HashToString(track_hashes[i])));
597+
586598
QBrush brush;
587-
if (verification_results[track])
599+
if (verification_results[i])
588600
{
589601
brush = QColor(0, 200, 0);
590602
row->setText(5, QString::fromUtf8(u8"\u2713"));

src/duckstation-qt/gamesummarywidget.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
1+
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
22
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
33

44
#pragma once
5+
6+
#include "util/cd_image_hasher.h"
7+
58
#include "common/types.h"
9+
610
#include <QtWidgets/QWidget>
711

812
#include "ui_gamesummarywidget.h"
@@ -13,6 +17,7 @@ namespace GameList {
1317
struct Entry;
1418
}
1519

20+
class ProgressCallback;
1621
class SettingsWindow;
1722

1823
class GameSummaryWidget : public QWidget
@@ -40,6 +45,11 @@ class GameSummaryWidget : public QWidget
4045
void onEditInputProfileClicked();
4146
void onComputeHashClicked();
4247

48+
bool computeImageHash(const std::string& path, CDImageHasher::TrackHashes& track_hashes,
49+
ProgressCallback* const progress, Error* const error) const;
50+
void processHashResults(const CDImageHasher::TrackHashes& track_hashes, bool result, bool cancelled,
51+
const Error& error);
52+
4353
Ui::GameSummaryWidget m_ui;
4454
SettingsWindow* m_dialog;
4555

src/util/cd_image_hasher.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ bool CDImageHasher::ReadIndex(CDImage* image, u8 track, u8 index, MD5Digest* dig
2727
const u32 index_length = image->GetTrackIndexLength(track, index);
2828
const u32 update_interval = std::max<u32>(index_length / 100u, 1u);
2929

30-
progress_callback->FormatStatusText(TRANSLATE_FS("CDImageHasher", "Computing hash for Track {}/Index {}..."), track,
31-
index);
3230
progress_callback->SetProgressRange(index_length);
3331

3432
if (!image->Seek(index_start))
@@ -67,6 +65,7 @@ bool CDImageHasher::ReadTrack(CDImage* image, u8 track, MD5Digest* digest, Progr
6765
progress_callback->PushState();
6866

6967
const bool dataTrack = track == 1;
68+
progress_callback->FormatStatusText(TRANSLATE_FS("CDImageHasher", "Computing hash for Track {}..."), track);
7069
progress_callback->SetProgressRange(dataTrack ? 1 : 2);
7170

7271
u8 progress = 0;

src/util/cd_image_hasher.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <array>
99
#include <optional>
1010
#include <string>
11+
#include <vector>
1112

1213
class CDImage;
1314
class Error;
@@ -16,6 +17,8 @@ class ProgressCallback;
1617
namespace CDImageHasher {
1718

1819
using Hash = std::array<u8, 16>;
20+
using TrackHashes = std::vector<Hash>;
21+
1922
std::string HashToString(const Hash& hash);
2023
std::optional<Hash> HashFromString(std::string_view str);
2124

0 commit comments

Comments
 (0)