Merge pull request #2668 from CookiePLMonster/dump-verification

Implement image verification
This commit is contained in:
Connor McLaughlin 2021-10-25 17:30:20 +10:00 committed by GitHub
commit ccf5006bc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 497671 additions and 394290 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -20,18 +20,22 @@
"maxPlayers": 2,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 222151104,
"md5": "d81cbbea604ca28f2c2b6d15b3f17aca"
}
],
"vibration": true,
"multitap": false,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 222151104,
"md5": "d81cbbea604ca28f2c2b6d15b3f17aca"
}
]
}
]
},
{
@ -51,18 +55,22 @@
"maxPlayers": 2,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 219999024,
"md5": "418a5f5b36babd392615439c5cb2a6b4"
}
],
"vibration": true,
"multitap": false,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 219999024,
"md5": "418a5f5b36babd392615439c5cb2a6b4"
}
]
}
]
},
{
@ -82,18 +90,22 @@
"maxPlayers": 1,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 353874864,
"md5": "b4306047f3c243a748409a81d6d5cf0b"
}
],
"vibration": true,
"multitap": false,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 353874864,
"md5": "b4306047f3c243a748409a81d6d5cf0b"
}
]
}
]
},
{
@ -102,10 +114,14 @@
"codes": [
"LSP-905314"
],
"tracks": [
"track_data": [
{
"size": 670098912,
"md5": "3e37dc959cc5f0c4c05adb700a634169"
"tracks": [
{
"size": 670098912,
"md5": "3e37dc959cc5f0c4c05adb700a634169"
}
]
}
]
},
@ -115,10 +131,14 @@
"codes": [
"LSP-905321"
],
"tracks": [
"track_data": [
{
"size": 638158752,
"md5": "334127628ed5ed169a01a554b617a745"
"tracks": [
{
"size": 638158752,
"md5": "334127628ed5ed169a01a554b617a745"
}
]
}
]
},
@ -128,10 +148,14 @@
"codes": [
"LSP-905338"
],
"tracks": [
"track_data": [
{
"size": 626958528,
"md5": "2c9f4b2b37a5028831ff8fa884edc779"
"tracks": [
{
"size": 626958528,
"md5": "2c9f4b2b37a5028831ff8fa884edc779"
}
]
}
]
},
@ -152,16 +176,6 @@
"maxPlayers": 1,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 318096240,
"md5": "5d285155f159e0f973616fedf91edbd2"
},
{
"size": 32104800,
"md5": "65aea234c174ee35fb574d981fe3fc4f"
}
],
"vibration": true,
"multitap": true,
"linkCable": false,
@ -169,6 +183,20 @@
"AnalogController",
"DigitalController",
"PlayStationMouse"
],
"track_data": [
{
"tracks": [
{
"size": 318096240,
"md5": "5d285155f159e0f973616fedf91edbd2"
},
{
"size": 32104800,
"md5": "65aea234c174ee35fb574d981fe3fc4f"
}
]
}
]
},
{
@ -188,16 +216,6 @@
"maxPlayers": 1,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 323642256,
"md5": "ca6eb702916d24f928a265bbaca00235"
},
{
"size": 32634000,
"md5": "f0bcda18a061e585c3ddf7dbc45bd887"
}
],
"vibration": true,
"multitap": true,
"linkCable": false,
@ -205,6 +223,20 @@
"AnalogController",
"DigitalController",
"PlayStationMouse"
],
"track_data": [
{
"tracks": [
{
"size": 323642256,
"md5": "ca6eb702916d24f928a265bbaca00235"
},
{
"size": 32634000,
"md5": "f0bcda18a061e585c3ddf7dbc45bd887"
}
]
}
]
},
{
@ -224,21 +256,25 @@
"maxPlayers": 1,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 717886848,
"md5": "593906095d0b6fab6106de278093853b"
},
{
"size": 33132624,
"md5": "92fc46fef20c496c98f18914c72eb983"
}
],
"vibration": false,
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 717886848,
"md5": "593906095d0b6fab6106de278093853b"
},
{
"size": 33132624,
"md5": "92fc46fef20c496c98f18914c72eb983"
}
]
}
]
},
{
@ -258,17 +294,21 @@
"maxPlayers": 1,
"minBlocks": 2,
"maxBlocks": 2,
"tracks": [
{
"size": 489314784,
"md5": "8dc550f780d42d599fe2ec08d38b2810"
}
],
"vibration": false,
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 489314784,
"md5": "8dc550f780d42d599fe2ec08d38b2810"
}
]
}
]
},
{
@ -288,17 +328,21 @@
"maxPlayers": 1,
"minBlocks": 5,
"maxBlocks": 5,
"tracks": [
{
"size": 741225744,
"md5": "0c1cc1e91ddecfb834ed96265ad6be62"
}
],
"vibration": false,
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 741225744,
"md5": "0c1cc1e91ddecfb834ed96265ad6be62"
}
]
}
]
},
{
@ -318,17 +362,21 @@
"maxPlayers": 1,
"minBlocks": 5,
"maxBlocks": 5,
"tracks": [
{
"size": 683618208,
"md5": "c3489730fec0e52fa1accc3e497a7182"
}
],
"vibration": false,
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 683618208,
"md5": "c3489730fec0e52fa1accc3e497a7182"
}
]
}
]
},
{
@ -348,18 +396,22 @@
"maxPlayers": 4,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 301434672,
"md5": "e4b9065af214d32a097992c0ba055b43"
}
],
"vibration": false,
"multitap": true,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 301434672,
"md5": "e4b9065af214d32a097992c0ba055b43"
}
]
}
]
},
{
@ -379,18 +431,22 @@
"maxPlayers": 4,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 280125552,
"md5": "ec89c9f3632e3c9ed6c46bae5e5b89ce"
}
],
"vibration": false,
"multitap": true,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 280125552,
"md5": "ec89c9f3632e3c9ed6c46bae5e5b89ce"
}
]
}
]
},
{
@ -410,18 +466,22 @@
"maxPlayers": 1,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 370971552,
"md5": "c3db64d87dab2f70dd08b7cbb7afc830"
}
],
"vibration": false,
"multitap": false,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 370971552,
"md5": "c3db64d87dab2f70dd08b7cbb7afc830"
}
]
}
]
},
{
@ -441,18 +501,22 @@
"maxPlayers": 4,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 340068624,
"md5": "bc157d6305089b0dafb08cce86820dde"
}
],
"vibration": false,
"multitap": true,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 340068624,
"md5": "bc157d6305089b0dafb08cce86820dde"
}
]
}
]
},
{
@ -472,7 +536,6 @@
"maxPlayers": 4,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [],
"vibration": false,
"multitap": true,
"linkCable": false,
@ -498,18 +561,22 @@
"maxPlayers": 4,
"minBlocks": 1,
"maxBlocks": 5,
"tracks": [
{
"size": 95128992,
"md5": "c863a4adac6eb4464d7ea8539f96334a"
}
],
"vibration": true,
"multitap": true,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 95128992,
"md5": "c863a4adac6eb4464d7ea8539f96334a"
}
]
}
]
},
{
@ -529,18 +596,22 @@
"maxPlayers": 4,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 171982944,
"md5": "6eca9acf975e80df1912d098d6576e39"
}
],
"vibration": true,
"multitap": true,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 171982944,
"md5": "6eca9acf975e80df1912d098d6576e39"
}
]
}
]
},
{
@ -560,7 +631,6 @@
"maxPlayers": 4,
"minBlocks": 2,
"maxBlocks": 2,
"tracks": [],
"vibration": false,
"multitap": true,
"linkCable": false,
@ -587,18 +657,22 @@
"maxPlayers": 4,
"minBlocks": 2,
"maxBlocks": 2,
"tracks": [
{
"size": 86181984,
"md5": "2f9a68f47bf3a002547660e52efb8c76"
}
],
"vibration": false,
"multitap": true,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 86181984,
"md5": "2f9a68f47bf3a002547660e52efb8c76"
}
]
}
]
},
{
@ -618,25 +692,29 @@
"maxPlayers": 2,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 486186624,
"md5": "22e91d34b8bd330d3c25345d251dbfc7"
},
{
"size": 5520144,
"md5": "7e93fb8b6be99dd62dab1d91fe1d20c4"
},
{
"size": 3055248,
"md5": "d687ef30d7aef16627a2bc571b3724d7"
}
],
"vibration": false,
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 486186624,
"md5": "22e91d34b8bd330d3c25345d251dbfc7"
},
{
"size": 5520144,
"md5": "7e93fb8b6be99dd62dab1d91fe1d20c4"
},
{
"size": 3055248,
"md5": "d687ef30d7aef16627a2bc571b3724d7"
}
]
}
]
},
{
@ -656,41 +734,45 @@
"maxPlayers": 1,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 495107760,
"md5": "f1a94bb2cfa694c8c96f614b0ab4731b"
},
{
"size": 10776864,
"md5": "5fc2fa8ad81327bb09f59faacde4c2aa"
},
{
"size": 20109600,
"md5": "00236025bceffafe6689fe094b4f1eec"
},
{
"size": 24987648,
"md5": "6329b9c990d6292aa778f0f5aeb89bd1"
},
{
"size": 34212192,
"md5": "f26d8d16a3b55c7b802dd94d01cc9613"
},
{
"size": 10362912,
"md5": "3336bdbc49016d6dec7cc146d482ef05"
},
{
"size": 32986800,
"md5": "b4635067b7e9d9cf9aadabe5c7a0809b"
}
],
"vibration": false,
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 495107760,
"md5": "f1a94bb2cfa694c8c96f614b0ab4731b"
},
{
"size": 10776864,
"md5": "5fc2fa8ad81327bb09f59faacde4c2aa"
},
{
"size": 20109600,
"md5": "00236025bceffafe6689fe094b4f1eec"
},
{
"size": 24987648,
"md5": "6329b9c990d6292aa778f0f5aeb89bd1"
},
{
"size": 34212192,
"md5": "f26d8d16a3b55c7b802dd94d01cc9613"
},
{
"size": 10362912,
"md5": "3336bdbc49016d6dec7cc146d482ef05"
},
{
"size": 32986800,
"md5": "b4635067b7e9d9cf9aadabe5c7a0809b"
}
]
}
]
},
{
@ -710,21 +792,25 @@
"maxPlayers": 1,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 491887872,
"md5": "0e1918590b809d62676ba99afe6545f7"
},
{
"size": 32986800,
"md5": "b4635067b7e9d9cf9aadabe5c7a0809b"
}
],
"vibration": false,
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 491887872,
"md5": "0e1918590b809d62676ba99afe6545f7"
},
{
"size": 32986800,
"md5": "b4635067b7e9d9cf9aadabe5c7a0809b"
}
]
}
]
},
{
@ -744,18 +830,22 @@
"maxPlayers": 4,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 391111728,
"md5": "44523ea0630f8123e75ec08399df2fe7"
}
],
"vibration": false,
"multitap": true,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 391111728,
"md5": "44523ea0630f8123e75ec08399df2fe7"
}
]
}
]
},
{
@ -775,18 +865,22 @@
"maxPlayers": 4,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 391114080,
"md5": "a7aae54b181b9b6b1eca9394cdf68caf"
}
],
"vibration": false,
"multitap": true,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 391114080,
"md5": "a7aae54b181b9b6b1eca9394cdf68caf"
}
]
}
]
},
{
@ -806,18 +900,22 @@
"maxPlayers": 4,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 398913312,
"md5": "fd353612045a7452cbe695c83575a80e"
}
],
"vibration": false,
"multitap": true,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 398913312,
"md5": "fd353612045a7452cbe695c83575a80e"
}
]
}
]
},
{
@ -837,29 +935,33 @@
"maxPlayers": 1,
"minBlocks": 1,
"maxBlocks": 1,
"tracks": [
{
"size": 584486112,
"md5": "404a63f5277313bc61c90890c4e06889"
},
{
"size": 30926448,
"md5": "43ad0f0e0f66c203073658b1ca30888a"
},
{
"size": 2041536,
"md5": "d746857c4ec59fd30751e83cdc42470e"
},
{
"size": 62878368,
"md5": "14ab0ed3d882b7f098bc985a5524920f"
}
],
"vibration": false,
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 584486112,
"md5": "404a63f5277313bc61c90890c4e06889"
},
{
"size": 30926448,
"md5": "43ad0f0e0f66c203073658b1ca30888a"
},
{
"size": 2041536,
"md5": "d746857c4ec59fd30751e83cdc42470e"
},
{
"size": 62878368,
"md5": "14ab0ed3d882b7f098bc985a5524920f"
}
]
}
]
}
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -46,15 +46,19 @@ static bool ReadTrack(CDImage* image, u8 track, MD5Digest* digest, ProgressCallb
progress_callback->PushState();
progress_callback->SetProgressRange(2);
const bool dataTrack = track == 1;
progress_callback->SetProgressRange(dataTrack ? 1 : 2);
u8 progress = 0;
for (u8 index = 0; index < INDICES_TO_READ; index++)
{
progress_callback->SetProgressValue(index);
progress_callback->SetProgressValue(progress);
// skip index 0 if data track
if (track == 1 && index == 0)
if (dataTrack && index == 0)
continue;
progress++;
progress_callback->PushState();
if (!ReadIndex(image, track, index, digest, progress_callback))
{
@ -66,7 +70,7 @@ static bool ReadTrack(CDImage* image, u8 track, MD5Digest* digest, ProgressCallb
progress_callback->PopState();
}
progress_callback->SetProgressValue(INDICES_TO_READ);
progress_callback->SetProgressValue(progress);
progress_callback->PopState();
return true;
}
@ -78,6 +82,17 @@ std::string HashToString(const Hash& hash)
hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]);
}
std::optional<Hash> HashFromString(const std::string_view& str) {
auto decoded = StringUtil::DecodeHex(str);
if (decoded && decoded->size() == std::tuple_size_v<Hash>)
{
Hash result;
std::copy(decoded->begin(), decoded->end(), result.begin());
return result;
}
return std::nullopt;
}
bool GetImageHash(CDImage* image, Hash* out_hash,
ProgressCallback* progress_callback /*= ProgressCallback::NullProgressCallback*/)
{

View File

@ -2,6 +2,7 @@
#include "progress_callback.h"
#include "types.h"
#include <array>
#include <optional>
#include <string>
class CDImage;
@ -10,6 +11,7 @@ namespace CDImageHasher {
using Hash = std::array<u8, 16>;
std::string HashToString(const Hash& hash);
std::optional<Hash> HashFromString(const std::string_view& str);
bool GetImageHash(CDImage* image, Hash* out_hash,
ProgressCallback* progress_callback = ProgressCallback::NullProgressCallback);

View File

@ -1,18 +1,23 @@
#include "gamepropertiesdialog.h"
#include "common/cd_image.h"
#include "common/cd_image_hasher.h"
#include "common/string_util.h"
#include "core/settings.h"
#include "core/system.h"
#include "frontend-common/game_database.h"
#include "frontend-common/game_list.h"
#include "qthostinterface.h"
#include "qtprogresscallback.h"
#include "qtutils.h"
#include "rapidjson/document.h"
#include "scmversion/scmversion.h"
#include <QtGui/QClipboard>
#include <QtGui/QGuiApplication>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMessageBox>
#include <map>
Log_SetChannel(GamePropertiesDialog);
static constexpr char MEMORY_CARD_IMAGE_FILTER[] =
QT_TRANSLATE_NOOP("MemoryCardSettingsWidget", "All Memory Card Types (*.mcd *.mcr *.mc)");
@ -71,6 +76,7 @@ void GamePropertiesDialog::populate(const GameListEntry* ge)
m_ui.gameCode->setText(QStringLiteral("%1 / %2").arg(ge->code.c_str()).arg(hash_code.c_str()));
else
m_ui.gameCode->setText(QString::fromStdString(ge->code));
m_ui.revision->setText(tr("<not verified>"));
m_ui.region->setCurrentIndex(static_cast<int>(ge->region));
@ -83,7 +89,6 @@ void GamePropertiesDialog::populate(const GameListEntry* ge)
m_ui.comments->setDisabled(true);
m_ui.versionTested->setDisabled(true);
m_ui.setToCurrent->setDisabled(true);
m_ui.verifyDump->setDisabled(true);
m_ui.exportCompatibilityInfo->setDisabled(true);
}
else
@ -261,6 +266,10 @@ void GamePropertiesDialog::populateTracksInfo(const std::string& image_path)
m_ui.tracks->setItem(row, 2, new QTableWidgetItem(MSFTotString(position)));
m_ui.tracks->setItem(row, 3, new QTableWidgetItem(MSFTotString(length)));
m_ui.tracks->setItem(row, 4, new QTableWidgetItem(tr("<not computed>")));
QTableWidgetItem* status = new QTableWidgetItem(QString());
status->setTextAlignment(Qt::AlignCenter);
m_ui.tracks->setItem(row, 5, status);
}
}
@ -532,7 +541,7 @@ void GamePropertiesDialog::resizeEvent(QResizeEvent* ev)
void GamePropertiesDialog::onResize()
{
QtUtils::ResizeColumnsForTableView(m_ui.tracks, {20, 85, 125, 125, -1});
QtUtils::ResizeColumnsForTableView(m_ui.tracks, {15, 85, 125, 125, -1, 25});
}
void GamePropertiesDialog::connectUi()
@ -546,14 +555,12 @@ void GamePropertiesDialog::connectUi()
&GamePropertiesDialog::saveCompatibilityInfoIfChanged);
connect(m_ui.setToCurrent, &QPushButton::clicked, this, &GamePropertiesDialog::onSetVersionTestedToCurrentClicked);
connect(m_ui.computeHashes, &QPushButton::clicked, this, &GamePropertiesDialog::onComputeHashClicked);
connect(m_ui.verifyDump, &QPushButton::clicked, this, &GamePropertiesDialog::onVerifyDumpClicked);
connect(m_ui.exportCompatibilityInfo, &QPushButton::clicked, this,
&GamePropertiesDialog::onExportCompatibilityInfoClicked);
connect(m_ui.close, &QPushButton::clicked, this, &QDialog::close);
connect(m_ui.tabWidget, &QTabWidget::currentChanged, [this](int index) {
const bool show_buttons = index == 0;
m_ui.computeHashes->setVisible(show_buttons);
m_ui.verifyDump->setVisible(show_buttons);
m_ui.exportCompatibilityInfo->setVisible(show_buttons);
});
@ -924,15 +931,19 @@ void GamePropertiesDialog::onSetVersionTestedToCurrentClicked()
void GamePropertiesDialog::onComputeHashClicked()
{
if (m_tracks_hashed)
return;
if (m_redump_search_keyword.empty())
{
computeTrackHashes(m_redump_search_keyword);
computeTrackHashes();
}
void GamePropertiesDialog::onVerifyDumpClicked()
{
QMessageBox::critical(this, tr("Not yet implemented"), tr("Not yet implemented"));
if (!m_redump_search_keyword.empty())
m_ui.computeHashes->setText(tr("Search on Redump.org"));
}
else
{
QtUtils::OpenURL(
this, StringUtil::StdStringFromFormat("http://redump.org/discs/quicksearch/%s", m_redump_search_keyword.c_str())
.c_str());
}
}
void GamePropertiesDialog::onExportCompatibilityInfoClicked()
@ -952,7 +963,7 @@ void GamePropertiesDialog::onExportCompatibilityInfoClicked()
QGuiApplication::clipboard()->setText(xml);
}
void GamePropertiesDialog::computeTrackHashes()
void GamePropertiesDialog::computeTrackHashes(std::string& redump_keyword)
{
if (m_path.empty())
return;
@ -961,9 +972,25 @@ void GamePropertiesDialog::computeTrackHashes()
if (!image)
return;
// Kick off hash preparation asynchronously, as building the map of results may take a while
auto hashes_map_job = std::async(std::launch::async, [] {
GameDatabase::TrackHashesMap result;
GameDatabase db;
if (db.Load())
{
result = db.GetTrackHashesMap();
}
return result;
});
QtProgressCallback progress_callback(this);
progress_callback.SetProgressRange(image->GetTrackCount());
std::vector<CDImageHasher::Hash> track_hashes;
track_hashes.reserve(image->GetTrackCount());
// Calculate hashes
bool calculate_hash_success = true;
for (u8 track = 1; track <= image->GetTrackCount(); track++)
{
progress_callback.SetProgressValue(track - 1);
@ -973,14 +1000,98 @@ void GamePropertiesDialog::computeTrackHashes()
if (!CDImageHasher::GetTrackHash(image.get(), track, &hash, &progress_callback))
{
progress_callback.PopState();
calculate_hash_success = false;
break;
}
QString hash_string(QString::fromStdString(CDImageHasher::HashToString(hash)));
track_hashes.emplace_back(hash);
QTableWidgetItem* item = m_ui.tracks->item(track - 1, 4);
item->setText(hash_string);
item->setText(QString::fromStdString(CDImageHasher::HashToString(hash)));
progress_callback.PopState();
}
// Verify hashes against gamedb
std::vector<bool> verification_results(image->GetTrackCount(), false);
if (calculate_hash_success)
{
std::string found_revision;
redump_keyword = CDImageHasher::HashToString(track_hashes.front());
progress_callback.SetStatusText("Verifying hashes...");
progress_callback.SetProgressValue(image->GetTrackCount());
const auto hashes_map = hashes_map_job.get();
// Verification strategy used:
// 1. First, find all matches for the data track
// If none are found, fail verification for all tracks
// 2. For each data track match, try to match all audio tracks
// If all match, assume this revision. Else, try other revisions,
// and accept the one with the most matches.
auto data_track_matches = hashes_map.equal_range(track_hashes[0]);
if (data_track_matches.first != data_track_matches.second)
{
auto best_data_match = data_track_matches.second;
for (auto iter = data_track_matches.first; iter != data_track_matches.second; ++iter)
{
std::vector<bool> current_verification_results(image->GetTrackCount(), false);
const auto& data_track_attribs = iter->second;
current_verification_results[0] = true; // Data track already matched
for (auto audio_tracks_iter = std::next(track_hashes.begin()); audio_tracks_iter != track_hashes.end();
++audio_tracks_iter)
{
auto audio_track_matches = hashes_map.equal_range(*audio_tracks_iter);
for (auto audio_iter = audio_track_matches.first; audio_iter != audio_track_matches.second; ++audio_iter)
{
// If audio track comes from the same revision and code as the data track, "pass" it
if (audio_iter->second == data_track_attribs)
{
current_verification_results[std::distance(track_hashes.begin(), audio_tracks_iter)] = true;
break;
}
}
}
const auto old_matches_count = std::count(verification_results.begin(), verification_results.end(), true);
const auto new_matches_count =
std::count(current_verification_results.begin(), current_verification_results.end(), true);
if (new_matches_count > old_matches_count)
{
best_data_match = iter;
verification_results = current_verification_results;
// If all elements got matched, early out
if (new_matches_count >= static_cast<ptrdiff_t>(verification_results.size()))
{
break;
}
}
}
found_revision = best_data_match->second.revisionString;
}
m_ui.revision->setText(!found_revision.empty() ? QString::fromStdString(found_revision) : QStringLiteral("-"));
}
for (u8 track = 0; track < image->GetTrackCount(); track++)
{
QTableWidgetItem* hash_text = m_ui.tracks->item(track, 4);
QTableWidgetItem* status_text = m_ui.tracks->item(track, 5);
QBrush brush;
if (verification_results[track])
{
brush = QColor(0, 200, 0);
status_text->setText(QString::fromUtf8(u8"\u2713"));
}
else
{
brush = QColor(200, 0, 0);
status_text->setText(QString::fromUtf8(u8"\u2715"));
}
status_text->setForeground(brush);
hash_text->setForeground(brush);
}
}

View File

@ -34,7 +34,6 @@ private Q_SLOTS:
void onSetVersionTestedToCurrentClicked();
void onComputeHashClicked();
void onVerifyDumpClicked();
void onExportCompatibilityInfoClicked();
void updateCPUClockSpeedLabel();
void onEnableCPUClockSpeedControlChecked(int state);
@ -49,7 +48,7 @@ private:
void connectBooleanUserSetting(QCheckBox* cb, std::optional<bool>* value);
void saveGameSettings();
void fillEntryFromUi(GameListCompatibilityEntry* entry);
void computeTrackHashes();
void computeTrackHashes(std::string& redump_keyword);
void onResize();
void onUserAspectRatioChanged();
@ -61,9 +60,9 @@ private:
std::string m_path;
std::string m_game_code;
std::string m_game_title;
std::string m_redump_search_keyword;
GameSettings::Entry m_game_settings;
bool m_compatibility_info_changed = false;
bool m_tracks_hashed = false;
};

View File

@ -70,58 +70,58 @@
</property>
</widget>
</item>
<item row="3" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Region:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="4" column="1">
<widget class="QComboBox" name="region">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<item row="5" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Compatibility:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="5" column="1">
<widget class="QComboBox" name="compatibility"/>
</item>
<item row="5" column="0">
<item row="6" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Upscaling Issues:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="6" column="1">
<widget class="QLineEdit" name="upscalingIssues"/>
</item>
<item row="6" column="0">
<item row="7" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Comments:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="7" column="1">
<widget class="QLineEdit" name="comments"/>
</item>
<item row="7" column="0">
<item row="8" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Version Tested:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="8" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLineEdit" name="versionTested"/>
@ -135,14 +135,14 @@
</item>
</layout>
</item>
<item row="8" column="0" colspan="2">
<item row="9" column="0" colspan="2">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Tracks:</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<item row="10" column="0" colspan="2">
<widget class="QTableWidget" name="tracks">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
@ -178,6 +178,28 @@
<string>Hash</string>
</property>
</column>
<column>
<property name="text">
<string>Status</string>
</property>
</column>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="revision">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="placeholderText">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_36">
<property name="text">
<string>Revision:</string>
</property>
</widget>
</item>
</layout>
@ -258,7 +280,7 @@
<string>Enable 8MB RAM (Dev Console)</string>
</property>
<property name="tristate">
<bool>true</bool>
<bool>true</bool>
</property>
</widget>
</item>
@ -1119,14 +1141,7 @@
<item>
<widget class="QPushButton" name="computeHashes">
<property name="text">
<string>Compute Hashes</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="verifyDump">
<property name="text">
<string>Verify Dump</string>
<string>Compute &amp;&amp; Verify Hashes</string>
</property>
</widget>
</item>

View File

@ -10,6 +10,8 @@ QtProgressCallback::QtProgressCallback(QWidget* parent_widget, float show_delay)
m_dialog.setWindowTitle(tr("DuckStation"));
m_dialog.setMinimumSize(QSize(500, 0));
m_dialog.setModal(parent_widget != nullptr);
m_dialog.setAutoClose(false);
m_dialog.setAutoReset(false);
checkForDelayedShow();
}
@ -57,10 +59,9 @@ void QtProgressCallback::SetProgressValue(u32 value)
BaseProgressCallback::SetProgressValue(value);
checkForDelayedShow();
if (!m_dialog.isVisible() || static_cast<u32>(m_dialog.value()) == m_progress_range)
return;
if (m_dialog.isVisible() && static_cast<u32>(m_dialog.value()) != m_progress_range)
m_dialog.setValue(m_progress_value);
m_dialog.setValue(m_progress_value);
QCoreApplication::processEvents();
}

View File

@ -8,6 +8,8 @@
#include "rapidjson/document.h"
#include "rapidjson/error/en.h"
#include <iomanip>
#include <memory>
#include <optional>
#include <sstream>
Log_SetChannel(GameDatabase);
@ -87,6 +89,23 @@ static bool GetUIntFromObject(const rapidjson::Value& object, const char* key, u
return true;
}
static bool GetArrayOfStringsFromObject(const rapidjson::Value& object, const char* key, std::vector<std::string>* dest)
{
dest->clear();
auto member = object.FindMember(key);
if (member == object.MemberEnd() || !member->value.IsArray())
return false;
for (const rapidjson::Value& str : member->value.GetArray())
{
if (str.IsString())
{
dest->emplace_back(str.GetString(), str.GetStringLength());
}
}
return true;
}
static const rapidjson::Value* FindDatabaseEntry(const std::string_view& code, rapidjson::Document* json)
{
for (const rapidjson::Value& current : json->GetArray())
@ -129,7 +148,7 @@ static const rapidjson::Value* FindDatabaseEntry(const std::string_view& code, r
return nullptr;
}
bool GameDatabase::GetEntryForCode(const std::string_view& code, GameDatabaseEntry* entry)
bool GameDatabase::GetEntryForCode(const std::string_view& code, GameDatabaseEntry* entry) const
{
if (!m_json)
return false;
@ -213,7 +232,88 @@ bool GameDatabase::GetEntryForCode(const std::string_view& code, GameDatabaseEnt
return true;
}
bool GameDatabase::GetEntryForDisc(CDImage* image, GameDatabaseEntry* entry)
GameDatabase::TrackHashesMap GameDatabase::GetTrackHashesMap() const
{
TrackHashesMap result;
auto json = static_cast<const rapidjson::Document*>(m_json);
for (const rapidjson::Value& current : json->GetArray())
{
if (!current.IsObject())
{
Log_WarningPrintf("entry is not an object");
continue;
}
std::vector<std::string> codes;
if (!GetArrayOfStringsFromObject(current, "codes", &codes))
{
Log_WarningPrintf("codes member is missing");
continue;
}
auto track_data = current.FindMember("track_data");
if (track_data == current.MemberEnd())
{
Log_WarningPrintf("track_data member is missing");
continue;
}
if (!track_data->value.IsArray())
{
Log_WarningPrintf("track_data is not an array");
continue;
}
uint32_t revision = 0;
for (const rapidjson::Value& track_revisions : track_data->value.GetArray())
{
if (!track_revisions.IsObject())
{
Log_WarningPrintf("track_data is not an array of object");
continue;
}
auto tracks = track_revisions.FindMember("tracks");
if (tracks == track_revisions.MemberEnd())
{
Log_WarningPrintf("tracks member is missing");
continue;
}
if (!tracks->value.IsArray())
{
Log_WarningPrintf("tracks is not an array");
continue;
}
std::string revisionString;
GetStringFromObject(track_revisions, "version", &revisionString);
for (const rapidjson::Value& track : tracks->value.GetArray())
{
auto md5_field = track.FindMember("md5");
if (md5_field == track.MemberEnd() || !md5_field->value.IsString())
{
continue;
}
auto md5 = CDImageHasher::HashFromString(
std::string_view(md5_field->value.GetString(), md5_field->value.GetStringLength()));
if (md5)
{
result.emplace(std::piecewise_construct, std::forward_as_tuple(md5.value()),
std::forward_as_tuple(codes, revisionString, revision));
}
}
revision++;
}
}
return result;
}
bool GameDatabase::GetEntryForDisc(CDImage* image, GameDatabaseEntry* entry) const
{
std::string exe_name_code(System::GetGameCodeForImage(image, false));
if (!exe_name_code.empty() && GetEntryForCode(exe_name_code, entry))

View File

@ -1,10 +1,9 @@
#pragma once
#include "common/cd_image_hasher.h"
#include "core/types.h"
#include <memory>
#include <optional>
#include <map>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
class CDImage;
@ -33,12 +32,31 @@ public:
bool Load();
void Unload();
bool GetEntryForDisc(CDImage* image, GameDatabaseEntry* entry);
bool GetEntryForDisc(CDImage* image, GameDatabaseEntry* entry) const;
bool GetEntryForCode(const std::string_view& code, GameDatabaseEntry* entry);
bool GetEntryForCode(const std::string_view& code, GameDatabaseEntry* entry) const;
bool GetTitleAndSerialForDisc(CDImage* image, GameDatabaseEntry* entry);
//bool Get
// Map of track hashes for image verification
struct TrackData
{
TrackData(std::vector<std::string> codes, std::string revisionString, uint32_t revision)
: codes(codes), revisionString(revisionString), revision(revision)
{
}
friend bool operator==(const TrackData& left, const TrackData& right)
{
// 'revisionString' is deliberately ignored in comparisons as it's redundant with comparing 'revision'! Do not
// change!
return left.codes == right.codes && left.revision == right.revision;
}
std::vector<std::string> codes;
std::string revisionString;
uint32_t revision;
};
using TrackHashesMap = std::multimap<CDImageHasher::Hash, TrackData>;
TrackHashesMap GetTrackHashesMap() const;
private:
void* m_json = nullptr;