1 #include <QCryptographicHash>
2 #include <QHeaderView>
3 #include <QDir>
4 #include <QFileInfo>
5 #include <QFile>
6 #include <QTextStream>
7 #include <QScrollBar>
8 #include <QTest>
9 #include <QMultiMap>
10 #include <QHashIterator>
11 #include <QFileDialog>
12 #include <QClipboard>
13 #include <QDateTime>
14 #include <QDate>
15 #include <QTime>
16 #include <QXmlQuery>
17 #include <QPalette>
18 #include <QRegExp>
19 #include <QChar>
20 #if defined(QMC2_OS_WIN)
21 #include <windows.h>
22 #endif
23 
24 #include "romalyzer.h"
25 #include "qmc2main.h"
26 #include "options.h"
27 #include "machinelist.h"
28 #include "softwarelist.h"
29 #include "macros.h"
30 #include "unzip.h"
31 #include "zip.h"
32 #include "sevenzipfile.h"
33 #if defined(QMC2_LIBARCHIVE_ENABLED)
34 #include "archivefile.h"
35 #endif
36 
37 // external global variables
38 extern MainWindow *qmc2MainWindow;
39 extern Settings *qmc2Config;
40 extern Options *qmc2Options;
41 extern MachineList *qmc2MachineList;
42 extern bool qmc2ReloadActive;
43 extern bool qmc2CleaningUp;
44 extern bool qmc2EarlyStartup;
45 extern bool qmc2LoadingInterrupted;
46 extern bool qmc2SuppressQtMessages;
47 extern SoftwareList *qmc2SoftwareList;
48 extern QHash<QString, QTreeWidgetItem *> qmc2MachineListItemHash;
49 extern QHash<QString, QTreeWidgetItem *> qmc2HierarchyItemHash;
50 extern QHash<QString, QTreeWidgetItem *> qmc2CategoryItemHash;
51 extern QHash<QString, QTreeWidgetItem *> qmc2VersionItemHash;
52 extern QAbstractItemView::ScrollHint qmc2CursorPositioningMode;
53 
54 /*
55   HOWTO: Calculate the 32-bit CRC of a QByteArray with zlib:
56 
57   #include <QByteArray>
58   #include <zlib.h>
59   ...
60   QByteArray ba("This is a test -- 123 :)!");
61   ulong crc = crc32(0, 0, 0);
62   crc = crc32(crc, (const Bytef *)ba.data(), ba.size());
63   printf("CRC-32 = %x\n", crc);
64 */
65 
66 /*
67   INFO: How MAME searches for ROMs & CHDs of a game
68 
69   <rompath> = <first_rompath>
70   foreach <file> {
71     1) try <rompath>/<machine>/<file> - if okay skip to 7)
72     2) try <file> from <rompath>/<machine>.7z/.zip - if okay skip to 7)
73     3) if more <rompaths> exists, retry 1) and 2) for <rompath> = <next_rompath>
74     4a) if a <merge> exists, retry 1), 2) and 3) for <romof>/<merge> instead of <machine>/<file>
75     4b) if <merge> is empty, retry 1), 2) and 3) for <romof>/<file> instead of <machine>/<file>
76     6) <file> was not found - stop
77     7) load <file> and check CRC
78     8) ...
79   }
80 
81   Backward engineering powered by strace :)!
82 */
83 
ROMAlyzer(QWidget * parent,int romalyzerMode)84 ROMAlyzer::ROMAlyzer(QWidget *parent, int romalyzerMode)
85 #if defined(QMC2_OS_WIN)
86 	: QDialog(parent, Qt::Dialog),
87 #else
88 	: QDialog(parent, Qt::Dialog | Qt::SubWindow),
89 #endif
90 	m_collectionRebuilder(0),
91 	m_romPathCleaner(0)
92 {
93 	setupUi(this);
94 	setMode(romalyzerMode);
95 	setActive(false);
96 	setPaused(false);
97 
98 #if !defined(QMC2_LIBARCHIVE_ENABLED)
99 	toolButtonCheckSumDbDeepScan->disconnect(toolButtonCheckSumDbLibArchive);
100 	toolButtonCheckSumDbLibArchive->setChecked(false);
101 	toolButtonCheckSumDbLibArchive->setVisible(false);
102 	stackedWidgetReproductionOptions->removeWidget(pageZipsLibArchive);
103 	comboBoxSetRewriterReproductionType->removeItem(1);
104 #endif
105 
106 	m_checkSumDbQueryStatusPixmap = QPixmap(QString::fromUtf8(":/data/img/database.png"));
107 
108 	treeWidgetChecksums->header()->setSortIndicatorShown(false);
109 	treeWidgetChecksums->header()->restoreState(qmc2Config->value(QMC2_FRONTEND_PREFIX + "Layout/" + m_settingsKey + "/ReportHeaderState", QByteArray()).toByteArray());
110 	treeWidgetChecksumWizardSearchResult->header()->restoreState(qmc2Config->value(QMC2_FRONTEND_PREFIX + "Layout/" + m_settingsKey + "/ChecksumWizardHeaderState", QByteArray()).toByteArray());
111 	move(qmc2Config->value(QMC2_FRONTEND_PREFIX + "Layout/" + m_settingsKey + "/Position", QPoint(0, 0)).toPoint());
112 	resize(qmc2Config->value(QMC2_FRONTEND_PREFIX + "Layout/" + m_settingsKey + "/Size", QSize(600, 800)).toSize());
113 
114 	// compression types for CHD v3 and v4
115 	chdCompressionTypes << tr("none") << tr("zlib") << tr("zlib+") << tr("A/V codec");
116 
117 	// compression types for CHD v5
118 	chdCompressionTypesV5["avhu"] = tr("avhu (A/V Huffman)");
119 	chdCompressionTypesV5["cdfl"] = tr("cdfl (CD FLAC)");
120 	chdCompressionTypesV5["cdlz"] = tr("cdlz (CD LZMA)");
121 	chdCompressionTypesV5["cdzl"] = tr("cdzl (CD Deflate)");
122 	chdCompressionTypesV5["flac"] = tr("flac (FLAC)");
123 	chdCompressionTypesV5["huff"] = tr("huff (Huffman)");
124 	chdCompressionTypesV5["lzma"] = tr("lzma (LZMA)");
125 	chdCompressionTypesV5["zlib"] = tr("zlib (Deflate)");
126 
127 	chdManagerRunning = chdManagerMD5Success = chdManagerSHA1Success = false;
128 #if QMC2_CHD_CURRENT_VERSION < 5
129 	chdManagerCurrentHunk = chdManagerTotalHunks = 0;
130 #else
131 	chdManagerCurrentHunk = 0;
132 	chdManagerTotalHunks = 100;
133 	checkBoxVerifyCHDs->setToolTip(tr("Verify CHDs through 'chdman verify'"));
134 	checkBoxUpdateCHDs->setToolTip(tr("Try to update CHDs if their header indicates an older version ('chdman copy')"));
135 #endif
136 	lastRowCount = -1;
137 
138 #if defined(QMC2_WIP_ENABLED) // FIXME: WIP
139 	m_romPathCleaner = new RomPathCleaner(mode() == QMC2_ROMALYZER_MODE_SYSTEM ? "RomPathCleaner" : "SoftwareRomPathCleaner", this);
140 	gridLayoutRomPathCleaner->addWidget(romPathCleaner(), 0, 0);
141 #else
142 	tabWidgetAnalysis->removeTab(tabWidgetAnalysis->indexOf(tabRomPathCleaner));
143 #endif
144 
145 	adjustIconSizes();
146 
147 	widgetCheckSumDbQueryStatus->setVisible(false);
148 	pushButtonPause->setVisible(false);
149 
150 	wizardSearch = quickSearch = false;
151 
152 	QFont logFont;
153 	logFont.fromString(qmc2Config->value(QMC2_FRONTEND_PREFIX + "GUI/LogFont").toString());
154 	textBrowserLog->setFont(logFont);
155 
156 	connect(&animTimer, SIGNAL(timeout()), this, SLOT(animationTimeout()));
157 
158 	QString s;
159 	QAction *action;
160 
161 	romFileContextMenu = new QMenu(this);
162 	romFileContextMenu->hide();
163 
164 	s = tr("Search check-sum");
165 	action = romFileContextMenu->addAction(s);
166 	action->setToolTip(s); action->setStatusTip(s);
167 	action->setIcon(QIcon(QString::fromUtf8(":/data/img/filefind.png")));
168 	connect(action, SIGNAL(triggered()), this, SLOT(runChecksumWizard()));
169 
170 	romSetContextMenu = new QMenu(this);
171 	romSetContextMenu->hide();
172 
173 	s = tr("Rewrite set");
174 	action = romSetContextMenu->addAction(s);
175 	action->setToolTip(s); action->setStatusTip(s);
176 	action->setIcon(QIcon(QString::fromUtf8(":/data/img/filesave.png")));
177 	connect(action, SIGNAL(triggered()), this, SLOT(runSetRewriter()));
178 	actionRewriteSet = action;
179 
180 	s = tr("Analyse referenced devices");
181 	action = romSetContextMenu->addAction(s);
182 	action->setToolTip(s); action->setStatusTip(s);
183 	action->setIcon(QIcon(QString::fromUtf8(":/data/img/romalyzer.png")));
184 	connect(action, SIGNAL(triggered()), this, SLOT(analyzeDeviceRefs()));
185 	actionAnalyzeDeviceRefs = action;
186 
187 	s = tr("Copy to clipboard");
188 	action = romSetContextMenu->addAction(s);
189 	action->setToolTip(s); action->setStatusTip(s);
190 	action->setIcon(QIcon(QString::fromUtf8(":/data/img/editcopy.png")));
191 	connect(action, SIGNAL(triggered()), this, SLOT(copyToClipboard()));
192 
193 	s = tr("Copy to clipboard (bad / missing dumps)");
194 	action = romSetContextMenu->addAction(s);
195 	action->setToolTip(s); action->setStatusTip(s);
196 	action->setIcon(QIcon(QString::fromUtf8(":/data/img/editcopybad.png")));
197 	connect(action, SIGNAL(triggered()), this, SLOT(copyBadToClipboard()));
198 	actionCopyBadToClipboard = action;
199 
200 	// setup tools-menu
201 	toolsMenu = new QMenu(this);
202 	actionImportFromDataFile = toolsMenu->addAction(QIcon(QString::fromUtf8(":/data/img/fileopen.png")), tr("Import from data file"), this, SLOT(importFromDataFile()));
203 	actionExportToDataFile = toolsMenu->addAction(QIcon(QString::fromUtf8(":/data/img/filesaveas.png")), tr("Export to data file"), this, SLOT(exportToDataFile()));
204 	actionExportToDataFile->setEnabled(false);
205 	toolButtonToolsMenu->setMenu(toolsMenu);
206 
207 	// check-sum DB related
208 	m_checkSumScannerLog = new CheckSumScannerLog(mode() == QMC2_ROMALYZER_MODE_SYSTEM ? "CheckSumScannerLog" : "SoftwareCheckSumScannerLog", 0);
209 	connect(checkSumScannerLog(), SIGNAL(windowOpened()), this, SLOT(checkSumScannerLog_windowOpened()));
210 	connect(checkSumScannerLog(), SIGNAL(windowClosed()), this, SLOT(checkSumScannerLog_windowClosed()));
211 	m_checkSumDb = new CheckSumDatabaseManager(this, m_settingsKey);
212 	connect(checkSumDb(), SIGNAL(log(const QString &)), this, SLOT(log(const QString &)));
213 	m_checkSumScannerThread = new CheckSumScannerThread(checkSumScannerLog(), m_settingsKey, this);
214 	checkSumScannerLog()->setLogSyncMutex(&checkSumScannerThread()->logSyncMutex);
215 	connect(checkSumScannerThread(), SIGNAL(log(const QString &)), checkSumScannerLog(), SLOT(log(const QString &)));
216 	connect(checkSumScannerThread(), SIGNAL(scanStarted()), this, SLOT(checkSumScannerThread_scanStarted()));
217 	connect(checkSumScannerThread(), SIGNAL(scanFinished()), this, SLOT(checkSumScannerThread_scanFinished()));
218 	connect(checkSumScannerThread(), SIGNAL(scanPaused()), this, SLOT(checkSumScannerThread_scanPaused()));
219 	connect(checkSumScannerThread(), SIGNAL(scanResumed()), this, SLOT(checkSumScannerThread_scanResumed()));
220 	connect(checkSumScannerThread(), SIGNAL(scanResumed()), this, SLOT(checkSumScannerThread_scanResumed()));
221 	connect(checkSumScannerThread(), SIGNAL(progressTextChanged(const QString &)), checkSumScannerLog(), SLOT(progressTextChanged(const QString &)));
222 	connect(checkSumScannerThread(), SIGNAL(progressRangeChanged(int, int)), checkSumScannerLog(), SLOT(progressRangeChanged(int, int)));
223 	connect(checkSumScannerThread(), SIGNAL(progressChanged(int)), checkSumScannerLog(), SLOT(progressChanged(int)));
224 	connect(&checkSumDbStatusTimer, SIGNAL(timeout()), this, SLOT(updateCheckSumDbStatus()));
225 	updateCheckSumDbStatus();
226 	checkSumDbStatusTimer.start(QMC2_CHECKSUM_DB_STATUS_UPDATE_LONG);
227 	pushButtonCheckSumDbPauseResumeScan->hide();
228 	connect(&m_checkSumTextChangedTimer, SIGNAL(timeout()), this, SLOT(lineEditChecksumWizardHash_textChanged_delayed()));
229 
230 	currentFilesSize = 0;
231 	if ( mode() == QMC2_ROMALYZER_MODE_SOFTWARE ) {
232 		if ( !qmc2SoftwareList ) {
233 			QLayout *vbl = qmc2MainWindow->tabSoftwareList->layout();
234 			if ( vbl )
235 				delete vbl;
236 			int left, top, right, bottom;
237 			qmc2MainWindow->gridLayout->getContentsMargins(&left, &top, &right, &bottom);
238 			QVBoxLayout *layout = new QVBoxLayout;
239 			layout->setContentsMargins(left, top, right, bottom);
240 			qmc2SoftwareList = new SoftwareList("qmc2_romalyzer_dummy", qmc2MainWindow->tabSoftwareList);
241 			layout->addWidget(qmc2SoftwareList);
242 			qmc2MainWindow->tabSoftwareList->setLayout(layout);
243 			qmc2MainWindow->isCreatingSoftList = false;
244 			setEnabled(false);
245 			connect(qmc2SoftwareList, SIGNAL(loadFinished(bool)), this, SLOT(softwareListLoadFinished(bool)));
246 			QTimer::singleShot(0, qmc2SoftwareList, SLOT(load()));
247 		}
248 	}
249 
250 #if defined(QMC2_OS_MAC)
251 	setParent(qmc2MainWindow, Qt::Dialog);
252 #endif
253 
254 	tabWidgetAnalysis->setCurrentIndex(qmc2Config->value(QMC2_FRONTEND_PREFIX + "Layout/" + m_settingsKey + "/AnalysisTab", 0).toInt());
255 }
256 
~ROMAlyzer()257 ROMAlyzer::~ROMAlyzer()
258 {
259 	if ( checkSumDb() )
260 		delete checkSumDb();
261 	if ( checkSumScannerLog() )
262 		delete checkSumScannerLog();
263 	if ( checkSumScannerThread() )
264 		delete checkSumScannerThread();
265 	if ( romPathCleaner() )
266 		delete romPathCleaner();
267 	if ( collectionRebuilder() )
268 		delete collectionRebuilder();
269 }
270 
adjustIconSizes()271 void ROMAlyzer::adjustIconSizes()
272 {
273 	QFont f;
274 	f.fromString(qmc2Config->value(QMC2_FRONTEND_PREFIX + "GUI/Font").toString());
275 	QFontMetrics fm(f);
276 	QSize iconSize = QSize(fm.height() - 2, fm.height() - 2);
277 	QSize iconSizeTreeWidgets = iconSize + QSize(2, 2);
278 	iconSizeTreeWidgets.setWidth(iconSizeTreeWidgets.width() * 2);
279 	pushButtonAnalyze->setIconSize(iconSize);
280 	pushButtonPause->setIconSize(iconSize);
281 	pushButtonClose->setIconSize(iconSize);
282 	pushButtonSearchForward->setIconSize(iconSize);
283 	pushButtonSearchBackward->setIconSize(iconSize);
284 	toolButtonSaveLog->setIconSize(iconSize);
285 	toolButtonBrowseCHDManagerExecutableFile->setIconSize(iconSize);
286 	toolButtonBrowseTemporaryWorkingDirectory->setIconSize(iconSize);
287 	toolButtonBrowseSetRewriterOutputPath->setIconSize(iconSize);
288 	toolButtonBrowseSetRewriterAdditionalRomPath->setIconSize(iconSize);
289 	toolButtonBrowseBackupFolder->setIconSize(iconSize);
290 	QTabBar *tabBar = tabWidgetAnalysis->findChild<QTabBar *>();
291 	if ( tabBar )
292 		tabBar->setIconSize(iconSize);
293 	toolButtonToolsMenu->setIconSize(iconSize);
294 	pushButtonChecksumWizardAnalyzeSelectedSets->setIconSize(iconSize);
295 	treeWidgetChecksums->setIconSize(iconSizeTreeWidgets);
296 	treeWidgetChecksumWizardSearchResult->setIconSize(iconSizeTreeWidgets);
297 	pushButtonChecksumWizardSearch->setIconSize(iconSize);
298 	toolButtonCheckSumDbAddPath->setIconSize(iconSize);
299 	toolButtonCheckSumDbRemovePath->setIconSize(iconSize);
300 	toolButtonCheckSumDbLibArchive->setIconSize(iconSize);
301 	toolButtonBrowseCheckSumDbDatabasePath->setIconSize(iconSize);
302 	pushButtonCheckSumDbScan->setIconSize(iconSize);
303 	pushButtonCheckSumDbPauseResumeScan->setIconSize(iconSize);
304 	widgetCheckSumDbQueryStatus->setFixedWidth(widgetCheckSumDbQueryStatus->height());
305 	QPalette pal = widgetCheckSumDbQueryStatus->palette();
306 	pal.setBrush(QPalette::Window, m_checkSumDbQueryStatusPixmap.scaled(widgetCheckSumDbQueryStatus->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
307 	widgetCheckSumDbQueryStatus->setPalette(pal);
308 	if ( romPathCleaner() )
309 		romPathCleaner()->adjustIconSizes();
310 }
311 
on_pushButtonClose_clicked()312 void ROMAlyzer::on_pushButtonClose_clicked()
313 {
314 	if ( active() )
315 		on_pushButtonAnalyze_clicked();
316 }
317 
on_pushButtonAnalyze_clicked()318 void ROMAlyzer::on_pushButtonAnalyze_clicked()
319 {
320 	if ( qmc2ReloadActive ) {
321 		qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("please wait for reload to finish and try again"));
322 		return;
323 	}
324 
325 	if ( active() ) {
326 		// stop ROMAlyzer
327 		log(tr("stopping analysis"));
328 		qmc2LoadingInterrupted = true;
329 	} else if ( qmc2MachineList->numMachines > 0 ) {
330 		// start ROMAlyzer
331 		log(tr("starting analysis"));
332 		qmc2LoadingInterrupted = false;
333 		QTimer::singleShot(0, this, SLOT(analyze()));
334 	}
335 }
336 
on_pushButtonPause_clicked()337 void ROMAlyzer::on_pushButtonPause_clicked()
338 {
339 	setPaused(!paused());
340 	if ( paused() ) {
341 		log(tr("pausing analysis"));
342 		pushButtonPause->setEnabled(false);
343 	} else {
344 		log(tr("resuming analysis"));
345 		pushButtonPause->setText(tr("&Pause"));
346 	}
347 }
348 
on_pushButtonSearchForward_clicked()349 void ROMAlyzer::on_pushButtonSearchForward_clicked()
350 {
351 	if ( !textBrowserLog->find(lineEditSearchString->text()) ) {
352 		lineEditSearchString->setEnabled(false);
353 		QTimer::singleShot(QMC2_ROMALYZER_FLASH_TIME, this, SLOT(enableSearchEdit()));
354 	}
355 }
356 
on_pushButtonSearchBackward_clicked()357 void ROMAlyzer::on_pushButtonSearchBackward_clicked()
358 {
359 	if ( !textBrowserLog->find(lineEditSearchString->text(), QTextDocument::FindBackward) ) {
360 		lineEditSearchString->setEnabled(false);
361 		QTimer::singleShot(QMC2_ROMALYZER_FLASH_TIME, this, SLOT(enableSearchEdit()));
362 	}
363 }
364 
on_lineEditSoftwareLists_textChanged(QString text)365 void ROMAlyzer::on_lineEditSoftwareLists_textChanged(QString text)
366 {
367 	if ( !active() )
368 		pushButtonAnalyze->setEnabled(!text.isEmpty() && !lineEditSets->text().isEmpty());
369 }
370 
on_lineEditSets_textChanged(QString text)371 void ROMAlyzer::on_lineEditSets_textChanged(QString text)
372 {
373 	if ( !active() )
374 		pushButtonAnalyze->setEnabled(mode() == QMC2_ROMALYZER_MODE_SYSTEM ? !text.isEmpty() : !text.isEmpty() && !lineEditSoftwareLists->text().isEmpty());
375 }
376 
on_checkBoxCalculateCRC_toggled(bool enable)377 void ROMAlyzer::on_checkBoxCalculateCRC_toggled(bool enable)
378 {
379 	treeWidgetChecksums->setColumnHidden(QMC2_ROMALYZER_COLUMN_CRC, !enable);
380 }
381 
on_checkBoxCalculateMD5_toggled(bool enable)382 void ROMAlyzer::on_checkBoxCalculateMD5_toggled(bool enable)
383 {
384 	treeWidgetChecksums->setColumnHidden(QMC2_ROMALYZER_COLUMN_MD5, !enable);
385 }
386 
on_checkBoxCalculateSHA1_toggled(bool enable)387 void ROMAlyzer::on_checkBoxCalculateSHA1_toggled(bool enable)
388 {
389 	treeWidgetChecksums->setColumnHidden(QMC2_ROMALYZER_COLUMN_SHA1, !enable);
390 	tabChecksumWizard->setEnabled(enable);
391 }
392 
animationTimeout()393 void ROMAlyzer::animationTimeout()
394 {
395 	switch ( ++animSeq ) {
396 		case 0:
397 			pushButtonAnalyze->setIcon(QIcon(QString::fromUtf8(":/data/img/romalyzer.png")));
398 			break;
399 
400 		case 2:
401 			pushButtonAnalyze->setIcon(QIcon(QString::fromUtf8(":/data/img/romalyzer_flipped.png")));
402 			break;
403 
404 		default:
405 			break;
406 	}
407 	if ( animSeq > 2 )
408 		animSeq = -1;
409 }
410 
closeEvent(QCloseEvent * e)411 void ROMAlyzer::closeEvent(QCloseEvent *e)
412 {
413 	if ( e && active() )
414 		on_pushButtonAnalyze_clicked();
415 
416 	// save settings
417 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/AppendReport", checkBoxAppendReport->isChecked());
418 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/ExpandFiles", checkBoxExpandFiles->isChecked());
419 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/ExpandChecksums", checkBoxExpandChecksums->isChecked());
420 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/AutoScroll", checkBoxAutoScroll->isChecked());
421 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CalculateCRC", checkBoxCalculateCRC->isChecked());
422 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CalculateSHA1", checkBoxCalculateSHA1->isChecked());
423 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CalculateMD5", checkBoxCalculateMD5->isChecked());
424 	if ( mode() == QMC2_ROMALYZER_MODE_SYSTEM )
425 		qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SelectMachine", checkBoxSelectMachine->isChecked());
426 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/MaxFileSize", spinBoxMaxFileSize->value());
427 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/MaxLogSize", spinBoxMaxLogSize->value());
428 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/MaxReports", spinBoxMaxReports->value());
429 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CreateBackups", checkBoxCreateBackups->isChecked());
430 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/BackupFolder", lineEditBackupFolder->text());
431 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/EnableCHDManager", groupBoxCHDManager->isChecked());
432 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CHDManagerExecutableFile", lineEditCHDManagerExecutableFile->text());
433 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/TemporaryWorkingDirectory", lineEditTemporaryWorkingDirectory->text());
434 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/VerifyCHDs", checkBoxVerifyCHDs->isChecked());
435 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/UpdateCHDs", checkBoxUpdateCHDs->isChecked());
436 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/EnableSetRewriter", groupBoxSetRewriter->isChecked());
437 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterWhileAnalyzing", checkBoxSetRewriterWhileAnalyzing->isChecked());
438 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterSelfContainedSets", checkBoxSetRewriterSelfContainedSets->isChecked());
439 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterGoodDumpsOnly", checkBoxSetRewriterGoodDumpsOnly->isChecked());
440 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterAbortOnError", checkBoxSetRewriterAbortOnError->isChecked());
441 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterReproductionType", comboBoxSetRewriterReproductionType->currentIndex());
442 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterZipLevel", spinBoxSetRewriterZipLevel->value());
443 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterUniqueCRCs", checkBoxSetRewriterUniqueCRCs->isChecked());
444 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterLibArchiveDeflate", comboBoxSetRewriterLibArchiveDeflate->currentIndex() == 0);
445 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterAddZipComment", checkBoxSetRewriterAddZipComment->isChecked());
446 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterOutputPath", lineEditSetRewriterOutputPath->text());
447 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterUseAdditionalRomPath", checkBoxSetRewriterUseAdditionalRomPath->isChecked());
448 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterAdditionalRomPath", lineEditSetRewriterAdditionalRomPath->text());
449 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CollectionRebuilderHashCache", checkBoxCollectionRebuilderHashCache->isChecked());
450 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CollectionRebuilderDryRun", checkBoxCollectionRebuilderDryRun->isChecked());
451 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CollectionRebuilderCHDHandling", comboBoxCollectionRebuilderCHDHandling->currentIndex());
452 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/ChecksumWizardHashType", comboBoxChecksumWizardHashType->currentIndex());
453 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/EnableCheckSumDb", groupBoxCheckSumDatabase->isChecked());
454 	QStringList checkSumDbScannedPaths;
455 	QStringList checkSumDbScannedPathsEnabled;
456 	for (int i = 0; i < listWidgetCheckSumDbScannedPaths->count(); i++) {
457 		checkSumDbScannedPaths << listWidgetCheckSumDbScannedPaths->item(i)->text();
458 		checkSumDbScannedPathsEnabled << (listWidgetCheckSumDbScannedPaths->item(i)->checkState() == Qt::Checked ? "true" : "false");
459 	}
460 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbScannedPaths", checkSumDbScannedPaths);
461 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbScannedPathsEnabled", checkSumDbScannedPathsEnabled);
462 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbDatabasePath", lineEditCheckSumDbDatabasePath->text());
463 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbScanIncrementally", toolButtonCheckSumDbScanIncrementally->isChecked());
464 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbDeepScan", toolButtonCheckSumDbDeepScan->isChecked());
465 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbHashCache", toolButtonCheckSumDbHashCache->isChecked());
466 #if defined(QMC2_LIBARCHIVE_ENABLED)
467 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbLibArchive", toolButtonCheckSumDbLibArchive->isChecked());
468 #endif
469 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + "Layout/" + m_settingsKey + "/ReportHeaderState", treeWidgetChecksums->header()->saveState());
470 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + "Layout/" + m_settingsKey + "/AnalysisTab", tabWidgetAnalysis->currentIndex());
471 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + "Layout/" + m_settingsKey + "/ChecksumWizardHeaderState", treeWidgetChecksumWizardSearchResult->header()->saveState());
472 	if ( !qmc2CleaningUp ) {
473 		qmc2Config->setValue(QMC2_FRONTEND_PREFIX + "Layout/" + m_settingsKey + "/Position", pos());
474 		qmc2Config->setValue(QMC2_FRONTEND_PREFIX + "Layout/" + m_settingsKey + "/Size", size());
475 	}
476 	if ( checkSumScannerLog() )
477 		checkSumScannerLog()->close();
478 	if ( e )
479 		e->accept();
480 }
481 
hideEvent(QHideEvent * e)482 void ROMAlyzer::hideEvent(QHideEvent *e)
483 {
484 	closeEvent(0);
485 	if ( e )
486 		e->accept();
487 }
488 
showEvent(QShowEvent * e)489 void ROMAlyzer::showEvent(QShowEvent *e)
490 {
491 	static bool initialCall = true;
492 
493 	QString userScopePath = Options::configPath();
494 	QString variantName = QMC2_VARIANT_NAME.toLower().replace(QRegExp("\\..*$"), "");
495 
496 	// restore settings
497 	checkBoxAppendReport->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/AppendReport", false).toBool());
498 	checkBoxExpandFiles->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/ExpandFiles", false).toBool());
499 	checkBoxExpandChecksums->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/ExpandChecksums", false).toBool());
500 	checkBoxAutoScroll->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/AutoScroll", true).toBool());
501 	checkBoxCalculateCRC->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CalculateCRC", true).toBool());
502 	checkBoxCalculateSHA1->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CalculateSHA1", true).toBool());
503 	checkBoxCalculateMD5->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CalculateMD5", false).toBool());
504 	if ( mode() == QMC2_ROMALYZER_MODE_SYSTEM )
505 		checkBoxSelectMachine->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SelectMachine", true).toBool());
506 	spinBoxMaxFileSize->setValue(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/MaxFileSize", 0).toInt());
507 	spinBoxMaxLogSize->setValue(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/MaxLogSize", 10000).toInt());
508 	spinBoxMaxReports->setValue(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/MaxReports", 1000).toInt());
509 	checkBoxCreateBackups->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CreateBackups", false).toBool());
510 	lineEditBackupFolder->setText(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/BackupFolder", QString()).toString());
511 	groupBoxCHDManager->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/EnableCHDManager", false).toBool());
512 	checkBoxVerifyCHDs->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/VerifyCHDs", true).toBool());
513 	checkBoxUpdateCHDs->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/UpdateCHDs", false).toBool());
514 	lineEditCHDManagerExecutableFile->setText(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CHDManagerExecutableFile", QString()).toString());
515 	lineEditTemporaryWorkingDirectory->setText(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/TemporaryWorkingDirectory", QString()).toString());
516 	lineEditSetRewriterOutputPath->setText(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterOutputPath", QString()).toString());
517 	checkBoxSetRewriterUseAdditionalRomPath->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterUseAdditionalRomPath", false).toBool());
518 	lineEditSetRewriterAdditionalRomPath->setText(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterAdditionalRomPath", QString()).toString());
519 	checkBoxCollectionRebuilderHashCache->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CollectionRebuilderHashCache", false).toBool());
520 	checkBoxCollectionRebuilderDryRun->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CollectionRebuilderDryRun", false).toBool());
521 	comboBoxCollectionRebuilderCHDHandling->setCurrentIndex(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CollectionRebuilderCHDHandling", QMC2_COLLECTIONREBUILDER_CHD_IGNORE).toInt());
522 	groupBoxSetRewriter->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/EnableSetRewriter", false).toBool());
523 	checkBoxSetRewriterWhileAnalyzing->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterWhileAnalyzing", false).toBool());
524 	checkBoxSetRewriterSelfContainedSets->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterSelfContainedSets", false).toBool());
525 	checkBoxSetRewriterGoodDumpsOnly->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterGoodDumpsOnly", true).toBool());
526 	checkBoxSetRewriterAbortOnError->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterAbortOnError", true).toBool());
527 	int index = qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterReproductionType", QMC2_ROMALYZER_RT_ZIP_BUILTIN).toInt();
528 	if ( index > QMC2_ROMALYZER_RT_FOLDERS )
529 		index = QMC2_ROMALYZER_RT_FOLDERS;
530 	comboBoxSetRewriterReproductionType->setCurrentIndex(index);
531 	spinBoxSetRewriterZipLevel->setValue(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterZipLevel", Z_DEFAULT_COMPRESSION).toInt());
532 	checkBoxSetRewriterUniqueCRCs->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterUniqueCRCs", false).toBool());
533 	comboBoxSetRewriterLibArchiveDeflate->setCurrentIndex(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterLibArchiveDeflate", true).toBool() ? 0 : 1);
534 	checkBoxSetRewriterAddZipComment->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/SetRewriterAddZipComment", true).toBool());
535 	comboBoxChecksumWizardHashType->setCurrentIndex(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/ChecksumWizardHashType", QMC2_ROMALYZER_CSF_HASHTYPE_SHA1).toInt());
536 	groupBoxCheckSumDatabase->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/EnableCheckSumDb", false).toBool());
537 	on_groupBoxCheckSumDatabase_toggled(groupBoxCheckSumDatabase->isChecked());
538 	bool enable = true;
539 	if ( initialCall || !checkSumScannerThread() )
540 		enable = groupBoxCheckSumDatabase->isChecked() && groupBoxSetRewriter->isChecked();
541 	else
542 		enable = groupBoxCheckSumDatabase->isChecked() && groupBoxSetRewriter->isChecked() && !checkSumScannerThread()->isActive;
543 	tabWidgetAnalysis->setTabEnabled(QMC2_ROMALYZER_PAGE_RCR, enable);
544 	labelCollectionRebuilderSpecific->setEnabled(enable);
545 	lineCollectionRebuilderSpecific->setEnabled(enable);
546 	checkBoxCollectionRebuilderHashCache->setEnabled(enable);
547 	checkBoxCollectionRebuilderDryRun->setEnabled(enable);
548 	labelCollectionRebuilderCHDHandling->setEnabled(enable);
549 	comboBoxCollectionRebuilderCHDHandling->setEnabled(enable);
550 	listWidgetCheckSumDbScannedPaths->clear();
551 	QStringList checkSumDbScannedPaths = qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbScannedPaths", QStringList()).toStringList();
552 	QStringList checkSumDbScannedPathsEnabled = qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbScannedPathsEnabled", QStringList()).toStringList();
553 	for (int i = 0; i < checkSumDbScannedPaths.count(); i++) {
554 		QListWidgetItem *item = new QListWidgetItem(checkSumDbScannedPaths[i]);
555 		if ( i < checkSumDbScannedPathsEnabled.count() )
556 			item->setCheckState(checkSumDbScannedPathsEnabled[i] == "true" ? Qt::Checked : Qt::Unchecked);
557 		else
558 			item->setCheckState(Qt::Checked);
559 		listWidgetCheckSumDbScannedPaths->addItem(item);
560 	}
561 	pushButtonCheckSumDbScan->setEnabled(listWidgetCheckSumDbScannedPaths->count() > 0);
562 	switch ( mode() ) {
563 		case QMC2_ROMALYZER_MODE_SOFTWARE:
564 			lineEditCheckSumDbDatabasePath->setText(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbDatabasePath", QString(userScopePath + "/%1-software-checksum.db").arg(variantName)).toString());
565 			break;
566 		case QMC2_ROMALYZER_MODE_SYSTEM:
567 		default:
568 			lineEditCheckSumDbDatabasePath->setText(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbDatabasePath", QString(userScopePath + "/%1-checksum.db").arg(variantName)).toString());
569 			break;
570 	}
571 	lineEditCheckSumDbDatabasePath->setText(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbDatabasePath", QString(userScopePath + "/%1-checksum.db").arg(variantName)).toString());
572 	toolButtonCheckSumDbScanIncrementally->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbScanIncrementally", true).toBool());
573 	toolButtonCheckSumDbDeepScan->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbDeepScan", true).toBool());
574 	toolButtonCheckSumDbHashCache->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbHashCache", false).toBool());
575 #if defined(QMC2_LIBARCHIVE_ENABLED)
576 	toolButtonCheckSumDbLibArchive->setChecked(qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbLibArchive", false).toBool());
577 #endif
578 	if ( e )
579 		e->accept();
580 	initialCall = false;
581 }
582 
on_spinBoxMaxLogSize_valueChanged(int value)583 void ROMAlyzer::on_spinBoxMaxLogSize_valueChanged(int value)
584 {
585 	textBrowserLog->setMaximumBlockCount(spinBoxMaxLogSize->value());
586 }
587 
moveEvent(QMoveEvent * e)588 void ROMAlyzer::moveEvent(QMoveEvent *e)
589 {
590 	if ( !qmc2CleaningUp && !qmc2EarlyStartup )
591 		qmc2Config->setValue(QMC2_FRONTEND_PREFIX + "Layout/" + m_settingsKey + "/Position", pos());
592 	if ( e )
593 		e->accept();
594 }
595 
resizeEvent(QResizeEvent * e)596 void ROMAlyzer::resizeEvent(QResizeEvent *e)
597 {
598 	if ( !qmc2CleaningUp && !qmc2EarlyStartup )
599 		qmc2Config->setValue(QMC2_FRONTEND_PREFIX + "Layout/" + m_settingsKey +"/Size", size());
600 	if ( e )
601 		e->accept();
602 }
603 
analyze()604 void ROMAlyzer::analyze()
605 {
606 	setActive(true);
607 	QString myRomPath;
608 	if ( qmc2Config->contains(QMC2_EMULATOR_PREFIX + "Configuration/Global/rompath") )
609 		myRomPath = qmc2Config->value(QMC2_EMULATOR_PREFIX + "Configuration/Global/rompath").toString();
610 	else
611 		myRomPath = "roms";
612 
613 	if ( groupBoxSetRewriter->isChecked() && checkBoxSetRewriterUseAdditionalRomPath->isChecked() && !lineEditSetRewriterAdditionalRomPath->text().isEmpty() )
614 		myRomPath.prepend(lineEditSetRewriterAdditionalRomPath->text() + ";");
615 
616 	myRomPath.replace("~", QDir::homePath());
617 	myRomPath.replace("$HOME", QDir::homePath());
618 	romPaths = myRomPath.split(";", QString::SkipEmptyParts);
619 
620 	QStringList analyzerList;
621 	QStringList softwareListPatternList = lineEditSoftwareLists->text().simplified().split(" ", QString::SkipEmptyParts);
622 	QStringList setPatternList = lineEditSets->text().simplified().split(" ", QString::SkipEmptyParts);
623 
624 	if ( !checkBoxAppendReport->isChecked() ) {
625 		treeWidgetChecksums->clear();
626 		textBrowserLog->clear();
627 		analyzerBadSets.clear();
628 	}
629 
630 	if ( mode() == QMC2_ROMALYZER_MODE_SOFTWARE && qmc2SoftwareList ) {
631 		qmc2SoftwareList->analyzeMenuAction->setEnabled(false);
632 		qmc2SoftwareList->actionAnalyzeSoftware->setEnabled(false);
633 		qmc2SoftwareList->actionAnalyzeSoftwareList->setEnabled(false);
634 		qmc2SoftwareList->actionAnalyzeSoftwareLists->setEnabled(false);
635 		qmc2SoftwareList->toolButtonAnalyzeSoftware->setEnabled(false);
636 	}
637 
638 	setPaused(false);
639 	animSeq = -1;
640 	animationTimeout();
641 	animTimer.start(QMC2_ANIMATION_TIMEOUT);
642 	pushButtonAnalyze->setText(tr("&Stop"));
643 	pushButtonPause->setVisible(true);
644 	pushButtonPause->setEnabled(true);
645 	pushButtonPause->setText(tr("&Pause"));
646 	lineEditSoftwareLists->setEnabled(false);
647 	lineEditSets->setEnabled(false);
648 	toolButtonToolsMenu->setEnabled(false);
649 	if ( checkBoxCalculateSHA1->isChecked() )
650 		tabChecksumWizard->setEnabled(false);
651 	QTime analysisTimer, elapsedTime(0, 0, 0, 0);
652 	analysisTimer.start();
653 	log(tr("analysis started"));
654 	log(tr("determining list of sets to analyze"));
655 
656 	int i = 0;
657 	QRegExp wildcardRx("(\\*|\\?)");
658 	switch ( mode() ) {
659 		case QMC2_ROMALYZER_MODE_SOFTWARE:
660 			if ( wizardSearch || quickSearch || (wildcardRx.indexIn(lineEditSets->text().simplified()) == -1 && wildcardRx.indexIn(lineEditSoftwareLists->text().simplified()) == -1 )) {
661 				// no wild-cards => no need to search!
662 				if ( wizardSearch ) {
663 					for (int j = 0; j < softwareListPatternList.count(); j++) {
664 						QString list = softwareListPatternList[j];
665 						QString set = setPatternList[j];
666 						if ( qmc2MainWindow->swlDb->exists(list, set) )
667 							analyzerList << list + ":" + set;
668 					}
669 				} else {
670 					foreach (QString list, softwareListPatternList)
671 						foreach (QString set, setPatternList)
672 							if ( qmc2MainWindow->swlDb->exists(list, set) )
673 								analyzerList << list + ":" + set;
674 				}
675 			} else {
676 				if ( softwareListPatternList.count() == 1 && setPatternList.count() == 1 ) {
677 					// special case for exactly ONE matching softlist + set -- no need to search
678 					if ( qmc2MainWindow->swlDb->exists(softwareListPatternList.first(), setPatternList.first()) )
679 						analyzerList << softwareListPatternList.first() + ":" + setPatternList.first();
680 				}
681 				if ( analyzerList.empty() ) {
682 					// determine list of softlists + sets to analyze
683 					QStringList uniqueSoftwareLists = qmc2MainWindow->swlDb->uniqueSoftwareLists();
684 					if ( !uniqueSoftwareLists.isEmpty() ) {
685 						labelStatus->setText(tr("Searching sets"));
686 						i = 0;
687 						bool matchAllSoftwareLists = (lineEditSoftwareLists->text().simplified() == "*");
688 						bool matchAllSets = (lineEditSets->text().simplified() == "*");
689 						progressBar->setRange(0, uniqueSoftwareLists.count());
690 						progressBar->reset();
691 						foreach (QString softList, uniqueSoftwareLists) {
692 							progressBar->setValue(++i);
693 							bool swlMatched = matchAllSoftwareLists;
694 							if ( !swlMatched ) {
695 								foreach (QString pattern, softwareListPatternList) {
696 									QRegExp regexp(pattern, Qt::CaseSensitive, QRegExp::Wildcard);
697 									if ( regexp.exactMatch(softList) ) {
698 										swlMatched = true;
699 										break;
700 									}
701 								}
702 							}
703 							if ( swlMatched ) {
704 								QStringList uniqueSoftwareSets = qmc2MainWindow->swlDb->uniqueSoftwareSets(softList);
705 								foreach (QString setName, uniqueSoftwareSets) {
706 									QString softwareKey = softList + ":" + setName;
707 									if ( matchAllSets )
708 										analyzerList << softwareKey;
709 									else foreach (QString pattern, setPatternList) {
710 										QRegExp regexp(pattern, Qt::CaseSensitive, QRegExp::Wildcard);
711 										if ( regexp.exactMatch(setName) )
712 											analyzerList << softwareKey;
713 									}
714 								}
715 							}
716 							qApp->processEvents();
717 						}
718 						progressBar->reset();
719 						labelStatus->setText(tr("Idle"));
720 					}
721 				}
722 			}
723 			break;
724 		case QMC2_ROMALYZER_MODE_SYSTEM:
725 		default:
726 			if ( wizardSearch || quickSearch || wildcardRx.indexIn(lineEditSets->text().simplified()) == -1 ) {
727 				// no wild-cards => no need to search!
728 				foreach (QString id, setPatternList)
729 					if ( qmc2MachineList->xmlDb()->exists(id) )
730 						analyzerList << id;
731 			} else {
732 				if ( setPatternList.count() == 1 ) {
733 					// special case for exactly ONE matching set -- no need to search
734 					if ( qmc2MachineListItemHash.contains(setPatternList[0]) )
735 						analyzerList << setPatternList[0];
736 				}
737 				if ( analyzerList.empty() ) {
738 					// determine list of sets to analyze
739 					labelStatus->setText(tr("Searching sets"));
740 					progressBar->setRange(0, qmc2MachineList->numMachines);
741 					progressBar->reset();
742 					QHashIterator<QString, QTreeWidgetItem *> it(qmc2MachineListItemHash);
743 					i = 0;
744 					bool matchAll = (lineEditSets->text().simplified() == "*");
745 					while ( it.hasNext() && !qmc2LoadingInterrupted ) {
746 						it.next();
747 						progressBar->setValue(++i);
748 						QString gameID = it.key();
749 						if ( matchAll )
750 							analyzerList << gameID;
751 						else foreach (QString pattern, setPatternList) {
752 							QRegExp regexp(pattern, Qt::CaseSensitive, QRegExp::Wildcard);
753 							if ( regexp.exactMatch(gameID) )
754 								if ( !analyzerList.contains(gameID) )
755 									analyzerList << gameID;
756 						}
757 						if ( i % QMC2_ROMALYZER_SEARCH_RESPONSE == 0 )
758 							qApp->processEvents();
759 					}
760 					progressBar->reset();
761 					labelStatus->setText(tr("Idle"));
762 				}
763 			}
764 			break;
765 	}
766 
767 	analyzerList.sort();
768 	quickSearch = false;
769 
770 	if ( !qmc2LoadingInterrupted ) {
771 		log(tr("done (determining list of sets to analyze)"));
772 		log(tr("%n set(s) to analyze", "", analyzerList.count()));
773 		i = 0;
774 		int setsInMemory = 0;
775 		foreach (QString setKey, analyzerList) {
776 			QString gameName, softListName;
777 			QStringList setKeyTokens;
778 			switch ( mode() ) {
779 				case QMC2_ROMALYZER_MODE_SOFTWARE:
780 					setKeyTokens = setKey.split(":", QString::SkipEmptyParts);
781 					if ( setKeyTokens.count() < 2 )
782 						continue;
783 					softListName = setKeyTokens[0];
784 					gameName = setKeyTokens[1];
785 					break;
786 				case QMC2_ROMALYZER_MODE_SYSTEM:
787 				default:
788 					gameName = setKey;
789 					break;
790 			}
791 			// wait if paused...
792 			for (quint64 waitCounter = 0; paused() && !qmc2LoadingInterrupted; waitCounter++) {
793 				if ( waitCounter == 0 ) {
794 					log(tr("analysis paused"));
795 					pushButtonPause->setText(tr("&Resume"));
796 					pushButtonPause->setEnabled(true);
797 					progressBar->reset();
798 					labelStatus->setText(tr("Paused"));
799 				}
800 				QTest::qWait(QMC2_ROMALYZER_PAUSE_TIMEOUT);
801 			}
802 
803 			bool filesSkipped = false;
804 			bool filesUnknown = false;
805 			bool filesError = false;
806 
807 			if ( qmc2LoadingInterrupted )
808 				break;
809 
810 			// remove the 'oldest' sets from the report if the report limit has been reached
811 			int reportLimit = spinBoxMaxReports->value();
812 			if ( reportLimit > 0 ) {
813 				if ( setsInMemory >= reportLimit ) {
814 					int setsToBeRemoved = setsInMemory - reportLimit + 1;
815 					log(tr("report limit reached, removing %n set(s) from the report", "", setsToBeRemoved));
816 					QChar splitChar(' ');
817 					for (int j = 0; j < setsToBeRemoved; j++) {
818 						QTreeWidgetItem *ti = treeWidgetChecksums->topLevelItem(0);
819 						if ( ti ) {
820 							analyzerBadSets.removeAll(ti->text(QMC2_ROMALYZER_COLUMN_SET).split(splitChar, QString::SkipEmptyParts)[0]);
821 							if ( ti->isSelected() )
822 								treeWidgetChecksums->selectionModel()->clear();
823 							delete treeWidgetChecksums->takeTopLevelItem(0);
824 							setsInMemory--;
825 						}
826 					}
827 				}
828 			}
829 			setsInMemory++;
830 
831 			QLocale locale;
832 
833 			// analyze set
834 			log(tr("analyzing '%1'").arg(setKey));
835 			setRewriterSetCount = analyzerList.count() - i;
836 			labelStatus->setText(tr("Analyzing '%1'").arg(setKey) + QString(" - %1").arg(locale.toString(setRewriterSetCount)));
837 
838 			// step 1: retrieve XML data, insert item with game name
839 			QTreeWidgetItem *item = new QTreeWidgetItem(treeWidgetChecksums);
840 			item->setText(QMC2_ROMALYZER_COLUMN_SET, setKey);
841 			QString xmlBuffer;
842 			switch ( mode() ) {
843 				case QMC2_ROMALYZER_MODE_SOFTWARE:
844 					xmlBuffer = getSoftwareXmlData(softListName, gameName);
845 					break;
846 				case QMC2_ROMALYZER_MODE_SYSTEM:
847 				default:
848 					xmlBuffer = getXmlData(gameName);
849 					break;
850 			}
851 
852 			if ( qmc2LoadingInterrupted )
853 				break;
854 
855 			// step 2: parse XML data, insert ROMs / CHDs and check-sums as they *should* be
856 			log(tr("parsing XML data for '%1'").arg(setKey));
857 			QXmlInputSource xmlInputSource;
858 			xmlInputSource.setData(xmlBuffer);
859 			ROMAlyzerXmlHandler xmlHandler(item, checkBoxExpandFiles->isChecked(), checkBoxAutoScroll->isChecked(), mode());
860 			QXmlSimpleReader xmlReader;
861 			xmlReader.setContentHandler(&xmlHandler);
862 			if ( xmlReader.parse(xmlInputSource) )
863 				log(tr("done (parsing XML data for '%1')").arg(setKey));
864 			else
865 				log(tr("error (parsing XML data for '%1')").arg(setKey));
866 
867 			if ( qmc2LoadingInterrupted )
868 				break;
869 
870 			if ( !xmlHandler.deviceReferences.isEmpty() )
871 				item->setWhatsThis(QMC2_ROMALYZER_COLUMN_SET, xmlHandler.deviceReferences.join(","));
872 
873 			int numWizardFiles = 1;
874 			if ( wizardSearch )
875 				numWizardFiles = treeWidgetChecksumWizardSearchResult->findItems(setKey, Qt::MatchExactly, QMC2_ROMALYZER_CSF_COLUMN_ID).count();
876 
877 			// step 3: check file status of ROMs and CHDs, recalculate check-sums
878 			log(tr("checking %n file(s) for '%1'", "", wizardSearch ? numWizardFiles : xmlHandler.fileCounter).arg(setKey));
879 			progressBar->reset();
880 			progressBar->setRange(0, xmlHandler.fileCounter);
881 			int fileCounter;
882 			int notFoundCounter = 0;
883 			int noDumpCounter = 0;
884 			bool gameOkay = true;
885 			int mergeStatus = QMC2_ROMALYZER_MERGE_STATUS_OK;
886 
887 			setRewriterFileMap.clear();
888 			setRewriterSetName = setKey;
889 			setRewriterItem = item;
890 
891 			for (fileCounter = 0; fileCounter < xmlHandler.fileCounter && !qmc2LoadingInterrupted; fileCounter++) {
892 				progressBar->setValue(fileCounter + 1);
893 				progressBar->setFormat(QString("%1 / %2").arg(fileCounter + 1).arg(xmlHandler.fileCounter));
894 				qApp->processEvents();
895 				QByteArray data;
896 				bool sevenZipped = false;
897 				bool zipped = false;
898 				bool merged = false;
899 				bool fromCheckSumDb = false;
900 				QTreeWidgetItem *childItem = xmlHandler.childItems.at(fileCounter);
901 				QTreeWidgetItem *parentItem = xmlHandler.parentItem;
902 				QString sha1Calculated, md5Calculated, fallbackPath;
903 				bool optionalRom = xmlHandler.optionalROMs.contains(childItem->text(QMC2_ROMALYZER_COLUMN_CRC));
904 
905 				if ( wizardSearch ) {
906 					QList<QTreeWidgetItem *> il = treeWidgetChecksumWizardSearchResult->findItems(setKey, Qt::MatchExactly, QMC2_ROMALYZER_CSF_COLUMN_ID);
907 					bool itemFound = false;
908 					foreach (QTreeWidgetItem *it, il)
909 						if ( it->text(QMC2_ROMALYZER_CSF_COLUMN_FILENAME) == childItem->text(QMC2_ROMALYZER_COLUMN_SET) ) {
910 							itemFound = true;
911 							break;
912 						}
913 					if ( !itemFound ) {
914 						childItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("skipped"));
915 						childItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.blueBrush);
916 						continue;
917 					}
918 				}
919 
920 				QString effectiveFile = getEffectiveFile(childItem, softListName, gameName, childItem->text(QMC2_ROMALYZER_COLUMN_SET), childItem->text(QMC2_ROMALYZER_COLUMN_CRC),
921 									 parentItem->text(QMC2_ROMALYZER_COLUMN_MERGE), childItem->text(QMC2_ROMALYZER_COLUMN_MERGE), childItem->text(QMC2_ROMALYZER_COLUMN_TYPE),
922 									 &data, &sha1Calculated, &md5Calculated, &zipped, &sevenZipped, &merged, fileCounter, &fallbackPath, optionalRom, &fromCheckSumDb);
923 
924 				if ( qmc2LoadingInterrupted )
925 					continue;
926 
927 				if ( effectiveFile.isEmpty() ) {
928 					childItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("not found"));
929 					childItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.greyBrush);
930 					if ( xmlHandler.optionalROMs.contains(childItem->text(QMC2_ROMALYZER_COLUMN_CRC)) )
931 						childItem->setIcon(QMC2_ROMALYZER_COLUMN_SET, QIcon(QIcon(QString::fromUtf8(":/data/img/remove.png")).pixmap(QSize(64, 64), QIcon::Disabled)));
932 					else
933 						childItem->setIcon(QMC2_ROMALYZER_COLUMN_SET, QIcon(QString::fromUtf8(":/data/img/remove.png")));
934 					notFoundCounter++;
935 				} else {
936 					QString fileStatus;
937 					bool somethingsWrong = false;
938 					bool goodDump = false;
939 					bool isCHD = childItem->text(QMC2_ROMALYZER_COLUMN_TYPE).split(" ", QString::SkipEmptyParts)[0] == QObject::tr("CHD");
940 					bool isROM = childItem->text(QMC2_ROMALYZER_COLUMN_TYPE).startsWith(tr("ROM"));
941 					bool hasDump = childItem->text(QMC2_ROMALYZER_COLUMN_EMUSTATUS) != QObject::tr("no dump");
942 					QTreeWidgetItem *fileItem = 0;
943 
944 					if ( effectiveFile != QMC2_ROMALYZER_FILE_NOT_FOUND ) {
945 						QIcon icon;
946 						if ( isROM )
947 							icon = QIcon(QString::fromUtf8(":/data/img/rom.png"));
948 						else if ( isCHD )
949 							icon = QIcon(QString::fromUtf8(":/data/img/disk2.png"));
950 						else if ( hasDump )
951 							icon = QIcon(QString::fromUtf8(":/data/img/fileopen.png"));
952 						else
953 							icon = QIcon(QString::fromUtf8(":/data/img/wip.png"));
954 						if ( fromCheckSumDb ) {
955 							QPainter p;
956 							QPixmap pm(128, 64);
957 							QPixmap pmIcon = icon.pixmap(64, 64);
958 							QPixmap pmDb = QIcon(QString::fromUtf8(":/data/img/database.png")).pixmap(64, 64);
959 							pm.fill(Qt::transparent);
960 							p.begin(&pm);
961 							p.setBackgroundMode(Qt::TransparentMode);
962 							p.drawPixmap(0, 0, pmIcon);
963 							p.drawPixmap(64, 0, pmDb);
964 							p.end();
965 							icon = QIcon(pm);
966 						}
967 						childItem->setIcon(QMC2_ROMALYZER_COLUMN_SET, icon);
968 					}
969 
970 					if ( effectiveFile == QMC2_ROMALYZER_FILE_TOO_BIG ) {
971 						fileStatus = tr("skipped");
972 						filesSkipped = true;
973 					} else if ( effectiveFile == QMC2_ROMALYZER_FILE_NOT_SUPPORTED ) {
974 						fileStatus = tr("skipped");
975 						filesUnknown = true;
976 					} else if ( effectiveFile == QMC2_ROMALYZER_FILE_ERROR ) {
977 						fileStatus = tr("error");
978 						childItem->setIcon(QMC2_ROMALYZER_COLUMN_SET, QIcon(QString::fromUtf8(":/data/img/warning.png")));
979 						if ( mode() == QMC2_ROMALYZER_MODE_SYSTEM ) {
980 							QString mergeName = childItem->text(QMC2_ROMALYZER_COLUMN_MERGE);
981 							if ( !mergeName.isEmpty() ) {
982 								if ( mergeStatus < QMC2_ROMALYZER_MERGE_STATUS_CRIT )
983 									mergeStatus = QMC2_ROMALYZER_MERGE_STATUS_CRIT;
984 								childItem->setIcon(QMC2_ROMALYZER_COLUMN_MERGE, QIcon(QString::fromUtf8(":/data/img/merge_nok.png")));
985 							}
986 						}
987 						filesError = true;
988 						if ( wizardSelectedSets.contains(setKey) ) {
989 							QList<QTreeWidgetItem *> il = treeWidgetChecksumWizardSearchResult->findItems(setKey, Qt::MatchExactly, QMC2_ROMALYZER_CSF_COLUMN_ID);
990 							foreach (QTreeWidgetItem *item, il)
991 								if ( item->text(QMC2_ROMALYZER_CSF_COLUMN_FILENAME) == childItem->text(QMC2_ROMALYZER_COLUMN_SET) ||
992 										item->whatsThis(QMC2_ROMALYZER_CSF_COLUMN_FILENAME) == childItem->text(QMC2_ROMALYZER_COLUMN_CRC) ) {
993 									item->setText(QMC2_ROMALYZER_CSF_COLUMN_STATUS, tr("bad"));
994 									item->setForeground(QMC2_ROMALYZER_CSF_COLUMN_STATUS, xmlHandler.redBrush);
995 									item->setText(QMC2_ROMALYZER_CSF_COLUMN_PATH, fallbackPath);
996 								}
997 							on_treeWidgetChecksumWizardSearchResult_itemSelectionChanged();
998 						}
999 					} else if ( effectiveFile == QMC2_ROMALYZER_FILE_NOT_FOUND || effectiveFile == QMC2_ROMALYZER_NO_DUMP ) {
1000 						if ( hasDump ) {
1001 							fileStatus = tr("not found");
1002 							if ( xmlHandler.optionalROMs.contains(childItem->text(QMC2_ROMALYZER_COLUMN_CRC)) )
1003 								childItem->setIcon(QMC2_ROMALYZER_COLUMN_SET, QIcon(QIcon(QString::fromUtf8(":/data/img/remove.png")).pixmap(QSize(64, 64), QIcon::Disabled)));
1004 							else
1005 								childItem->setIcon(QMC2_ROMALYZER_COLUMN_SET, QIcon(QString::fromUtf8(":/data/img/remove.png")));
1006 							notFoundCounter++;
1007 							if ( mode() == QMC2_ROMALYZER_MODE_SYSTEM ) {
1008 								QString mergeName = childItem->text(QMC2_ROMALYZER_COLUMN_MERGE);
1009 								if ( !mergeName.isEmpty() ) {
1010 									if ( mergeStatus < QMC2_ROMALYZER_MERGE_STATUS_CRIT )
1011 										mergeStatus = QMC2_ROMALYZER_MERGE_STATUS_CRIT;
1012 									childItem->setIcon(QMC2_ROMALYZER_COLUMN_MERGE, QIcon(QString::fromUtf8(":/data/img/merge_nok.png")));
1013 								}
1014 							}
1015 							if ( wizardSelectedSets.contains(setKey) ) {
1016 								QList<QTreeWidgetItem *> il = treeWidgetChecksumWizardSearchResult->findItems(setKey, Qt::MatchExactly, QMC2_ROMALYZER_CSF_COLUMN_ID);
1017 								foreach (QTreeWidgetItem *item, il) {
1018 									if ( item->text(QMC2_ROMALYZER_CSF_COLUMN_FILENAME) == childItem->text(QMC2_ROMALYZER_COLUMN_SET) || item->whatsThis(QMC2_ROMALYZER_CSF_COLUMN_FILENAME) == childItem->text(QMC2_ROMALYZER_COLUMN_CRC) ) {
1019 										item->setText(QMC2_ROMALYZER_CSF_COLUMN_STATUS, tr("bad"));
1020 										item->setForeground(QMC2_ROMALYZER_CSF_COLUMN_STATUS, xmlHandler.redBrush);
1021 										item->setText(QMC2_ROMALYZER_CSF_COLUMN_PATH, fallbackPath);
1022 									}
1023 								}
1024 								on_treeWidgetChecksumWizardSearchResult_itemSelectionChanged();
1025 							}
1026 						} else {
1027 							fileStatus = tr("no dump");
1028 							childItem->setIcon(QMC2_ROMALYZER_COLUMN_SET, QIcon(QString::fromUtf8(":/data/img/wip.png")));
1029 							noDumpCounter++;
1030 						}
1031 					} else {
1032 						fileItem = new QTreeWidgetItem(childItem);
1033 						fileItem->setIcon(QMC2_ROMALYZER_COLUMN_SET, QIcon(QString::fromUtf8(":/data/img/hash.png")));
1034 						fileItem->setText(QMC2_ROMALYZER_COLUMN_SET, tr("Calculated check-sums"));
1035 						childItem->setExpanded(false);
1036 						if ( mode() == QMC2_ROMALYZER_MODE_SYSTEM ) {
1037 							QString mergeName = childItem->text(QMC2_ROMALYZER_COLUMN_MERGE);
1038 							if ( !mergeName.isEmpty() ) {
1039 								if ( !parentItem->text(QMC2_ROMALYZER_COLUMN_MERGE).isEmpty() ) {
1040 									if ( !merged ) {
1041 										log(tr("WARNING: %1 file '%2' loaded from '%3' may be obsolete, should be merged from parent set '%4'").arg(isCHD ? tr("CHD") : tr("ROM")).arg(childItem->text(QMC2_ROMALYZER_COLUMN_SET)).arg(effectiveFile).arg(parentItem->text(QMC2_ROMALYZER_COLUMN_MERGE)));
1042 										childItem->setIcon(QMC2_ROMALYZER_COLUMN_MERGE, QIcon(QString::fromUtf8(":/data/img/merge.png")));
1043 										if ( mergeStatus < QMC2_ROMALYZER_MERGE_STATUS_WARN )
1044 											mergeStatus = QMC2_ROMALYZER_MERGE_STATUS_WARN;
1045 									} else
1046 										childItem->setIcon(QMC2_ROMALYZER_COLUMN_MERGE, QIcon(QString::fromUtf8(":/data/img/merge_ok.png")));
1047 								} else {
1048 									// this is actually an XML bug in the driver, inform via log and ignore...
1049 									log(tr("INFORMATION: %1 file '%2' has a named merge ('%3') but no parent set -- ignored, but should be reported to the MAME developers as a possible XML bug of the respective driver").arg(isCHD ? tr("CHD") : tr("ROM")).arg(childItem->text(QMC2_ROMALYZER_COLUMN_SET)).arg(mergeName));
1050 									childItem->setIcon(QMC2_ROMALYZER_COLUMN_MERGE, QIcon(QString::fromUtf8(":/data/img/merge_ok.png")));
1051 								}
1052 							}
1053 						}
1054 
1055 						// Size
1056 						QString sizeStr = childItem->text(QMC2_ROMALYZER_COLUMN_SIZE);
1057 						if ( !sizeStr.isEmpty() ){
1058 							QString sizeItemStr;
1059 							if ( fromCheckSumDb )
1060 								sizeItemStr = sizeStr;
1061 							else
1062 								sizeItemStr = QString::number(data.size());
1063 							fileItem->setText(QMC2_ROMALYZER_COLUMN_SIZE, sizeItemStr);
1064 							if ( !fileStatus.isEmpty() )
1065 								fileStatus += " ";
1066 							if ( sizeItemStr == sizeStr )
1067 								fileStatus += tr("SIZE");
1068 							else {
1069 								fileStatus += tr("size");
1070 								if ( hasDump ) {
1071 									somethingsWrong = true;
1072 									fileItem->setForeground(QMC2_ROMALYZER_COLUMN_SIZE, xmlHandler.redBrush);
1073 								}
1074 							}
1075 							qApp->processEvents();
1076 						}
1077 
1078 						// CRC
1079 						QString crcStr = childItem->text(QMC2_ROMALYZER_COLUMN_CRC);
1080 						if ( !crcStr.isEmpty() && checkBoxCalculateCRC->isChecked() ) {
1081 							QString crcItemStr;
1082 							if ( fromCheckSumDb )
1083 								crcItemStr = crcStr;
1084 							else {
1085 								ulong crc = crc32(0, 0, 0);
1086 								crc = crc32(crc, (const Bytef *)data.data(), data.size());
1087 								crcItemStr = crcToString(crc);
1088 							}
1089 							fileItem->setText(QMC2_ROMALYZER_COLUMN_CRC, crcItemStr);
1090 							if ( !fileStatus.isEmpty() )
1091 								fileStatus += " ";
1092 							if ( crcItemStr == crcStr )
1093 								fileStatus += tr("CRC");
1094 							else {
1095 								fileStatus += tr("crc");
1096 							        if ( hasDump ) {
1097 									somethingsWrong = true;
1098 									fileItem->setForeground(QMC2_ROMALYZER_COLUMN_CRC, xmlHandler.redBrush);
1099 								}
1100 							}
1101 							qApp->processEvents();
1102 						}
1103 
1104 						// SHA1
1105 						QString sha1Str = childItem->text(QMC2_ROMALYZER_COLUMN_SHA1);
1106 						if ( !sha1Str.isEmpty() && checkBoxCalculateSHA1->isChecked() ) {
1107 							fileItem->setText(QMC2_ROMALYZER_COLUMN_SHA1, sha1Calculated);
1108 							if ( !fileStatus.isEmpty() )
1109 								fileStatus += " ";
1110 							if ( sha1Str == sha1Calculated )
1111 								fileStatus += tr("SHA-1");
1112 							else
1113 								fileStatus += tr("sha-1");
1114 							if ( sha1Str != sha1Calculated && hasDump ) {
1115 								somethingsWrong = true;
1116 								fileItem->setForeground(QMC2_ROMALYZER_COLUMN_SHA1, xmlHandler.redBrush);
1117 							} else if ( hasDump )
1118 								goodDump = true;
1119 							if ( wizardSelectedSets.contains(setKey) ) {
1120 								QList<QTreeWidgetItem *> il = treeWidgetChecksumWizardSearchResult->findItems(setKey, Qt::MatchExactly, QMC2_ROMALYZER_CSF_COLUMN_ID);
1121 								foreach (QTreeWidgetItem *item, il) {
1122 									if ( item->text(QMC2_ROMALYZER_CSF_COLUMN_FILENAME) == childItem->text(QMC2_ROMALYZER_COLUMN_SET) ||
1123 									     item->whatsThis(QMC2_ROMALYZER_CSF_COLUMN_FILENAME) == childItem->text(QMC2_ROMALYZER_COLUMN_CRC) ) {
1124 										if ( fromCheckSumDb ) {
1125 											item->setIcon(QMC2_ROMALYZER_CSF_COLUMN_STATUS, QIcon(QString::fromUtf8(":/data/img/database_good.png")));
1126 											item->setText(QMC2_ROMALYZER_CSF_COLUMN_STATUS, tr("bad"));
1127 											item->setForeground(QMC2_ROMALYZER_CSF_COLUMN_STATUS, xmlHandler.redBrush);
1128 										} else {
1129 											item->setText(QMC2_ROMALYZER_CSF_COLUMN_STATUS, goodDump ? tr("good") : tr("bad"));
1130 											item->setForeground(QMC2_ROMALYZER_CSF_COLUMN_STATUS, goodDump ? xmlHandler.greenBrush : xmlHandler.redBrush);
1131 										}
1132 										item->setText(QMC2_ROMALYZER_CSF_COLUMN_PATH, effectiveFile);
1133 										if ( goodDump || fromCheckSumDb )
1134 											item->setWhatsThis(QMC2_ROMALYZER_CSF_COLUMN_FILENAME, childItem->text(QMC2_ROMALYZER_COLUMN_CRC));
1135 									}
1136 								}
1137 								on_treeWidgetChecksumWizardSearchResult_itemSelectionChanged();
1138 							}
1139 							qApp->processEvents();
1140 						}
1141 
1142 						// MD5
1143 						QString md5Str = childItem->text(QMC2_ROMALYZER_COLUMN_MD5);
1144 						if ( !md5Str.isEmpty() && checkBoxCalculateMD5->isChecked() ) {
1145 							fileItem->setText(QMC2_ROMALYZER_COLUMN_MD5, md5Calculated);
1146 							if ( !fileStatus.isEmpty() )
1147 								fileStatus += " ";
1148 							if ( md5Str == md5Calculated )
1149 								fileStatus += tr("MD5");
1150 							else
1151 								fileStatus += tr("md5");
1152 							if ( md5Str != md5Calculated && hasDump ) {
1153 								somethingsWrong = true;
1154 								fileItem->setForeground(QMC2_ROMALYZER_COLUMN_MD5, xmlHandler.redBrush);
1155 							}
1156 							qApp->processEvents();
1157 						}
1158 					}
1159 
1160 					if ( somethingsWrong ) {
1161 						quint64 size = childItem->text(QMC2_ROMALYZER_COLUMN_SIZE).toULongLong();
1162 						if ( !fromCheckSumDb && groupBoxCheckSumDatabase->isChecked() && checkSumDb()->exists(childItem->text(QMC2_ROMALYZER_COLUMN_SHA1), childItem->text(QMC2_ROMALYZER_COLUMN_CRC), size) ) {
1163 							QString pathFromDb, memberFromDb, typeFromDb;
1164 							if ( checkSumDb()->getData(childItem->text(QMC2_ROMALYZER_COLUMN_SHA1), childItem->text(QMC2_ROMALYZER_COLUMN_CRC), &size, &pathFromDb, &memberFromDb, &typeFromDb) ) {
1165 								QStringList sl;
1166 								switch ( checkSumDb()->nameToType(typeFromDb) ) {
1167 									case QMC2_CHECKSUM_SCANNER_FILE_ZIP:
1168 										//    fromName        fromPath      toName                                      fromZip
1169 										sl << memberFromDb << pathFromDb << childItem->text(QMC2_ROMALYZER_COLUMN_SET) << "zip";
1170 										log(tr("check-sum database") + ": " + tr("using member '%1' from archive '%2' with SHA-1 '%3' and CRC '%4' as '%5'").arg(memberFromDb).arg(pathFromDb).arg(childItem->text(QMC2_ROMALYZER_COLUMN_SHA1)).arg(childItem->text(QMC2_ROMALYZER_COLUMN_CRC)).arg(childItem->text(QMC2_ROMALYZER_COLUMN_SET)));
1171 										break;
1172 									case QMC2_CHECKSUM_SCANNER_FILE_7Z:
1173 										//    fromName        fromPath      toName                                      fromZip
1174 										sl << memberFromDb << pathFromDb << childItem->text(QMC2_ROMALYZER_COLUMN_SET) << "7z";
1175 										log(tr("check-sum database") + ": " + tr("using member '%1' from archive '%2' with SHA-1 '%3' and CRC '%4' as '%5'").arg(memberFromDb).arg(pathFromDb).arg(childItem->text(QMC2_ROMALYZER_COLUMN_SHA1)).arg(childItem->text(QMC2_ROMALYZER_COLUMN_CRC)).arg(childItem->text(QMC2_ROMALYZER_COLUMN_SET)));
1176 										break;
1177 									case QMC2_CHECKSUM_SCANNER_FILE_CHD:
1178 										//    fromName        fromPath      toName                                      fromZip
1179 										sl << memberFromDb << pathFromDb << childItem->text(QMC2_ROMALYZER_COLUMN_SET) << "chd";
1180 										log(tr("check-sum database") + ": " + tr("using CHD '%1' with SHA-1 '%2' as '%3'").arg(pathFromDb).arg(childItem->text(QMC2_ROMALYZER_COLUMN_SHA1)).arg(childItem->text(QMC2_ROMALYZER_COLUMN_SET)));
1181 										break;
1182 									case QMC2_CHECKSUM_SCANNER_FILE_REGULAR:
1183 										//    fromName        fromPath      toName                                      fromZip
1184 										sl << memberFromDb << pathFromDb << childItem->text(QMC2_ROMALYZER_COLUMN_SET) << "file";
1185 										log(tr("check-sum database") + ": " + tr("using file '%1' with SHA-1 '%2' and CRC '%3' as '%4'").arg(pathFromDb).arg(childItem->text(QMC2_ROMALYZER_COLUMN_SHA1)).arg(childItem->text(QMC2_ROMALYZER_COLUMN_CRC)).arg(childItem->text(QMC2_ROMALYZER_COLUMN_SET)));
1186 										break;
1187 									default:
1188 										break;
1189 								}
1190 								if ( !sl.isEmpty() ) {
1191 									setRewriterFileMap.insert(childItem->text(QMC2_ROMALYZER_COLUMN_CRC), sl);
1192 									QIcon icon;
1193 									if ( isROM )
1194 										icon = QIcon(QString::fromUtf8(":/data/img/rom.png"));
1195 									else if ( isCHD )
1196 										icon = QIcon(QString::fromUtf8(":/data/img/disk2.png"));
1197 									else if ( hasDump )
1198 										icon = QIcon(QString::fromUtf8(":/data/img/fileopen.png"));
1199 									else
1200 										icon = QIcon(QString::fromUtf8(":/data/img/wip.png"));
1201 									QPainter p;
1202 									QPixmap pm(128, 64);
1203 									QPixmap pmIcon = icon.pixmap(64, 64);
1204 									QPixmap pmDb = QIcon(QString::fromUtf8(":/data/img/database.png")).pixmap(64, 64);
1205 									pm.fill(Qt::transparent);
1206 									p.begin(&pm);
1207 									p.setBackgroundMode(Qt::TransparentMode);
1208 									p.drawPixmap(0, 0, pmIcon);
1209 									p.drawPixmap(64, 0, pmDb);
1210 									p.end();
1211 									icon = QIcon(pm);
1212 									childItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.greenBrush);
1213 									childItem->setIcon(QMC2_ROMALYZER_COLUMN_SET, icon);
1214 								}
1215 							}
1216 						} else {
1217 							gameOkay = false;
1218 							childItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.redBrush);
1219 							childItem->setIcon(QMC2_ROMALYZER_COLUMN_SET, QIcon(QString::fromUtf8(":/data/img/warning.png")));
1220 							log(tr("WARNING: %1 file '%2' loaded from '%3' has incorrect / unexpected check-sums").arg(isCHD ? tr("CHD") : tr("ROM")).arg(childItem->text(QMC2_ROMALYZER_COLUMN_SET)).arg(effectiveFile));
1221 						}
1222 					} else {
1223 						if ( fileStatus == tr("skipped") )
1224 							childItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.blueBrush);
1225 						else if ( fileStatus == tr("not found") )
1226 							childItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.greyBrush);
1227 						else if ( fileStatus == tr("no dump") )
1228 							childItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.brownBrush);
1229 						else
1230 							childItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.greenBrush);
1231 					}
1232 
1233 					childItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, fileStatus);
1234 
1235 					if ( checkBoxExpandChecksums->isChecked() && checkBoxExpandFiles->isChecked() )
1236 						childItem->setExpanded(true);
1237 
1238 					qApp->processEvents();
1239 				}
1240 			}
1241 
1242 			if ( xmlHandler.fileCounter == 0 )
1243 				progressBar->setRange(0, 1);
1244 			progressBar->setFormat(QString());
1245 			progressBar->reset();
1246 			qApp->processEvents();
1247 
1248 			if ( qmc2LoadingInterrupted )
1249 				log(tr("interrupted (checking %n file(s) for '%1')", "", wizardSearch ? numWizardFiles : xmlHandler.fileCounter).arg(setKey));
1250 			else {
1251 				gameOkay |= filesError;
1252 				filesSkipped |= filesUnknown;
1253 				if ( !wizardSearch ) {
1254 					if ( gameOkay ) {
1255 						if ( notFoundCounter == xmlHandler.fileCounter ) {
1256 							if ( xmlHandler.fileCounter == 0 ) {
1257 								xmlHandler.parentItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("good"));
1258 								xmlHandler.parentItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.greenBrush);
1259 							} else {
1260 								xmlHandler.parentItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("not found"));
1261 								xmlHandler.parentItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.greyBrush);
1262 								gameOkay = false;
1263 							}
1264 						} else if ( notFoundCounter > 0 ) {
1265 							if ( filesSkipped )
1266 								xmlHandler.parentItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("good / not found / skipped"));
1267 							else
1268 								xmlHandler.parentItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("good / not found"));
1269 							xmlHandler.parentItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.brownBrush);
1270 							gameOkay = false;
1271 						} else if ( noDumpCounter > 0 ) {
1272 							if ( filesSkipped )
1273 								xmlHandler.parentItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("good / no dump / skipped"));
1274 							else
1275 								xmlHandler.parentItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("good / no dump"));
1276 							xmlHandler.parentItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.brownBrush);
1277 						} else {
1278 							if ( filesSkipped )
1279 								xmlHandler.parentItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("good / skipped"));
1280 							else
1281 								xmlHandler.parentItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("good"));
1282 							xmlHandler.parentItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.greenBrush);
1283 						}
1284 					} else {
1285 						if ( notFoundCounter > 0 ) {
1286 							if ( filesSkipped )
1287 								xmlHandler.parentItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("bad / not found / skipped"));
1288 							else
1289 								xmlHandler.parentItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("bad / not found"));
1290 							xmlHandler.parentItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.redBrush);
1291 						} else if ( noDumpCounter > 0 ) {
1292 							if ( filesSkipped )
1293 								xmlHandler.parentItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("bad / no dump / skipped"));
1294 							else
1295 								xmlHandler.parentItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("bad / no dump"));
1296 							xmlHandler.parentItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.redBrush);
1297 						} else {
1298 							if ( filesSkipped )
1299 								xmlHandler.parentItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("bad / skipped"));
1300 							else
1301 								xmlHandler.parentItem->setText(QMC2_ROMALYZER_COLUMN_FILESTATUS, tr("bad"));
1302 							xmlHandler.parentItem->setForeground(QMC2_ROMALYZER_COLUMN_FILESTATUS, xmlHandler.redBrush);
1303 						}
1304 					}
1305 					if ( mode() == QMC2_ROMALYZER_MODE_SYSTEM ) {
1306 						if ( !xmlHandler.parentItem->text(QMC2_ROMALYZER_COLUMN_MERGE).isEmpty() ) {
1307 							switch ( mergeStatus ) {
1308 								case QMC2_ROMALYZER_MERGE_STATUS_OK:
1309 									xmlHandler.parentItem->setIcon(QMC2_ROMALYZER_COLUMN_MERGE, QIcon(QString::fromUtf8(":/data/img/merge_ok.png")));
1310 									break;
1311 								case QMC2_ROMALYZER_MERGE_STATUS_WARN:
1312 									xmlHandler.parentItem->setIcon(QMC2_ROMALYZER_COLUMN_MERGE, QIcon(QString::fromUtf8(":/data/img/merge.png")));
1313 									break;
1314 								case QMC2_ROMALYZER_MERGE_STATUS_CRIT:
1315 									xmlHandler.parentItem->setIcon(QMC2_ROMALYZER_COLUMN_MERGE, QIcon(QString::fromUtf8(":/data/img/merge_nok.png")));
1316 									break;
1317 								default:
1318 									break;
1319 							}
1320 						}
1321 					}
1322 				}
1323 
1324 				log(tr("done (checking %n file(s) for '%1')", "", wizardSearch ? numWizardFiles : xmlHandler.fileCounter).arg(setKey));
1325 
1326 				if ( !gameOkay )
1327 					analyzerBadSets << setKey;
1328 
1329 				if ( gameOkay || !checkBoxSetRewriterGoodDumpsOnly->isChecked() )
1330 					if ( groupBoxSetRewriter->isChecked() )
1331 						if ( checkBoxSetRewriterWhileAnalyzing->isChecked() && !qmc2LoadingInterrupted && !wizardSearch )
1332 							runSetRewriter();
1333 			}
1334 			if ( qmc2LoadingInterrupted )
1335 				break;
1336 
1337 			treeWidgetChecksums->update();
1338 
1339 			i++;
1340 			log(tr("done (analyzing '%1')").arg(setKey));
1341 			log(tr("%n set(s) remaining", "", analyzerList.count() - i));
1342 		}
1343 	}
1344 
1345 	animTimer.stop();
1346 	pushButtonAnalyze->setText(tr("&Analyze"));
1347 	pushButtonPause->setVisible(false);
1348 	pushButtonAnalyze->setIcon(QIcon(QString::fromUtf8(":/data/img/romalyzer.png")));
1349 	lineEditSoftwareLists->setEnabled(true);
1350 	lineEditSets->setEnabled(true);
1351 	toolButtonToolsMenu->setEnabled(true);
1352 	if ( checkBoxCalculateSHA1->isChecked() )
1353 		tabChecksumWizard->setEnabled(true);
1354 
1355 	progressBar->reset();
1356 	progressBar->setFormat(QString());
1357 	labelStatus->setText(tr("Idle"));
1358 	qApp->processEvents();
1359 	elapsedTime = elapsedTime.addMSecs(analysisTimer.elapsed());
1360 	log(tr("analysis ended") + " - " + tr("elapsed time = %1").arg(elapsedTime.toString("hh:mm:ss.zzz")));
1361 
1362 	actionExportToDataFile->setEnabled(!analyzerBadSets.isEmpty());
1363 
1364 	setActive(false);
1365 	wizardSearch = false;
1366 
1367 	if ( mode() == QMC2_ROMALYZER_MODE_SOFTWARE && qmc2SoftwareList ) {
1368 		qmc2SoftwareList->analyzeMenuAction->setEnabled(true);
1369 		qmc2SoftwareList->actionAnalyzeSoftware->setEnabled(true);
1370 		qmc2SoftwareList->actionAnalyzeSoftwareList->setEnabled(true);
1371 		qmc2SoftwareList->actionAnalyzeSoftwareLists->setEnabled(true);
1372 		qmc2SoftwareList->toolButtonAnalyzeSoftware->setEnabled(true);
1373 	}
1374 }
1375 
getXmlData(QString gameName,bool includeDTD)1376 QString &ROMAlyzer::getXmlData(QString gameName, bool includeDTD)
1377 {
1378 	static QString xmlBuffer;
1379 
1380 	xmlBuffer.clear();
1381 
1382 	if ( includeDTD ) {
1383 		xmlBuffer = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1384 		xmlBuffer += qmc2MachineList->xmlDb()->dtd();
1385 	}
1386 	xmlBuffer += qmc2MachineList->xmlDb()->xml(gameName);
1387 
1388 	return xmlBuffer;
1389 }
1390 
getSoftwareXmlData(QString listName,QString setName,bool includeDTD)1391 QString &ROMAlyzer::getSoftwareXmlData(QString listName, QString setName, bool includeDTD)
1392 {
1393 	static QString swlBuffer;
1394 
1395 	swlBuffer.clear();
1396 
1397 	if ( includeDTD ) {
1398 		swlBuffer = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1399 		swlBuffer += qmc2MainWindow->swlDb->dtd();
1400 	}
1401 	swlBuffer += qmc2MainWindow->swlDb->xml(listName, setName);
1402 
1403 	return swlBuffer;
1404 }
1405 
getEffectiveFile(QTreeWidgetItem * myItem,QString listName,QString gameName,QString fileName,QString wantedCRC,QString merge,QString mergeFile,QString type,QByteArray * fileData,QString * sha1Str,QString * md5Str,bool * isZipped,bool * isSevenZipped,bool * mergeUsed,int fileCounter,QString * fallbackPath,bool isOptionalROM,bool * fromCheckSumDb)1406 QString &ROMAlyzer::getEffectiveFile(QTreeWidgetItem *myItem, QString listName, QString gameName, QString fileName, QString wantedCRC, QString merge, QString mergeFile, QString type, QByteArray *fileData, QString *sha1Str, QString *md5Str, bool *isZipped, bool *isSevenZipped, bool *mergeUsed, int fileCounter, QString *fallbackPath, bool isOptionalROM, bool *fromCheckSumDb)
1407 {
1408 	static QCryptographicHash sha1Hash(QCryptographicHash::Sha1);
1409 	static QCryptographicHash md5Hash(QCryptographicHash::Md5);
1410 	static QString effectiveFile;
1411 	static char buffer[QMC2_MAX(QMC2_ROMALYZER_ZIP_BUFFER_SIZE, QMC2_ROMALYZER_FILE_BUFFER_SIZE)];
1412 
1413 	effectiveFile.clear();
1414 	fileData->clear();
1415 
1416 	bool calcMD5 = checkBoxCalculateMD5->isChecked();
1417 	bool calcSHA1 = checkBoxCalculateSHA1->isChecked();
1418 	bool isCHD = type.split(" ", QString::SkipEmptyParts)[0] == tr("CHD");
1419 	bool sizeLimited = spinBoxMaxFileSize->value() > 0;
1420 	bool chdManagerVerifyCHDs = checkBoxVerifyCHDs->isChecked();
1421 	bool chdManagerUpdateCHDs = checkBoxUpdateCHDs->isChecked();
1422 	bool chdManagerEnabled = groupBoxCHDManager->isChecked() && (chdManagerVerifyCHDs || chdManagerUpdateCHDs);
1423 	bool needProgressWidget;
1424 	QProgressBar *progressWidget;
1425 	qint64 totalSize, myProgress, sizeLeft, len;
1426 
1427 	// search for file in ROM paths (first search for "machine/file", then search for "file" in "machine.7z", then in "machine.zip"), load file data when found
1428 	int romPathCount = 0;
1429 	QStringList actualRomPaths;
1430 	foreach (QString romPath, romPaths) {
1431 		if ( mode() == QMC2_ROMALYZER_MODE_SOFTWARE )
1432 			actualRomPaths << romPath + "/" + listName;
1433 		actualRomPaths << romPath;
1434 	}
1435 	foreach (QString romPath, actualRomPaths) {
1436 		romPathCount++;
1437 		progressWidget = 0;
1438 		needProgressWidget = false;
1439 		QString filePath(romPath + "/" + gameName + "/" + fileName);
1440 		if ( isCHD ) {
1441 			filePath += ".chd";
1442 			if ( fallbackPath->isEmpty() )
1443 				*fallbackPath = filePath;
1444 		}
1445 		if ( QFile::exists(filePath) ) {
1446 			QFileInfo fi(filePath);
1447 			if ( fi.isReadable() ) {
1448 				totalSize = fi.size();
1449 				// load data from a regular file
1450 				if ( totalSize > (qlonglong)QMC2_2G ) {
1451 					log(tr("size of '%1' is greater than 2 GB -- skipped").arg(filePath));
1452 					*isZipped = false;
1453 					progressBarFileIO->reset();
1454 					effectiveFile = QMC2_ROMALYZER_FILE_TOO_BIG;
1455 					continue;
1456 				} else if ( sizeLimited ) {
1457 					if ( totalSize > (qint64) spinBoxMaxFileSize->value() * QMC2_ONE_MEGABYTE ) {
1458 						log(tr("size of '%1' is greater than allowed maximum -- skipped").arg(filePath));
1459 						*isZipped = false;
1460 						progressBarFileIO->reset();
1461 						effectiveFile = QMC2_ROMALYZER_FILE_TOO_BIG;
1462 						continue;
1463 					}
1464 				}
1465 				QFile romFile(filePath);
1466 				if ( romFile.open(QIODevice::ReadOnly) ) {
1467 					log(tr("loading '%1'%2").arg(filePath).arg(*mergeUsed ? tr(" (merged)") : ""));
1468 					progressBarFileIO->setRange(0, totalSize);
1469 					progressBarFileIO->reset();
1470 					if ( totalSize > QMC2_ROMALYZER_PROGRESS_THRESHOLD ) {
1471 						if ( isCHD && !chdManagerEnabled )
1472 							needProgressWidget = false;
1473 						else
1474 							needProgressWidget = true;
1475 						if ( needProgressWidget ) {
1476 							progressWidget = new QProgressBar(0);
1477 #if QMC2_CHD_CURRENT_VERSION >= 5
1478 							if ( isCHD ) {
1479 								progressWidget->setRange(0, 100);
1480 								progressWidget->setValue(0);
1481 							} else {
1482 								progressWidget->setRange(0, totalSize);
1483 								progressWidget->setValue(0);
1484 							}
1485 #else
1486 							progressWidget->setRange(0, totalSize);
1487 							progressWidget->setValue(0);
1488 #endif
1489 							treeWidgetChecksums->setItemWidget(myItem, QMC2_ROMALYZER_COLUMN_FILESTATUS, progressWidget);
1490 						}
1491 					} else
1492 						needProgressWidget = false;
1493 					sizeLeft = totalSize;
1494 					if ( calcSHA1 )
1495 						sha1Hash.reset();
1496 					if ( calcMD5 )
1497 						md5Hash.reset();
1498 					if ( isCHD ) {
1499 						QString chdFilePath;
1500 						quint32 chdTotalHunks = 0;
1501 						if ( (len = romFile.read(buffer, QMC2_CHD_HEADER_V3_LENGTH)) > 0 ) {
1502 							if ( len >= QMC2_CHD_HEADER_V3_LENGTH ) {
1503 								log(tr("CHD header information:"));
1504 								QByteArray chdTag(buffer + QMC2_CHD_HEADER_TAG_OFFSET, QMC2_CHD_HEADER_TAG_LENGTH);
1505 								log(tr("  tag: %1").arg(chdTag.constData()));
1506 								quint32 chdVersion = QMC2_TO_UINT32(buffer + QMC2_CHD_HEADER_VERSION_OFFSET);
1507 								log(tr("  version: %1").arg(chdVersion));
1508 								myItem->setText(QMC2_ROMALYZER_COLUMN_TYPE, tr("CHD v%1").arg(chdVersion));
1509 								QLocale locale;
1510 								switch ( chdVersion ) {
1511 									case 3: {
1512 											quint32 chdCompression = QMC2_TO_UINT32(buffer + QMC2_CHD_HEADER_COMPRESSION_OFFSET);
1513 											log(tr("  compression: %1").arg(chdCompressionTypes[chdCompression]));
1514 											quint32 chdFlags = QMC2_TO_UINT32(buffer + QMC2_CHD_HEADER_FLAGS_OFFSET);
1515 											log(tr("  flags: %1, %2").arg(chdFlags & QMC2_CHD_HEADER_FLAG_HASPARENT ? tr("has parent") : tr("no parent")).arg(chdFlags & QMC2_CHD_HEADER_FLAG_ALLOWSWRITES ? tr("allows writes") : tr("read only")));
1516 											chdTotalHunks = QMC2_TO_UINT32(buffer + QMC2_CHD_HEADER_V3_TOTALHUNKS_OFFSET);
1517 											log(tr("  number of total hunks: %1").arg(locale.toString(chdTotalHunks)));
1518 											quint32 chdHunkBytes = QMC2_TO_UINT32(buffer + QMC2_CHD_HEADER_V3_HUNKBYTES_OFFSET);
1519 											log(tr("  number of bytes per hunk: %1").arg(locale.toString(chdHunkBytes)));
1520 											quint64 chdLogicalBytes = QMC2_TO_UINT64(buffer + QMC2_CHD_HEADER_V3_LOGICALBYTES_OFFSET);
1521 											log(tr("  logical size: %1 (%2 B)").arg(humanReadable(chdLogicalBytes)).arg(locale.toString(chdLogicalBytes)));
1522 											log(tr("  real size: %1 (%2 B)").arg(humanReadable(fi.size())).arg(locale.toString(fi.size())));
1523 											QByteArray md5Data((const char *)(buffer + QMC2_CHD_HEADER_V3_MD5_OFFSET), QMC2_CHD_HEADER_V3_MD5_LENGTH);
1524 											log(tr("  MD5 check-sum: %1").arg(QString(md5Data.toHex())));
1525 											QByteArray sha1Data((const char *)(buffer + QMC2_CHD_HEADER_V3_SHA1_OFFSET), QMC2_CHD_HEADER_V3_SHA1_LENGTH);
1526 											log(tr("  SHA-1 check-sum: %1").arg(QString(sha1Data.toHex())));
1527 											if ( chdFlags & QMC2_CHD_HEADER_FLAG_HASPARENT ) {
1528 												QByteArray md5Data((const char *)(buffer + QMC2_CHD_HEADER_V3_PARENTMD5_OFFSET), QMC2_CHD_HEADER_V3_PARENTMD5_LENGTH);
1529 												log(tr("  parent CHD's MD5 check-sum: %1").arg(QString(md5Data.toHex())));
1530 												QByteArray sha1Data((const char *)(buffer + QMC2_CHD_HEADER_V3_PARENTSHA1_OFFSET), QMC2_CHD_HEADER_V3_PARENTSHA1_LENGTH);
1531 												log(tr("  parent CHD's SHA-1 check-sum: %1").arg(QString(sha1Data.toHex())));
1532 											}
1533 										}
1534 										break;
1535 
1536 									case 4: {
1537 											quint32 chdCompression = QMC2_TO_UINT32(buffer + QMC2_CHD_HEADER_COMPRESSION_OFFSET);
1538 											log(tr("  compression: %1").arg(chdCompressionTypes[chdCompression]));
1539 											quint32 chdFlags = QMC2_TO_UINT32(buffer + QMC2_CHD_HEADER_FLAGS_OFFSET);
1540 											log(tr("  flags: %1, %2").arg(chdFlags & QMC2_CHD_HEADER_FLAG_HASPARENT ? tr("has parent") : tr("no parent")).arg(chdFlags & QMC2_CHD_HEADER_FLAG_ALLOWSWRITES ? tr("allows writes") : tr("read only")));
1541 											chdTotalHunks = QMC2_TO_UINT32(buffer + QMC2_CHD_HEADER_V4_TOTALHUNKS_OFFSET);
1542 											log(tr("  number of total hunks: %1").arg(locale.toString(chdTotalHunks)));
1543 											quint32 chdHunkBytes = QMC2_TO_UINT32(buffer + QMC2_CHD_HEADER_V4_HUNKBYTES_OFFSET);
1544 											log(tr("  number of bytes per hunk: %1").arg(locale.toString(chdHunkBytes)));
1545 											quint64 chdLogicalBytes = QMC2_TO_UINT64(buffer + QMC2_CHD_HEADER_V4_LOGICALBYTES_OFFSET);
1546 											log(tr("  logical size: %1 (%2 B)").arg(humanReadable(chdLogicalBytes)).arg(locale.toString(chdLogicalBytes)));
1547 											log(tr("  real size: %1 (%2 B)").arg(humanReadable(fi.size())).arg(locale.toString(fi.size())));
1548 											QByteArray sha1Data((const char *)(buffer + QMC2_CHD_HEADER_V4_SHA1_OFFSET), QMC2_CHD_HEADER_V4_SHA1_LENGTH);
1549 											log(tr("  SHA-1 check-sum: %1").arg(QString(sha1Data.toHex())));
1550 											if ( chdFlags & QMC2_CHD_HEADER_FLAG_HASPARENT ) {
1551 												QByteArray sha1Data((const char *)(buffer + QMC2_CHD_HEADER_V4_PARENTSHA1_OFFSET), QMC2_CHD_HEADER_V4_PARENTSHA1_LENGTH);
1552 												log(tr("  parent CHD's SHA-1 check-sum: %1").arg(QString(sha1Data.toHex())));
1553 											}
1554 											QByteArray rawsha1Data((const char *)(buffer + QMC2_CHD_HEADER_V4_RAWSHA1_OFFSET), QMC2_CHD_HEADER_V4_SHA1_LENGTH);
1555 											log(tr("  raw SHA-1 check-sum: %1").arg(QString(rawsha1Data.toHex())));
1556 										}
1557 										break;
1558 
1559 									case 5: {
1560 											QString chdCompressors;
1561 											for (int i = 0; i < QMC2_CHD_HEADER_V5_COMPRESSORS_COUNT; i++) {
1562 												QString compressor = QString::fromLocal8Bit(buffer + i * 4 + QMC2_CHD_HEADER_V5_COMPRESSORS_OFFSET, 4);
1563 												if ( chdCompressionTypesV5.contains(compressor) ) {
1564 													if ( i > 0 ) chdCompressors += ", ";
1565 													chdCompressors += chdCompressionTypesV5[compressor];
1566 												} else if ( i == 0 ) {
1567 													chdCompressors = tr("none (uncompressed)");
1568 													break;
1569 												} else
1570 													break;
1571 											}
1572 											log(tr("  compressors: %1").arg(chdCompressors));
1573 											quint32 chdHunkBytes = QMC2_TO_UINT32(buffer + QMC2_CHD_HEADER_V5_HUNKBYTES_OFFSET);
1574 											log(tr("  number of bytes per hunk: %1").arg(locale.toString(chdHunkBytes)));
1575 											quint32 chdUnitBytes = QMC2_TO_UINT32(buffer + QMC2_CHD_HEADER_V5_UNITBYTES_OFFSET);
1576 											log(tr("  number of bytes per unit: %1").arg(locale.toString(chdUnitBytes)));
1577 											quint64 chdLogicalBytes = QMC2_TO_UINT64(buffer + QMC2_CHD_HEADER_V5_LOGICALBYTES_OFFSET);
1578 											log(tr("  logical size: %1 (%2 B)").arg(humanReadable(chdLogicalBytes)).arg(locale.toString(chdLogicalBytes)));
1579 											log(tr("  real size: %1 (%2 B)").arg(humanReadable(fi.size())).arg(locale.toString(fi.size())));
1580 											QByteArray sha1Data((const char *)(buffer + QMC2_CHD_HEADER_V5_SHA1_OFFSET), QMC2_CHD_HEADER_V5_SHA1_LENGTH);
1581 											log(tr("  SHA-1 check-sum: %1").arg(QString(sha1Data.toHex())));
1582 											QByteArray parentSha1DataHex = QByteArray((const char *)(buffer + QMC2_CHD_HEADER_V5_PARENTSHA1_OFFSET), QMC2_CHD_HEADER_V5_PARENTSHA1_LENGTH).toHex();
1583 											if ( !QMC2_CHD_CHECK_NULL_SHA1(parentSha1DataHex) )
1584 												log(tr("  parent CHD's SHA-1 check-sum: %1").arg(QString(parentSha1DataHex)));
1585 											QByteArray rawsha1Data((const char *)(buffer + QMC2_CHD_HEADER_V5_RAWSHA1_OFFSET), QMC2_CHD_HEADER_V5_SHA1_LENGTH);
1586 											log(tr("  raw SHA-1 check-sum: %1").arg(QString(rawsha1Data.toHex())));
1587 										}
1588 										break;
1589 
1590 									default:
1591 										log(tr("CHD v%1 not supported -- rest of header information skipped").arg(chdVersion));
1592 										break;
1593 								}
1594 
1595 								if ( calcSHA1 || calcMD5 || chdManagerEnabled ) {
1596 									chdFilePath = fi.absoluteFilePath();
1597 									QString chdTempFilePath = QDir::cleanPath(lineEditTemporaryWorkingDirectory->text());
1598 									if ( !chdTempFilePath.endsWith("/") )
1599 										chdTempFilePath += "/";
1600 									chdTempFilePath += fi.completeBaseName() + "-chdman-update.chd";
1601 									if ( chdManagerEnabled ) {
1602 										romFile.close();
1603 										chdManagerCurrentHunk = 0;
1604 #if QMC2_CHD_CURRENT_VERSION >= 5
1605 										chdTotalHunks = 100;
1606 #endif
1607 										chdManagerTotalHunks = chdTotalHunks;
1608 										if ( progressWidget ) {
1609 											progressWidget->setRange(0, chdTotalHunks);
1610 											progressWidget->setValue(0);
1611 										}
1612 										progressBarFileIO->setRange(0, chdTotalHunks);
1613 										progressBarFileIO->reset();
1614 										int step;
1615 										for (step = 0; step < 2 && !qmc2LoadingInterrupted; step++) {
1616 											QStringList args;
1617 											QString oldFormat;
1618 											if ( progressWidget ) oldFormat = progressWidget->format();
1619 											switch ( step ) {
1620 												case 0:
1621 													if ( chdManagerVerifyCHDs ) {
1622 														if ( progressWidget ) progressWidget->setFormat(tr("Verify - %p%"));
1623 #if QMC2_CHD_CURRENT_VERSION >= 5
1624 														log(tr("CHD manager: verifying CHD"));
1625 														args << "verify" << "--input" << chdFilePath;
1626 #else
1627 														log(tr("CHD manager: verifying CHD"));
1628 														args << "-verify" << chdFilePath;
1629 #endif
1630 													} else
1631 														continue;
1632 													break;
1633 
1634 												case 1:
1635 													if ( chdManagerUpdateCHDs ) {
1636 														if ( chdVersion < QMC2_CHD_CURRENT_VERSION ) {
1637 															if ( progressWidget ) progressWidget->setFormat(tr("Update - %p%"));
1638 															log(tr("CHD manager: updating CHD (v%1 -> v%2)").arg(chdVersion).arg(QMC2_CHD_CURRENT_VERSION));
1639 #if QMC2_CHD_CURRENT_VERSION >= 5
1640 															args << "copy" << "--input" << chdFilePath << "--output" << chdTempFilePath;
1641 #else
1642 															args << "-update" << chdFilePath << chdTempFilePath;
1643 #endif
1644 														} else if ( !chdManagerVerifyCHDs ) {
1645 															switch ( chdVersion ) {
1646 																case 3:
1647 																	log(tr("CHD manager: using CHD v%1 header check-sums for CHD verification").arg(chdVersion));
1648 																	if ( calcSHA1 ) {
1649 																		QByteArray sha1Data((const char *)(buffer + QMC2_CHD_HEADER_V3_SHA1_OFFSET), QMC2_CHD_HEADER_V3_SHA1_LENGTH);
1650 																		*sha1Str = QString(sha1Data.toHex());
1651 																	}
1652 																	if ( calcMD5 ) {
1653 																		QByteArray md5Data((const char *)(buffer + QMC2_CHD_HEADER_V3_MD5_OFFSET), QMC2_CHD_HEADER_V3_MD5_LENGTH);
1654 																		*md5Str = QString(md5Data.toHex());
1655 																	}
1656 																	break;
1657 
1658 																case 4:
1659 																	log(tr("CHD manager: using CHD v%1 header check-sums for CHD verification").arg(chdVersion));
1660 																	if ( calcSHA1 ) {
1661 																		QByteArray sha1Data((const char *)(buffer + QMC2_CHD_HEADER_V4_SHA1_OFFSET), QMC2_CHD_HEADER_V4_SHA1_LENGTH);
1662 																		*sha1Str = QString(sha1Data.toHex());
1663 																	}
1664 																	break;
1665 
1666 																case 5:
1667 																	log(tr("CHD manager: using CHD v%1 header check-sums for CHD verification").arg(chdVersion));
1668 																	if ( calcSHA1 ) {
1669 																		QByteArray sha1Data((const char *)(buffer + QMC2_CHD_HEADER_V5_SHA1_OFFSET), QMC2_CHD_HEADER_V5_SHA1_LENGTH);
1670 																		*sha1Str = QString(sha1Data.toHex());
1671 																	}
1672 																	break;
1673 
1674 																default:
1675 																	log(tr("CHD manager: no header check-sums available for CHD verification"));
1676 																	effectiveFile = QMC2_ROMALYZER_FILE_NOT_SUPPORTED;
1677 																	if ( fallbackPath->isEmpty() )
1678 																		*fallbackPath = chdFilePath;
1679 																	break;
1680 															}
1681 															continue;
1682 														} else
1683 															continue;
1684 													} else
1685 														continue;
1686 													break;
1687 											}
1688 											QString command = lineEditCHDManagerExecutableFile->text();
1689 											QProcess *chdManagerProc = new QProcess(this);
1690 											connect(chdManagerProc, SIGNAL(error(QProcess::ProcessError)), this, SLOT(chdManagerError(QProcess::ProcessError)));
1691 											connect(chdManagerProc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(chdManagerFinished(int, QProcess::ExitStatus)));
1692 											connect(chdManagerProc, SIGNAL(readyReadStandardOutput()), this, SLOT(chdManagerReadyReadStandardOutput()));
1693 											connect(chdManagerProc, SIGNAL(readyReadStandardError()), this, SLOT(chdManagerReadyReadStandardError()));
1694 											connect(chdManagerProc, SIGNAL(started()), this, SLOT(chdManagerStarted()));
1695 											chdManagerProc->start(command, args);
1696 											chdManagerRunning = true;
1697 											chdManagerMD5Success = chdManagerSHA1Success = false;
1698 
1699 											// wait for CHD manager to finish...
1700 											while ( chdManagerRunning && !qmc2LoadingInterrupted ) {
1701 												QTest::qWait(QMC2_ROMALYZER_PAUSE_TIMEOUT);
1702 												if ( qmc2LoadingInterrupted ) {
1703 													log(tr("CHD manager: terminating external process"));
1704 													chdManagerProc->kill();
1705 													chdManagerProc->waitForFinished();
1706 													disconnect(chdManagerProc);
1707 													chdManagerProc->deleteLater();
1708 												} else {
1709 													if ( progressWidget ) {
1710 														if ( chdManagerTotalHunks != (quint64)progressWidget->maximum() )
1711 															progressWidget->setRange(0, chdManagerTotalHunks);
1712 														if ( chdManagerCurrentHunk != (quint64)progressWidget->value() )
1713 															progressWidget->setValue(chdManagerCurrentHunk);
1714 													}
1715 													if ( chdManagerTotalHunks != (quint64)progressBarFileIO->maximum() )
1716 														progressBarFileIO->setRange(0, chdManagerTotalHunks);
1717 													if ( chdManagerCurrentHunk != (quint64)progressBarFileIO->value() )
1718 														progressBarFileIO->setValue(chdManagerCurrentHunk);
1719 													progressBarFileIO->update();
1720 													qApp->processEvents();
1721 												}
1722 											}
1723 											chdManagerRunning = false;
1724 											if ( !qmc2LoadingInterrupted ) {
1725 												if ( chdManagerMD5Success && calcMD5 )
1726 													log(tr("CHD manager: CHD file integrity is good"));
1727 												else if ( chdManagerSHA1Success && calcSHA1 )
1728 													log(tr("CHD manager: CHD file integrity is good"));
1729 												else
1730 													log(tr("CHD manager: WARNING: CHD file integrity is bad"));
1731 
1732 												if ( step == 1 && (chdManagerMD5Success || chdManagerSHA1Success) ) {
1733 													log(tr("CHD manager: replacing CHD"));
1734 													if ( progressWidget ) {
1735 														progressWidget->setFormat(tr("Copy"));
1736 														progressWidget->setRange(-1, -1);
1737 														progressWidget->setValue(-1);
1738 													}
1739 													QFile::remove(chdFilePath);
1740 													if ( QFile::rename(chdTempFilePath, chdFilePath) ) {
1741 														log(tr("CHD manager: CHD replaced"));
1742 														myItem->setText(QMC2_ROMALYZER_COLUMN_TYPE, tr("CHD v%1").arg(QMC2_CHD_CURRENT_VERSION));
1743 														QFile romFileTmp(chdFilePath);
1744 														if ( romFileTmp.open(QIODevice::ReadOnly) ) {
1745 															char bufferCopy[QMC2_ROMALYZER_FILE_BUFFER_SIZE];
1746 															strncpy(bufferCopy, buffer, QMC2_ROMALYZER_FILE_BUFFER_SIZE);
1747 															if ( romFileTmp.read(buffer, QMC2_CHD_HEADER_V3_LENGTH) <= 0 ) {
1748 																log(tr("CHD manager: WARNING: failed updating CHD header information"));
1749 																strncpy(buffer, bufferCopy, QMC2_ROMALYZER_FILE_BUFFER_SIZE);
1750 															} else
1751 																chdVersion = QMC2_CHD_CURRENT_VERSION;
1752 															romFileTmp.close();
1753 														}
1754 													} else
1755 														log(tr("CHD manager: FATAL: failed to replace CHD -- updated CHD preserved as '%1', please copy it to '%2' manually!").arg(chdTempFilePath).arg(chdFilePath));
1756 												}
1757 
1758 												switch ( chdVersion ) {
1759 													case 3:
1760 														log(tr("CHD manager: using CHD v%1 header check-sums for CHD verification").arg(chdVersion));
1761 														if ( chdManagerSHA1Success && calcSHA1 ) {
1762 															QByteArray sha1Data((const char *)(buffer + QMC2_CHD_HEADER_V3_SHA1_OFFSET), QMC2_CHD_HEADER_V3_SHA1_LENGTH);
1763 															*sha1Str = QString(sha1Data.toHex());
1764 														}
1765 														if ( chdManagerMD5Success && calcMD5 ) {
1766 															QByteArray md5Data((const char *)(buffer + QMC2_CHD_HEADER_V3_MD5_OFFSET), QMC2_CHD_HEADER_V3_MD5_LENGTH);
1767 															*md5Str = QString(md5Data.toHex());
1768 														}
1769 														break;
1770 
1771 													case 4:
1772 														log(tr("CHD manager: using CHD v%1 header check-sums for CHD verification").arg(chdVersion));
1773 														if ( chdManagerSHA1Success && calcSHA1 ) {
1774 															QByteArray sha1Data((const char *)(buffer + QMC2_CHD_HEADER_V4_SHA1_OFFSET), QMC2_CHD_HEADER_V4_SHA1_LENGTH);
1775 															*sha1Str = QString(sha1Data.toHex());
1776 														}
1777 														break;
1778 
1779 													case 5:
1780 														log(tr("CHD manager: using CHD v%1 header check-sums for CHD verification").arg(chdVersion));
1781 														if ( chdManagerSHA1Success && calcSHA1 ) {
1782 															QByteArray sha1Data((const char *)(buffer + QMC2_CHD_HEADER_V5_SHA1_OFFSET), QMC2_CHD_HEADER_V5_SHA1_LENGTH);
1783 															*sha1Str = QString(sha1Data.toHex());
1784 														}
1785 														break;
1786 
1787 													default:
1788 														log(tr("CHD manager: WARNING: no header check-sums available for CHD verification"));
1789 														effectiveFile = QMC2_ROMALYZER_FILE_NOT_SUPPORTED;
1790 														if ( fallbackPath->isEmpty() )
1791 															*fallbackPath = chdFilePath;
1792 														break;
1793 												}
1794 											}
1795 											if ( QFile::exists(chdTempFilePath) ) {
1796 												log(tr("CHD manager: cleaning up"));
1797 												QFile::remove(chdTempFilePath);
1798 											}
1799 											if ( progressWidget ) {
1800 												progressWidget->setFormat(oldFormat);
1801 												progressWidget->reset();
1802 											}
1803 											progressBarFileIO->reset();
1804 										}
1805 									} else {
1806 										switch ( chdVersion ) {
1807 											case 3:
1808 												log(tr("using CHD v%1 header check-sums for CHD verification").arg(chdVersion));
1809 												if ( calcSHA1 ) {
1810 													QByteArray sha1Data((const char *)(buffer + QMC2_CHD_HEADER_V3_SHA1_OFFSET), QMC2_CHD_HEADER_V3_SHA1_LENGTH);
1811 													*sha1Str = QString(sha1Data.toHex());
1812 												}
1813 												if ( calcMD5 ) {
1814 													QByteArray md5Data((const char *)(buffer + QMC2_CHD_HEADER_V3_MD5_OFFSET), QMC2_CHD_HEADER_V3_MD5_LENGTH);
1815 													*md5Str = QString(md5Data.toHex());
1816 												}
1817 												break;
1818 
1819 											case 4:
1820 												log(tr("using CHD v%1 header check-sums for CHD verification").arg(chdVersion));
1821 												if ( calcSHA1 ) {
1822 													QByteArray sha1Data((const char *)(buffer + QMC2_CHD_HEADER_V4_SHA1_OFFSET), QMC2_CHD_HEADER_V4_SHA1_LENGTH);
1823 													*sha1Str = QString(sha1Data.toHex());
1824 												}
1825 												break;
1826 
1827 											case 5:
1828 												log(tr("using CHD v%1 header check-sums for CHD verification").arg(chdVersion));
1829 												if ( calcSHA1 ) {
1830 													QByteArray sha1Data((const char *)(buffer + QMC2_CHD_HEADER_V5_SHA1_OFFSET), QMC2_CHD_HEADER_V5_SHA1_LENGTH);
1831 													*sha1Str = QString(sha1Data.toHex());
1832 												}
1833 												break;
1834 
1835 											default:
1836 												log(tr("WARNING: no header check-sums available for CHD verification"));
1837 												effectiveFile = QMC2_ROMALYZER_FILE_NOT_SUPPORTED;
1838 												if ( fallbackPath->isEmpty() )
1839 													*fallbackPath = chdFilePath;
1840 												break;
1841 										}
1842 									}
1843 								}
1844 							} else {
1845 								log(tr("WARNING: can't read CHD header information"));
1846 								effectiveFile = QMC2_ROMALYZER_FILE_ERROR;
1847 								if ( fallbackPath->isEmpty() )
1848 									*fallbackPath = chdFilePath;
1849 							}
1850 						} else {
1851 							log(tr("WARNING: can't read CHD header information"));
1852 							effectiveFile = QMC2_ROMALYZER_FILE_ERROR;
1853 							if ( fallbackPath->isEmpty() )
1854 								*fallbackPath = chdFilePath;
1855 						}
1856 					} else {
1857 						while ( (len = romFile.read(buffer, QMC2_ROMALYZER_FILE_BUFFER_SIZE)) > 0 && !qmc2LoadingInterrupted ) {
1858 							QByteArray readData((const char *)buffer, len);
1859 							fileData->append(readData);
1860 							if ( calcSHA1 )
1861 								sha1Hash.addData(readData);
1862 							if ( calcMD5 )
1863 								md5Hash.addData(readData);
1864 							sizeLeft -= len;
1865 							myProgress = totalSize - sizeLeft;
1866 							progressBarFileIO->setValue(myProgress);
1867 							if ( needProgressWidget )
1868 								if ( progressWidget ) progressWidget->setValue(myProgress);
1869 							progressBarFileIO->update();
1870 							qApp->processEvents();
1871 						}
1872 
1873 						ulong crc = crc32(0, 0, 0);
1874 						crc = crc32(crc, (const Bytef *)fileData->data(), fileData->size());
1875 						QFileInfo fi(filePath);
1876 						QStringList sl;
1877 						//    fromName         fromPath    toName           fromZip
1878 						sl << fi.fileName() << filePath << fi.fileName() << "file";
1879 						setRewriterFileMap.insert(crcToString(crc), sl);
1880 
1881 						if ( calcSHA1 )
1882 							*sha1Str = sha1Hash.result().toHex();
1883 						if ( calcMD5 )
1884 							*md5Str = md5Hash.result().toHex();
1885 					}
1886 					romFile.close();
1887 					effectiveFile = filePath;
1888 					*isZipped = false;
1889 					if ( needProgressWidget ) {
1890 						if ( progressWidget ) {
1891 							progressWidget->reset();
1892 							treeWidgetChecksums->removeItemWidget(myItem, QMC2_ROMALYZER_COLUMN_FILESTATUS);
1893 						}
1894 						if ( progressWidget )
1895 							delete progressWidget;
1896 					}
1897 					progressBarFileIO->reset();
1898 				} else {
1899 					log(tr("WARNING: found '%1' but can't read from it although permissions seem okay - check file integrity").arg(filePath));
1900 				}
1901 			} else
1902 				log(tr("WARNING: found '%1' but can't read from it - check permission").arg(filePath));
1903 		} else {
1904 			if ( isCHD ) {
1905 				if ( romPathCount == actualRomPaths.count() ) {
1906 					QString baseName = QFileInfo(filePath).completeBaseName();
1907 					QStringList chdPaths = actualRomPaths;
1908 					for (int i = 0; i < chdPaths.count(); i++)
1909 						chdPaths[i] = chdPaths[i] + QDir::separator() + gameName + QDir::separator() + baseName + ".chd";
1910 					QString sP;
1911 					if ( actualRomPaths.count() > 1 )
1912 						sP = tr("searched paths: %1").arg(chdPaths.join(", "));
1913 					else
1914 						sP = tr("searched path: %1").arg(chdPaths[0]);
1915 					if ( myItem->text(QMC2_ROMALYZER_COLUMN_EMUSTATUS) == tr("no dump") )
1916 						sP += " (" + tr("no dump exists") + " / " + tr("SHA-1") + " " + tr("unknown") + ")";
1917 					log(tr("WARNING: CHD file '%1' not found").arg(baseName) + " -- " + sP);
1918 				}
1919 				if ( mergeFile.isEmpty() && merge.isEmpty() )
1920 					if ( fallbackPath->isEmpty() )
1921 						*fallbackPath = filePath;
1922 			}
1923 		}
1924 
1925 		if ( effectiveFile.isEmpty() && !qmc2LoadingInterrupted ) {
1926 			filePath = romPath + "/" + gameName + ".7z";
1927 			if ( QFile::exists(filePath) ) {
1928 				QFileInfo fi(filePath);
1929 				if ( fi.isReadable() ) {
1930 					// try loading data from a 7z archive
1931 					SevenZipFile sevenZipFile(filePath);
1932 					if ( sevenZipFile.open() ) {
1933 						// identify file by CRC
1934 						int index = sevenZipFile.indexOfCrc(wantedCRC);
1935 						if ( index >= 0 ) {
1936 							if ( sevenZipFile.isCrcDuplicate(wantedCRC) ) {
1937 								int nameIndex = sevenZipFile.indexOfName(fileName);
1938 								if ( nameIndex >= 0 )
1939 									index = nameIndex;
1940 							}
1941 							SevenZipMetaData metaData = sevenZipFile.entryList().at(index);
1942 							if ( metaData.size() > QMC2_2G ) {
1943 								log(tr("size of '%1' from '%2' is greater than 2 GB -- skipped").arg(metaData.name()).arg(filePath));
1944 								*isSevenZipped = true;
1945 								effectiveFile = QMC2_ROMALYZER_FILE_TOO_BIG;
1946 								if ( fallbackPath->isEmpty() )
1947 									*fallbackPath = filePath;
1948 								continue;
1949 							} else if ( sizeLimited ) {
1950 								if ( metaData.size() > (quint64) spinBoxMaxFileSize->value() * QMC2_ONE_MEGABYTE ) {
1951 									log(tr("size of '%1' from '%2' is greater than allowed maximum -- skipped").arg(metaData.name()).arg(filePath));
1952 									*isSevenZipped = true;
1953 									effectiveFile = QMC2_ROMALYZER_FILE_TOO_BIG;
1954 									if ( fallbackPath->isEmpty() )
1955 										*fallbackPath = filePath;
1956 									continue;
1957 								}
1958 							}
1959 							log(tr("loading '%1' with CRC '%2' from '%3' as '%4'%5").arg(metaData.name()).arg(wantedCRC).arg(filePath).arg(myItem->text(QMC2_ROMALYZER_COLUMN_SET)).arg(*mergeUsed ? tr(" (merged)") : ""));
1960 							QByteArray data;
1961 							qApp->processEvents();
1962 							quint64 readLength = sevenZipFile.read(index, &data);
1963 							qApp->processEvents();
1964 							if ( sevenZipFile.hasError() )
1965 								log(tr("ERROR") + ": " + sevenZipFile.lastError());
1966 							if ( readLength != metaData.size() )
1967 								log(tr("WARNING") + ": " + tr("actual bytes read != file size in header") + " - " + tr("check archive integrity"));
1968 							ulong crc = crc32(0, 0, 0);
1969 							crc = crc32(crc, (const Bytef *)data.data(), data.size());
1970 							QString crcString = crcToString(crc);
1971 							if ( crcString != wantedCRC )
1972 								log(tr("WARNING") + ": " + tr("actual CRC != CRC in header") + " - " + tr("check archive integrity"));
1973 							else {
1974 								if ( !wantedCRC.isEmpty() ) {
1975 									QStringList sl;
1976 									//    fromName           fromPath    toName                                      fromZip
1977 									sl << metaData.name() << filePath << myItem->text(QMC2_ROMALYZER_COLUMN_SET) << "7z";
1978 									setRewriterFileMap.insert(wantedCRC, sl);
1979 								} else {
1980 									if ( !crcString.isEmpty() ) {
1981 										QStringList sl;
1982 										//    fromName           fromPath    toName                                      fromZip
1983 										sl << metaData.name() << filePath << myItem->text(QMC2_ROMALYZER_COLUMN_SET) << "7z";
1984 										setRewriterFileMap.insert(crcString, sl);
1985 										if ( groupBoxSetRewriter->isChecked() )
1986 											log(tr("WARNING: the CRC for '%1' from '%2' is unknown to the emulator, the set rewriter will use the recalculated CRC '%3' to qualify the file").arg(metaData.name()).arg(filePath).arg(crcString));
1987 									} else if ( groupBoxSetRewriter->isChecked() )
1988 										log(tr("WARNING: unable to determine the CRC for '%1' from '%2', the set rewriter will NOT store this file in the new set").arg(metaData.name()).arg(filePath));
1989 								}
1990 
1991 								if ( calcSHA1 ) {
1992 									sha1Hash.reset();
1993 									sha1Hash.addData(data);
1994 									*sha1Str = sha1Hash.result().toHex();
1995 								}
1996 
1997 								if ( calcMD5 ) {
1998 									md5Hash.reset();
1999 									md5Hash.addData(data);
2000 									*md5Str = md5Hash.result().toHex();
2001 								}
2002 
2003 								if ( !isCHD )
2004 									*fileData = data;
2005 
2006 								effectiveFile = filePath;
2007 								*isSevenZipped = true;
2008 							}
2009 						} else if ( mergeFile.isEmpty() ) {
2010 							if ( !isCHD ) {
2011 								if ( wantedCRC.isEmpty() ) {
2012 									log(tr("WARNING: unable to identify '%1' from '%2' by CRC (no dump exists / CRC unknown)").arg(fileName).arg(filePath));
2013 									effectiveFile = QMC2_ROMALYZER_NO_DUMP;
2014 								} else
2015 									log(tr("WARNING: unable to identify '%1' from '%2' by CRC '%3'").arg(fileName).arg(filePath).arg(wantedCRC) + QString(isOptionalROM ? " (" + tr("optional") + ")" : ""));
2016 							}
2017 						}
2018 					} else
2019 						log(tr("WARNING: found '%1' but can't open it for decompression - check file integrity").arg(filePath));
2020 				} else
2021 					log(tr("WARNING: found '%1' but can't read from it - check permission").arg(filePath));
2022 			}
2023 
2024 			if ( !effectiveFile.isEmpty() || qmc2LoadingInterrupted )
2025 				break;
2026 
2027 			filePath = romPath + "/" + gameName + ".zip";
2028 			if ( fallbackPath->isEmpty() )
2029 				*fallbackPath = filePath;
2030 			if ( QFile::exists(filePath) ) {
2031 				QFileInfo fi(filePath);
2032 				if ( fi.isReadable() ) {
2033 					// try loading data from a ZIP archive
2034 					unzFile zipFile = unzOpen(filePath.toUtf8().constData());
2035 					if ( zipFile ) {
2036 						// identify file by CRC
2037 						unz_file_info zipInfo;
2038 						QMultiMap<uLong, QString> crcIdentMap;
2039 						uLong ulCRC = wantedCRC.toULong(0, 16);
2040 						do {
2041 							if ( unzGetCurrentFileInfo(zipFile, &zipInfo, buffer, QMC2_ROMALYZER_ZIP_BUFFER_SIZE, 0, 0, 0, 0) == UNZ_OK )
2042 								crcIdentMap.insert(zipInfo.crc, QString((const char *)buffer));
2043 						} while ( unzGoToNextFile(zipFile) == UNZ_OK );
2044 						unzGoToFirstFile(zipFile);
2045 						QString fn("QMC2_DUMMY_FILENAME");
2046 						if ( crcIdentMap.contains(ulCRC) ) {
2047 							QStringList names(crcIdentMap.values(ulCRC));
2048 							if ( names.contains(fileName) )
2049 								fn = fileName;
2050 							else
2051 								fn = names.at(0);
2052 						} else if ( mergeFile.isEmpty() ) {
2053 							if ( !isCHD ) {
2054 								if ( wantedCRC.isEmpty() ) {
2055 									log(tr("WARNING: unable to identify '%1' from '%2' by CRC (no dump exists / CRC unknown)").arg(fileName).arg(filePath));
2056 									effectiveFile = QMC2_ROMALYZER_NO_DUMP;
2057 								} else
2058 									log(tr("WARNING: unable to identify '%1' from '%2' by CRC '%3'").arg(fileName).arg(filePath).arg(wantedCRC) + QString(isOptionalROM ? " (" + tr("optional") + ")" : ""));
2059 							}
2060 							fn = fileName;
2061 						}
2062 						if ( unzLocateFile(zipFile, fn.toUtf8().constData(), 0) == UNZ_OK ) { // NOT case-sensitive filename compare!
2063 							totalSize = 0;
2064 							if ( unzGetCurrentFileInfo(zipFile, &zipInfo, 0, 0, 0, 0, 0, 0) == UNZ_OK )
2065 								totalSize = zipInfo.uncompressed_size;
2066 							if ( totalSize > (qlonglong)QMC2_2G ) {
2067 								log(tr("size of '%1' from '%2' is greater than 2 GB -- skipped").arg(fn).arg(filePath));
2068 								*isZipped = true;
2069 								progressBarFileIO->reset();
2070 								effectiveFile = QMC2_ROMALYZER_FILE_TOO_BIG;
2071 								if ( fallbackPath->isEmpty() )
2072 									*fallbackPath = filePath;
2073 								continue;
2074 							} else if ( sizeLimited ) {
2075 								if ( totalSize > (qint64) spinBoxMaxFileSize->value() * QMC2_ONE_MEGABYTE ) {
2076 									log(tr("size of '%1' from '%2' is greater than allowed maximum -- skipped").arg(fn).arg(filePath));
2077 									*isZipped = true;
2078 									progressBarFileIO->reset();
2079 									effectiveFile = QMC2_ROMALYZER_FILE_TOO_BIG;
2080 									if ( fallbackPath->isEmpty() )
2081 										*fallbackPath = filePath;
2082 									continue;
2083 								}
2084 							}
2085 							if ( unzOpenCurrentFile(zipFile) == UNZ_OK ) {
2086 								log(tr("loading '%1' with CRC '%2' from '%3' as '%4'%5").arg(fn).arg(wantedCRC).arg(filePath).arg(myItem->text(QMC2_ROMALYZER_COLUMN_SET)).arg(*mergeUsed ? tr(" (merged)") : ""));
2087 								progressBarFileIO->setRange(0, totalSize);
2088 								progressBarFileIO->reset();
2089 								needProgressWidget = totalSize > QMC2_ROMALYZER_PROGRESS_THRESHOLD;
2090 								if ( needProgressWidget ) {
2091 									progressWidget = new QProgressBar(0);
2092 									progressWidget->setRange(0, totalSize);
2093 									progressWidget->setValue(0);
2094 									treeWidgetChecksums->setItemWidget(myItem, QMC2_ROMALYZER_COLUMN_FILESTATUS, progressWidget);
2095 								}
2096 								sizeLeft = totalSize;
2097 								if ( calcSHA1 )
2098 									sha1Hash.reset();
2099 								if ( calcMD5 )
2100 									md5Hash.reset();
2101 								while ( (len = unzReadCurrentFile(zipFile, buffer, QMC2_ROMALYZER_ZIP_BUFFER_SIZE)) > 0 && !qmc2LoadingInterrupted ) {
2102 									QByteArray readData((const char *)buffer, len);
2103 									if ( !isCHD )
2104 										fileData->append(readData);
2105 									if ( calcSHA1 )
2106 										sha1Hash.addData(readData);
2107 									if ( calcMD5 )
2108 										md5Hash.addData(readData);
2109 									sizeLeft -= len;
2110 									myProgress = totalSize - sizeLeft;
2111 									progressBarFileIO->setValue(myProgress);
2112 									if ( needProgressWidget )
2113 										progressWidget->setValue(myProgress);
2114 									progressBarFileIO->update();
2115 									qApp->processEvents();
2116 								}
2117 								unzCloseCurrentFile(zipFile);
2118 								effectiveFile = filePath;
2119 								if ( fallbackPath->isEmpty() )
2120 									*fallbackPath = filePath;
2121 								QString fromName = fileName;
2122 								if ( fn != "QMC2_DUMMY_FILENAME" )
2123 									fromName = fn;
2124 								if ( !wantedCRC.isEmpty() ) {
2125 									QStringList sl;
2126 									//    fromName    fromPath    toName                                      fromZip
2127 									sl << fromName << filePath << myItem->text(QMC2_ROMALYZER_COLUMN_SET) << "zip";
2128 									setRewriterFileMap.insert(wantedCRC, sl);
2129 								} else {
2130 									ulong crc = crc32(0, 0, 0);
2131 									crc = crc32(crc, (const Bytef *)fileData->data(), fileData->size());
2132 									QString fallbackCRC = crcToString(crc);
2133 									if ( !fallbackCRC.isEmpty() ) {
2134 										QStringList sl;
2135 										//    fromName    fromPath    toName                                      fromZip
2136 										sl << fromName << filePath << myItem->text(QMC2_ROMALYZER_COLUMN_SET) << "zip";
2137 										setRewriterFileMap.insert(fallbackCRC, sl);
2138 										if ( groupBoxSetRewriter->isChecked() )
2139 											log(tr("WARNING: the CRC for '%1' from '%2' is unknown to the emulator, the set rewriter will use the recalculated CRC '%3' to qualify the file").arg(fileName).arg(filePath).arg(fallbackCRC));
2140 									} else if ( groupBoxSetRewriter->isChecked() )
2141 										log(tr("WARNING: unable to determine the CRC for '%1' from '%2', the set rewriter will NOT store this file in the new set").arg(fileName).arg(filePath));
2142 								}
2143 								*isZipped = true;
2144 								if ( calcSHA1 )
2145 									*sha1Str = sha1Hash.result().toHex();
2146 								if ( calcMD5 )
2147 									*md5Str = md5Hash.result().toHex();
2148 								if ( needProgressWidget ) {
2149 									if ( progressWidget ) {
2150 										progressWidget->reset();
2151 										treeWidgetChecksums->removeItemWidget(myItem, QMC2_ROMALYZER_COLUMN_FILESTATUS);
2152 									}
2153 									if ( progressWidget )
2154 										delete progressWidget;
2155 								}
2156 								progressBarFileIO->reset();
2157 							} else
2158 								log(tr("WARNING: unable to decompress '%1' from '%2' - check file integrity").arg(fn).arg(filePath));
2159 						}
2160 						unzClose(zipFile);
2161 					} else
2162 						log(tr("WARNING: found '%1' but can't open it for decompression - check file integrity").arg(filePath));
2163 				} else
2164 					log(tr("WARNING: found '%1' but can't read from it - check permission").arg(filePath));
2165 			}
2166 		}
2167 
2168 		if ( !effectiveFile.isEmpty() || qmc2LoadingInterrupted )
2169 			break;
2170 	}
2171 
2172 	// try merging if applicable...
2173 	if ( effectiveFile.isEmpty() && !qmc2LoadingInterrupted ) {
2174 		if ( mergeFile.isEmpty() && !merge.isEmpty() ) {
2175 			// romof/cloneof is set, but the merge's file name is missing... use the same file name for the merge
2176 			mergeFile = fileName;
2177 		}
2178 		if ( !mergeFile.isEmpty() && !qmc2LoadingInterrupted ) {
2179 			// romof/clonef is set, and the merge's file name is given
2180 			*mergeUsed = true;
2181 			switch ( mode() ) {
2182 				case QMC2_ROMALYZER_MODE_SOFTWARE:
2183 					effectiveFile = getEffectiveFile(myItem, listName, merge, mergeFile, wantedCRC, "", "", type, fileData, sha1Str, md5Str, isZipped, isSevenZipped, mergeUsed, fileCounter, fallbackPath, isOptionalROM, fromCheckSumDb);
2184 					break;
2185 				case QMC2_ROMALYZER_MODE_SYSTEM:
2186 				default: {
2187 						QString nextMerge = getXmlData(merge).split("\n")[0];
2188 						int position = nextMerge.indexOf("romof=");
2189 						if ( position > -1 ) {
2190 							nextMerge = nextMerge.mid(position + 7);
2191 							nextMerge = nextMerge.left(nextMerge.indexOf("\""));
2192 							effectiveFile = getEffectiveFile(myItem, listName, merge, mergeFile, wantedCRC, nextMerge, mergeFile, type, fileData, sha1Str, md5Str, isZipped, isSevenZipped, mergeUsed, fileCounter, fallbackPath, isOptionalROM, fromCheckSumDb);
2193 						} else
2194 							effectiveFile = getEffectiveFile(myItem, listName, merge, mergeFile, wantedCRC, "", "", type, fileData, sha1Str, md5Str, isZipped, isSevenZipped, mergeUsed, fileCounter, fallbackPath, isOptionalROM, fromCheckSumDb);
2195 					}
2196 					break;
2197 			}
2198 		}
2199 	}
2200 
2201 	// try check-sum database if available/applicable...
2202 	if ( effectiveFile.isEmpty() && !qmc2LoadingInterrupted && groupBoxCheckSumDatabase->isChecked() ) {
2203 		QString wantedSHA1 = myItem->text(QMC2_ROMALYZER_COLUMN_SHA1);
2204 		quint64 size = myItem->text(QMC2_ROMALYZER_COLUMN_SIZE).toULongLong();
2205 		if ( checkSumDb()->exists(wantedSHA1, wantedCRC, size) ) {
2206 			QString pathFromDb, memberFromDb, typeFromDb;
2207 			if ( checkSumDb()->getData(wantedSHA1, wantedCRC, &size, &pathFromDb, &memberFromDb, &typeFromDb) ) {
2208 				QStringList sl;
2209 				switch ( checkSumDb()->nameToType(typeFromDb) ) {
2210 					case QMC2_CHECKSUM_SCANNER_FILE_ZIP:
2211 						//    fromName        fromPath      toName                                      fromZip
2212 						sl << memberFromDb << pathFromDb << myItem->text(QMC2_ROMALYZER_COLUMN_SET) << "zip";
2213 						log(tr("check-sum database") + ": " + tr("using member '%1' from archive '%2' with SHA-1 '%3' and CRC '%4' as '%5'").arg(memberFromDb).arg(pathFromDb).arg(wantedSHA1).arg(wantedCRC).arg(myItem->text(QMC2_ROMALYZER_COLUMN_SET)));
2214 						*isZipped = true;
2215 						break;
2216 					case QMC2_CHECKSUM_SCANNER_FILE_7Z:
2217 						//    fromName        fromPath      toName                                      fromZip
2218 						sl << memberFromDb << pathFromDb << myItem->text(QMC2_ROMALYZER_COLUMN_SET) << "7z";
2219 						log(tr("check-sum database") + ": " + tr("using member '%1' from archive '%2' with SHA-1 '%3' and CRC '%4' as '%5'").arg(memberFromDb).arg(pathFromDb).arg(wantedSHA1).arg(wantedCRC).arg(myItem->text(QMC2_ROMALYZER_COLUMN_SET)));
2220 						*isSevenZipped = true;
2221 						break;
2222 					case QMC2_CHECKSUM_SCANNER_FILE_CHD:
2223 						//    fromName        fromPath      toName                                      fromZip
2224 						sl << memberFromDb << pathFromDb << myItem->text(QMC2_ROMALYZER_COLUMN_SET) << "chd";
2225 						log(tr("check-sum database") + ": " + tr("using CHD '%1' with SHA-1 '%2' as '%3'").arg(pathFromDb).arg(wantedSHA1).arg(myItem->text(QMC2_ROMALYZER_COLUMN_SET) + ".chd"));
2226 						break;
2227 					case QMC2_CHECKSUM_SCANNER_FILE_REGULAR:
2228 						//    fromName        fromPath      toName                                      fromZip
2229 						sl << memberFromDb << pathFromDb << myItem->text(QMC2_ROMALYZER_COLUMN_SET) << "file";
2230 						log(tr("check-sum database") + ": " + tr("using file '%1' with SHA-1 '%2' and CRC '%3' as '%4'").arg(pathFromDb).arg(wantedSHA1).arg(wantedCRC).arg(myItem->text(QMC2_ROMALYZER_COLUMN_SET)));
2231 						break;
2232 					default:
2233 						break;
2234 				}
2235 				if ( !sl.isEmpty() ) {
2236 					*sha1Str = wantedSHA1;
2237 					*fromCheckSumDb = true;
2238 					effectiveFile = pathFromDb;
2239 					setRewriterFileMap.insert(wantedCRC, sl);
2240 				}
2241 			}
2242 		}
2243 	}
2244 
2245 	if ( effectiveFile.isEmpty() )
2246 		effectiveFile = QMC2_ROMALYZER_FILE_NOT_FOUND;
2247 
2248 	return effectiveFile;
2249 }
2250 
createBackup(QString filePath)2251 bool ROMAlyzer::createBackup(QString filePath)
2252 {
2253 	if ( !checkBoxCreateBackups->isChecked() || lineEditBackupFolder->text().isEmpty() )
2254 		return true;
2255 	QFile sourceFile(filePath);
2256 	if ( !sourceFile.exists() )
2257 		return true;
2258 	QDir backupDir(lineEditBackupFolder->text());
2259 	QFileInfo backupDirInfo(backupDir.absolutePath());
2260 	if ( backupDirInfo.exists() ) {
2261 		if ( backupDirInfo.isWritable() ) {
2262 #if defined(QMC2_OS_WIN)
2263 			QString filePathCopy = filePath;
2264 			QString destinationPath = QDir::cleanPath(QString(backupDir.absolutePath() + "/" + filePathCopy.replace(":", "")));
2265 #else
2266 			QString destinationPath = QDir::cleanPath(backupDir.absolutePath() + "/" + filePath);
2267 #endif
2268 			QFileInfo destinationPathInfo(destinationPath);
2269 			if ( !destinationPathInfo.dir().exists() ) {
2270 				if ( !backupDir.mkpath(destinationPathInfo.dir().absolutePath()) ) {
2271 					log(tr("backup") + ": " + tr("FATAL: target path '%1' cannot be created").arg(destinationPathInfo.dir().absolutePath()));
2272 					return false;
2273 				}
2274 			}
2275 			if ( !sourceFile.open(QIODevice::ReadOnly) ) {
2276 				log(tr("backup") + ": " + tr("FATAL: source file '%1' cannot be opened for reading").arg(filePath));
2277 				return false;
2278 			}
2279 			QFile destinationFile(destinationPath);
2280 			if ( destinationFile.open(QIODevice::WriteOnly) ) {
2281 				log(tr("backup") + ": " + tr("creating backup copy of '%1' as '%2'").arg(filePath).arg(destinationPath));
2282 				char ioBuffer[QMC2_ROMALYZER_FILE_BUFFER_SIZE];
2283 				int count = 0;
2284 				int len = 0;
2285 				bool success = true;
2286 				while ( success && (len = sourceFile.read(ioBuffer, QMC2_ROMALYZER_FILE_BUFFER_SIZE)) > 0 ) {
2287 					if ( count++ % QMC2_BACKUP_IO_RESPONSE == 0 )
2288 						qApp->processEvents();
2289 					if ( destinationFile.write(ioBuffer, len) != len ) {
2290 						log(tr("backup") + ": " + tr("FATAL: I/O error while writing to '%1'").arg(destinationPath));
2291 						success = false;
2292 					}
2293 				}
2294 				sourceFile.close();
2295 				destinationFile.close();
2296 				if ( success ) {
2297 					log(tr("backup") + ": " + tr("done (creating backup copy of '%1' as '%2')").arg(filePath).arg(destinationPath));
2298 					return true;
2299 				} else
2300 					return false;
2301 			} else {
2302 				log(tr("backup") + ": " + tr("FATAL: destination file '%1' cannot be opened for writing").arg(destinationPath));
2303 				sourceFile.close();
2304 				return false;
2305 			}
2306 		} else {
2307 			log(tr("backup") + ": " + tr("FATAL: backup folder '%1' isn't writable").arg(backupDir.absolutePath()));
2308 			return false;
2309 		}
2310 	} else {
2311 		log(tr("backup") + ": " + tr("FATAL: backup folder '%1' doesn't exist").arg(backupDir.absolutePath()));
2312 		return false;
2313 	}
2314 }
2315 
setMode(int mode)2316 void ROMAlyzer::setMode(int mode)
2317 {
2318 	switch ( mode ) {
2319 		case QMC2_ROMALYZER_MODE_SOFTWARE:
2320 			m_currentMode = QMC2_ROMALYZER_MODE_SOFTWARE;
2321 			checkBoxAutoScroll->setToolTip(tr("Automatically scroll to the currently analyzed software"));
2322 			tabWidgetAnalysis->setTabText(QMC2_ROMALYZER_PAGE_RCR, tr("Software collection rebuilder"));
2323 			setWindowTitle(tr("ROMAlyzer") + " [" + tr("software mode") + "]");
2324 			m_settingsKey = "SoftwareROMAlyzer";
2325 			lineEditSoftwareLists->setVisible(true);
2326 			toolButtonToolsMenu->setVisible(false);
2327 			checkBoxSelectMachine->setChecked(false);
2328 			checkBoxSelectMachine->setVisible(false);
2329 			break;
2330 		case QMC2_ROMALYZER_MODE_SYSTEM:
2331 		default:
2332 			m_currentMode = QMC2_ROMALYZER_MODE_SYSTEM;
2333 			checkBoxSelectMachine->setText(tr("Select machine"));
2334 			checkBoxSelectMachine->setToolTip(tr("Select machine in machine list if selected in analysis report?"));
2335 			checkBoxAutoScroll->setToolTip(tr("Automatically scroll to the currently analyzed machine"));
2336 			tabWidgetAnalysis->setTabText(QMC2_ROMALYZER_PAGE_RCR, tr("ROM collection rebuilder"));
2337 			setWindowTitle(tr("ROMAlyzer") + " [" + tr("system mode") + "]");
2338 			m_settingsKey = "ROMAlyzer";
2339 			lineEditSoftwareLists->setVisible(false);
2340 			toolButtonToolsMenu->setVisible(true);
2341 			checkBoxSelectMachine->setVisible(true);
2342 			break;
2343 	}
2344 }
2345 
on_tabWidgetAnalysis_currentChanged(int index)2346 void ROMAlyzer::on_tabWidgetAnalysis_currentChanged(int index)
2347 {
2348 	switch ( index ) {
2349 		case QMC2_ROMALYZER_PAGE_LOG:
2350 			textBrowserLog->horizontalScrollBar()->setValue(0);
2351 			break;
2352 		case QMC2_ROMALYZER_PAGE_CSF:
2353 			on_groupBoxCheckSumDatabase_toggled(groupBoxCheckSumDatabase->isChecked());
2354 			break;
2355 		case QMC2_ROMALYZER_PAGE_RCR:
2356 			switchToCollectionRebuilder();
2357 			break;
2358 		default:
2359 			break;
2360 	}
2361 }
2362 
on_treeWidgetChecksums_itemSelectionChanged()2363 void ROMAlyzer::on_treeWidgetChecksums_itemSelectionChanged()
2364 {
2365 	if ( checkBoxSelectMachine->isChecked() ) {
2366 		QList<QTreeWidgetItem *> items = treeWidgetChecksums->selectedItems();
2367 		if ( items.count() > 0 ) {
2368 			QTreeWidgetItem *item = items[0];
2369 			while ( (void*)item->parent() != (void *)treeWidgetChecksums && item->parent() != 0 )
2370 				item = item->parent();
2371 			QStringList words = item->text(QMC2_ROMALYZER_COLUMN_SET).split(" ", QString::SkipEmptyParts);
2372 			selectItem(words[0]);
2373 		}
2374 	}
2375 }
2376 
selectItem(QString gameName)2377 void ROMAlyzer::selectItem(QString gameName)
2378 {
2379 	switch ( qmc2MainWindow->stackedWidgetView->currentIndex() ) {
2380 		case QMC2_VIEWMACHINELIST_INDEX: {
2381 			      QTreeWidgetItem *gameItem = qmc2MachineListItemHash.value(gameName);
2382 			      if ( gameItem ) {
2383 				      qmc2MainWindow->treeWidgetMachineList->clearSelection();
2384 				      qmc2MainWindow->treeWidgetMachineList->setCurrentItem(gameItem);
2385 				      qmc2MainWindow->treeWidgetMachineList->scrollToItem(gameItem, qmc2CursorPositioningMode);
2386 				      gameItem->setSelected(true);
2387 			      }
2388 			      break;
2389 		      }
2390 		case QMC2_VIEWHIERARCHY_INDEX: {
2391 			       QTreeWidgetItem *hierarchyItem = qmc2HierarchyItemHash.value(gameName);
2392 			       if ( hierarchyItem ) {
2393 				       qmc2MainWindow->treeWidgetHierarchy->clearSelection();
2394 				       qmc2MainWindow->treeWidgetHierarchy->setCurrentItem(hierarchyItem);
2395 				       qmc2MainWindow->treeWidgetHierarchy->scrollToItem(hierarchyItem, qmc2CursorPositioningMode);
2396 				       hierarchyItem->setSelected(true);
2397 			       }
2398 			       break;
2399 		       }
2400 		case QMC2_VIEWCATEGORY_INDEX: {
2401 			      QTreeWidgetItem *categoryItem = qmc2CategoryItemHash.value(gameName);
2402 			      if ( categoryItem ) {
2403 				      qmc2MainWindow->treeWidgetCategoryView->clearSelection();
2404 				      qmc2MainWindow->treeWidgetCategoryView->setCurrentItem(categoryItem);
2405 				      qmc2MainWindow->treeWidgetCategoryView->scrollToItem(categoryItem, qmc2CursorPositioningMode);
2406 				      categoryItem->setSelected(true);
2407 			      }
2408 			      break;
2409 		      }
2410 		case QMC2_VIEWVERSION_INDEX: {
2411 			     QTreeWidgetItem *versionItem = qmc2VersionItemHash.value(gameName);
2412 			     if ( versionItem ) {
2413 				     qmc2MainWindow->treeWidgetVersionView->clearSelection();
2414 				     qmc2MainWindow->treeWidgetVersionView->setCurrentItem(versionItem);
2415 				     qmc2MainWindow->treeWidgetVersionView->scrollToItem(versionItem, qmc2CursorPositioningMode);
2416 				     versionItem->setSelected(true);
2417 			     }
2418 			     break;
2419 		     }
2420 	}
2421 }
2422 
humanReadable(quint64 value,int digits)2423 QString ROMAlyzer::humanReadable(quint64 value, int digits)
2424 {
2425 	static QString humanReadableString;
2426 	static qreal humanReadableValue;
2427 	QLocale locale;
2428 
2429 #if __WORDSIZE == 64
2430 	if ( (qreal)value / (qreal)QMC2_ONE_KILOBYTE < (qreal)QMC2_ONE_KILOBYTE ) {
2431 		humanReadableValue = (qreal)value / (qreal)QMC2_ONE_KILOBYTE;
2432 		humanReadableString = locale.toString(humanReadableValue, 'f', digits) + QString(tr(" KB"));
2433 	} else if ( (qreal)value / (qreal)QMC2_ONE_MEGABYTE < (qreal)QMC2_ONE_KILOBYTE ) {
2434 		humanReadableValue = (qreal)value / (qreal)QMC2_ONE_MEGABYTE;
2435 		humanReadableString = locale.toString(humanReadableValue, 'f', digits) + QString(tr(" MB"));
2436 	} else if ( (qreal)value / (qreal)QMC2_ONE_GIGABYTE < (qreal)QMC2_ONE_KILOBYTE ) {
2437 		humanReadableValue = (qreal)value / (qreal)QMC2_ONE_GIGABYTE;
2438 		humanReadableString = locale.toString(humanReadableValue, 'f', digits) + QString(tr(" GB"));
2439 	} else {
2440 		humanReadableValue = (qreal)value / (qreal)QMC2_ONE_TERABYTE;
2441 		humanReadableString = locale.toString(humanReadableValue, 'f', digits) + QString(tr(" TB"));
2442 	}
2443 #else
2444 	if ( (qreal)value / (qreal)QMC2_ONE_KILOBYTE < (qreal)QMC2_ONE_KILOBYTE ) {
2445 		humanReadableValue = (qreal)value / (qreal)QMC2_ONE_KILOBYTE;
2446 		humanReadableString = locale.toString(humanReadableValue, 'f', digits) + QString(tr(" KB"));
2447 	} else if ( (qreal)value / (qreal)QMC2_ONE_MEGABYTE < (qreal)QMC2_ONE_KILOBYTE ) {
2448 		humanReadableValue = (qreal)value / (qreal)QMC2_ONE_MEGABYTE;
2449 		humanReadableString = locale.toString(humanReadableValue, 'f', digits) + QString(tr(" MB"));
2450 	} else {
2451 		humanReadableValue = (qreal)value / (qreal)QMC2_ONE_GIGABYTE;
2452 		humanReadableString = locale.toString(humanReadableValue, 'f', digits) + QString(tr(" GB"));
2453 	}
2454 #endif
2455 
2456 	return humanReadableString;
2457 }
2458 
log(const QString & msg)2459 void ROMAlyzer::log(const QString &msg)
2460 {
2461 	QString message = msg;
2462 	message.prepend(QTime::currentTime().toString("hh:mm:ss.zzz") + ": ");
2463 	bool scrollBarMaximum = (textBrowserLog->verticalScrollBar()->value() == textBrowserLog->verticalScrollBar()->maximum());
2464 	textBrowserLog->appendPlainText(message);
2465 	if ( scrollBarMaximum ) {
2466 		textBrowserLog->update();
2467 		qApp->processEvents();
2468 		textBrowserLog->verticalScrollBar()->setValue(textBrowserLog->verticalScrollBar()->maximum());
2469 	}
2470 }
2471 
on_toolButtonBrowseBackupFolder_clicked()2472 void ROMAlyzer::on_toolButtonBrowseBackupFolder_clicked()
2473 {
2474 	QString s = QFileDialog::getExistingDirectory(this, tr("Choose backup folder"), lineEditBackupFolder->text(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks | (qmc2Options->useNativeFileDialogs() ? (QFileDialog::Options)0 : QFileDialog::DontUseNativeDialog));
2475 	if ( !s.isNull() )
2476 		lineEditBackupFolder->setText(s);
2477 	raise();
2478 }
2479 
on_toolButtonBrowseCHDManagerExecutableFile_clicked()2480 void ROMAlyzer::on_toolButtonBrowseCHDManagerExecutableFile_clicked()
2481 {
2482 	QString s = QFileDialog::getOpenFileName(this, tr("Choose CHD manager executable file"), lineEditCHDManagerExecutableFile->text(), tr("All files (*)"), 0, qmc2Options->useNativeFileDialogs() ? (QFileDialog::Options)0 : QFileDialog::DontUseNativeDialog);
2483 	if ( !s.isNull() )
2484 		lineEditCHDManagerExecutableFile->setText(s);
2485 	raise();
2486 }
2487 
on_toolButtonBrowseTemporaryWorkingDirectory_clicked()2488 void ROMAlyzer::on_toolButtonBrowseTemporaryWorkingDirectory_clicked()
2489 {
2490 	QString s = QFileDialog::getExistingDirectory(this, tr("Choose temporary working directory"), lineEditTemporaryWorkingDirectory->text(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks | (qmc2Options->useNativeFileDialogs() ? (QFileDialog::Options)0 : QFileDialog::DontUseNativeDialog));
2491 	if ( !s.isNull() )
2492 		lineEditTemporaryWorkingDirectory->setText(s);
2493 	raise();
2494 }
2495 
on_toolButtonBrowseSetRewriterOutputPath_clicked()2496 void ROMAlyzer::on_toolButtonBrowseSetRewriterOutputPath_clicked()
2497 {
2498 	QString s = QFileDialog::getExistingDirectory(this, tr("Choose output directory"), lineEditSetRewriterOutputPath->text(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks | (qmc2Options->useNativeFileDialogs() ? (QFileDialog::Options)0 : QFileDialog::DontUseNativeDialog));
2499 	if ( !s.isNull() )
2500 		lineEditSetRewriterOutputPath->setText(s);
2501 	raise();
2502 }
2503 
on_toolButtonBrowseSetRewriterAdditionalRomPath_clicked()2504 void ROMAlyzer::on_toolButtonBrowseSetRewriterAdditionalRomPath_clicked()
2505 {
2506 	QString s = QFileDialog::getExistingDirectory(this, tr("Choose additional ROM path"), lineEditSetRewriterAdditionalRomPath->text(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks | (qmc2Options->useNativeFileDialogs() ? (QFileDialog::Options)0 : QFileDialog::DontUseNativeDialog));
2507 	if ( !s.isNull() )
2508 		lineEditSetRewriterAdditionalRomPath->setText(s);
2509 	raise();
2510 }
2511 
chdManagerStarted()2512 void ROMAlyzer::chdManagerStarted()
2513 {
2514 	chdManagerRunning = true;
2515 	chdManagerCurrentHunk = 0;
2516 	log(tr("CHD manager: external process started"));
2517 }
2518 
chdManagerFinished(int exitCode,QProcess::ExitStatus exitStatus)2519 void ROMAlyzer::chdManagerFinished(int exitCode, QProcess::ExitStatus exitStatus)
2520 {
2521 	chdManagerRunning = false;
2522 	QString statusString = tr("unknown");
2523 	switch ( exitStatus ) {
2524 		case QProcess::NormalExit:
2525 			statusString = tr("normal");
2526 			break;
2527 		case QProcess::CrashExit:
2528 			statusString = tr("crashed");
2529 			break;
2530 	}
2531 	log(tr("CHD manager: external process finished (exit code = %1, exit status = %2)").arg(exitCode).arg(statusString));
2532 }
2533 
chdManagerReadyReadStandardOutput()2534 void ROMAlyzer::chdManagerReadyReadStandardOutput()
2535 {
2536 	QProcess *proc = (QProcess *)sender();
2537 	QString output = proc->readAllStandardOutput();
2538 	QStringList sl = output.split("\n");
2539 	foreach (QString s, sl) {
2540 		s = s.trimmed();
2541 		if ( !s.isEmpty() ) {
2542 			log(tr("CHD manager: stdout: %1").arg(s));
2543 			if ( s.contains("MD5 verification successful") )
2544 				chdManagerMD5Success = true;
2545 			if ( s.contains("SHA1 verification successful") )
2546 				chdManagerSHA1Success = true;
2547 		}
2548 	}
2549 }
2550 
chdManagerReadyReadStandardError()2551 void ROMAlyzer::chdManagerReadyReadStandardError()
2552 {
2553 	QProcess *proc = (QProcess *)sender();
2554 	QString output = proc->readAllStandardError();
2555 	QStringList sl = output.split("\n");
2556 	foreach (QString s, sl) {
2557 		s = s.trimmed();
2558 		if ( !s.isEmpty() ) {
2559 			log(tr("CHD manager: stderr: %1").arg(s));
2560 #if QMC2_CHD_CURRENT_VERSION >= 5
2561 			if ( s.contains(QRegExp(", \\d+\\.\\d+\\%\\ complete\\.\\.\\.")) ) {
2562 				QRegExp rx(", (\\d+)\\.(\\d+)\\%\\ complete\\.\\.\\.");
2563 				int pos = rx.indexIn(s);
2564 				if ( pos > -1 ) {
2565 					chdManagerCurrentHunk = rx.cap(1).toInt(); // 'current hunk' is misused as a percentage value, and 'total hunks' is thus set to 100 constantly
2566 					int decimal = rx.cap(2).toInt();
2567 					if ( decimal >= 5 ) chdManagerCurrentHunk += 1;
2568 				}
2569 			} else {
2570 				if ( s.contains("Compression complete ... final ratio =") )
2571 					chdManagerSHA1Success = true;
2572 			}
2573 #else
2574 			if ( s.contains(QRegExp("hunk \\d+/\\d+\\.\\.\\.")) ) {
2575 				QRegExp rx("(\\d+)/(\\d+)");
2576 				int pos = rx.indexIn(s);
2577 				if ( pos > -1 ) {
2578 					chdManagerCurrentHunk = rx.cap(1).toInt();
2579 					chdManagerTotalHunks = rx.cap(2).toInt();
2580 				}
2581 			} else {
2582 				if ( s.contains("Input MD5 verified") )
2583 					chdManagerMD5Success = true;
2584 				if ( s.contains("Input SHA-1 verified") )
2585 					chdManagerSHA1Success = true;
2586 			}
2587 #endif
2588 		}
2589 	}
2590 }
2591 
chdManagerError(QProcess::ProcessError processError)2592 void ROMAlyzer::chdManagerError(QProcess::ProcessError processError)
2593 {
2594 	switch ( processError ) {
2595 		case QProcess::FailedToStart:
2596 			log(tr("CHD manager: failed to start"));
2597 			break;
2598 
2599 		case QProcess::Crashed:
2600 			log(tr("CHD manager: crashed"));
2601 			break;
2602 
2603 		case QProcess::WriteError:
2604 			log(tr("CHD manager: write error"));
2605 			break;
2606 
2607 		case QProcess::ReadError:
2608 			log(tr("CHD manager: read error"));
2609 			break;
2610 
2611 		default:
2612 			log(tr("CHD manager: unknown error %1").arg(processError));
2613 			break;
2614 	}
2615 
2616 	chdManagerRunning = false;
2617 }
2618 
on_lineEditChecksumWizardHash_textChanged(const QString &)2619 void ROMAlyzer::on_lineEditChecksumWizardHash_textChanged(const QString &/*text*/)
2620 {
2621 	if ( groupBoxCheckSumDatabase->isChecked() )
2622 		QTimer::singleShot(0, this, SLOT(indicateCheckSumDbQueryStatusUnknown()));
2623 	m_checkSumTextChangedTimer.start(QMC2_SEARCH_DELAY);
2624 }
2625 
lineEditChecksumWizardHash_textChanged_delayed()2626 void ROMAlyzer::lineEditChecksumWizardHash_textChanged_delayed()
2627 {
2628 	m_checkSumTextChangedTimer.stop();
2629 	QString sha1, crc;
2630 	switch ( comboBoxChecksumWizardHashType->currentIndex() ) {
2631 		case QMC2_ROMALYZER_CSF_HASHTYPE_CRC:
2632 			crc = lineEditChecksumWizardHash->text();
2633 			if ( !groupBoxCheckSumDatabase->isChecked() )
2634 				currentFilesCrcChecksum = crc;
2635 			break;
2636 
2637 		default:
2638 		case QMC2_ROMALYZER_CSF_HASHTYPE_SHA1:
2639 			sha1 = lineEditChecksumWizardHash->text();
2640 			if ( !groupBoxCheckSumDatabase->isChecked() )
2641 				currentFilesSHA1Checksum = sha1;
2642 			break;
2643 	}
2644 	if ( groupBoxCheckSumDatabase->isChecked() ) {
2645 		if ( sha1.length() != 40 && crc.length() != 8 )
2646 			QTimer::singleShot(0, this, SLOT(indicateCheckSumDbQueryStatusUnknown()));
2647 		else {
2648 			if ( checkSumDb()->exists(sha1, crc, currentFilesSize) ) {
2649 				if ( crc.isEmpty() )
2650 					currentFilesCrcChecksum = checkSumDb()->getCrc(sha1);
2651 				else if ( sha1.isEmpty() )
2652 					currentFilesSHA1Checksum = checkSumDb()->getSha1(crc);
2653 				QTimer::singleShot(0, this, SLOT(indicateCheckSumDbQueryStatusGood()));
2654 			} else
2655 				QTimer::singleShot(0, this, SLOT(indicateCheckSumDbQueryStatusBad()));
2656 		}
2657 	}
2658 }
2659 
on_groupBoxSetRewriter_toggled(bool enable)2660 void ROMAlyzer::on_groupBoxSetRewriter_toggled(bool enable)
2661 {
2662 	bool doEnable = groupBoxCheckSumDatabase->isChecked() && enable && !checkSumScannerThread()->isActive;
2663 	tabWidgetAnalysis->setTabEnabled(QMC2_ROMALYZER_PAGE_RCR, doEnable);
2664 	labelCollectionRebuilderSpecific->setEnabled(doEnable);
2665 	lineCollectionRebuilderSpecific->setEnabled(doEnable);
2666 	checkBoxCollectionRebuilderHashCache->setEnabled(doEnable);
2667 	checkBoxCollectionRebuilderDryRun->setEnabled(doEnable);
2668 	labelCollectionRebuilderCHDHandling->setEnabled(doEnable);
2669 	comboBoxCollectionRebuilderCHDHandling->setEnabled(doEnable);
2670 	if ( mode() == QMC2_ROMALYZER_MODE_SYSTEM )
2671 		qmc2MainWindow->update_rebuildRomActions_visibility();
2672 	else if ( qmc2SoftwareList )
2673 		qmc2SoftwareList->updateRebuildSoftwareMenuVisibility();
2674 	if ( !enable ) {
2675 		if ( collectionRebuilder() ) {
2676 			delete collectionRebuilder();
2677 			m_collectionRebuilder = 0;
2678 		}
2679 	}
2680 }
2681 
on_groupBoxCheckSumDatabase_toggled(bool enable)2682 void ROMAlyzer::on_groupBoxCheckSumDatabase_toggled(bool enable)
2683 {
2684 	widgetCheckSumDbQueryStatus->setVisible(enable);
2685 	bool doEnable = true;
2686 	if ( checkSumScannerThread() )
2687 		doEnable = groupBoxSetRewriter->isChecked() && enable && !checkSumScannerThread()->isActive;
2688 	else
2689 		doEnable = groupBoxSetRewriter->isChecked() && enable;
2690 	tabWidgetAnalysis->setTabEnabled(QMC2_ROMALYZER_PAGE_RCR, doEnable);
2691 	labelCollectionRebuilderSpecific->setEnabled(doEnable);
2692 	lineCollectionRebuilderSpecific->setEnabled(doEnable);
2693 	checkBoxCollectionRebuilderHashCache->setEnabled(doEnable);
2694 	checkBoxCollectionRebuilderDryRun->setEnabled(doEnable);
2695 	labelCollectionRebuilderCHDHandling->setEnabled(doEnable);
2696 	comboBoxCollectionRebuilderCHDHandling->setEnabled(doEnable);
2697 	if ( mode() == QMC2_ROMALYZER_MODE_SYSTEM )
2698 		qmc2MainWindow->update_rebuildRomActions_visibility();
2699 	else if ( qmc2SoftwareList )
2700 		qmc2SoftwareList->updateRebuildSoftwareMenuVisibility();
2701 	if ( enable )
2702 		QTimer::singleShot(0, this, SLOT(lineEditChecksumWizardHash_textChanged_delayed()));
2703 	else {
2704 		if ( collectionRebuilder() ) {
2705 			delete collectionRebuilder();
2706 			m_collectionRebuilder = 0;
2707 		}
2708 	}
2709 }
2710 
on_comboBoxChecksumWizardHashType_currentIndexChanged(int index)2711 void ROMAlyzer::on_comboBoxChecksumWizardHashType_currentIndexChanged(int index)
2712 {
2713 	switch ( index ) {
2714 		case QMC2_ROMALYZER_CSF_HASHTYPE_CRC:
2715 			if ( !currentFilesCrcChecksum.isEmpty() && lineEditChecksumWizardHash->text().length() == 40 )
2716 				lineEditChecksumWizardHash->setText(currentFilesCrcChecksum);
2717 			else
2718 				lineEditChecksumWizardHash->clear();
2719 			break;
2720 
2721 		default:
2722 		case QMC2_ROMALYZER_CSF_HASHTYPE_SHA1:
2723 			if ( !currentFilesSHA1Checksum.isEmpty() && lineEditChecksumWizardHash->text().length() == 8 )
2724 				lineEditChecksumWizardHash->setText(currentFilesSHA1Checksum);
2725 			else
2726 				lineEditChecksumWizardHash->clear();
2727 			break;
2728 	}
2729 }
2730 
on_pushButtonChecksumWizardSearch_clicked()2731 void ROMAlyzer::on_pushButtonChecksumWizardSearch_clicked()
2732 {
2733 	qmc2LoadingInterrupted = false;
2734 
2735 	treeWidgetChecksumWizardSearchResult->clear();
2736 	QString searchedChecksum(lineEditChecksumWizardHash->text().toLower());
2737 	if ( searchedChecksum.isEmpty() )
2738 		return;
2739 
2740 	pushButtonChecksumWizardSearch->setEnabled(false);
2741 	lineEditChecksumWizardHash->setReadOnly(true);
2742 	pushButtonAnalyze->setEnabled(false);
2743 	toolButtonToolsMenu->setEnabled(false);
2744 	lineEditSoftwareLists->setEnabled(false);
2745 	lineEditSets->setEnabled(false);
2746 	labelStatus->setText(tr("Check-sum search"));
2747 
2748 	QString hashStartString;
2749 	int hashStartOffset;
2750 	switch ( comboBoxChecksumWizardHashType->currentIndex() ) {
2751 		case QMC2_ROMALYZER_CSF_HASHTYPE_CRC:
2752 			hashStartString = "crc=\"";
2753 			hashStartOffset = 5;
2754 			break;
2755 
2756 		default:
2757 		case QMC2_ROMALYZER_CSF_HASHTYPE_SHA1:
2758 			hashStartString = "sha1=\"";
2759 			hashStartOffset = 6;
2760 			break;
2761 	}
2762 
2763 	switch ( mode() ) {
2764 		case QMC2_ROMALYZER_MODE_SOFTWARE: {
2765 				QStringList uniqueSoftwareLists(qmc2MainWindow->swlDb->uniqueSoftwareLists());
2766 				progressBar->setRange(0, uniqueSoftwareLists.count());
2767 				int progressCount = 0, updateCount = 0;
2768 				foreach (QString list, uniqueSoftwareLists) {
2769 					foreach (QString set, qmc2MainWindow->swlDb->uniqueSoftwareSets(list)) {
2770 						QStringList xmlLines(qmc2MainWindow->swlDb->xml(list, set).split('\n', QString::SkipEmptyParts));
2771 						for (int i = 0; i < xmlLines.count(); i++) {
2772 							QString xmlLine(xmlLines.at(i));
2773 							int hashIndex = xmlLine.indexOf(hashStartString);
2774 							if ( hashIndex >= 0 ) {
2775 								int hashPos = hashIndex + hashStartOffset;
2776 								QString currentChecksum(xmlLine.mid(hashPos, xmlLine.indexOf('\"', hashPos) - hashPos).toLower());
2777 								if ( currentChecksum.compare(searchedChecksum) == 0 ) {
2778 									int fileNamePos;
2779 									QString fileType;
2780 									if ( xmlLine.startsWith("<disk name=\"") ) {
2781 										fileType = tr("CHD");
2782 										fileNamePos = xmlLine.indexOf("<disk name=\"") + 12;
2783 									} else {
2784 										fileType = tr("ROM");
2785 										fileNamePos = xmlLine.indexOf("<rom name=\"") + 11;
2786 									}
2787 									QString fileName(xmlLine.mid(fileNamePos, xmlLine.indexOf('\"', fileNamePos) - fileNamePos));
2788 									QTreeWidgetItem *item = new QTreeWidgetItem(treeWidgetChecksumWizardSearchResult);
2789 									item->setText(QMC2_ROMALYZER_CSF_COLUMN_ID, list + ":" + set);
2790 									item->setText(QMC2_ROMALYZER_CSF_COLUMN_FILENAME, fileName.replace("&amp;", "&").replace("&lt;", "<").replace("&gt;", ">").replace("&quot;", "\"").replace("&apos;", "'"));
2791 									item->setText(QMC2_ROMALYZER_CSF_COLUMN_TYPE, fileType);
2792 									item->setText(QMC2_ROMALYZER_CSF_COLUMN_STATUS, tr("unknown"));
2793 								}
2794 							}
2795 						}
2796 						if ( updateCount++ % QMC2_ROMALYZER_CKSUM_SEARCH_RESPONSE == 0 ) {
2797 							progressBar->setValue(progressCount);
2798 							qApp->processEvents();
2799 						}
2800 					}
2801 					progressBar->setValue(progressCount++);
2802 					qApp->processEvents();
2803 				}
2804 			}
2805 			break;
2806 		case QMC2_ROMALYZER_MODE_SYSTEM:
2807 		default:
2808 			progressBar->setRange(0, qmc2MainWindow->treeWidgetMachineList->topLevelItemCount());
2809 			for (int i = 0; i < qmc2MainWindow->treeWidgetMachineList->topLevelItemCount(); i++) {
2810 				if ( i % QMC2_ROMALYZER_CKSUM_SEARCH_RESPONSE == 0 ) {
2811 					progressBar->setValue(i);
2812 					qApp->processEvents();
2813 				}
2814 				QString currentMachine(qmc2MainWindow->treeWidgetMachineList->topLevelItem(i)->text(QMC2_MACHINELIST_COLUMN_NAME));
2815 				QStringList xmlLines(qmc2MachineList->xmlDb()->xml(currentMachine).split('\n', QString::SkipEmptyParts));
2816 				for (int j = 0; j < xmlLines.count(); j++) {
2817 					QString xmlLine(xmlLines.at(j));
2818 					int hashIndex = xmlLine.indexOf(hashStartString);
2819 					if ( hashIndex >= 0 ) {
2820 						int hashPos = hashIndex + hashStartOffset;
2821 						QString currentChecksum(xmlLine.mid(hashPos, xmlLine.indexOf('\"', hashPos) - hashPos).toLower());
2822 						if ( currentChecksum.compare(searchedChecksum) == 0 ) {
2823 							int fileNamePos;
2824 							QString fileType;
2825 							if ( xmlLine.startsWith("<disk name=\"") ) {
2826 								fileType = tr("CHD");
2827 								fileNamePos = xmlLine.indexOf("<disk name=\"") + 12;
2828 							} else {
2829 								fileType = tr("ROM");
2830 								fileNamePos = xmlLine.indexOf("<rom name=\"") + 11;
2831 							}
2832 							QString fileName(xmlLine.mid(fileNamePos, xmlLine.indexOf('\"', fileNamePos) - fileNamePos));
2833 							QTreeWidgetItem *item = new QTreeWidgetItem(treeWidgetChecksumWizardSearchResult);
2834 							item->setText(QMC2_ROMALYZER_CSF_COLUMN_ID, currentMachine);
2835 							item->setText(QMC2_ROMALYZER_CSF_COLUMN_FILENAME, fileName.replace("&amp;", "&").replace("&lt;", "<").replace("&gt;", ">").replace("&quot;", "\"").replace("&apos;", "'"));
2836 							item->setText(QMC2_ROMALYZER_CSF_COLUMN_TYPE, fileType);
2837 							item->setText(QMC2_ROMALYZER_CSF_COLUMN_STATUS, tr("unknown"));
2838 						}
2839 					}
2840 				}
2841 			}
2842 			break;
2843 	}
2844 
2845 	progressBar->reset();
2846 	labelStatus->setText(tr("Idle"));
2847 	pushButtonAnalyze->setEnabled(true);
2848 	toolButtonToolsMenu->setEnabled(true);
2849 	lineEditSoftwareLists->setEnabled(true);
2850 	lineEditSets->setEnabled(true);
2851 	pushButtonChecksumWizardSearch->setEnabled(true);
2852 	lineEditChecksumWizardHash->setReadOnly(false);
2853 	qApp->processEvents();
2854 
2855 	currentFilesSize = 0;
2856 }
2857 
runChecksumWizard()2858 void ROMAlyzer::runChecksumWizard()
2859 {
2860 	if ( !currentFilesSHA1Checksum.isEmpty() ) {
2861 		comboBoxChecksumWizardHashType->setCurrentIndex(QMC2_ROMALYZER_CSF_HASHTYPE_SHA1);
2862 		lineEditChecksumWizardHash->setText(currentFilesSHA1Checksum);
2863 		tabWidgetAnalysis->setCurrentWidget(tabChecksumWizard);
2864 		pushButtonChecksumWizardSearch->animateClick();
2865 	} else if ( !currentFilesCrcChecksum.isEmpty() ) {
2866 		comboBoxChecksumWizardHashType->setCurrentIndex(QMC2_ROMALYZER_CSF_HASHTYPE_CRC);
2867 		lineEditChecksumWizardHash->setText(currentFilesCrcChecksum);
2868 		tabWidgetAnalysis->setCurrentWidget(tabChecksumWizard);
2869 		pushButtonChecksumWizardSearch->animateClick();
2870 	}
2871 }
2872 
runSetRewriter()2873 void ROMAlyzer::runSetRewriter()
2874 {
2875 	// when called through the context menu and multiple sets are in the report, rerun the analyzer on the respective set (to make sure that pre-check data is current)
2876 	if ( setRewriterItem == 0 ) {
2877 		QList<QTreeWidgetItem *> il = treeWidgetChecksums->selectedItems();
2878 		if ( il.count() > 0 ) {
2879 			QTreeWidgetItem *item = il[0];
2880 			while ( item->parent() != 0 ) item = item->parent();
2881 			if ( item != 0 ) {
2882 				if ( treeWidgetChecksums->topLevelItemCount() > 1 || qmc2LoadingInterrupted ) {
2883 					groupBoxSetRewriter->setEnabled(false);
2884 					bool savedSRWA = checkBoxSetRewriterWhileAnalyzing->isChecked();
2885 					checkBoxSetRewriterWhileAnalyzing->setChecked(false);
2886 					QStringList setKeyTokens = item->text(QMC2_ROMALYZER_COLUMN_SET).split(" ", QString::SkipEmptyParts)[0].split(":", QString::SkipEmptyParts);
2887 					switch ( mode() ) {
2888 						case QMC2_ROMALYZER_MODE_SOFTWARE:
2889 							lineEditSoftwareLists->setText(setKeyTokens[0]);
2890 							lineEditSets->setText(setKeyTokens[1]);
2891 							break;
2892 						case QMC2_ROMALYZER_MODE_SYSTEM:
2893 						default:
2894 							lineEditSets->setText(setKeyTokens[0]);
2895 							break;
2896 					}
2897 					qmc2LoadingInterrupted = false;
2898 					analyze();
2899 					checkBoxSetRewriterWhileAnalyzing->setChecked(savedSRWA);
2900 					groupBoxSetRewriter->setEnabled(true);
2901 					setRewriterItem = treeWidgetChecksums->topLevelItem(0);
2902 					if ( setRewriterItem == 0 ) return;
2903 				} else
2904 					setRewriterItem = item;
2905 			} else
2906 				return;
2907 		} else
2908 			return;
2909 	}
2910 
2911 	// check output path and write permission
2912 	QString outPath = lineEditSetRewriterOutputPath->text();
2913 	if ( !outPath.isEmpty() ) {
2914 		QDir dir(outPath);
2915 		if ( dir.exists() ) {
2916 			QFileInfo dirInfo(outPath);
2917 			if ( !dirInfo.isDir() ) {
2918 				log(tr("set rewriter: WARNING: can't rewrite set '%1', output path is not a directory").arg(setRewriterSetName));
2919 				return;
2920 			}
2921 			if ( !dirInfo.isWritable() ) {
2922 				log(tr("set rewriter: WARNING: can't rewrite set '%1', output path is not writable").arg(setRewriterSetName));
2923 				return;
2924 			}
2925 		} else {
2926 			log(tr("set rewriter: WARNING: can't rewrite set '%1', output path does not exist").arg(setRewriterSetName));
2927 			return;
2928 		}
2929 	} else {
2930 		log(tr("set rewriter: WARNING: can't rewrite set '%1', output path is empty").arg(setRewriterSetName));
2931 		return;
2932 	}
2933 
2934 	QString setName, listName;
2935 	QStringList setKeyTokens;
2936 	switch ( mode() ) {
2937 		case QMC2_ROMALYZER_MODE_SOFTWARE:
2938 			setKeyTokens = setRewriterSetName.split(":", QString::SkipEmptyParts);
2939 			if ( setKeyTokens.count() > 1 ) {
2940 				listName = setKeyTokens[0];
2941 				setName = setKeyTokens[1];
2942 				outPath += "/" + listName;
2943 				bool success = true;
2944 				QDir d(QDir::cleanPath(outPath));
2945 				if ( !d.exists() )
2946 					success = d.mkdir(QDir::cleanPath(outPath));
2947 				if ( !success ) {
2948 					log(tr("set rewriter: WARNING: can't rewrite set '%1', output path is not writable").arg(setRewriterSetName));
2949 					return;
2950 				}
2951 			}
2952 			break;
2953 		case QMC2_ROMALYZER_MODE_SYSTEM:
2954 		default:
2955 			setName = setRewriterSetName;
2956 			break;
2957 	}
2958 
2959 	if ( !outPath.endsWith("/") )
2960 		outPath += "/";
2961 
2962 	if ( comboBoxSetRewriterReproductionType->currentIndex() < QMC2_ROMALYZER_RT_FOLDERS )
2963 		outPath += setName + ".zip";
2964 	else
2965 		outPath += setName;
2966 
2967 	QLocale locale;
2968 
2969 	QString savedStatusText = labelStatus->text();
2970 	labelStatus->setText(tr("Reading '%1' - %2").arg(setRewriterSetName).arg(locale.toString(setRewriterSetCount)));
2971 	progressBar->reset();
2972 	progressBar->setFormat(QString("%1 / %2").arg(0).arg(setRewriterFileMap.count()));
2973 	qApp->processEvents();
2974 	QString modeString = tr("space-efficient");
2975 	if ( checkBoxSetRewriterSelfContainedSets->isChecked() )
2976 		modeString = tr("self-contained");
2977 
2978 	log(tr("set rewriter: rewriting %1 set '%2' to '%3'").arg(modeString).arg(setRewriterSetName).arg(outPath));
2979 
2980 	bool loadOkay = true;
2981 	bool ignoreErrors = !checkBoxSetRewriterAbortOnError->isChecked();
2982 	QMapIterator<QString, QStringList> it(setRewriterFileMap);
2983 	QMap<QString, QByteArray> outputDataMap;
2984 	int count = 0;
2985 	QStringList uniqueCRCs;
2986 	while ( it.hasNext() && loadOkay ) {
2987 		progressBar->setValue(++count);
2988 		progressBar->setFormat(QString("%1 / %2").arg(count).arg(setRewriterFileMap.count()));
2989 		it.next();
2990 
2991 		QString fileCRC = it.key();
2992 		QString fileName = it.value()[0];
2993 		QString filePath = it.value()[1];
2994 		QString outputFileName = it.value()[2];
2995 		bool fromZip = (it.value()[3] == "zip");
2996 		bool fromSevenZip = (it.value()[3] == "7z");
2997 
2998 		if ( checkBoxSetRewriterUniqueCRCs->isChecked() ) {
2999 			if ( uniqueCRCs.contains(fileCRC) ) {
3000 				log(tr("set rewriter: skipping '%1' with CRC '%2' from '%3' as '%4'").arg(fileName).arg(fileCRC).arg(filePath).arg(outputFileName));
3001 				continue;
3002 			}
3003 		}
3004 
3005 		log(tr("set rewriter: loading '%1' with CRC '%2' from '%3' as '%4'").arg(fileName).arg(fileCRC).arg(filePath).arg(outputFileName));
3006 
3007 		QByteArray fileData;
3008 		if ( fromZip ) {
3009 			if ( readZipFileData(filePath, fileCRC, fileName, &fileData) ) {
3010 				outputDataMap[outputFileName] = fileData;
3011 				uniqueCRCs << fileCRC;
3012 			} else {
3013 				if ( checkBoxSetRewriterGoodDumpsOnly->isChecked() ) {
3014 					log(tr("set rewriter: FATAL: can't load '%1' with CRC '%2' from '%3', aborting").arg(fileName).arg(fileCRC).arg(filePath));
3015 					loadOkay = ignoreErrors ? true : false;
3016 				} else
3017 					log(tr("set rewriter: WARNING: can't load '%1' with CRC '%2' from '%3', ignoring this file").arg(fileName).arg(fileCRC).arg(filePath));
3018 			}
3019 		} else if ( fromSevenZip ) {
3020 			if ( readSevenZipFileData(filePath, fileCRC, fileName, &fileData) ) {
3021 				outputDataMap[outputFileName] = fileData;
3022 				uniqueCRCs << fileCRC;
3023 			} else {
3024 				if ( checkBoxSetRewriterGoodDumpsOnly->isChecked() ) {
3025 					log(tr("set rewriter: FATAL: can't load '%1' with CRC '%2' from '%3', aborting").arg(fileName).arg(fileCRC).arg(filePath));
3026 					loadOkay = ignoreErrors ? true : false;
3027 				} else
3028 					log(tr("set rewriter: WARNING: can't load '%1' with CRC '%2' from '%3', ignoring this file").arg(fileName).arg(fileCRC).arg(filePath));
3029 			}
3030 		} else {
3031 			if ( readFileData(filePath, fileCRC, &fileData) ) {
3032 				outputDataMap[outputFileName] = fileData;
3033 				uniqueCRCs << fileCRC;
3034 			} else {
3035 				if ( checkBoxSetRewriterGoodDumpsOnly->isChecked() ) {
3036 					log(tr("set rewriter: FATAL: can't load '%1' with CRC '%2', aborting").arg(filePath).arg(fileCRC));
3037 					loadOkay = ignoreErrors ? true : false;
3038 				} else
3039 					log(tr("set rewriter: WARNING: can't load '%1' with CRC '%2', ignoring this file").arg(filePath).arg(fileCRC));
3040 			}
3041 		}
3042 	}
3043 	progressBar->reset();
3044 	progressBar->setFormat(QString());
3045 
3046 	if ( loadOkay ) {
3047 		if ( !checkBoxSetRewriterSelfContainedSets->isChecked() ) {
3048 			// remove redundant files (if applicable)
3049 			bool hasValidParent = !setRewriterItem->text(QMC2_ROMALYZER_COLUMN_MERGE).isEmpty();
3050 			for (int i = 0; i < setRewriterItem->childCount(); i++) {
3051 				QTreeWidgetItem *childItem = setRewriterItem->child(i);
3052 				if ( !childItem->text(QMC2_ROMALYZER_COLUMN_MERGE).isEmpty() ) {
3053 					if ( !childItem->text(QMC2_ROMALYZER_COLUMN_CRC).isEmpty() ) {
3054 						QList<QStringList> fileEntries = setRewriterFileMap.values(childItem->text(QMC2_ROMALYZER_COLUMN_CRC));
3055 						foreach (QStringList fileEntry, fileEntries) {
3056 							if ( fileEntry.count() == 4 ) { // valid entry?
3057 								QString localName = fileEntry[2];
3058 								if ( outputDataMap.contains(localName) && hasValidParent ) {
3059 									log(tr("set rewriter: removing redundant file '%1' with CRC '%2' from output data").arg(localName).arg(childItem->text(QMC2_ROMALYZER_COLUMN_CRC)));
3060 									outputDataMap.remove(localName);
3061 								}
3062 							}
3063 						}
3064 					}
3065 				}
3066 			}
3067 		}
3068 		if ( !outputDataMap.isEmpty() ) {
3069 			log(tr("set rewriter: writing new %1 set '%2' in '%3'").arg(modeString).arg(setRewriterSetName).arg(outPath));
3070 			labelStatus->setText(tr("Writing '%1' - %2").arg(setRewriterSetName).arg(locale.toString(setRewriterSetCount)));
3071 			switch ( comboBoxSetRewriterReproductionType->currentIndex() ) {
3072 				case QMC2_ROMALYZER_RT_ZIP_BUILTIN:
3073 					if ( writeAllZipData(outPath, &outputDataMap, true, progressBar) )
3074 						log(tr("set rewriter: new %1 set '%2' in '%3' successfully created").arg(modeString).arg(setRewriterSetName).arg(outPath));
3075 					else {
3076 						log(tr("set rewriter: FATAL: failed to create new %1 set '%2' in '%3'").arg(modeString).arg(setRewriterSetName).arg(outPath));
3077 						loadOkay = ignoreErrors ? true : false;
3078 					}
3079 					break;
3080 #if defined(QMC2_LIBARCHIVE_ENABLED)
3081 				case QMC2_ROMALYZER_RT_ZIP_LIBARCHIVE:
3082 					if ( writeAllArchiveData(outPath, &outputDataMap, true, progressBar) )
3083 						log(tr("set rewriter: new %1 set '%2' in '%3' successfully created").arg(modeString).arg(setRewriterSetName).arg(outPath));
3084 					else {
3085 						log(tr("set rewriter: FATAL: failed to create new %1 set '%2' in '%3'").arg(modeString).arg(setRewriterSetName).arg(outPath));
3086 						loadOkay = ignoreErrors ? true : false;
3087 					}
3088 					break;
3089 #endif
3090 				case QMC2_ROMALYZER_RT_FOLDERS:
3091 					if ( writeAllFileData(outPath, &outputDataMap, true, progressBar) ) {
3092 						log(tr("set rewriter: new %1 set '%2' in '%3' successfully created").arg(modeString).arg(setRewriterSetName).arg(outPath));
3093 					} else {
3094 						log(tr("set rewriter: FATAL: failed to create new %1 set '%2' in '%3'").arg(modeString).arg(setRewriterSetName).arg(outPath));
3095 						loadOkay = ignoreErrors ? true : false;
3096 					}
3097 					break;
3098 			}
3099 		} else {
3100 			log(tr("set rewriter: INFORMATION: no output data available, thus not rewriting set '%1' to '%2'").arg(setRewriterSetName).arg(outPath));
3101 			setRewriterItem->setIcon(QMC2_ROMALYZER_COLUMN_SET, QIcon(QIcon(QString::fromUtf8(":/data/img/filesaveas.png")).pixmap(QSize(64, 64), QIcon::Disabled)));
3102 			loadOkay = false;
3103 		}
3104 	}
3105 
3106 	if ( loadOkay ) {
3107 		log(tr("set rewriter: done (rewriting %1 set '%2' to '%3')").arg(modeString).arg(setRewriterSetName).arg(outPath));
3108 		setRewriterItem->setIcon(QMC2_ROMALYZER_COLUMN_SET, QIcon(QString::fromUtf8(":/data/img/filesaveas.png")));
3109 	}
3110 
3111 	labelStatus->setText(savedStatusText);
3112 	progressBar->setRange(0, 100);
3113 	progressBar->setValue(0);
3114 	progressBar->reset();
3115 	progressBar->setFormat(QString());
3116 	qApp->processEvents();
3117 }
3118 
analyzeDeviceRefs()3119 void ROMAlyzer::analyzeDeviceRefs()
3120 {
3121 	QList<QTreeWidgetItem *> il = treeWidgetChecksums->selectedItems();
3122 	if ( !il.isEmpty() ) {
3123 		QStringList deviceRefs = il[0]->whatsThis(QMC2_ROMALYZER_COLUMN_SET).split(",", QString::SkipEmptyParts);
3124 		deviceRefs.removeDuplicates();
3125 		if ( !deviceRefs.isEmpty() ) {
3126 			lineEditSets->setText(deviceRefs.join(" "));
3127 			QTimer::singleShot(0, this, SLOT(analyze()));
3128 		}
3129 	}
3130 }
3131 
importFromDataFile()3132 void ROMAlyzer::importFromDataFile()
3133 {
3134 	QString storedPath;
3135 	if ( qmc2Config->contains(QMC2_FRONTEND_PREFIX + m_settingsKey + "/LastDataFilePath") )
3136 		storedPath = qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/LastDataFilePath").toString();
3137 	QString dataFilePath = QFileDialog::getOpenFileName(this, tr("Choose data file to import from"), storedPath, tr("Data files (*.dat)") + ";;" + tr("All files (*)"), 0, qmc2Options->useNativeFileDialogs() ? (QFileDialog::Options)0 : QFileDialog::DontUseNativeDialog);
3138 	if ( !dataFilePath.isNull() ) {
3139 		QStringList nameList;
3140 		QFile dataFile(dataFilePath);
3141 		if ( dataFile.open(QIODevice::ReadOnly | QIODevice::Text) ) {
3142 			setActive(true);
3143 			QTextStream ts(&dataFile);
3144 			QString pattern1 = "<machine name=\"";
3145 			QString pattern2 = "<game name=\"";
3146 			while ( !ts.atEnd() ) {
3147 				QString line = ts.readLine().trimmed();
3148 				if ( line.startsWith(pattern1) )
3149 					nameList << line.mid(pattern1.length(), line.indexOf("\"", pattern1.length()) - pattern1.length());
3150 				else if ( line.startsWith(pattern2) )
3151 					nameList << line.mid(pattern2.length(), line.indexOf("\"", pattern2.length()) - pattern2.length());
3152 			}
3153 			dataFile.close();
3154 			if ( !nameList.isEmpty() )
3155 				lineEditSets->setText(nameList.join(" "));
3156 			setActive(false);
3157 		} else
3158 			qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("FATAL: can't open data file '%1' for reading").arg(dataFilePath));
3159 		qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/LastDataFilePath", dataFilePath);
3160 	}
3161 }
3162 
exportToDataFile()3163 void ROMAlyzer::exportToDataFile()
3164 {
3165 	QString storedPath;
3166 	if ( qmc2Config->contains(QMC2_FRONTEND_PREFIX + m_settingsKey + "/LastDataFilePath") )
3167 		storedPath = qmc2Config->value(QMC2_FRONTEND_PREFIX + m_settingsKey + "/LastDataFilePath").toString();
3168 	QString dataFilePath = QFileDialog::getSaveFileName(this, tr("Choose data file to export to"), storedPath, tr("Data files (*.dat)") + ";;" + tr("All files (*)"), 0, qmc2Options->useNativeFileDialogs() ? (QFileDialog::Options)0 : QFileDialog::DontUseNativeDialog);
3169 	if ( !dataFilePath.isNull() ) {
3170 		QFile dataFile(dataFilePath);
3171 		QFileInfo fi(dataFilePath);
3172 		if ( dataFile.open(QIODevice::WriteOnly | QIODevice::Text) ) {
3173 			setActive(true);
3174 			progressBar->setRange(0, qmc2MainWindow->treeWidgetMachineList->topLevelItemCount());
3175 			labelStatus->setText(tr("Exporting"));
3176 			QTextStream ts(&dataFile);
3177 			ts << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
3178 			ts << "<!DOCTYPE datafile PUBLIC \"-//Logiqx//DTD ROM Management Datafile//EN\" \"http://www.logiqx.com/Dats/datafile.dtd\">\n\n";
3179 			ts << "<datafile>\n";
3180 			ts << "\t<header>\n";
3181 			ts << "\t\t<name>" << fi.completeBaseName() << "</name>\n";
3182 			ts << "\t\t<description>" << fi.completeBaseName() << "</description>\n";
3183 			ts << "\t\t<category>FIXDATFILE</category>\n";
3184 			ts << "\t\t<version>" << QDateTime::currentDateTime().toString("MM/dd/yy hh:mm:ss") << "</version>\n";
3185 			ts << "\t\t<date>" << QDateTime::currentDateTime().toString("yyyy-MM-dd") << "</date>\n";
3186 			ts << "\t\t<author>auto-create</author>\n";
3187 			ts << "\t\t<email></email>\n";
3188 			ts << "\t\t<homepage></homepage>\n";
3189 			ts << "\t\t<url></url>\n";
3190 			ts << "\t\t<comment>" << tr("Created by QMC2 v%1").arg(XSTR(QMC2_VERSION)) << "</comment>\n";
3191 			ts << "\t</header>\n";
3192 			QString mainEntityName = "machine";
3193 			for (int i = 0; i < treeWidgetChecksums->topLevelItemCount(); i++) {
3194 				if ( i % QMC2_ROMALYZER_EXPORT_RESPONSE ) {
3195 					progressBar->setValue(i);
3196 					qApp->processEvents();
3197 				}
3198 				QTreeWidgetItem *item = treeWidgetChecksums->topLevelItem(i);
3199 				QString name = item->text(QMC2_ROMALYZER_COLUMN_SET).split(" ", QString::SkipEmptyParts)[0];
3200 				if ( analyzerBadSets.contains(name) ) {
3201 					QString sourcefile, isbios, cloneof, romof, sampleof;
3202 					QByteArray xmlDocument(ROMAlyzer::getXmlData(name, true).toUtf8());
3203 					QBuffer xmlQueryBuffer(&xmlDocument);
3204 					xmlQueryBuffer.open(QIODevice::ReadOnly);
3205 					QXmlQuery xmlQuery(QXmlQuery::XQuery10);
3206 					xmlQuery.bindVariable("xmlDocument", &xmlQueryBuffer);
3207 					xmlQuery.setQuery(QString("doc($xmlDocument)//%1/@sourcefile/string()").arg(mainEntityName));
3208 					xmlQuery.evaluateTo(&sourcefile);
3209 					sourcefile = sourcefile.trimmed();
3210 					xmlQuery.setQuery(QString("doc($xmlDocument)//%1/@isbios/string()").arg(mainEntityName));
3211 					xmlQuery.evaluateTo(&isbios);
3212 					isbios = isbios.trimmed();
3213 					xmlQuery.setQuery(QString("doc($xmlDocument)//%1/@cloneof/string()").arg(mainEntityName));
3214 					xmlQuery.evaluateTo(&cloneof);
3215 					cloneof = cloneof.trimmed();
3216 					xmlQuery.setQuery(QString("doc($xmlDocument)//%1/@romof/string()").arg(mainEntityName));
3217 					xmlQuery.evaluateTo(&romof);
3218 					romof = romof.trimmed();
3219 					xmlQuery.setQuery(QString("doc($xmlDocument)//%1/@sampleof/string()").arg(mainEntityName));
3220 					xmlQuery.evaluateTo(&sampleof);
3221 					sampleof = sampleof.trimmed();
3222 					ts << "\t<machine name=\"" << name << "\"";
3223 					if ( !sourcefile.isEmpty() )
3224 						ts << " sourcefile=\"" << sourcefile << "\"";
3225 					if ( !isbios.isEmpty() && isbios != "no" )
3226 						ts << " isbios=\"" << isbios << "\"";
3227 					if ( !cloneof.isEmpty() )
3228 						ts << " cloneof=\"" << cloneof << "\"";
3229 					if ( !romof.isEmpty() )
3230 						ts << " romof=\"" << romof << "\"";
3231 					if ( !sampleof.isEmpty() )
3232 						ts << " sampleof=\"" << sampleof << "\"";
3233 					ts << ">\n";
3234 					QString description, year, manufacturer;
3235 					xmlQuery.setQuery(QString("doc($xmlDocument)//%1/description/string()").arg(mainEntityName));
3236 					xmlQuery.evaluateTo(&description);
3237 					description = description.trimmed();
3238 					ts << "\t\t<description>" << description << "</description>\n";
3239 					xmlQuery.setQuery(QString("doc($xmlDocument)//%1/year/string()").arg(mainEntityName));
3240 					xmlQuery.evaluateTo(&year);
3241 					year = year.trimmed();
3242 					ts << "\t\t<year>" << year << "</year>\n";
3243 					xmlQuery.setQuery(QString("doc($xmlDocument)//%1/manufacturer/string()").arg(mainEntityName));
3244 					xmlQuery.evaluateTo(&manufacturer);
3245 					manufacturer = manufacturer.trimmed();
3246 					ts << "\t\t<manufacturer>" << manufacturer << "</manufacturer>\n";
3247 					for (int j = 0; j < item->childCount(); j++) {
3248 						QTreeWidgetItem *childItem = item->child(j);
3249 						QString filestatus = childItem->text(QMC2_ROMALYZER_COLUMN_FILESTATUS);
3250 						if ( filestatus == tr("not found") || (filestatus.toUpper() != filestatus && filestatus != tr("no dump")) ) {
3251 							QString type = childItem->text(QMC2_ROMALYZER_COLUMN_TYPE);
3252 							QString filename = childItem->text(QMC2_ROMALYZER_COLUMN_SET);
3253 							QString size = childItem->text(QMC2_ROMALYZER_COLUMN_SIZE);
3254 							QString crc = childItem->text(QMC2_ROMALYZER_COLUMN_CRC);
3255 							QString sha1 = childItem->text(QMC2_ROMALYZER_COLUMN_SHA1);
3256 							QString merge = childItem->text(QMC2_ROMALYZER_COLUMN_MERGE);
3257 							if ( type == "ROM" ) {
3258 								ts << "\t\t<rom name=\"" << filename << "\"";
3259 								if ( !merge.isEmpty() )
3260 									ts << " merge=\"" << merge << "\"";
3261 							       	if ( !size.isEmpty() )
3262 									ts << " size=\"" << size << "\"";
3263 								if ( !crc.isEmpty() )
3264 									ts << " crc=\"" << crc << "\"";
3265 								if ( !sha1.isEmpty() )
3266 									ts << " sha1=\"" << sha1 << "\"";
3267 								ts << "/>\n";
3268 							} else {
3269 								ts << "\t\t<disk name=\"" << filename << "\"";
3270 								if ( !merge.isEmpty() )
3271 									ts << " merge=\"" << merge << "\"";
3272 								if ( !sha1.isEmpty() )
3273 									ts << " sha1=\"" << sha1 << "\"";
3274 								ts << "/>\n";
3275 							}
3276 						}
3277 					}
3278 					ts << "\t</machine>\n";
3279 				}
3280 			}
3281 			ts << "</datafile>\n";
3282 			dataFile.close();
3283 			progressBar->reset();
3284 			labelStatus->setText(tr("Idle"));
3285 			setActive(false);
3286 		} else
3287 			qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("FATAL: can't open data file '%1' for writing").arg(dataFilePath));
3288 		qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/LastDataFilePath", dataFilePath);
3289 	}
3290 }
3291 
copyToClipboard(bool onlyBadOrMissing)3292 void ROMAlyzer::copyToClipboard(bool onlyBadOrMissing)
3293 {
3294 	static QStringList excludedColumnsBadOrMissing = QStringList() << tr("Merge") << tr("Emu status") << tr("File status");
3295 
3296 	QList<QTreeWidgetItem *> il = treeWidgetChecksums->selectedItems();
3297 	if ( !il.isEmpty() ) {
3298 		QHeaderView *header = treeWidgetChecksums->header();
3299 		QTreeWidgetItem *headerItem = treeWidgetChecksums->headerItem();
3300 		QTreeWidgetItem *item = il[0];
3301 		QStringList columnTitles;
3302 		QList<int> columnWidths;
3303 		QList<QStringList> rows;
3304 		QStringList firstRow;
3305 		for (int i = 0; i < treeWidgetChecksums->columnCount(); i++) {
3306 			if ( !treeWidgetChecksums->isColumnHidden(header->logicalIndex(i)) ) {
3307 				QString h = headerItem->text(header->logicalIndex(i));
3308 				if ( onlyBadOrMissing )
3309 					if ( excludedColumnsBadOrMissing.contains(h) )
3310 						continue;
3311 				columnTitles << h;
3312 				QString t = item->text(header->logicalIndex(i));
3313 				if ( i == 0 )
3314 					if ( onlyBadOrMissing )
3315 						t = t.split(" ", QString::SkipEmptyParts)[0];
3316 				firstRow << t;
3317 				columnWidths.append(QMC2_MAX(t.length(), h.length()));
3318 			}
3319 		}
3320 		rows.append(firstRow);
3321 		for (int i = 0; i < item->childCount(); i++) {
3322 			QTreeWidgetItem *childItem = item->child(i);
3323 			if ( onlyBadOrMissing ) {
3324 				QString filestatus = childItem->text(QMC2_ROMALYZER_COLUMN_FILESTATUS);
3325 				if ( !(filestatus == tr("not found") || (filestatus.toUpper() != filestatus && filestatus != tr("no dump"))) )
3326 					continue;
3327 			}
3328 			QStringList row;
3329 			int columnCount = 0;
3330 			for (int j = 0; j < treeWidgetChecksums->columnCount(); j++) {
3331 				if ( !treeWidgetChecksums->isColumnHidden(header->logicalIndex(j)) ) {
3332 					if ( onlyBadOrMissing ) {
3333 						if ( excludedColumnsBadOrMissing.contains(headerItem->text(header->logicalIndex(j))) )
3334 							continue;
3335 					}
3336 					QString t = childItem->text(header->logicalIndex(j));
3337 					if ( j == 0 )
3338 						t.prepend("\\ ");
3339 					row << t;
3340 					if ( columnWidths[columnCount] < t.length() )
3341 						columnWidths[columnCount] = t.length();
3342 				}
3343 				columnCount++;
3344 			}
3345 			rows.append(row);
3346 		}
3347 
3348 		QString cbText, cbLine;
3349 		QRegExp removeTrailingSpacesRx("\\s+$");
3350 		for (int i = 0; i < columnTitles.count(); i++) {
3351 			if ( i == columnTitles.count() - 1 )
3352 				cbLine += columnTitles[i].leftJustified(columnWidths[i], ' ');
3353 			else
3354 				cbLine += columnTitles[i].leftJustified(columnWidths[i] + 2, ' ');
3355 		}
3356 		cbLine.replace(removeTrailingSpacesRx, QString());
3357 		cbText += cbLine + "\n";
3358 		cbLine.clear();
3359 		for (int i = 0; i < columnTitles.count(); i++) {
3360 			if ( i == columnTitles.count() - 1 )
3361 				cbLine += QString().fill('-', columnWidths[i]);
3362 			else
3363 				cbLine += QString().fill('-', columnWidths[i]) + "  ";
3364 		}
3365 		cbLine.replace(removeTrailingSpacesRx, QString());
3366 		cbText += cbLine + "\n";
3367 		foreach (QStringList row, rows) {
3368 			cbLine.clear();
3369 			for (int i = 0; i < row.count(); i++) {
3370 				if ( i == row.count() - 1 )
3371 					cbLine += row[i].leftJustified(columnWidths[i], ' ');
3372 				else
3373 					cbLine += row[i].leftJustified(columnWidths[i] + 2, ' ');
3374 			}
3375 			cbLine.replace(removeTrailingSpacesRx, QString());
3376 			cbText += cbLine + "\n";
3377 		}
3378 
3379 		qApp->clipboard()->setText(cbText);
3380 	}
3381 }
3382 
copyBadToClipboard()3383 void ROMAlyzer::copyBadToClipboard()
3384 {
3385 	copyToClipboard(true);
3386 }
3387 
on_treeWidgetChecksumWizardSearchResult_itemSelectionChanged()3388 void ROMAlyzer::on_treeWidgetChecksumWizardSearchResult_itemSelectionChanged()
3389 {
3390 	QList<QTreeWidgetItem *> il = treeWidgetChecksumWizardSearchResult->selectedItems();
3391 	pushButtonChecksumWizardAnalyzeSelectedSets->setEnabled(!il.isEmpty());
3392 	wizardSelectedSets.clear();
3393 	int selectedGoodSets = 0;
3394 	int selectedBadSets = 0;
3395 	foreach (QTreeWidgetItem *item, il) {
3396 		wizardSelectedSets << item->text(QMC2_ROMALYZER_CSF_COLUMN_ID);
3397 		if ( item->text(QMC2_ROMALYZER_CSF_COLUMN_STATUS) == tr("good") )
3398 			selectedGoodSets++;
3399 		if ( item->text(QMC2_ROMALYZER_CSF_COLUMN_STATUS) == tr("bad") ) {
3400 			if ( !item->icon(QMC2_ROMALYZER_CSF_COLUMN_STATUS).isNull() )
3401 				selectedGoodSets++;
3402 			selectedBadSets++;
3403 		}
3404 	}
3405 	wizardSelectedSets.removeDuplicates();
3406 }
3407 
on_pushButtonChecksumWizardAnalyzeSelectedSets_clicked()3408 void ROMAlyzer::on_pushButtonChecksumWizardAnalyzeSelectedSets_clicked()
3409 {
3410 	switch ( mode() ) {
3411 		case QMC2_ROMALYZER_MODE_SOFTWARE: {
3412 				QStringList lists, sets;
3413 				foreach (QString setKey, wizardSelectedSets) {
3414 					QStringList setKeyTokens = setKey.split(":", QString::SkipEmptyParts);
3415 					if ( setKeyTokens.count() < 2 )
3416 						continue;
3417 					lists << setKeyTokens[0];
3418 					sets << setKeyTokens[1];
3419 				}
3420 				lineEditSoftwareLists->setText(lists.join(" "));
3421 				lineEditSets->setText(sets.join(" "));
3422 			}
3423 			break;
3424 		case QMC2_ROMALYZER_MODE_SYSTEM:
3425 		default:
3426 			lineEditSets->setText(wizardSelectedSets.join(" "));
3427 			break;
3428 	}
3429 	wizardSearch = true;
3430 	pushButtonAnalyze->animateClick();
3431 }
3432 
3433 // reads the file 'fileName' with the expected CRC 'crc' and returns its data in 'data'
readFileData(QString fileName,QString crc,QByteArray * data)3434 bool ROMAlyzer::readFileData(QString fileName, QString crc, QByteArray *data)
3435 {
3436 	QFile romFile(fileName);
3437 	data->clear();
3438 	if ( romFile.open(QIODevice::ReadOnly) ) {
3439 		quint64 sizeLeft = romFile.size();
3440   		char ioBuffer[QMC2_ROMALYZER_FILE_BUFFER_SIZE];
3441 		int len = 0;
3442 		progressBarFileIO->setRange(0, sizeLeft);
3443 		progressBarFileIO->reset();
3444 		while ( (len = romFile.read(ioBuffer, QMC2_ROMALYZER_FILE_BUFFER_SIZE)) > 0 ) {
3445 			QByteArray readData((const char *)ioBuffer, len);
3446 			data->append(readData);
3447 			sizeLeft -= len;
3448 			progressBarFileIO->setValue(romFile.size() - sizeLeft);
3449 			progressBarFileIO->update();
3450 			if ( data->length() % QMC2_128K == 0 || data->length() == romFile.size() ) qApp->processEvents();
3451 		}
3452 		romFile.close();
3453 		progressBarFileIO->reset();
3454 		ulong calculatedCrc = crc32(0, 0, 0);
3455 		calculatedCrc = crc32(calculatedCrc, (const Bytef *)data->data(), data->size());
3456 		return ( crcToString(calculatedCrc) == crc );
3457 	} else
3458 		return false;
3459 }
3460 
3461 // reads the file with the CRC 'crc' in the 7z archive 'fileName' and returns its data in 'data'
readSevenZipFileData(QString fileName,QString crc,QString member,QByteArray * data)3462 bool ROMAlyzer::readSevenZipFileData(QString fileName, QString crc, QString member, QByteArray *data)
3463 {
3464 	SevenZipFile sevenZipFile(fileName);
3465 	if ( sevenZipFile.open() ) {
3466 		int index = sevenZipFile.indexOfCrc(crc);
3467 		if ( index >= 0 ) {
3468 			if ( sevenZipFile.isCrcDuplicate(crc) ) {
3469 				int nameIndex = sevenZipFile.indexOfName(member);
3470 				if ( nameIndex >= 0 )
3471 					index = nameIndex;
3472 			}
3473 			SevenZipMetaData metaData = sevenZipFile.entryList()[index];
3474 			qApp->processEvents();
3475 			quint64 readLength = sevenZipFile.read(index, data);
3476 			qApp->processEvents();
3477 			if ( sevenZipFile.hasError() )
3478 				return false;
3479 			if ( readLength != metaData.size() )
3480 				return false;
3481 			ulong ulCrc = crc32(0, 0, 0);
3482 			ulCrc = crc32(ulCrc, (const Bytef *)data->data(), data->size());
3483 			if ( crcToString(ulCrc) != crc )
3484 				return false;
3485 			return true;
3486 		} else
3487 			return false;
3488 	} else
3489 		return false;
3490 }
3491 
3492 // reads the file with the CRC 'crc' in the ZIP 'fileName' and returns its data in 'data'
readZipFileData(QString fileName,QString crc,QString member,QByteArray * data)3493 bool ROMAlyzer::readZipFileData(QString fileName, QString crc, QString member, QByteArray *data)
3494 {
3495 	bool success = true;
3496 	unzFile zipFile = unzOpen(fileName.toUtf8().constData());
3497 
3498 	if ( zipFile ) {
3499   		char ioBuffer[QMC2_ROMALYZER_ZIP_BUFFER_SIZE];
3500 
3501 		// identify file by CRC
3502 		unz_file_info zipInfo;
3503 		QMultiMap<uLong, QString> crcIdentMap;
3504 		uLong ulCRC = crc.toULong(0, 16);
3505 		do {
3506 			if ( unzGetCurrentFileInfo(zipFile, &zipInfo, ioBuffer, QMC2_ROMALYZER_ZIP_BUFFER_SIZE, 0, 0, 0, 0) == UNZ_OK )
3507 				crcIdentMap.insert(zipInfo.crc, QString((const char *)ioBuffer));
3508 		} while ( unzGoToNextFile(zipFile) == UNZ_OK );
3509 		unzGoToFirstFile(zipFile);
3510 		if ( crcIdentMap.contains(ulCRC) ) {
3511 			QString fn;
3512 			QStringList names(crcIdentMap.values(ulCRC));
3513 			if ( names.contains(member) )
3514 				fn = member;
3515 			else
3516 				fn = names.at(0);
3517 			if ( unzLocateFile(zipFile, fn.toUtf8().constData(), 0) == UNZ_OK ) { // NOT case-sensitive filename compare!
3518 				if ( unzOpenCurrentFile(zipFile) == UNZ_OK ) {
3519 					qint64 len;
3520 					progressBarFileIO->setRange(0, zipInfo.uncompressed_size);
3521 					progressBarFileIO->reset();
3522 					while ( (len = unzReadCurrentFile(zipFile, ioBuffer, QMC2_ROMALYZER_ZIP_BUFFER_SIZE)) > 0 ) {
3523 						QByteArray readData((const char *)ioBuffer, len);
3524 						*data += readData;
3525 						progressBarFileIO->setValue(data->length());
3526 						progressBarFileIO->update();
3527 						progressBar->update();
3528 						if ( data->length() % QMC2_128K == 0 || (uLong)data->length() == zipInfo.uncompressed_size ) qApp->processEvents();
3529 					}
3530 					unzCloseCurrentFile(zipFile);
3531 				} else
3532 					success = false;
3533 			} else
3534 				success = false;
3535 		} else
3536 			success = false;
3537 
3538 		unzClose(zipFile);
3539 	} else
3540 		success = false;
3541 
3542 	progressBarFileIO->reset();
3543 	return success;
3544 }
3545 
3546 // creates the directory 'dirName' and stores the data found in 'fileDataMap' into individual files ('fileDataMap' maps file names to their data)
writeAllFileData(QString dirName,QMap<QString,QByteArray> * fileDataMap,bool writeLog,QProgressBar * pBar)3547 bool ROMAlyzer::writeAllFileData(QString dirName, QMap<QString, QByteArray> *fileDataMap, bool writeLog, QProgressBar *pBar)
3548 {
3549 	bool success = true;
3550 	if ( pBar ) {
3551 		pBar->setRange(0, fileDataMap->count());
3552 		pBar->reset();
3553 	}
3554 	QDir d(dirName);
3555 	if ( !d.exists() )
3556 		success = d.mkdir(dirName);
3557 	QMapIterator<QString, QByteArray> it(*fileDataMap);
3558 	int count = 0;
3559 	while ( it.hasNext() && success ) {
3560 		if ( pBar )
3561 			pBar->setValue(++count);
3562 		it.next();
3563 		QString file = dirName + "/" + it.key();
3564 		QFile f(file);
3565 		QByteArray data = it.value();
3566 		if ( writeLog )
3567 			log(tr("set rewriter: writing '%1' (size: %2)").arg(file).arg(humanReadable(data.length())));
3568 		createBackup(file);
3569 		if ( f.open(QIODevice::WriteOnly) ) {
3570 			quint64 bytesWritten = 0;
3571 			progressBarFileIO->setInvertedAppearance(true);
3572 			progressBarFileIO->setRange(0, data.length());
3573 			progressBarFileIO->reset();
3574 			qApp->processEvents();
3575 			while ( bytesWritten < (quint64)data.length() && success ) {
3576 				quint64 bufferLength = QMC2_ZIP_BUFFER_SIZE;
3577 				if ( bytesWritten + bufferLength > (quint64)data.length() )
3578 					bufferLength = data.length() - bytesWritten;
3579 				qint64 len = f.write(data.mid(bytesWritten, bufferLength));
3580 				success = (len >= 0);
3581 				if ( success ) {
3582 					bytesWritten += len;
3583 					progressBarFileIO->setValue(bytesWritten);
3584 					progressBarFileIO->update();
3585 					progressBar->update();
3586 					if ( bytesWritten % QMC2_128K == 0 || bytesWritten == (quint64)data.length() )
3587 						qApp->processEvents();
3588 				} else if ( writeLog )
3589 					log(tr("set rewriter: WARNING: failed to write '%1'").arg(file));
3590 			}
3591 			f.close();
3592 		} else
3593 			success = false;
3594 	}
3595 	if ( pBar )
3596 		pBar->reset();
3597 	progressBarFileIO->reset();
3598 	progressBarFileIO->setInvertedAppearance(false);
3599 	return success;
3600 }
3601 
3602 // creates the new ZIP 'fileName' through minizip and stores the data found in 'fileDataMap' into the ZIP ('fileDataMap' maps file names to their data)
writeAllZipData(QString fileName,QMap<QString,QByteArray> * fileDataMap,bool writeLog,QProgressBar * pBar)3603 bool ROMAlyzer::writeAllZipData(QString fileName, QMap<QString, QByteArray> *fileDataMap, bool writeLog, QProgressBar *pBar)
3604 {
3605 	bool success = true;
3606 	QFile f(fileName);
3607 	if ( f.exists() )
3608 		success = createBackup(fileName) && f.remove();
3609 	zipFile zip = 0;
3610 	if ( success )
3611 		zip = zipOpen(fileName.toUtf8().constData(), APPEND_STATUS_CREATE);
3612 	if ( zip ) {
3613 		zip_fileinfo zipInfo;
3614 		QDateTime cDT = QDateTime::currentDateTime();
3615 		zipInfo.tmz_date.tm_sec = cDT.time().second();
3616 		zipInfo.tmz_date.tm_min = cDT.time().minute();
3617 		zipInfo.tmz_date.tm_hour = cDT.time().hour();
3618 		zipInfo.tmz_date.tm_mday = cDT.date().day();
3619 		zipInfo.tmz_date.tm_mon = cDT.date().month() - 1;
3620 		zipInfo.tmz_date.tm_year = cDT.date().year();
3621 		zipInfo.dosDate = zipInfo.internal_fa = zipInfo.external_fa = 0;
3622 		if ( pBar ) {
3623 			pBar->setRange(0, fileDataMap->count());
3624 			pBar->reset();
3625 		}
3626 		QMapIterator<QString, QByteArray> it(*fileDataMap);
3627 		int count = 0;
3628 		while ( it.hasNext() && success ) {
3629 			if ( pBar ) {
3630 				pBar->setValue(++count);
3631 				pBar->setFormat(QString("%1 / %2").arg(count).arg(fileDataMap->count()));
3632 			}
3633 			it.next();
3634 			QString file = it.key();
3635 			QByteArray data = it.value();
3636 			if ( writeLog )
3637 				log(tr("set rewriter: deflating '%1' (uncompressed size: %2)").arg(file).arg(humanReadable(data.length())));
3638 			if ( zipOpenNewFileInZip(zip, file.toUtf8().constData(), &zipInfo, file.toUtf8().constData(), file.length(), 0, 0, 0, Z_DEFLATED, spinBoxSetRewriterZipLevel->value()) == ZIP_OK ) {
3639 				quint64 bytesWritten = 0;
3640 				progressBarFileIO->setInvertedAppearance(true);
3641 				progressBarFileIO->setRange(0, data.length());
3642 				progressBarFileIO->reset();
3643 				qApp->processEvents();
3644 				while ( bytesWritten < (quint64)data.length() && success ) {
3645 					quint64 bufferLength = QMC2_ZIP_BUFFER_SIZE;
3646 					if ( bytesWritten + bufferLength > (quint64)data.length() )
3647 						bufferLength = data.length() - bytesWritten;
3648 					QByteArray writeBuffer = data.mid(bytesWritten, bufferLength);
3649 					success = (zipWriteInFileInZip(zip, (const void *)writeBuffer.data(), bufferLength) == ZIP_OK);
3650 					if ( success ) {
3651 						bytesWritten += bufferLength;
3652 						progressBarFileIO->setValue(bytesWritten);
3653 						progressBarFileIO->update();
3654 						progressBar->update();
3655 						if ( bytesWritten % QMC2_128K == 0 || bytesWritten == (quint64)data.length() )
3656 							qApp->processEvents();
3657 					} else if ( writeLog )
3658 						log(tr("set rewriter: WARNING: failed to deflate '%1'").arg(file));
3659 				}
3660 				zipCloseFileInZip(zip);
3661 			} else
3662 				success = false;
3663 		}
3664 		if ( checkBoxSetRewriterAddZipComment->isChecked() )
3665 			zipClose(zip, tr("Created by QMC2 v%1 (%2)").arg(XSTR(QMC2_VERSION)).arg(cDT.toString(Qt::SystemLocaleShortDate)).toUtf8().constData());
3666 		else
3667 			zipClose(zip, "");
3668 	} else
3669 		success = false;
3670 	if ( pBar ) {
3671 		pBar->reset();
3672 		pBar->setFormat(QString());
3673 	}
3674 	progressBarFileIO->reset();
3675 	progressBarFileIO->setInvertedAppearance(false);
3676 	return success;
3677 }
3678 
3679 #if defined(QMC2_LIBARCHIVE_ENABLED)
3680 // creates the new ZIP 'fileName' through libarchive and stores the data found in 'fileDataMap' into the ZIP ('fileDataMap' maps file names to their data)
writeAllArchiveData(QString fileName,QMap<QString,QByteArray> * fileDataMap,bool writeLog,QProgressBar * pBar)3681 bool ROMAlyzer::writeAllArchiveData(QString fileName, QMap<QString, QByteArray> *fileDataMap, bool writeLog, QProgressBar *pBar)
3682 {
3683 	bool success = true;
3684 	QFile f(fileName);
3685 	if ( f.exists() )
3686 		success = createBackup(fileName) && f.remove();
3687         ArchiveFile af(fileName, true, comboBoxSetRewriterLibArchiveDeflate->currentIndex() == 0);
3688 	if ( success && af.open(QIODevice::WriteOnly) ) {
3689 		if ( pBar ) {
3690 			pBar->setRange(0, fileDataMap->count());
3691 			pBar->reset();
3692 		}
3693 		QMapIterator<QString, QByteArray> it(*fileDataMap);
3694 		int count = 0;
3695 		while ( it.hasNext() && success ) {
3696 			if ( pBar ) {
3697 				pBar->setValue(++count);
3698 				pBar->setFormat(QString("%1 / %2").arg(count).arg(fileDataMap->count()));
3699 			}
3700 			it.next();
3701 			QString file = it.key();
3702 			QByteArray data = it.value();
3703 			if ( writeLog )
3704 				log(tr("set rewriter: deflating '%1' (uncompressed size: %2)").arg(file).arg(humanReadable(data.length())));
3705 			if ( af.createEntry(file, data.size()) ) {
3706 				quint64 bytesWritten = 0;
3707 				progressBarFileIO->setInvertedAppearance(true);
3708 				progressBarFileIO->setRange(0, data.length());
3709 				progressBarFileIO->reset();
3710 				qApp->processEvents();
3711 				while ( bytesWritten < (quint64)data.length() && success ) {
3712 					quint64 bufferLength = QMC2_ZIP_BUFFER_SIZE;
3713 					if ( bytesWritten + bufferLength > (quint64)data.length() )
3714 						bufferLength = data.length() - bytesWritten;
3715 					QByteArray writeBuffer = data.mid(bytesWritten, bufferLength);
3716 					success = af.writeEntryData(writeBuffer);
3717 					if ( success ) {
3718 						bytesWritten += bufferLength;
3719 						progressBarFileIO->setValue(bytesWritten);
3720 						progressBarFileIO->update();
3721 						progressBar->update();
3722 						if ( bytesWritten % QMC2_128K == 0 || bytesWritten == (quint64)data.length() )
3723 							qApp->processEvents();
3724 					} else if ( writeLog )
3725 						log(tr("set rewriter: WARNING: failed to deflate '%1'").arg(file));
3726 				}
3727 				af.closeEntry();
3728                         } else
3729 				success = false;
3730 		}
3731 	} else
3732 		success = false;
3733 	if ( pBar ) {
3734 		pBar->reset();
3735 		pBar->setFormat(QString());
3736 	}
3737 	progressBarFileIO->reset();
3738 	progressBarFileIO->setInvertedAppearance(false);
3739 	return success;
3740 }
3741 #endif
3742 
on_treeWidgetChecksums_customContextMenuRequested(const QPoint & p)3743 void ROMAlyzer::on_treeWidgetChecksums_customContextMenuRequested(const QPoint &p)
3744 {
3745 	if ( active() )
3746 		return;
3747 
3748 	QTreeWidgetItem *item = treeWidgetChecksums->itemAt(p);
3749 	if ( item ) {
3750 		if ( item->parent() != 0 ) {
3751 			currentFilesSHA1Checksum = item->text(QMC2_ROMALYZER_COLUMN_SHA1);
3752 			currentFilesCrcChecksum = item->text(QMC2_ROMALYZER_COLUMN_CRC);
3753 			currentFilesSize = item->text(QMC2_ROMALYZER_COLUMN_SIZE).toULongLong();
3754 			if ( !currentFilesSHA1Checksum.isEmpty() || !currentFilesCrcChecksum.isEmpty() ) {
3755 				treeWidgetChecksums->setItemSelected(item, true);
3756 				romFileContextMenu->move(qmc2MainWindow->adjustedWidgetPosition(treeWidgetChecksums->viewport()->mapToGlobal(p), romFileContextMenu));
3757 				romFileContextMenu->show();
3758 			}
3759 		} else {
3760 			bool hasBadOrMissingDumps = analyzerBadSets.contains(item->text(QMC2_ROMALYZER_COLUMN_SET).split(" ", QString::SkipEmptyParts)[0]);
3761 			actionCopyBadToClipboard->setVisible(hasBadOrMissingDumps);
3762 			actionCopyBadToClipboard->setEnabled(hasBadOrMissingDumps);
3763 			actionRewriteSet->setVisible(groupBoxSetRewriter->isChecked());
3764 			actionRewriteSet->setEnabled(groupBoxSetRewriter->isChecked());
3765 			QStringList deviceRefs = item->whatsThis(QMC2_ROMALYZER_COLUMN_SET).split(",", QString::SkipEmptyParts);
3766 			deviceRefs.removeDuplicates();
3767 			actionAnalyzeDeviceRefs->setText(tr("Analyse referenced devices") + QString(" [%1]").arg(deviceRefs.count()));
3768 			actionAnalyzeDeviceRefs->setVisible(!deviceRefs.isEmpty());
3769 			actionAnalyzeDeviceRefs->setEnabled(!deviceRefs.isEmpty());
3770 			treeWidgetChecksums->setItemSelected(item, true);
3771 			setRewriterItem = 0;
3772 			romSetContextMenu->move(qmc2MainWindow->adjustedWidgetPosition(treeWidgetChecksums->viewport()->mapToGlobal(p), romSetContextMenu));
3773 			romSetContextMenu->show();
3774 		}
3775 	}
3776 }
3777 
on_toolButtonSaveLog_clicked()3778 void ROMAlyzer::on_toolButtonSaveLog_clicked()
3779 {
3780 	QString fileName = QFileDialog::getSaveFileName(this, tr("Choose file to store the ROMAlyzer log"), "qmc2-romalyzer.log", tr("All files (*)"), 0, qmc2Options->useNativeFileDialogs() ? (QFileDialog::Options)0 : QFileDialog::DontUseNativeDialog);
3781 	if ( !fileName.isEmpty() ) {
3782 		QFile f(fileName);
3783 		if ( f.open(QIODevice::WriteOnly | QIODevice::Text) ) {
3784 			QTextStream ts(&f);
3785 			qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("saving ROMAlyzer log to '%1'").arg(fileName));
3786 			log(tr("saving ROMAlyzer log to '%1'").arg(fileName));
3787 			ts << textBrowserLog->toPlainText();
3788 			f.close();
3789 			qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("done (saving ROMAlyzer log to '%1')").arg(fileName));
3790 			log(tr("done (saving ROMAlyzer log to '%1')").arg(fileName));
3791 		} else {
3792 			qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("WARNING: can't open file '%1' for writing, please check permissions").arg(fileName));
3793 			log(tr("WARNING: can't open file '%1' for writing, please check permissions").arg(fileName));
3794 		}
3795 	}
3796 }
3797 
on_toolButtonCheckSumDbAddPath_clicked()3798 void ROMAlyzer::on_toolButtonCheckSumDbAddPath_clicked()
3799 {
3800 	QString newPath = QFileDialog::getExistingDirectory(this, tr("Choose path to be added to scan-list"), QString(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks | (qmc2Options->useNativeFileDialogs() ? (QFileDialog::Options)0 : QFileDialog::DontUseNativeDialog));
3801 	if ( !newPath.isEmpty() ) {
3802 		QStringList checkSumDbScannedPaths;
3803 		for (int i = 0; i < listWidgetCheckSumDbScannedPaths->count(); i++)
3804 			checkSumDbScannedPaths << listWidgetCheckSumDbScannedPaths->item(i)->text();
3805 		if ( !checkSumDbScannedPaths.contains(newPath) ) {
3806 			QListWidgetItem *item = new QListWidgetItem(newPath);
3807 			item->setCheckState(Qt::Checked);
3808 			listWidgetCheckSumDbScannedPaths->addItem(item);
3809 		}
3810 	}
3811 
3812 	pushButtonCheckSumDbScan->setEnabled(listWidgetCheckSumDbScannedPaths->count() > 0);
3813 }
3814 
on_toolButtonCheckSumDbRemovePath_clicked()3815 void ROMAlyzer::on_toolButtonCheckSumDbRemovePath_clicked()
3816 {
3817 	foreach (QListWidgetItem *item, listWidgetCheckSumDbScannedPaths->selectedItems()) {
3818 		listWidgetCheckSumDbScannedPaths->takeItem(listWidgetCheckSumDbScannedPaths->row(item));
3819 		delete item;
3820 	}
3821 
3822 	pushButtonCheckSumDbScan->setEnabled(listWidgetCheckSumDbScannedPaths->count() > 0);
3823 }
3824 
on_lineEditCheckSumDbDatabasePath_textChanged(const QString & text)3825 void ROMAlyzer::on_lineEditCheckSumDbDatabasePath_textChanged(const QString &text)
3826 {
3827 	qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbDatabasePath", text);
3828 }
3829 
on_toolButtonBrowseCheckSumDbDatabasePath_clicked()3830 void ROMAlyzer::on_toolButtonBrowseCheckSumDbDatabasePath_clicked()
3831 {
3832 	QString fileName = QFileDialog::getSaveFileName(this, tr("Choose check-sum database file"), lineEditCheckSumDbDatabasePath->text(), tr("All files (*)"), 0, qmc2Options->useNativeFileDialogs() ? (QFileDialog::Options)0 : QFileDialog::DontUseNativeDialog);
3833 	if ( !fileName.isEmpty() ) {
3834 		lineEditCheckSumDbDatabasePath->setText(fileName);
3835 		qmc2Config->setValue(QMC2_FRONTEND_PREFIX + m_settingsKey + "/CheckSumDbDatabasePath", fileName);
3836 	}
3837 }
3838 
on_toolButtonCheckSumDbViewLog_clicked()3839 void ROMAlyzer::on_toolButtonCheckSumDbViewLog_clicked()
3840 {
3841 	if ( checkSumScannerLog()->isMinimized() ) {
3842 		checkSumScannerLog()->showNormal();
3843 		checkSumScannerLog()->scrollToEnd();
3844 		checkSumScannerLog()->raise();
3845 	} else if ( checkSumScannerLog()->isVisible() ) {
3846 		checkSumScannerLog()->close();
3847 	} else {
3848 		checkSumScannerLog()->show();
3849 		checkSumScannerLog()->scrollToEnd();
3850 		checkSumScannerLog()->raise();
3851 	}
3852 }
3853 
on_pushButtonCheckSumDbScan_clicked()3854 void ROMAlyzer::on_pushButtonCheckSumDbScan_clicked()
3855 {
3856 	pushButtonCheckSumDbScan->setEnabled(false);
3857 	pushButtonCheckSumDbScan->update();
3858 	pushButtonCheckSumDbPauseResumeScan->setEnabled(false);
3859 	pushButtonCheckSumDbPauseResumeScan->update();
3860 	qApp->processEvents();
3861 	if ( checkSumScannerThread()->isActive )
3862 		checkSumScannerThread()->stopScan = true;
3863 	else if ( checkSumScannerThread()->isWaiting ) {
3864 		checkSumDb()->disconnect(this);
3865 		delete checkSumDb();
3866 		m_checkSumDb = new CheckSumDatabaseManager(this, m_settingsKey);
3867 		connect(checkSumDb(), SIGNAL(log(const QString &)), this, SLOT(log(const QString &)));
3868 		checkSumScannerThread()->reopenCheckSumDb();
3869 		checkSumScannerThread()->scannedPaths.clear();
3870 		for (int i = 0; i < listWidgetCheckSumDbScannedPaths->count(); i++)
3871 			if ( listWidgetCheckSumDbScannedPaths->item(i)->checkState() == Qt::Checked )
3872 				checkSumScannerThread()->scannedPaths << listWidgetCheckSumDbScannedPaths->item(i)->text();
3873 		checkSumScannerThread()->scanIncrementally = toolButtonCheckSumDbScanIncrementally->isChecked();
3874 		checkSumScannerThread()->deepScan = toolButtonCheckSumDbDeepScan->isChecked();
3875 		checkSumScannerThread()->useHashCache = toolButtonCheckSumDbHashCache->isChecked();
3876 #if defined(QMC2_LIBARCHIVE_ENABLED)
3877 		checkSumScannerThread()->useLibArchive = checkSumScannerThread()->deepScan && toolButtonCheckSumDbLibArchive->isChecked();
3878 #endif
3879 		checkSumScannerThread()->waitCondition.wakeAll();
3880 	}
3881 	qApp->processEvents();
3882 }
3883 
on_pushButtonCheckSumDbPauseResumeScan_clicked()3884 void ROMAlyzer::on_pushButtonCheckSumDbPauseResumeScan_clicked()
3885 {
3886 	pushButtonCheckSumDbPauseResumeScan->setEnabled(false);
3887 	if ( checkSumScannerThread()->isPaused )
3888 		QTimer::singleShot(0, checkSumScannerThread(), SLOT(resume()));
3889 	else
3890 		QTimer::singleShot(0, checkSumScannerThread(), SLOT(pause()));
3891 }
3892 
on_listWidgetCheckSumDbScannedPaths_customContextMenuRequested(const QPoint &)3893 void ROMAlyzer::on_listWidgetCheckSumDbScannedPaths_customContextMenuRequested(const QPoint &/*p*/)
3894 {
3895 	// FIXME
3896 }
3897 
on_listWidgetCheckSumDbScannedPaths_itemSelectionChanged()3898 void ROMAlyzer::on_listWidgetCheckSumDbScannedPaths_itemSelectionChanged()
3899 {
3900 	toolButtonCheckSumDbRemovePath->setEnabled(!listWidgetCheckSumDbScannedPaths->selectedItems().isEmpty());
3901 }
3902 
checkSumScannerLog_windowOpened()3903 void ROMAlyzer::checkSumScannerLog_windowOpened()
3904 {
3905 	toolButtonCheckSumDbViewLog->setText(tr("Close log"));
3906 }
3907 
checkSumScannerLog_windowClosed()3908 void ROMAlyzer::checkSumScannerLog_windowClosed()
3909 {
3910 	toolButtonCheckSumDbViewLog->setText(tr("Open log"));
3911 }
3912 
checkSumScannerThread_scanStarted()3913 void ROMAlyzer::checkSumScannerThread_scanStarted()
3914 {
3915 	if ( collectionRebuilder() ) {
3916 		delete collectionRebuilder();
3917 		m_collectionRebuilder = 0;
3918 	}
3919 	pushButtonCheckSumDbScan->setIcon(QIcon(QString::fromUtf8(":/data/img/halt.png")));
3920 	pushButtonCheckSumDbScan->setText(tr("Stop scanner"));
3921 	pushButtonCheckSumDbPauseResumeScan->setText(tr("Pause"));
3922 	pushButtonCheckSumDbPauseResumeScan->show();
3923 	checkSumDbStatusTimer.stop();
3924 	checkSumDbStatusTimer.start(QMC2_CHECKSUM_DB_STATUS_UPDATE_SHORT);
3925 	pushButtonCheckSumDbScan->setEnabled(true);
3926 	pushButtonCheckSumDbPauseResumeScan->setEnabled(true);
3927 	tabWidgetAnalysis->setTabEnabled(QMC2_ROMALYZER_PAGE_RCR, false);
3928 	labelCollectionRebuilderSpecific->setEnabled(false);
3929 	lineCollectionRebuilderSpecific->setEnabled(false);
3930 	checkBoxCollectionRebuilderHashCache->setEnabled(false);
3931 	checkBoxCollectionRebuilderDryRun->setEnabled(false);
3932 	labelCollectionRebuilderCHDHandling->setEnabled(false);
3933 	comboBoxCollectionRebuilderCHDHandling->setEnabled(false);
3934 	lineEditCheckSumDbDatabasePath->setEnabled(false);
3935 	toolButtonBrowseCheckSumDbDatabasePath->setEnabled(false);
3936 	qApp->processEvents();
3937 	QTimer::singleShot(0, this, SLOT(updateCheckSumDbStatus()));
3938 }
3939 
checkSumScannerThread_scanFinished()3940 void ROMAlyzer::checkSumScannerThread_scanFinished()
3941 {
3942 	pushButtonCheckSumDbScan->setIcon(QIcon(QString::fromUtf8(":/data/img/refresh.png")));
3943 	pushButtonCheckSumDbScan->setText(tr("Start scanner"));
3944 	pushButtonCheckSumDbPauseResumeScan->hide();
3945 	checkSumDbStatusTimer.stop();
3946 	checkSumDbStatusTimer.start(QMC2_CHECKSUM_DB_STATUS_UPDATE_LONG);
3947 	pushButtonCheckSumDbScan->setEnabled(true);
3948 	pushButtonCheckSumDbPauseResumeScan->setEnabled(true);
3949 	bool enable = groupBoxCheckSumDatabase->isChecked() && groupBoxSetRewriter->isChecked();
3950 	tabWidgetAnalysis->setTabEnabled(QMC2_ROMALYZER_PAGE_RCR, enable);
3951 	labelCollectionRebuilderSpecific->setEnabled(enable);
3952 	lineCollectionRebuilderSpecific->setEnabled(enable);
3953 	checkBoxCollectionRebuilderHashCache->setEnabled(enable);
3954 	checkBoxCollectionRebuilderDryRun->setEnabled(enable);
3955 	labelCollectionRebuilderCHDHandling->setEnabled(enable);
3956 	comboBoxCollectionRebuilderCHDHandling->setEnabled(enable);
3957 	lineEditCheckSumDbDatabasePath->setEnabled(enable);
3958 	toolButtonBrowseCheckSumDbDatabasePath->setEnabled(enable);
3959 	qApp->processEvents();
3960 	updateCheckSumDbStatus();
3961 	QTimer::singleShot(50, this, SLOT(updateCheckSumDbStatus()));
3962 }
3963 
checkSumScannerThread_scanPaused()3964 void ROMAlyzer::checkSumScannerThread_scanPaused()
3965 {
3966 	pushButtonCheckSumDbPauseResumeScan->setText(tr("Resume"));
3967 	pushButtonCheckSumDbPauseResumeScan->setEnabled(true);
3968 }
3969 
checkSumScannerThread_scanResumed()3970 void ROMAlyzer::checkSumScannerThread_scanResumed()
3971 {
3972 	pushButtonCheckSumDbPauseResumeScan->setText(tr("Pause"));
3973 	pushButtonCheckSumDbPauseResumeScan->setEnabled(true);
3974 }
3975 
updateCheckSumDbStatus()3976 void ROMAlyzer::updateCheckSumDbStatus()
3977 {
3978 	bool isScanning = checkSumScannerThread()->status() == tr("scanning");
3979 	bool isPreparing = isScanning ? false : checkSumScannerThread()->status() == tr("preparing");
3980 
3981 	QDateTime now = QDateTime::currentDateTime();
3982 	QString statusString("<center><table border=\"0\" cellpadding=\"2\" cellspacing=\"2\">");
3983 	if ( isScanning ) {
3984 		qint64 currentRowCount = checkSumDb()->checkSumRowCount();
3985 		if ( currentRowCount >= 0 ) {
3986 			if ( lastRowCount > 0 && currentRowCount >= lastRowCount )
3987 				statusString += "<tr><td nowrap width=\"50%\" valign=\"top\" align=\"right\"><b>" + tr("Objects in database") + "</b></td><td nowrap width=\"50%\" valign=\"top\">" + QString::number(currentRowCount) + " | &Delta; " + QString::number(currentRowCount - lastRowCount) + "</td></tr>";
3988 			else
3989 				statusString += "<tr><td nowrap width=\"50%\" valign=\"top\" align=\"right\"><b>" + tr("Objects in database") + "</b></td><td nowrap width=\"50%\" valign=\"top\">" + QString::number(currentRowCount) + "</td></tr>";
3990 			lastRowCount = currentRowCount;
3991 		} else {
3992 			if ( lastRowCount >= 0 )
3993 				statusString += "<tr><td nowrap width=\"50%\" valign=\"top\" align=\"right\"><b>" + tr("Objects in database") + "</b></td><td nowrap width=\"50%\" valign=\"top\">" + QString::number(lastRowCount) + "</td></tr>";
3994 			else
3995 				statusString += "<tr><td nowrap width=\"50%\" valign=\"top\" align=\"right\"><b>" + tr("Objects in database") + "</b></td><td nowrap width=\"50%\" valign=\"top\">?</td></tr>";
3996 		}
3997 	} else {
3998 		statusString += "<tr><td nowrap width=\"50%\" valign=\"top\" align=\"right\"><b>" + tr("Objects in database") + "</b></td><td nowrap width=\"50%\" valign=\"top\">" + QString::number(checkSumDb()->checkSumRowCount()) + "</td></tr>";
3999 		lastRowCount = 0;
4000 	}
4001 	statusString += "<tr><td nowrap width=\"50%\" valign=\"top\" align=\"right\"><b>" + tr("Database size") + "</b></td><td nowrap width=\"50%\" valign=\"top\">" + humanReadable(checkSumDb()->databaseSize()) + "</td></tr>";
4002 	QDateTime scanTime = QDateTime::fromTime_t(checkSumDb()->scanTime());
4003 	QString ageString;
4004 	int days = scanTime.daysTo(now);
4005 	if ( days > 0 )
4006 		ageString = tr("%n day(s)", "", days);
4007 	else {
4008 		int seconds = scanTime.secsTo(now);
4009 		if ( seconds < 60 ) {
4010 			ageString = tr("%n second(s)", "", seconds);
4011 			if ( !checkSumScannerThread()->isActive )
4012 				QTimer::singleShot(QMC2_CHECKSUM_DB_STATUS_UPDATE_SHORT, this, SLOT(updateCheckSumDbStatus()));
4013 		} else {
4014 			int hours = seconds / 3600;
4015 			if ( hours > 0 )
4016 				ageString = tr("%n hour(s)", "", hours);
4017 			else
4018 				ageString = tr("%n minute(s)", "", seconds / 60);
4019 		}
4020 	}
4021 	statusString += "<tr><td nowrap width=\"50%\" valign=\"top\" align=\"right\"><b>" + tr("Age of stored data") + "</b></td><td nowrap width=\"50%\" valign=\"top\">" + ageString + "</td></tr>";
4022 	statusString += "<tr><td nowrap width=\"50%\" valign=\"top\" align=\"right\"><b>" + tr("Pending updates") + "</b></td><td nowrap width=\"50%\" valign=\"top\">" + QString::number(checkSumScannerThread()->pendingUpdates()) + "</td></tr>";
4023 	if ( isScanning ) {
4024 		if ( checkSumScannerLog()->progress() >= 0 )
4025 			statusString += "<tr><td nowrap width=\"50%\" valign=\"top\" align=\"right\"><b>" + tr("Scanner status") + "</b></td><td nowrap width=\"50%\" valign=\"top\">" + checkSumScannerThread()->status() + " | " + checkSumScannerThread()->scanTime() + " | " + QString::number(checkSumScannerLog()->progress(), 'f', 1) + "%</td></tr>";
4026 		else
4027 			statusString += "<tr><td nowrap width=\"50%\" valign=\"top\" align=\"right\"><b>" + tr("Scanner status") + "</b></td><td nowrap width=\"50%\" valign=\"top\">" + checkSumScannerThread()->status() + "</td></tr>";
4028 	} else if ( isPreparing )
4029 		statusString += "<tr><td nowrap width=\"50%\" valign=\"top\" align=\"right\"><b>" + tr("Scanner status") + "</b></td><td nowrap width=\"50%\" valign=\"top\">" + checkSumScannerThread()->status() + " | " + checkSumScannerThread()->scanTime() + "</td></tr>";
4030 	else
4031 		statusString += "<tr><td nowrap width=\"50%\" valign=\"top\" align=\"right\"><b>" + tr("Scanner status") + "</b></td><td nowrap width=\"50%\" valign=\"top\">" + checkSumScannerThread()->status() + "</td></tr>";
4032 	statusString += "</table></center>";
4033 	labelCheckSumDbStatusDisplay->setText(statusString);
4034 	qApp->processEvents();
4035 }
4036 
indicateCheckSumDbQueryStatusGood()4037 void ROMAlyzer::indicateCheckSumDbQueryStatusGood()
4038 {
4039 	m_checkSumDbQueryStatusPixmap = QPixmap(QString::fromUtf8(":/data/img/database_good.png"));
4040 	widgetCheckSumDbQueryStatus->setFixedWidth(widgetCheckSumDbQueryStatus->height());
4041 	QPalette pal = widgetCheckSumDbQueryStatus->palette();
4042 	pal.setBrush(QPalette::Window, m_checkSumDbQueryStatusPixmap.scaled(widgetCheckSumDbQueryStatus->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
4043 	widgetCheckSumDbQueryStatus->setPalette(pal);
4044 }
4045 
indicateCheckSumDbQueryStatusBad()4046 void ROMAlyzer::indicateCheckSumDbQueryStatusBad()
4047 {
4048 	m_checkSumDbQueryStatusPixmap = QPixmap(QString::fromUtf8(":/data/img/database_bad.png"));
4049 	widgetCheckSumDbQueryStatus->setFixedWidth(widgetCheckSumDbQueryStatus->height());
4050 	QPalette pal = widgetCheckSumDbQueryStatus->palette();
4051 	pal.setBrush(QPalette::Window, m_checkSumDbQueryStatusPixmap.scaled(widgetCheckSumDbQueryStatus->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
4052 	widgetCheckSumDbQueryStatus->setPalette(pal);
4053 }
4054 
indicateCheckSumDbQueryStatusUnknown()4055 void ROMAlyzer::indicateCheckSumDbQueryStatusUnknown()
4056 {
4057 	m_checkSumDbQueryStatusPixmap = QPixmap(QString::fromUtf8(":/data/img/database.png"));
4058 	widgetCheckSumDbQueryStatus->setFixedWidth(widgetCheckSumDbQueryStatus->height());
4059 	QPalette pal = widgetCheckSumDbQueryStatus->palette();
4060 	pal.setBrush(QPalette::Window, m_checkSumDbQueryStatusPixmap.scaled(widgetCheckSumDbQueryStatus->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
4061 	widgetCheckSumDbQueryStatus->setPalette(pal);
4062 }
4063 
softwareListLoadFinished(bool)4064 void ROMAlyzer::softwareListLoadFinished(bool /* success */)
4065 {
4066 	setEnabled(true);
4067 }
4068 
switchToCollectionRebuilder()4069 void ROMAlyzer::switchToCollectionRebuilder()
4070 {
4071 	if ( !collectionRebuilder() ) {
4072 		m_collectionRebuilder = new CollectionRebuilder(this, tabCollectionRebuilder);
4073 		gridLayoutCollectionRebuilder->addWidget(collectionRebuilder(), 0, 0);
4074 	}
4075 	tabWidgetAnalysis->setCurrentIndex(QMC2_ROMALYZER_PAGE_RCR);
4076 	QTimer::singleShot(0, collectionRebuilder(), SLOT(scrollToEnd()));
4077 }
4078 
ROMAlyzerXmlHandler(QTreeWidgetItem * parent,bool expand,bool scroll,int mode)4079 ROMAlyzerXmlHandler::ROMAlyzerXmlHandler(QTreeWidgetItem *parent, bool expand, bool scroll, int mode)
4080 {
4081 	parentItem = parent;
4082 	autoExpand = expand;
4083 	autoScroll = scroll;
4084 	romalyzerMode = mode;
4085 	redBrush = QBrush(QColor(255, 0, 0));
4086 	greenBrush = QBrush(QColor(0, 255, 0));
4087 	blueBrush = QBrush(QColor(0, 0, 255));
4088 	yellowBrush = QBrush(QColor(255, 255, 0));
4089 	brownBrush = QBrush(QColor(128, 128, 0));
4090 	greyBrush = QBrush(QColor(128, 128, 128));
4091 }
4092 
startElement(const QString &,const QString &,const QString & qName,const QXmlAttributes & attributes)4093 bool ROMAlyzerXmlHandler::startElement(const QString &/*namespaceURI*/, const QString &/*localName*/, const QString &qName, const QXmlAttributes &attributes)
4094 {
4095 	QString s;
4096 	QString mainEntityName;
4097 
4098 	switch ( romalyzerMode ) {
4099 		case QMC2_ROMALYZER_MODE_SOFTWARE:
4100 			mainEntityName = "software";
4101 			break;
4102 		case QMC2_ROMALYZER_MODE_SYSTEM:
4103 		default:
4104 			mainEntityName = "machine";
4105 			break;
4106 	}
4107 
4108 	if ( qName == mainEntityName ) {
4109 		switch ( romalyzerMode ) {
4110 			case QMC2_ROMALYZER_MODE_SOFTWARE:
4111 				parentItem->setText(QMC2_ROMALYZER_COLUMN_MERGE, attributes.value("cloneof"));
4112 				break;
4113 			case QMC2_ROMALYZER_MODE_SYSTEM:
4114 			default:
4115 				parentItem->setText(QMC2_ROMALYZER_COLUMN_MERGE, attributes.value("romof"));
4116 				break;
4117 		}
4118 		parentItem->setExpanded(false);
4119 		emuStatus = 0;
4120 		fileCounter = 0;
4121 		currentText.clear();
4122 		childItems.clear();
4123 		deviceReferences.clear();
4124 		optionalROMs.clear();
4125 	} else if ( qName == "rom" || qName == "disk" ) {
4126 		if ( !attributes.value("name").isEmpty() ) {
4127 			fileCounter++;
4128 			childItem = new QTreeWidgetItem(parentItem);
4129 			childItems << childItem;
4130 			childItem->setText(QMC2_ROMALYZER_COLUMN_SET, attributes.value("name"));
4131 			childItem->setText(QMC2_ROMALYZER_COLUMN_TYPE, qName == "rom" ? QObject::tr("ROM") : QObject::tr("CHD"));
4132 			childItem->setText(QMC2_ROMALYZER_COLUMN_MERGE, attributes.value("merge"));
4133 			s = attributes.value("status");
4134 			if ( s.isEmpty() || s == "good" ) {
4135 				childItem->setText(QMC2_ROMALYZER_COLUMN_EMUSTATUS, QObject::tr("good"));
4136 				childItem->setForeground(QMC2_ROMALYZER_COLUMN_EMUSTATUS, greenBrush);
4137 				emuStatus |= QMC2_ROMALYZER_EMUSTATUS_GOOD;
4138 			} else if ( s == "nodump" ) {
4139 				childItem->setText(QMC2_ROMALYZER_COLUMN_EMUSTATUS, QObject::tr("no dump"));
4140 				childItem->setForeground(QMC2_ROMALYZER_COLUMN_EMUSTATUS, brownBrush);
4141 				emuStatus |= QMC2_ROMALYZER_EMUSTATUS_NODUMP;
4142 			} else if ( s == "baddump" ) {
4143 				childItem->setText(QMC2_ROMALYZER_COLUMN_EMUSTATUS, QObject::tr("bad dump"));
4144 				childItem->setForeground(QMC2_ROMALYZER_COLUMN_EMUSTATUS, brownBrush);
4145 				emuStatus |= QMC2_ROMALYZER_EMUSTATUS_BADDUMP;
4146 			} else {
4147 				childItem->setText(QMC2_ROMALYZER_COLUMN_EMUSTATUS, QObject::tr("unknown"));
4148 				childItem->setForeground(QMC2_ROMALYZER_COLUMN_EMUSTATUS, blueBrush);
4149 				emuStatus |= QMC2_ROMALYZER_EMUSTATUS_UNKNOWN;
4150 			}
4151 			childItem->setText(QMC2_ROMALYZER_COLUMN_SIZE, attributes.value("size"));
4152 			childItem->setText(QMC2_ROMALYZER_COLUMN_CRC, attributes.value("crc"));
4153 			childItem->setText(QMC2_ROMALYZER_COLUMN_SHA1, attributes.value("sha1"));
4154 			childItem->setText(QMC2_ROMALYZER_COLUMN_MD5, attributes.value("md5"));
4155 			if ( attributes.value("optional") == "yes" )
4156 				optionalROMs << attributes.value("crc");
4157 		}
4158 	} else if ( qName == "device_ref" )
4159 		deviceReferences << attributes.value("name");
4160 
4161 	return true;
4162 }
4163 
endElement(const QString &,const QString &,const QString & qName)4164 bool ROMAlyzerXmlHandler::endElement(const QString &/*namespaceURI*/, const QString &/*localName*/, const QString &qName)
4165 {
4166 	QString mainEntityName;
4167 
4168 	switch ( romalyzerMode ) {
4169 		case QMC2_ROMALYZER_MODE_SOFTWARE:
4170 			mainEntityName = "software";
4171 			break;
4172 		case QMC2_ROMALYZER_MODE_SYSTEM:
4173 		default:
4174 			mainEntityName = "machine";
4175 			break;
4176 	}
4177 
4178 	if ( qName == mainEntityName ) {
4179 		QString s(parentItem->text(QMC2_ROMALYZER_COLUMN_SET));
4180 		s += " [" + QString::number(fileCounter) + "]";
4181 		parentItem->setText(QMC2_ROMALYZER_COLUMN_SET, s);
4182 		QString emuStatusStr;
4183 		QBrush myBrush;
4184 		if ( emuStatus == QMC2_ROMALYZER_EMUSTATUS_GOOD ) {
4185 			emuStatusStr = QObject::tr("good");
4186 			myBrush = greenBrush;
4187 		} else if ( emuStatus & QMC2_ROMALYZER_EMUSTATUS_UNKNOWN ) {
4188 			emuStatusStr = QObject::tr("unknown");
4189 			myBrush = blueBrush;
4190 		} else if ( emuStatus & QMC2_ROMALYZER_EMUSTATUS_NODUMP && emuStatus & QMC2_ROMALYZER_EMUSTATUS_BADDUMP ) {
4191 			emuStatusStr = QObject::tr("no / bad dump");
4192 			myBrush = brownBrush;
4193 		} else if ( emuStatus & QMC2_ROMALYZER_EMUSTATUS_NODUMP ) {
4194 			emuStatusStr = QObject::tr("no dump");
4195 			myBrush = brownBrush;
4196 		} else if ( emuStatus & QMC2_ROMALYZER_EMUSTATUS_BADDUMP ) {
4197 			emuStatusStr = QObject::tr("bad dump");
4198 			myBrush = brownBrush;
4199 		} else {
4200 			emuStatusStr = QObject::tr("unknown");
4201 			myBrush = blueBrush;
4202 		}
4203 		if ( fileCounter == 0 ) {
4204 			emuStatusStr = QObject::tr("good");
4205 			myBrush = greenBrush;
4206 		}
4207 		parentItem->setText(QMC2_ROMALYZER_COLUMN_EMUSTATUS, emuStatusStr);
4208 		parentItem->setForeground(QMC2_ROMALYZER_COLUMN_EMUSTATUS, myBrush);
4209 		if ( autoExpand )
4210 			parentItem->setExpanded(true);
4211 		if ( autoScroll )
4212 			parentItem->treeWidget()->scrollToItem(parentItem, QAbstractItemView::PositionAtTop);
4213 	}
4214 
4215 	return true;
4216 }
4217 
characters(const QString & str)4218 bool ROMAlyzerXmlHandler::characters(const QString &str)
4219 {
4220 	currentText += QString::fromUtf8(str.toUtf8());
4221 	return true;
4222 }
4223 
CheckSumScannerThread(CheckSumScannerLog * scannerLog,QString settingsKey,QObject * parent)4224 CheckSumScannerThread::CheckSumScannerThread(CheckSumScannerLog *scannerLog, QString settingsKey, QObject *parent)
4225 	: QThread(parent)
4226 {
4227 	isActive = exitThread = isWaiting = isPaused = pauseRequested = stopScan = scanIncrementally = deepScan = useHashCache = false;
4228 #if defined(QMC2_LIBARCHIVE_ENABLED)
4229 	useLibArchive = false;
4230 #endif
4231 	m_preparingIncrementalScan = false;
4232 	m_checkSumDb = 0;
4233 	m_scannerLog = scannerLog;
4234 	m_settingsKey = settingsKey;
4235 	m_pendingUpdates = 0;
4236 	reopenCheckSumDb();
4237 	start();
4238 }
4239 
~CheckSumScannerThread()4240 CheckSumScannerThread::~CheckSumScannerThread()
4241 {
4242 	exitThread = true;
4243 	waitCondition.wakeAll();
4244 	wait();
4245 	if ( checkSumDb() ) {
4246 		checkSumDb()->disconnect(m_scannerLog);
4247 		delete checkSumDb();
4248 	}
4249 }
4250 
status()4251 QString CheckSumScannerThread::status()
4252 {
4253 	if ( m_preparingIncrementalScan )
4254 		return tr("preparing");
4255 	if ( exitThread )
4256 		return tr("exiting");
4257 	if ( stopScan )
4258 		return tr("stopping");
4259 	if ( isPaused )
4260 		return tr("paused");
4261 	if ( isActive )
4262 		return tr("scanning");
4263 	return tr("idle");
4264 }
4265 
prepareIncrementalScan(QStringList * fileList)4266 void CheckSumScannerThread::prepareIncrementalScan(QStringList *fileList)
4267 {
4268 	m_preparingIncrementalScan = true;
4269 	emitlog(tr("preparing incremental scan"));
4270 	// step 1: remove entries from the database that "point to nowhere" (a.k.a. aren't contained in 'fileList'), storing all paths kept in the database
4271 	//         in the 'pathsInDatabase' hash for use in steps 2 and 3 so we don't need to query the database again
4272 	QHash<QString, bool> pathsInDatabase;
4273 	emit progressTextChanged(tr("Preparing") + " - " + tr("Step %1 of %2").arg(1).arg(3));
4274 	emit progressRangeChanged(0, checkSumDb()->checkSumRowCount() - 1);
4275 	emit progressChanged(0);
4276 	int count = 0;
4277 	qint64 pathsRemoved = 0;
4278 	QHash<QString, bool> fileHash;
4279 	foreach (QString file, *fileList)
4280 		fileHash.insert(file, true);
4281 	qint64 row = checkSumDb()->nextRowId(true);
4282 	checkSumDb()->beginTransaction();
4283 	while ( row > 0 && !exitThread && !stopScan ) {
4284 		emit progressChanged(count++);
4285 		QString key;
4286 		QString path = checkSumDb()->pathOfRow(row, &key);
4287 		if ( !path.isEmpty() ) {
4288 			if ( !fileHash.contains(path) ) {
4289 				checkSumDb()->pathRemove(path);
4290 				pathsRemoved++;
4291 				if ( useHashCache )
4292 					m_hashCache.remove(key);
4293 			} else {
4294 				pathsInDatabase[path] = true;
4295 				if ( useHashCache )
4296 					m_hashCache[key] = true;
4297 			}
4298 		}
4299 		row = checkSumDb()->nextRowId();
4300 	}
4301 	checkSumDb()->commitTransaction();
4302 	emitlog(tr("%n obsolete path(s) removed from database", "", pathsRemoved));
4303 	if ( !exitThread && !stopScan ) {
4304 		// step 2: remove entries from 'fileList' where 'scanTime' is later than the file's modification time *and* the database has entries for it
4305 		emit progressTextChanged(tr("Preparing") + " - " + tr("Step %1 of %2").arg(2).arg(3));
4306 		int oldFileListCount = fileList->count();
4307 		emit progressRangeChanged(0, oldFileListCount - 1);
4308 		emit progressChanged(0);
4309 		int filesRemoved = 0;
4310 		uint scanTime = checkSumDb()->scanTime();
4311 		count = 0;
4312 		for (int i = 0; i < fileList->count() && !exitThread && !stopScan; i++) {
4313 			emit progressChanged(count++);
4314 			QFileInfo fi(fileList->at(i));
4315 			if ( fi.lastModified().toTime_t() < scanTime && pathsInDatabase.contains(fileList->at(i)) ) {
4316 				fileList->removeAt(i);
4317 				filesRemoved++;
4318 				i--;
4319 			}
4320 		}
4321 		emitlog(tr("%n unchanged file(s) removed from scan", "", filesRemoved));
4322 		fileHash.clear();
4323 		foreach (QString file, *fileList)
4324 			fileHash.insert(file, true);
4325 		if ( !exitThread && !stopScan ) {
4326 			// step 3: remove entries from the database that "point to new stuff" (a.k.a. are still contained in the modified 'fileList')
4327 			emit progressTextChanged(tr("Preparing") + " - " + tr("Step %1 of %2").arg(3).arg(3));
4328 			emit progressRangeChanged(0, checkSumDb()->checkSumRowCount() - 1);
4329 			emit progressChanged(0);
4330 			pathsRemoved = 0;
4331 			count = 0;
4332 			row = checkSumDb()->nextRowId(true);
4333 			checkSumDb()->beginTransaction();
4334 			while ( row > 0 && !exitThread && !stopScan ) {
4335 				emit progressChanged(count++);
4336 				QString key;
4337 				if ( fileHash.contains(checkSumDb()->pathOfRow(row, &key)) ) {
4338 					checkSumDb()->invalidateRow(row);
4339 					pathsRemoved++;
4340 					if ( useHashCache )
4341 						m_hashCache.remove(key);
4342 				}
4343 				row = checkSumDb()->nextRowId();
4344 				if ( count % QMC2_CHECKSUM_DB_MAX_TRANSACTIONS ) {
4345 					checkSumDb()->removeInvalidatedRows();
4346 					checkSumDb()->commitTransaction();
4347 					checkSumDb()->beginTransaction();
4348 				}
4349 			}
4350 			checkSumDb()->removeInvalidatedRows();
4351 			checkSumDb()->commitTransaction();
4352 			emitlog(tr("%n outdated path(s) removed from database", "", pathsRemoved));
4353 			if ( !exitThread && !stopScan ) {
4354 				emit progressTextChanged(tr("Preparing"));
4355 				emit progressRangeChanged(0, 0);
4356 				emit progressChanged(-1);
4357 				emitlog(tr("freeing unused space previously occupied by database"));
4358 				checkSumDb()->vacuum();
4359 			}
4360 		}
4361 	}
4362 	m_preparingIncrementalScan = false;
4363 }
4364 
reopenCheckSumDb()4365 void CheckSumScannerThread::reopenCheckSumDb()
4366 {
4367 	if ( checkSumDb() ) {
4368 		checkSumDb()->disconnect(m_scannerLog);
4369 		delete checkSumDb();
4370 	}
4371 	m_checkSumDb = new CheckSumDatabaseManager(this, m_settingsKey);
4372 	checkSumDb()->setSyncMode(QMC2_DB_SYNC_MODE_OFF);
4373 	checkSumDb()->setJournalMode(QMC2_DB_JOURNAL_MODE_MEMORY);
4374 	connect(checkSumDb(), SIGNAL(log(const QString &)), m_scannerLog, SLOT(log(const QString &)));
4375 }
4376 
pause()4377 void CheckSumScannerThread::pause()
4378 {
4379 	pauseRequested = true;
4380 }
4381 
resume()4382 void CheckSumScannerThread::resume()
4383 {
4384 	isPaused = false;
4385 }
4386 
checkSumExists(QString sha1,QString crc,quint64 size)4387 bool CheckSumScannerThread::checkSumExists(QString sha1, QString crc, quint64 size)
4388 {
4389 	if ( useHashCache )
4390 		return m_hashCache.contains(QString("%1-%2-%3").arg(sha1).arg(crc).arg(size));
4391 	else
4392 		return checkSumDb()->exists(sha1, crc, size);
4393 }
4394 
scanTime()4395 QString CheckSumScannerThread::scanTime()
4396 {
4397 	QTime elapsedTime(0, 0, 0, 0);
4398 	elapsedTime = elapsedTime.addMSecs(scanTimer.elapsed());
4399 	if ( elapsedTime.hour() > 0 )
4400 		return elapsedTime.toString("hh:mm:ss");
4401 	else
4402 		return elapsedTime.toString("mm:ss");
4403 }
4404 
emitlog(QString message)4405 void CheckSumScannerThread::emitlog(QString message)
4406 {
4407 	m_queuedMessages << QDateTime::currentDateTime().toString("hh:mm:ss.zzz") + ": " + message;
4408 	if ( logSyncMutex.tryLock() ) {
4409 		//bool oldSQM = qmc2SuppressQtMessages;
4410 		//qmc2SuppressQtMessages = true;
4411 		//moveToThread(this);
4412 		for (int i = 0; i < m_queuedMessages.count(); i++)
4413 			emit log(m_queuedMessages[i]);
4414 		m_queuedMessages.clear();
4415 		//qmc2SuppressQtMessages = oldSQM;
4416 		logSyncMutex.unlock();
4417 	}
4418 }
4419 
flushMessageQueue()4420 void CheckSumScannerThread::flushMessageQueue()
4421 {
4422 	if ( !m_queuedMessages.isEmpty() ) {
4423 		logSyncMutex.lock();
4424 		//bool oldSQM = qmc2SuppressQtMessages;
4425 		//qmc2SuppressQtMessages = true;
4426 		//moveToThread(this);
4427 		for (int i = 0; i < m_queuedMessages.count(); i++)
4428 			emit log(m_queuedMessages[i]);
4429 		m_queuedMessages.clear();
4430 		//qmc2SuppressQtMessages = oldSQM;
4431 		logSyncMutex.unlock();
4432 	}
4433 	QTimer::singleShot(0, m_scannerLog, SLOT(flushMessageQueue()));
4434 }
4435 
4436 #define QMC2_CHECKSUM_SCANNER_MESSAGE_QUEUE_FULL	(m_scannerLog->queuedMessages() >= QMC2_CHECKSUM_SCANNER_MAX_QUEUED_MSGS || m_queuedMessages.count() >= QMC2_CHECKSUM_SCANNER_MAX_QUEUED_MSGS)
4437 
run()4438 void CheckSumScannerThread::run()
4439 {
4440 	emitlog(tr("scanner thread started"));
4441 	while ( !exitThread ) {
4442 		emitlog(tr("waiting for work"));
4443 		mutex.lock();
4444 		isWaiting = true;
4445 		isActive = stopScan = isPaused = false;
4446 		waitCondition.wait(&mutex);
4447 		isActive = true;
4448 		isWaiting = false;
4449 		mutex.unlock();
4450 		if ( !exitThread && !stopScan ) {
4451 			emit scanStarted();
4452 			QTime elapsedTime(0, 0, 0, 0);
4453 			scanTimer.start();
4454 			QStringList fileList;
4455 			emit progressTextChanged(tr("Scanning"));
4456 			emit progressRangeChanged(0, 0);
4457 			emit progressChanged(-1);
4458 			foreach (QString path, scannedPaths) {
4459 				emitlog(tr("searching available files for path '%1'").arg(path));
4460 				QStringList pathFileList;
4461 				recursiveFileList(path, &pathFileList);
4462 				fileList.append(pathFileList);
4463 				emitlog(tr("found %n file(s) for path '%1'", "", pathFileList.count()).arg(path));
4464 				QTest::qWait(0);
4465 				if ( exitThread || stopScan )
4466 					break;
4467 			}
4468 			if ( scanIncrementally )
4469 				prepareIncrementalScan(&fileList);
4470 			else
4471 				checkSumDb()->recreateDatabase();
4472 			emit progressTextChanged(tr("Scanning"));
4473 			emit progressRangeChanged(0, fileList.count());
4474 			emit progressChanged(0);
4475 			emitlog(tr("starting database transaction"));
4476 			checkSumDb()->beginTransaction();
4477 			int counter = 0;
4478 			foreach (QString filePath, fileList) {
4479 				emitlog(tr("scan started for file '%1'").arg(filePath));
4480 				emit progressChanged(counter++);
4481 				QStringList memberList, sha1List, crcList;
4482 				QString sha1, crc;
4483 				QList<quint64> sizeList;
4484 				quint64 size;
4485 				bool isZip = false;
4486 				bool is7z = false;
4487 				int type = fileType(filePath, isZip, is7z);
4488 				bool doDbUpdate = true;
4489 				switch ( type ) {
4490 					case QMC2_CHECKSUM_SCANNER_FILE_ZIP:
4491 						if ( !scanZip(filePath, &memberList, &sizeList, &sha1List, &crcList) ) {
4492 							emitlog(tr("WARNING: scan failed for file '%1'").arg(filePath));
4493 							doDbUpdate = false;
4494 						}
4495 						break;
4496 					case QMC2_CHECKSUM_SCANNER_FILE_7Z:
4497 						if ( !scanSevenZip(filePath, &memberList, &sizeList, &sha1List, &crcList) ) {
4498 							emitlog(tr("WARNING: scan failed for file '%1'").arg(filePath));
4499 							doDbUpdate = false;
4500 						}
4501 						break;
4502 					case QMC2_CHECKSUM_SCANNER_FILE_CHD:
4503 						if ( !scanChd(filePath, &size, &sha1) ) {
4504 							emitlog(tr("WARNING: scan failed for file '%1'").arg(filePath));
4505 							doDbUpdate = false;
4506 						}
4507 						break;
4508 					case QMC2_CHECKSUM_SCANNER_FILE_REGULAR:
4509 						if ( !scanRegularFile(filePath, &size, &sha1, &crc) ) {
4510 							emitlog(tr("WARNING: scan failed for file '%1'").arg(filePath));
4511 							doDbUpdate = false;
4512 						}
4513 						break;
4514 #if defined(QMC2_LIBARCHIVE_ENABLED)
4515 					case QMC2_CHECKSUM_SCANNER_FILE_ARCHIVE:
4516 						if ( !scanArchive(filePath, &memberList, &sizeList, &sha1List, &crcList) ) {
4517 							emitlog(tr("WARNING: scan failed for file '%1'").arg(filePath));
4518 							doDbUpdate = false;
4519 						}
4520 						break;
4521 #endif
4522 					default:
4523 					case QMC2_CHECKSUM_SCANNER_FILE_NO_ACCESS:
4524 						emitlog(tr("WARNING: can't access file '%1', please check permissions").arg(filePath));
4525 						doDbUpdate = false;
4526 						break;
4527 				}
4528 				if ( exitThread || stopScan )
4529 					break;
4530 				if ( doDbUpdate ) {
4531 					switch ( type ) {
4532 						case QMC2_CHECKSUM_SCANNER_FILE_ZIP:
4533 						case QMC2_CHECKSUM_SCANNER_FILE_7Z:
4534 #if defined(QMC2_LIBARCHIVE_ENABLED)
4535 						case QMC2_CHECKSUM_SCANNER_FILE_ARCHIVE:
4536 #endif
4537 							for (int i = 0; i < memberList.count(); i++) {
4538 								if ( !checkSumExists(sha1List[i], crcList[i], sizeList[i]) ) {
4539 									emitlog(tr("database update") + ": " + tr("adding member '%1' from archive '%2' with SHA-1 '%3' and CRC '%4' to database").arg(memberList[i]).arg(filePath).arg(sha1List[i]).arg(crcList[i]));
4540 #if defined(QMC2_LIBARCHIVE_ENABLED)
4541 									checkSumDb()->setData(sha1List[i], crcList[i], sizeList[i], filePath, memberList[i], isZip ? "ZIP" : (is7z ? "7Z" : "UNKNOWN"));
4542 #else
4543 									checkSumDb()->setData(sha1List[i], crcList[i], sizeList[i], filePath, memberList[i], checkSumDb()->typeToName(type));
4544 #endif
4545 									if ( useHashCache )
4546 										m_hashCache[QString("%1-%2-%3").arg(sha1List[i]).arg(crcList[i]).arg(sizeList[i])] = true;
4547 									m_pendingUpdates++;
4548 								} else
4549 									emitlog(tr("database update") + ": " + tr("an object with SHA-1 '%1' and CRC '%2' already exists in the database").arg(sha1List[i]).arg(crcList[i]) + ", " + tr("member '%1' from archive '%2' ignored").arg(memberList[i]).arg(filePath));
4550 								if ( m_pendingUpdates >= QMC2_CHECKSUM_DB_MAX_TRANSACTIONS ) {
4551 									emitlog(tr("committing database transaction"));
4552 									checkSumDb()->setScanTime(QDateTime::currentDateTime().toTime_t());
4553 									checkSumDb()->commitTransaction();
4554 									m_pendingUpdates = 0;
4555 									emitlog(tr("starting database transaction"));
4556 									checkSumDb()->beginTransaction();
4557 								}
4558 								if ( QMC2_CHECKSUM_SCANNER_MESSAGE_QUEUE_FULL ) {
4559 									flushMessageQueue();
4560 									QTest::qWait(100);
4561 									yieldCurrentThread();
4562 								}
4563 							}
4564 							break;
4565 						case QMC2_CHECKSUM_SCANNER_FILE_CHD:
4566 							if ( !checkSumExists(sha1, crc, size) ) {
4567 								emitlog(tr("database update") + ": " + tr("adding CHD '%1' with SHA-1 '%2' to database").arg(filePath).arg(sha1));
4568 								checkSumDb()->setData(sha1, QString(), size, filePath, QString(), checkSumDb()->typeToName(type));
4569 								if ( useHashCache )
4570 									m_hashCache[QString("%1--%2").arg(sha1).arg(size)] = true;
4571 								m_pendingUpdates++;
4572 							} else
4573 								emitlog(tr("database update") + ": " + tr("an object with SHA-1 '%1' and CRC '%2' already exists in the database").arg(sha1).arg(crc) + ", " + tr("CHD '%1' ignored").arg(filePath));
4574 							break;
4575 						case QMC2_CHECKSUM_SCANNER_FILE_REGULAR:
4576 							if ( !checkSumExists(sha1, crc, size) ) {
4577 								emitlog(tr("database update") + ": " + tr("adding file '%1' with SHA-1 '%2' and CRC '%3' to database").arg(filePath).arg(sha1).arg(crc));
4578 								checkSumDb()->setData(sha1, crc, size, filePath, QString(), checkSumDb()->typeToName(type));
4579 								if ( useHashCache )
4580 									m_hashCache[QString("%1-%2-%3").arg(sha1).arg(crc).arg(size)] = true;
4581 								m_pendingUpdates++;
4582 							} else
4583 								emitlog(tr("database update") + ": " + tr("an object with SHA-1 '%1' and CRC '%2' already exists in the database").arg(sha1).arg(crc) + ", " + tr("file '%1' ignored").arg(filePath));
4584 							break;
4585 						default:
4586 							break;
4587 					}
4588 				}
4589 				if ( m_pendingUpdates >= QMC2_CHECKSUM_DB_MAX_TRANSACTIONS ) {
4590 					emitlog(tr("committing database transaction"));
4591 					checkSumDb()->setScanTime(QDateTime::currentDateTime().toTime_t());
4592 					checkSumDb()->commitTransaction();
4593 					m_pendingUpdates = 0;
4594 					emitlog(tr("starting database transaction"));
4595 					checkSumDb()->beginTransaction();
4596 				}
4597 				bool pauseMessageLogged = false;
4598 				while ( (pauseRequested || isPaused) && !exitThread && !stopScan ) {
4599 					if ( !pauseMessageLogged ) {
4600 						pauseMessageLogged = true;
4601 						isPaused = true;
4602 						pauseRequested = false;
4603 						emit scanPaused();
4604 						emitlog(tr("scanner paused"));
4605 						emit progressTextChanged(tr("Paused"));
4606 						flushMessageQueue();
4607 					}
4608 					QTest::qWait(100);
4609 					yieldCurrentThread();
4610 				}
4611 				if ( pauseMessageLogged && !exitThread && !stopScan ) {
4612 					isPaused = false;
4613 					emit scanResumed();
4614 					emitlog(tr("scanner resumed"));
4615 					emit progressTextChanged(tr("Scanning"));
4616 				}
4617 				if ( exitThread || stopScan )
4618 					break;
4619 				else
4620 					emitlog(tr("scan finished for file '%1'").arg(filePath));
4621 				if ( exitThread || stopScan )
4622 					break;
4623 				if ( QMC2_CHECKSUM_SCANNER_MESSAGE_QUEUE_FULL ) {
4624 					flushMessageQueue();
4625 					QTest::qWait(100);
4626 					yieldCurrentThread();
4627 				}
4628 			}
4629 			if ( exitThread || stopScan )
4630 				emitlog(tr("scanner interrupted"));
4631 			emitlog(tr("committing database transaction"));
4632 			checkSumDb()->setScanTime(QDateTime::currentDateTime().toTime_t());
4633 			checkSumDb()->commitTransaction();
4634 			if ( useHashCache ) {
4635 				m_hashCache.clear();
4636 				m_hashCache.squeeze();
4637 			}
4638 			m_pendingUpdates = 0;
4639 			emit progressTextChanged(tr("Idle"));
4640 			emit progressRangeChanged(0, 100);
4641 			emit progressChanged(0);
4642 			elapsedTime = elapsedTime.addMSecs(scanTimer.elapsed());
4643 			emitlog(tr("scan finished - total scanning time = %1, objects in database = %2, database size = %3").arg(elapsedTime.toString("hh:mm:ss.zzz")).arg(checkSumDb()->checkSumRowCount()).arg(ROMAlyzer::humanReadable(checkSumDb()->databaseSize())));
4644 			emit scanFinished();
4645 			flushMessageQueue();
4646 		}
4647 	}
4648 	emitlog(tr("scanner thread ended"));
4649 }
4650 
recursiveFileList(const QString & sDir,QStringList * fileNames)4651 void CheckSumScannerThread::recursiveFileList(const QString &sDir, QStringList *fileNames)
4652 {
4653 	if ( exitThread || stopScan )
4654 		return;
4655 #if defined(QMC2_OS_WIN)
4656 	WIN32_FIND_DATA ffd;
4657 	QString dirName(QDir::toNativeSeparators(QDir::cleanPath(sDir + "/*")));
4658 #ifdef UNICODE
4659 	HANDLE hFind = FindFirstFile((TCHAR *)dirName.utf16(), &ffd);
4660 #else
4661 	HANDLE hFind = FindFirstFile((TCHAR *)dirName.toUtf8().constData(), &ffd);
4662 #endif
4663 	if ( !exitThread && !stopScan && hFind != INVALID_HANDLE_VALUE ) {
4664 		do {
4665 #ifdef UNICODE
4666 			QString fName(QString::fromUtf16((ushort*)ffd.cFileName));
4667 #else
4668 			QString fName(QString::fromLocal8Bit(ffd.cFileName));
4669 #endif
4670 			if ( ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) {
4671 				if ( fName != ".." && fName != "." )
4672 					recursiveFileList(sDir + "/" + fName, fileNames);
4673 			} else
4674 				fileNames->append(sDir + "/" + fName);
4675 		} while ( !exitThread && !stopScan && FindNextFile(hFind, &ffd) != 0 );
4676 	}
4677 #else
4678 	QDir dir(sDir);
4679 	foreach (QFileInfo info, dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden | QDir::System)) {
4680 		if ( exitThread || stopScan )
4681 			break;
4682 		QString path(info.filePath());
4683 		if ( info.isDir() ) {
4684 			// directory recursion
4685 			if ( info.fileName() != ".." && info.fileName() != "." )
4686 				recursiveFileList(path, fileNames);
4687 		} else
4688 			fileNames->append(path);
4689 	}
4690 #endif
4691 }
4692 
fileType(QString fileName,bool & isZip,bool & is7z)4693 int CheckSumScannerThread::fileType(QString fileName, bool &isZip, bool &is7z)
4694 {
4695 	static QRegExp zipRx("[Zz][Ii][Pp]");
4696 	static QRegExp sevenZipRx("7[Zz]");
4697 	static QRegExp chdRx("[Cc][Hh][Dd]");
4698 
4699 	QFileInfo fileInfo(fileName);
4700 	if ( fileInfo.isReadable() ) {
4701 #if defined(QMC2_LIBARCHIVE_ENABLED)
4702 		if ( useLibArchive ) {
4703 			if ( fileInfo.suffix().indexOf(zipRx) == 0 ) {
4704 				isZip = true;
4705 				return QMC2_CHECKSUM_SCANNER_FILE_ARCHIVE;
4706 			}
4707 			if ( fileInfo.suffix().indexOf(sevenZipRx) == 0 ) {
4708 				is7z = true;
4709 				return QMC2_CHECKSUM_SCANNER_FILE_ARCHIVE;
4710 			}
4711 		} else {
4712 			if ( fileInfo.suffix().indexOf(zipRx) == 0 ) {
4713 				isZip = true;
4714 				return QMC2_CHECKSUM_SCANNER_FILE_ZIP;
4715 			}
4716 			if ( fileInfo.suffix().indexOf(sevenZipRx) == 0 ) {
4717 				is7z = true;
4718 				return QMC2_CHECKSUM_SCANNER_FILE_7Z;
4719 			}
4720 		}
4721 		if ( fileInfo.suffix().indexOf(chdRx) == 0 )
4722 			return QMC2_CHECKSUM_SCANNER_FILE_CHD;
4723 		return QMC2_CHECKSUM_SCANNER_FILE_REGULAR;
4724 #else
4725 		if ( fileInfo.suffix().indexOf(zipRx) == 0 )
4726 			return QMC2_CHECKSUM_SCANNER_FILE_ZIP;
4727 		if ( fileInfo.suffix().indexOf(sevenZipRx) == 0 )
4728 			return QMC2_CHECKSUM_SCANNER_FILE_7Z;
4729 		if ( fileInfo.suffix().indexOf(chdRx) == 0 )
4730 			return QMC2_CHECKSUM_SCANNER_FILE_CHD;
4731 		return QMC2_CHECKSUM_SCANNER_FILE_REGULAR;
4732 #endif
4733 	} else
4734 		return QMC2_CHECKSUM_SCANNER_FILE_NO_ACCESS;
4735 }
4736 
scanZip(QString fileName,QStringList * memberList,QList<quint64> * sizeList,QStringList * sha1List,QStringList * crcList)4737 bool CheckSumScannerThread::scanZip(QString fileName, QStringList *memberList, QList<quint64> *sizeList, QStringList *sha1List, QStringList *crcList)
4738 {
4739 	unzFile zipFile = unzOpen(fileName.toUtf8().constData());
4740 	if ( zipFile ) {
4741   		char ioBuffer[QMC2_ROMALYZER_ZIP_BUFFER_SIZE];
4742 		unz_file_info zipInfo;
4743 		do {
4744 			if ( exitThread || stopScan )
4745 				break;
4746 			if ( unzGetCurrentFileInfo(zipFile, &zipInfo, ioBuffer, QMC2_ROMALYZER_ZIP_BUFFER_SIZE, 0, 0, 0, 0) == UNZ_OK ) {
4747 				QString fn((const char *)ioBuffer);
4748 				if ( exitThread || stopScan )
4749 					break;
4750 				if ( deepScan ) {
4751 					if ( unzOpenCurrentFile(zipFile) == UNZ_OK ) {
4752 						quint64 memberSize = 0;
4753 						qint64 len;
4754 						QCryptographicHash sha1Hash(QCryptographicHash::Sha1);
4755 						ulong crc1 = crc32(0, 0, 0);
4756 						while ( (len = unzReadCurrentFile(zipFile, ioBuffer, QMC2_ROMALYZER_ZIP_BUFFER_SIZE)) > 0 ) {
4757 							QByteArray fileData((const char *)ioBuffer, len);
4758 							sha1Hash.addData(fileData);
4759 							if ( crc1 > 0 ) {
4760 								ulong crc2 = crc32(0, 0, 0);
4761 								crc2 = crc32(crc2, (const Bytef *)fileData.data(), fileData.size());
4762 								crc1 = crc32_combine(crc1, crc2, fileData.size());
4763 							} else
4764 								crc1 = crc32(crc1, (const Bytef *)fileData.data(), fileData.size());
4765 							memberSize += len;
4766 							if ( exitThread || stopScan )
4767 								break;
4768 						}
4769 						unzCloseCurrentFile(zipFile);
4770 						if ( !exitThread && !stopScan ) {
4771 							memberList->append(fn);
4772 							sizeList->append(memberSize);
4773 							sha1List->append(sha1Hash.result().toHex());
4774 							crcList->append(crcToString(crc1));
4775 							emitlog(tr("ZIP scan") + ": " + tr("member '%1' from archive '%2' has SHA-1 '%3' and CRC '%4'").arg(fn).arg(fileName).arg(sha1List->last()).arg(crcList->last()));
4776 						}
4777 					} else
4778 						emitlog(tr("ZIP scan") + ": " + tr("WARNING: can't open member '%1' from archive '%2'").arg(fn).arg(fileName));
4779 				} else {
4780 					memberList->append(fn);
4781 					sizeList->append(zipInfo.uncompressed_size);
4782 					sha1List->append(QString());
4783 					crcList->append(crcToString(zipInfo.crc));
4784 					emitlog(tr("ZIP scan") + ": " + tr("member '%1' from archive '%2' has SHA-1 '%3' and CRC '%4'").arg(fn).arg(fileName).arg(sha1List->last()).arg(crcList->last()));
4785 				}
4786 			}
4787 		} while ( unzGoToNextFile(zipFile) == UNZ_OK );
4788 		unzClose(zipFile);
4789 		return true;
4790 	} else
4791 		return false;
4792 }
4793 
scanSevenZip(QString fileName,QStringList * memberList,QList<quint64> * sizeList,QStringList * sha1List,QStringList * crcList)4794 bool CheckSumScannerThread::scanSevenZip(QString fileName, QStringList *memberList, QList<quint64> *sizeList, QStringList *sha1List, QStringList *crcList)
4795 {
4796 	SevenZipFile sevenZipFile(fileName);
4797 	if ( sevenZipFile.open() ) {
4798 		foreach (SevenZipMetaData metaData, sevenZipFile.entryList()) {
4799 			if ( exitThread || stopScan )
4800 				break;
4801 			if ( deepScan ) {
4802 				QByteArray fileData;
4803 				quint64 readLength = sevenZipFile.read(metaData.name(), &fileData);
4804 				if ( readLength > 0 ) {
4805 					if ( exitThread || stopScan )
4806 						break;
4807 					memberList->append(metaData.name());
4808 					sizeList->append(metaData.size());
4809 					QCryptographicHash sha1Hash(QCryptographicHash::Sha1);
4810 					sha1Hash.addData(fileData);
4811 					sha1List->append(sha1Hash.result().toHex());
4812 					ulong crc = crc32(0, 0, 0);
4813 					crc = crc32(crc, (const Bytef *)fileData.data(), fileData.size());
4814 					crcList->append(crcToString(crc));
4815 					emitlog(tr("7Z scan") + ": " + tr("member '%1' from archive '%2' has SHA-1 '%3' and CRC '%4'").arg(metaData.name()).arg(fileName).arg(sha1List->last()).arg(crcList->last()));
4816 				} else
4817 					emitlog(tr("7Z scan") + ": " + tr("WARNING: can't read member '%1' from archive '%2'").arg(metaData.name()).arg(fileName));
4818 			} else {
4819 				memberList->append(metaData.name());
4820 				sizeList->append(metaData.size());
4821 				sha1List->append(QString());
4822 				crcList->append(metaData.crc());
4823 				emitlog(tr("7Z scan") + ": " + tr("member '%1' from archive '%2' has SHA-1 '%3' and CRC '%4'").arg(metaData.name()).arg(fileName).arg(sha1List->last()).arg(crcList->last()));
4824 			}
4825 		}
4826 		sevenZipFile.close();
4827 		return true;
4828 	} else
4829 		return false;
4830 }
4831 
4832 #if defined(QMC2_LIBARCHIVE_ENABLED)
scanArchive(QString fileName,QStringList * memberList,QList<quint64> * sizeList,QStringList * sha1List,QStringList * crcList)4833 bool CheckSumScannerThread::scanArchive(QString fileName, QStringList *memberList, QList<quint64> *sizeList, QStringList *sha1List, QStringList *crcList)
4834 {
4835 	ArchiveFile archiveFile(fileName, true);
4836 	if ( archiveFile.open() ) {
4837 		ArchiveEntryMetaData metaData;
4838 		while ( archiveFile.seekNextEntry(&metaData) ) {
4839 			if ( exitThread || stopScan )
4840 				break;
4841 			QByteArray ba;
4842 			if ( archiveFile.readEntry(ba) > 0 ) {
4843 				QCryptographicHash sha1Hash(QCryptographicHash::Sha1);
4844 				sha1Hash.addData(ba);
4845 				ulong crc = crc32(0, 0, 0);
4846 				crc = crc32(crc, (const Bytef *)ba.data(), ba.size());
4847 				memberList->append(metaData.name());
4848 				sizeList->append(metaData.size());
4849 				sha1List->append(sha1Hash.result().toHex());
4850 				crcList->append(crcToString(crc));
4851 				emitlog(tr("archive scan") + ": " + tr("member '%1' from archive '%2' has SHA-1 '%3' and CRC '%4'").arg(metaData.name()).arg(fileName).arg(sha1List->last()).arg(crcList->last()));
4852 			} else
4853 				emitlog(tr("archive scan") + ": " + tr("WARNING: can't read member '%1' from archive '%2'").arg(metaData.name()).arg(fileName));
4854 		}
4855 		archiveFile.close();
4856 		return true;
4857 	} else
4858 		return false;
4859 }
4860 #endif
4861 
scanChd(QString fileName,quint64 * size,QString * sha1)4862 bool CheckSumScannerThread::scanChd(QString fileName, quint64 *size, QString *sha1)
4863 {
4864 	QFile file(fileName);
4865 	if ( file.open(QIODevice::ReadOnly) ) {
4866   		char ioBuffer[QMC2_ROMALYZER_FILE_BUFFER_SIZE];
4867 		bool success = true;
4868 		int len = 0;
4869 		quint32 chdVersion = 0;
4870 		if ( (len = file.read(ioBuffer, QMC2_CHD_HEADER_V3_LENGTH)) > 0 ) {
4871 			if ( len < QMC2_CHD_HEADER_V3_LENGTH ) {
4872 				emitlog(tr("CHD scan") + ": " + tr("WARNING: can't read CHD '%1'").arg(fileName));
4873 				success = false;
4874 			} else {
4875 				chdVersion = QMC2_TO_UINT32(ioBuffer + QMC2_CHD_HEADER_VERSION_OFFSET);
4876 				switch ( chdVersion ) {
4877 					case 3: {
4878 						QByteArray sha1Data((const char *)(ioBuffer + QMC2_CHD_HEADER_V3_SHA1_OFFSET), QMC2_CHD_HEADER_V3_SHA1_LENGTH);
4879 						*sha1 = QString(sha1Data.toHex());
4880 						break;
4881 					}
4882 					case 4: {
4883 						QByteArray sha1Data((const char *)(ioBuffer + QMC2_CHD_HEADER_V4_SHA1_OFFSET), QMC2_CHD_HEADER_V4_SHA1_LENGTH);
4884 						*sha1 = QString(sha1Data.toHex());
4885 						break;
4886 					}
4887 					case 5: {
4888 						QByteArray sha1Data((const char *)(ioBuffer + QMC2_CHD_HEADER_V5_SHA1_OFFSET), QMC2_CHD_HEADER_V5_SHA1_LENGTH);
4889 						*sha1 = QString(sha1Data.toHex());
4890 						break;
4891 					}
4892 					default: {
4893 						emitlog(tr("CHD scan") + ": " + tr("WARNING: version '%1' of CHD '%2' unknown").arg(fileName));
4894 						success = false;
4895 						break;
4896 					}
4897 				}
4898 				*size = file.size();
4899 			}
4900 		} else {
4901 			emitlog(tr("CHD scan") + ": " + tr("WARNING: can't read CHD '%1'").arg(fileName));
4902 			success = false;
4903 		}
4904 		file.close();
4905 		if ( !success )
4906 			return false;
4907 		if ( exitThread || stopScan )
4908 			return true;
4909 		emitlog(tr("CHD scan") + ": " + tr("CHD '%1' has SHA-1 '%2' (CHD v%3)").arg(fileName).arg(*sha1).arg(chdVersion));
4910 		return true;
4911 	} else
4912 		return false;
4913 }
4914 
scanRegularFile(QString fileName,quint64 * size,QString * sha1,QString * crc)4915 bool CheckSumScannerThread::scanRegularFile(QString fileName, quint64 *size, QString *sha1, QString *crc)
4916 {
4917 	QFile file(fileName);
4918 	if ( file.open(QIODevice::ReadOnly) ) {
4919   		char ioBuffer[QMC2_ROMALYZER_FILE_BUFFER_SIZE];
4920 		QCryptographicHash sha1Hash(QCryptographicHash::Sha1);
4921 		ulong crc1 = crc32(0, 0, 0);
4922 		int len = 0;
4923 		while ( (len = file.read(ioBuffer, QMC2_ROMALYZER_FILE_BUFFER_SIZE)) > 0 ) {
4924 			QByteArray fileData((const char *)ioBuffer, len);
4925 			sha1Hash.addData(fileData);
4926 			if ( crc1 > 0 ) {
4927 				ulong crc2 = crc32(0, 0, 0);
4928 				crc2 = crc32(crc2, (const Bytef *)fileData.data(), fileData.size());
4929 				crc1 = crc32_combine(crc1, crc2, fileData.size());
4930 			} else
4931 				crc1 = crc32(crc1, (const Bytef *)fileData.data(), fileData.size());
4932 			if ( exitThread || stopScan )
4933 				break;
4934 		}
4935 		*size = file.size();
4936 		file.close();
4937 		if ( exitThread || stopScan )
4938 			return true;
4939 		*sha1 = sha1Hash.result().toHex();
4940 		*crc = crcToString(crc1);
4941 		emitlog(tr("file scan") + ": " + tr("file '%1' has SHA-1 '%2' and CRC '%3'").arg(fileName).arg(*sha1).arg(*crc));
4942 		return true;
4943 	} else
4944 		return false;
4945 }
4946