@ -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 ;
computeTrackHashes ( ) ;
}
if ( m_redump_search_keyword . empty ( ) )
{
computeTrackHashes ( m_redump_search_keyword ) ;
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 ) ;
}
}