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, "maxPlayers": 2,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"tracks": [
{
"size": 222151104,
"md5": "d81cbbea604ca28f2c2b6d15b3f17aca"
}
],
"vibration": true, "vibration": true,
"multitap": false, "multitap": false,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"AnalogController", "AnalogController",
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 222151104,
"md5": "d81cbbea604ca28f2c2b6d15b3f17aca"
}
]
}
] ]
}, },
{ {
@ -51,18 +55,22 @@
"maxPlayers": 2, "maxPlayers": 2,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"tracks": [
{
"size": 219999024,
"md5": "418a5f5b36babd392615439c5cb2a6b4"
}
],
"vibration": true, "vibration": true,
"multitap": false, "multitap": false,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"AnalogController", "AnalogController",
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 219999024,
"md5": "418a5f5b36babd392615439c5cb2a6b4"
}
]
}
] ]
}, },
{ {
@ -82,18 +90,22 @@
"maxPlayers": 1, "maxPlayers": 1,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"tracks": [
{
"size": 353874864,
"md5": "b4306047f3c243a748409a81d6d5cf0b"
}
],
"vibration": true, "vibration": true,
"multitap": false, "multitap": false,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"AnalogController", "AnalogController",
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 353874864,
"md5": "b4306047f3c243a748409a81d6d5cf0b"
}
]
}
] ]
}, },
{ {
@ -102,12 +114,16 @@
"codes": [ "codes": [
"LSP-905314" "LSP-905314"
], ],
"track_data": [
{
"tracks": [ "tracks": [
{ {
"size": 670098912, "size": 670098912,
"md5": "3e37dc959cc5f0c4c05adb700a634169" "md5": "3e37dc959cc5f0c4c05adb700a634169"
} }
] ]
}
]
}, },
{ {
"serial": "LSP-905321", "serial": "LSP-905321",
@ -115,12 +131,16 @@
"codes": [ "codes": [
"LSP-905321" "LSP-905321"
], ],
"track_data": [
{
"tracks": [ "tracks": [
{ {
"size": 638158752, "size": 638158752,
"md5": "334127628ed5ed169a01a554b617a745" "md5": "334127628ed5ed169a01a554b617a745"
} }
] ]
}
]
}, },
{ {
"serial": "LSP-905338", "serial": "LSP-905338",
@ -128,12 +148,16 @@
"codes": [ "codes": [
"LSP-905338" "LSP-905338"
], ],
"track_data": [
{
"tracks": [ "tracks": [
{ {
"size": 626958528, "size": 626958528,
"md5": "2c9f4b2b37a5028831ff8fa884edc779" "md5": "2c9f4b2b37a5028831ff8fa884edc779"
} }
] ]
}
]
}, },
{ {
"serial": "SLES-01534", "serial": "SLES-01534",
@ -152,6 +176,16 @@
"maxPlayers": 1, "maxPlayers": 1,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"vibration": true,
"multitap": true,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController",
"PlayStationMouse"
],
"track_data": [
{
"tracks": [ "tracks": [
{ {
"size": 318096240, "size": 318096240,
@ -161,14 +195,8 @@
"size": 32104800, "size": 32104800,
"md5": "65aea234c174ee35fb574d981fe3fc4f" "md5": "65aea234c174ee35fb574d981fe3fc4f"
} }
], ]
"vibration": true, }
"multitap": true,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController",
"PlayStationMouse"
] ]
}, },
{ {
@ -188,6 +216,16 @@
"maxPlayers": 1, "maxPlayers": 1,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"vibration": true,
"multitap": true,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController",
"PlayStationMouse"
],
"track_data": [
{
"tracks": [ "tracks": [
{ {
"size": 323642256, "size": 323642256,
@ -197,14 +235,8 @@
"size": 32634000, "size": 32634000,
"md5": "f0bcda18a061e585c3ddf7dbc45bd887" "md5": "f0bcda18a061e585c3ddf7dbc45bd887"
} }
], ]
"vibration": true, }
"multitap": true,
"linkCable": false,
"controllers": [
"AnalogController",
"DigitalController",
"PlayStationMouse"
] ]
}, },
{ {
@ -224,6 +256,14 @@
"maxPlayers": 1, "maxPlayers": 1,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"vibration": false,
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
],
"track_data": [
{
"tracks": [ "tracks": [
{ {
"size": 717886848, "size": 717886848,
@ -233,12 +273,8 @@
"size": 33132624, "size": 33132624,
"md5": "92fc46fef20c496c98f18914c72eb983" "md5": "92fc46fef20c496c98f18914c72eb983"
} }
], ]
"vibration": false, }
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
] ]
}, },
{ {
@ -258,17 +294,21 @@
"maxPlayers": 1, "maxPlayers": 1,
"minBlocks": 2, "minBlocks": 2,
"maxBlocks": 2, "maxBlocks": 2,
"tracks": [
{
"size": 489314784,
"md5": "8dc550f780d42d599fe2ec08d38b2810"
}
],
"vibration": false, "vibration": false,
"multitap": false, "multitap": false,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 489314784,
"md5": "8dc550f780d42d599fe2ec08d38b2810"
}
]
}
] ]
}, },
{ {
@ -288,17 +328,21 @@
"maxPlayers": 1, "maxPlayers": 1,
"minBlocks": 5, "minBlocks": 5,
"maxBlocks": 5, "maxBlocks": 5,
"tracks": [
{
"size": 741225744,
"md5": "0c1cc1e91ddecfb834ed96265ad6be62"
}
],
"vibration": false, "vibration": false,
"multitap": false, "multitap": false,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 741225744,
"md5": "0c1cc1e91ddecfb834ed96265ad6be62"
}
]
}
] ]
}, },
{ {
@ -318,17 +362,21 @@
"maxPlayers": 1, "maxPlayers": 1,
"minBlocks": 5, "minBlocks": 5,
"maxBlocks": 5, "maxBlocks": 5,
"tracks": [
{
"size": 683618208,
"md5": "c3489730fec0e52fa1accc3e497a7182"
}
],
"vibration": false, "vibration": false,
"multitap": false, "multitap": false,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 683618208,
"md5": "c3489730fec0e52fa1accc3e497a7182"
}
]
}
] ]
}, },
{ {
@ -348,18 +396,22 @@
"maxPlayers": 4, "maxPlayers": 4,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"tracks": [
{
"size": 301434672,
"md5": "e4b9065af214d32a097992c0ba055b43"
}
],
"vibration": false, "vibration": false,
"multitap": true, "multitap": true,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"AnalogController", "AnalogController",
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 301434672,
"md5": "e4b9065af214d32a097992c0ba055b43"
}
]
}
] ]
}, },
{ {
@ -379,18 +431,22 @@
"maxPlayers": 4, "maxPlayers": 4,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"tracks": [
{
"size": 280125552,
"md5": "ec89c9f3632e3c9ed6c46bae5e5b89ce"
}
],
"vibration": false, "vibration": false,
"multitap": true, "multitap": true,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"AnalogController", "AnalogController",
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 280125552,
"md5": "ec89c9f3632e3c9ed6c46bae5e5b89ce"
}
]
}
] ]
}, },
{ {
@ -410,18 +466,22 @@
"maxPlayers": 1, "maxPlayers": 1,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"tracks": [
{
"size": 370971552,
"md5": "c3db64d87dab2f70dd08b7cbb7afc830"
}
],
"vibration": false, "vibration": false,
"multitap": false, "multitap": false,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"AnalogController", "AnalogController",
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 370971552,
"md5": "c3db64d87dab2f70dd08b7cbb7afc830"
}
]
}
] ]
}, },
{ {
@ -441,18 +501,22 @@
"maxPlayers": 4, "maxPlayers": 4,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"tracks": [
{
"size": 340068624,
"md5": "bc157d6305089b0dafb08cce86820dde"
}
],
"vibration": false, "vibration": false,
"multitap": true, "multitap": true,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"AnalogController", "AnalogController",
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 340068624,
"md5": "bc157d6305089b0dafb08cce86820dde"
}
]
}
] ]
}, },
{ {
@ -472,7 +536,6 @@
"maxPlayers": 4, "maxPlayers": 4,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"tracks": [],
"vibration": false, "vibration": false,
"multitap": true, "multitap": true,
"linkCable": false, "linkCable": false,
@ -498,18 +561,22 @@
"maxPlayers": 4, "maxPlayers": 4,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 5, "maxBlocks": 5,
"tracks": [
{
"size": 95128992,
"md5": "c863a4adac6eb4464d7ea8539f96334a"
}
],
"vibration": true, "vibration": true,
"multitap": true, "multitap": true,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"AnalogController", "AnalogController",
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 95128992,
"md5": "c863a4adac6eb4464d7ea8539f96334a"
}
]
}
] ]
}, },
{ {
@ -529,18 +596,22 @@
"maxPlayers": 4, "maxPlayers": 4,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"tracks": [
{
"size": 171982944,
"md5": "6eca9acf975e80df1912d098d6576e39"
}
],
"vibration": true, "vibration": true,
"multitap": true, "multitap": true,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"AnalogController", "AnalogController",
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 171982944,
"md5": "6eca9acf975e80df1912d098d6576e39"
}
]
}
] ]
}, },
{ {
@ -560,7 +631,6 @@
"maxPlayers": 4, "maxPlayers": 4,
"minBlocks": 2, "minBlocks": 2,
"maxBlocks": 2, "maxBlocks": 2,
"tracks": [],
"vibration": false, "vibration": false,
"multitap": true, "multitap": true,
"linkCable": false, "linkCable": false,
@ -587,18 +657,22 @@
"maxPlayers": 4, "maxPlayers": 4,
"minBlocks": 2, "minBlocks": 2,
"maxBlocks": 2, "maxBlocks": 2,
"tracks": [
{
"size": 86181984,
"md5": "2f9a68f47bf3a002547660e52efb8c76"
}
],
"vibration": false, "vibration": false,
"multitap": true, "multitap": true,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"AnalogController", "AnalogController",
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 86181984,
"md5": "2f9a68f47bf3a002547660e52efb8c76"
}
]
}
] ]
}, },
{ {
@ -618,6 +692,14 @@
"maxPlayers": 2, "maxPlayers": 2,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"vibration": false,
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
],
"track_data": [
{
"tracks": [ "tracks": [
{ {
"size": 486186624, "size": 486186624,
@ -631,12 +713,8 @@
"size": 3055248, "size": 3055248,
"md5": "d687ef30d7aef16627a2bc571b3724d7" "md5": "d687ef30d7aef16627a2bc571b3724d7"
} }
], ]
"vibration": false, }
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
] ]
}, },
{ {
@ -656,6 +734,14 @@
"maxPlayers": 1, "maxPlayers": 1,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"vibration": false,
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
],
"track_data": [
{
"tracks": [ "tracks": [
{ {
"size": 495107760, "size": 495107760,
@ -685,12 +771,8 @@
"size": 32986800, "size": 32986800,
"md5": "b4635067b7e9d9cf9aadabe5c7a0809b" "md5": "b4635067b7e9d9cf9aadabe5c7a0809b"
} }
], ]
"vibration": false, }
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
] ]
}, },
{ {
@ -710,6 +792,14 @@
"maxPlayers": 1, "maxPlayers": 1,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"vibration": false,
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
],
"track_data": [
{
"tracks": [ "tracks": [
{ {
"size": 491887872, "size": 491887872,
@ -719,12 +809,8 @@
"size": 32986800, "size": 32986800,
"md5": "b4635067b7e9d9cf9aadabe5c7a0809b" "md5": "b4635067b7e9d9cf9aadabe5c7a0809b"
} }
], ]
"vibration": false, }
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
] ]
}, },
{ {
@ -744,18 +830,22 @@
"maxPlayers": 4, "maxPlayers": 4,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"tracks": [
{
"size": 391111728,
"md5": "44523ea0630f8123e75ec08399df2fe7"
}
],
"vibration": false, "vibration": false,
"multitap": true, "multitap": true,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"AnalogController", "AnalogController",
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 391111728,
"md5": "44523ea0630f8123e75ec08399df2fe7"
}
]
}
] ]
}, },
{ {
@ -775,18 +865,22 @@
"maxPlayers": 4, "maxPlayers": 4,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"tracks": [
{
"size": 391114080,
"md5": "a7aae54b181b9b6b1eca9394cdf68caf"
}
],
"vibration": false, "vibration": false,
"multitap": true, "multitap": true,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"AnalogController", "AnalogController",
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 391114080,
"md5": "a7aae54b181b9b6b1eca9394cdf68caf"
}
]
}
] ]
}, },
{ {
@ -806,18 +900,22 @@
"maxPlayers": 4, "maxPlayers": 4,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"tracks": [
{
"size": 398913312,
"md5": "fd353612045a7452cbe695c83575a80e"
}
],
"vibration": false, "vibration": false,
"multitap": true, "multitap": true,
"linkCable": false, "linkCable": false,
"controllers": [ "controllers": [
"AnalogController", "AnalogController",
"DigitalController" "DigitalController"
],
"track_data": [
{
"tracks": [
{
"size": 398913312,
"md5": "fd353612045a7452cbe695c83575a80e"
}
]
}
] ]
}, },
{ {
@ -837,6 +935,14 @@
"maxPlayers": 1, "maxPlayers": 1,
"minBlocks": 1, "minBlocks": 1,
"maxBlocks": 1, "maxBlocks": 1,
"vibration": false,
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
],
"track_data": [
{
"tracks": [ "tracks": [
{ {
"size": 584486112, "size": 584486112,
@ -854,12 +960,8 @@
"size": 62878368, "size": 62878368,
"md5": "14ab0ed3d882b7f098bc985a5524920f" "md5": "14ab0ed3d882b7f098bc985a5524920f"
} }
], ]
"vibration": false, }
"multitap": false,
"linkCable": false,
"controllers": [
"DigitalController"
] ]
} }
] ]

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->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++) for (u8 index = 0; index < INDICES_TO_READ; index++)
{ {
progress_callback->SetProgressValue(index); progress_callback->SetProgressValue(progress);
// skip index 0 if data track // skip index 0 if data track
if (track == 1 && index == 0) if (dataTrack && index == 0)
continue; continue;
progress++;
progress_callback->PushState(); progress_callback->PushState();
if (!ReadIndex(image, track, index, digest, progress_callback)) 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->PopState();
} }
progress_callback->SetProgressValue(INDICES_TO_READ); progress_callback->SetProgressValue(progress);
progress_callback->PopState(); progress_callback->PopState();
return true; 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]); 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, bool GetImageHash(CDImage* image, Hash* out_hash,
ProgressCallback* progress_callback /*= ProgressCallback::NullProgressCallback*/) ProgressCallback* progress_callback /*= ProgressCallback::NullProgressCallback*/)
{ {

View file

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

View file

@ -1,18 +1,23 @@
#include "gamepropertiesdialog.h" #include "gamepropertiesdialog.h"
#include "common/cd_image.h" #include "common/cd_image.h"
#include "common/cd_image_hasher.h" #include "common/cd_image_hasher.h"
#include "common/string_util.h"
#include "core/settings.h" #include "core/settings.h"
#include "core/system.h" #include "core/system.h"
#include "frontend-common/game_database.h"
#include "frontend-common/game_list.h" #include "frontend-common/game_list.h"
#include "qthostinterface.h" #include "qthostinterface.h"
#include "qtprogresscallback.h" #include "qtprogresscallback.h"
#include "qtutils.h" #include "qtutils.h"
#include "rapidjson/document.h"
#include "scmversion/scmversion.h" #include "scmversion/scmversion.h"
#include <QtGui/QClipboard> #include <QtGui/QClipboard>
#include <QtGui/QGuiApplication> #include <QtGui/QGuiApplication>
#include <QtWidgets/QFileDialog> #include <QtWidgets/QFileDialog>
#include <QtWidgets/QInputDialog> #include <QtWidgets/QInputDialog>
#include <QtWidgets/QMessageBox> #include <QtWidgets/QMessageBox>
#include <map>
Log_SetChannel(GamePropertiesDialog);
static constexpr char MEMORY_CARD_IMAGE_FILTER[] = static constexpr char MEMORY_CARD_IMAGE_FILTER[] =
QT_TRANSLATE_NOOP("MemoryCardSettingsWidget", "All Memory Card Types (*.mcd *.mcr *.mc)"); 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())); m_ui.gameCode->setText(QStringLiteral("%1 / %2").arg(ge->code.c_str()).arg(hash_code.c_str()));
else else
m_ui.gameCode->setText(QString::fromStdString(ge->code)); m_ui.gameCode->setText(QString::fromStdString(ge->code));
m_ui.revision->setText(tr("<not verified>"));
m_ui.region->setCurrentIndex(static_cast<int>(ge->region)); 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.comments->setDisabled(true);
m_ui.versionTested->setDisabled(true); m_ui.versionTested->setDisabled(true);
m_ui.setToCurrent->setDisabled(true); m_ui.setToCurrent->setDisabled(true);
m_ui.verifyDump->setDisabled(true);
m_ui.exportCompatibilityInfo->setDisabled(true); m_ui.exportCompatibilityInfo->setDisabled(true);
} }
else 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, 2, new QTableWidgetItem(MSFTotString(position)));
m_ui.tracks->setItem(row, 3, new QTableWidgetItem(MSFTotString(length))); m_ui.tracks->setItem(row, 3, new QTableWidgetItem(MSFTotString(length)));
m_ui.tracks->setItem(row, 4, new QTableWidgetItem(tr("<not computed>"))); 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() 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() void GamePropertiesDialog::connectUi()
@ -546,14 +555,12 @@ void GamePropertiesDialog::connectUi()
&GamePropertiesDialog::saveCompatibilityInfoIfChanged); &GamePropertiesDialog::saveCompatibilityInfoIfChanged);
connect(m_ui.setToCurrent, &QPushButton::clicked, this, &GamePropertiesDialog::onSetVersionTestedToCurrentClicked); connect(m_ui.setToCurrent, &QPushButton::clicked, this, &GamePropertiesDialog::onSetVersionTestedToCurrentClicked);
connect(m_ui.computeHashes, &QPushButton::clicked, this, &GamePropertiesDialog::onComputeHashClicked); 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, connect(m_ui.exportCompatibilityInfo, &QPushButton::clicked, this,
&GamePropertiesDialog::onExportCompatibilityInfoClicked); &GamePropertiesDialog::onExportCompatibilityInfoClicked);
connect(m_ui.close, &QPushButton::clicked, this, &QDialog::close); connect(m_ui.close, &QPushButton::clicked, this, &QDialog::close);
connect(m_ui.tabWidget, &QTabWidget::currentChanged, [this](int index) { connect(m_ui.tabWidget, &QTabWidget::currentChanged, [this](int index) {
const bool show_buttons = index == 0; const bool show_buttons = index == 0;
m_ui.computeHashes->setVisible(show_buttons); m_ui.computeHashes->setVisible(show_buttons);
m_ui.verifyDump->setVisible(show_buttons);
m_ui.exportCompatibilityInfo->setVisible(show_buttons); m_ui.exportCompatibilityInfo->setVisible(show_buttons);
}); });
@ -924,15 +931,19 @@ void GamePropertiesDialog::onSetVersionTestedToCurrentClicked()
void GamePropertiesDialog::onComputeHashClicked() void GamePropertiesDialog::onComputeHashClicked()
{ {
if (m_tracks_hashed) if (m_redump_search_keyword.empty())
return; {
computeTrackHashes(m_redump_search_keyword);
computeTrackHashes(); if (!m_redump_search_keyword.empty())
} m_ui.computeHashes->setText(tr("Search on Redump.org"));
}
void GamePropertiesDialog::onVerifyDumpClicked() else
{ {
QMessageBox::critical(this, tr("Not yet implemented"), tr("Not yet implemented")); QtUtils::OpenURL(
this, StringUtil::StdStringFromFormat("http://redump.org/discs/quicksearch/%s", m_redump_search_keyword.c_str())
.c_str());
}
} }
void GamePropertiesDialog::onExportCompatibilityInfoClicked() void GamePropertiesDialog::onExportCompatibilityInfoClicked()
@ -952,7 +963,7 @@ void GamePropertiesDialog::onExportCompatibilityInfoClicked()
QGuiApplication::clipboard()->setText(xml); QGuiApplication::clipboard()->setText(xml);
} }
void GamePropertiesDialog::computeTrackHashes() void GamePropertiesDialog::computeTrackHashes(std::string& redump_keyword)
{ {
if (m_path.empty()) if (m_path.empty())
return; return;
@ -961,9 +972,25 @@ void GamePropertiesDialog::computeTrackHashes()
if (!image) if (!image)
return; 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); QtProgressCallback progress_callback(this);
progress_callback.SetProgressRange(image->GetTrackCount()); 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++) for (u8 track = 1; track <= image->GetTrackCount(); track++)
{ {
progress_callback.SetProgressValue(track - 1); progress_callback.SetProgressValue(track - 1);
@ -973,14 +1000,98 @@ void GamePropertiesDialog::computeTrackHashes()
if (!CDImageHasher::GetTrackHash(image.get(), track, &hash, &progress_callback)) if (!CDImageHasher::GetTrackHash(image.get(), track, &hash, &progress_callback))
{ {
progress_callback.PopState(); progress_callback.PopState();
calculate_hash_success = false;
break; break;
} }
track_hashes.emplace_back(hash);
QString hash_string(QString::fromStdString(CDImageHasher::HashToString(hash)));
QTableWidgetItem* item = m_ui.tracks->item(track - 1, 4); QTableWidgetItem* item = m_ui.tracks->item(track - 1, 4);
item->setText(hash_string); item->setText(QString::fromStdString(CDImageHasher::HashToString(hash)));
progress_callback.PopState(); 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 onSetVersionTestedToCurrentClicked();
void onComputeHashClicked(); void onComputeHashClicked();
void onVerifyDumpClicked();
void onExportCompatibilityInfoClicked(); void onExportCompatibilityInfoClicked();
void updateCPUClockSpeedLabel(); void updateCPUClockSpeedLabel();
void onEnableCPUClockSpeedControlChecked(int state); void onEnableCPUClockSpeedControlChecked(int state);
@ -49,7 +48,7 @@ private:
void connectBooleanUserSetting(QCheckBox* cb, std::optional<bool>* value); void connectBooleanUserSetting(QCheckBox* cb, std::optional<bool>* value);
void saveGameSettings(); void saveGameSettings();
void fillEntryFromUi(GameListCompatibilityEntry* entry); void fillEntryFromUi(GameListCompatibilityEntry* entry);
void computeTrackHashes(); void computeTrackHashes(std::string& redump_keyword);
void onResize(); void onResize();
void onUserAspectRatioChanged(); void onUserAspectRatioChanged();
@ -61,9 +60,9 @@ private:
std::string m_path; std::string m_path;
std::string m_game_code; std::string m_game_code;
std::string m_game_title; std::string m_game_title;
std::string m_redump_search_keyword;
GameSettings::Entry m_game_settings; GameSettings::Entry m_game_settings;
bool m_compatibility_info_changed = false; bool m_compatibility_info_changed = false;
bool m_tracks_hashed = false;
}; };

View file

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

View file

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

View file

@ -8,6 +8,8 @@
#include "rapidjson/document.h" #include "rapidjson/document.h"
#include "rapidjson/error/en.h" #include "rapidjson/error/en.h"
#include <iomanip> #include <iomanip>
#include <memory>
#include <optional>
#include <sstream> #include <sstream>
Log_SetChannel(GameDatabase); Log_SetChannel(GameDatabase);
@ -87,6 +89,23 @@ static bool GetUIntFromObject(const rapidjson::Value& object, const char* key, u
return true; 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) static const rapidjson::Value* FindDatabaseEntry(const std::string_view& code, rapidjson::Document* json)
{ {
for (const rapidjson::Value& current : json->GetArray()) for (const rapidjson::Value& current : json->GetArray())
@ -129,7 +148,7 @@ static const rapidjson::Value* FindDatabaseEntry(const std::string_view& code, r
return nullptr; 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) if (!m_json)
return false; return false;
@ -213,7 +232,88 @@ bool GameDatabase::GetEntryForCode(const std::string_view& code, GameDatabaseEnt
return true; 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)); std::string exe_name_code(System::GetGameCodeForImage(image, false));
if (!exe_name_code.empty() && GetEntryForCode(exe_name_code, entry)) if (!exe_name_code.empty() && GetEntryForCode(exe_name_code, entry))

View file

@ -1,10 +1,9 @@
#pragma once #pragma once
#include "common/cd_image_hasher.h"
#include "core/types.h" #include "core/types.h"
#include <memory> #include <map>
#include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <unordered_map>
#include <vector> #include <vector>
class CDImage; class CDImage;
@ -33,12 +32,31 @@ public:
bool Load(); bool Load();
void Unload(); 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); // Map of track hashes for image verification
//bool Get 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: private:
void* m_json = nullptr; void* m_json = nullptr;