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("&", "&").replace("<", "<").replace(">", ">").replace(""", "\"").replace("'", "'"));
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("&", "&").replace("<", "<").replace(">", ">").replace(""", "\"").replace("'", "'"));
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) + " | Δ " + 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