25
25
#include < QStatusBar>
26
26
#include < QStyle>
27
27
#include < QStyleFactory>
28
+ #include < QThread>
28
29
29
30
#include < sapp/FilesystemSearchProvider.h>
30
31
@@ -42,24 +43,25 @@ constexpr auto VPK_SAVE_FILTER = "Valve PacK (*.vpk);;All files (*.*)";
42
43
43
44
Window::Window (QSettings& options, QWidget* parent)
44
45
: QMainWindow(parent)
46
+ , extractWorkerThread(nullptr )
45
47
, modified(false ) {
46
48
this ->setWindowIcon (QIcon (" :/icon.png" ));
47
49
this ->setMinimumSize (900 , 500 );
48
50
49
51
// File menu
50
52
auto * fileMenu = this ->menuBar ()->addMenu (tr (" &File" ));
51
- fileMenu->addAction (this ->style ()->standardIcon (QStyle::SP_FileIcon), tr (" &Create Empty..." ), Qt::CTRL | Qt::Key_N, [=] {
53
+ this -> createEmptyVPKAction = fileMenu->addAction (this ->style ()->standardIcon (QStyle::SP_FileIcon), tr (" &Create Empty..." ), Qt::CTRL | Qt::Key_N, [=] {
52
54
this ->newVPK (false );
53
55
});
54
- fileMenu->addAction (this ->style ()->standardIcon (QStyle::SP_FileIcon), tr (" Create From &Folder..." ), Qt::CTRL | Qt::Key_N, [=] {
56
+ this -> createVPKFromDirAction = fileMenu->addAction (this ->style ()->standardIcon (QStyle::SP_FileIcon), tr (" Create From &Folder..." ), Qt::CTRL | Qt::Key_N, [=] {
55
57
this ->newVPK (true );
56
58
});
57
- fileMenu->addAction (this ->style ()->standardIcon (QStyle::SP_DirIcon), tr (" &Open..." ), Qt::CTRL | Qt::Key_O, [=] {
59
+ this -> openVPKAction = fileMenu->addAction (this ->style ()->standardIcon (QStyle::SP_DirIcon), tr (" &Open..." ), Qt::CTRL | Qt::Key_O, [=] {
58
60
this ->openVPK ();
59
61
});
60
62
61
63
if (CFileSystemSearchProvider provider; provider.Available ()) {
62
- auto * openRelativeToMenu = fileMenu->addMenu (this ->style ()->standardIcon (QStyle::SP_DirLinkIcon), tr (" Open &In..." ));
64
+ this -> openVPKRelativeToMenu = fileMenu->addMenu (this ->style ()->standardIcon (QStyle::SP_DirLinkIcon), tr (" Open &In..." ));
63
65
64
66
QList<std::tuple<QString, QString, QDir>> sourceGames;
65
67
auto installedSteamAppCount = provider.GetNumInstalledApps ();
@@ -83,10 +85,12 @@ Window::Window(QSettings& options, QWidget* parent)
83
85
84
86
for (const auto & [gameName, iconPath, relativeDirectoryPath] : sourceGames) {
85
87
const auto relativeDirectory = relativeDirectoryPath.path ();
86
- openRelativeToMenu ->addAction (QIcon (iconPath), gameName, [=] {
88
+ this -> openVPKRelativeToMenu ->addAction (QIcon (iconPath), gameName, [=] {
87
89
this ->openVPK (relativeDirectory);
88
90
});
89
91
}
92
+ } else {
93
+ this ->openVPKRelativeToMenu = nullptr ;
90
94
}
91
95
92
96
this ->saveVPKAction = fileMenu->addAction (this ->style ()->standardIcon (QStyle::SP_DialogSaveButton), tr (" &Save" ), Qt::CTRL | Qt::Key_S, [=] {
@@ -410,19 +414,19 @@ void Window::aboutQt() {
410
414
}
411
415
412
416
std::optional<std::vector<std::byte>> Window::readBinaryEntry (const QString& path) {
413
- auto entry = (* this ->vpk ). findEntry (path.toStdString ());
417
+ auto entry = this ->vpk -> findEntry (path.toStdString ());
414
418
if (!entry) {
415
419
return std::nullopt;
416
420
}
417
- return (* this ->vpk ). readBinaryEntry (*entry);
421
+ return this ->vpk -> readBinaryEntry (*entry);
418
422
}
419
423
420
424
std::optional<QString> Window::readTextEntry (const QString& path) {
421
- auto entry = (* this ->vpk ). findEntry (path.toStdString ());
425
+ auto entry = this ->vpk -> findEntry (path.toStdString ());
422
426
if (!entry) {
423
427
return std::nullopt;
424
428
}
425
- auto textData = (* this ->vpk ). readTextEntry (*entry);
429
+ auto textData = this ->vpk -> readTextEntry (*entry);
426
430
if (!textData) {
427
431
return std::nullopt;
428
432
}
@@ -442,7 +446,7 @@ void Window::selectSubItemInDir(const QString& name) {
442
446
}
443
447
444
448
void Window::extractFile (const QString& path, QString savePath) {
445
- auto entry = (* this ->vpk ). findEntry (path.toStdString ());
449
+ auto entry = this ->vpk -> findEntry (path.toStdString ());
446
450
if (!entry) {
447
451
QMessageBox::critical (this , tr (" Error" ), " Failed to find file in VPK." );
448
452
return ;
@@ -466,23 +470,48 @@ void Window::extractFile(const QString& path, QString savePath) {
466
470
}
467
471
468
472
void Window::extractFilesIf (const QString& saveDir, const std::function<bool (const QString&)>& predicate) {
469
- for (const auto & [directory, entries] : (*this ->vpk ).getEntries ()) {
470
- QString dir (directory.c_str ());
471
- if (!predicate (dir)) {
473
+ // Set up progress bar
474
+ this ->statusText ->hide ();
475
+ this ->statusProgressBar ->show ();
476
+
477
+ // Get progress bar maximum
478
+ int progressBarMax = 0 ;
479
+ for (const auto & [directory, entries] : this ->vpk ->getEntries ()) {
480
+ if (!predicate (QString (directory.c_str ()))) {
472
481
continue ;
473
482
}
483
+ progressBarMax += static_cast <int >(entries.size ());
484
+ }
474
485
475
- QDir qDir;
476
- if (!qDir.mkpath (saveDir + ' /' + dir)) {
477
- QMessageBox::critical (this , tr (" Error" ), " Failed to create directory." );
478
- return ;
479
- }
486
+ this ->statusProgressBar ->setMinimum (0 );
487
+ this ->statusProgressBar ->setMaximum (progressBarMax);
488
+ this ->statusProgressBar ->setValue (0 );
480
489
481
- for (const auto & entry : entries) {
482
- auto filePath = saveDir + ' /' + dir + ' /' + entry.filename .c_str ();
483
- this ->writeEntryToFile (filePath, entry);
484
- }
485
- }
490
+ this ->freezeActions (true );
491
+
492
+ // Set up thread
493
+ this ->extractWorkerThread = new QThread (this );
494
+ auto * worker = new ExtractVPKWorker ();
495
+ worker->moveToThread (this ->extractWorkerThread );
496
+ QObject::connect (this ->extractWorkerThread , &QThread::started, worker, [=] {
497
+ worker->run (this , saveDir, predicate);
498
+ });
499
+ QObject::connect (worker, &ExtractVPKWorker::progressUpdated, this , [=] {
500
+ this ->statusProgressBar ->setValue (this ->statusProgressBar ->value () + 1 );
501
+ });
502
+ QObject::connect (worker, &ExtractVPKWorker::taskFinished, this , [=] {
503
+ // Kill thread
504
+ this ->extractWorkerThread ->quit ();
505
+ this ->extractWorkerThread ->wait ();
506
+ delete this ->extractWorkerThread ;
507
+ this ->extractWorkerThread = nullptr ;
508
+
509
+ this ->freezeActions (false );
510
+
511
+ this ->statusText ->show ();
512
+ this ->statusProgressBar ->hide ();
513
+ });
514
+ this ->extractWorkerThread ->start ();
486
515
}
487
516
488
517
void Window::extractDir (const QString& path, QString saveDir) {
@@ -503,7 +532,7 @@ void Window::extractAll(QString saveDir) {
503
532
return ;
504
533
}
505
534
saveDir += ' /' ;
506
- saveDir += (* this ->vpk ). getPrettyFileName ();
535
+ saveDir += this ->vpk -> getRealFileName ();
507
536
508
537
this ->extractFilesIf (saveDir, [](const QString&) { return true ; });
509
538
}
@@ -547,15 +576,12 @@ void Window::clearContents() {
547
576
this ->searchBar ->setDisabled (true );
548
577
549
578
this ->entryTree ->clearContents ();
579
+ this ->entryTree ->setDisabled (true );
550
580
551
581
this ->fileViewer ->clearContents ();
552
582
553
- this ->saveAsVPKAction ->setDisabled (true );
554
- this ->closeFileAction ->setDisabled (true );
555
- this ->addFileAction ->setDisabled (true );
556
- this ->extractAllAction ->setDisabled (true );
557
-
558
583
this ->markModified (false );
584
+ this ->freezeActions (true , false ); // Leave create/open unfrozen
559
585
}
560
586
561
587
void Window::closeEvent (QCloseEvent* event) {
@@ -566,11 +592,29 @@ void Window::closeEvent(QCloseEvent* event) {
566
592
event->accept ();
567
593
}
568
594
595
+ void Window::freezeActions (bool freeze, bool freezeCreationActions) {
596
+ this ->createEmptyVPKAction ->setDisabled (freeze && freezeCreationActions);
597
+ this ->createVPKFromDirAction ->setDisabled (freeze && freezeCreationActions);
598
+ this ->openVPKAction ->setDisabled (freeze && freezeCreationActions);
599
+ if (this ->openVPKRelativeToMenu ) this ->openVPKRelativeToMenu ->setDisabled (freeze && freezeCreationActions);
600
+ this ->saveVPKAction ->setDisabled (freeze || !this ->modified );
601
+ this ->saveAsVPKAction ->setDisabled (freeze);
602
+ this ->closeFileAction ->setDisabled (freeze);
603
+ this ->addFileAction ->setDisabled (freeze);
604
+ this ->extractAllAction ->setDisabled (freeze);
605
+
606
+ this ->searchBar ->setDisabled (freeze);
607
+ this ->entryTree ->setDisabled (freeze);
608
+ this ->fileViewer ->setDisabled (freeze);
609
+ }
610
+
569
611
bool Window::loadVPK (const QString& path) {
570
612
QString fixedPath (path);
571
613
fixedPath.replace (' \\ ' , ' /' );
572
614
573
615
this ->clearContents ();
616
+ this ->freezeActions (true );
617
+
574
618
this ->vpk = VPK::open (fixedPath.toStdString ());
575
619
if (!this ->vpk ) {
576
620
QMessageBox::critical (this , tr (" Error" ), " Unable to load given VPK. Please ensure you are loading a "
@@ -584,13 +628,8 @@ bool Window::loadVPK(const QString& path) {
584
628
this ->statusText ->hide ();
585
629
this ->statusProgressBar ->show ();
586
630
587
- this ->searchBar ->setDisabled (false );
588
-
589
631
this ->entryTree ->loadVPK (this ->vpk .value (), this ->statusProgressBar , [=] {
590
- this ->saveAsVPKAction ->setDisabled (false );
591
- this ->closeFileAction ->setDisabled (false );
592
- this ->addFileAction ->setDisabled (false );
593
- this ->extractAllAction ->setDisabled (false );
632
+ this ->freezeActions (false );
594
633
595
634
this ->statusText ->setText (' ' + QString (" Loaded \" " ) + path + ' \" ' );
596
635
this ->statusText ->show ();
@@ -601,7 +640,7 @@ bool Window::loadVPK(const QString& path) {
601
640
}
602
641
603
642
void Window::writeEntryToFile (const QString& path, const VPKEntry& entry) {
604
- auto data = (* this ->vpk ). readBinaryEntry (entry);
643
+ auto data = this ->vpk -> readBinaryEntry (entry);
605
644
if (!data) {
606
645
QMessageBox::critical (this , tr (" Error" ), QString (" Failed to read data from the VPK for \" " ) + entry.filename .c_str () + " \" . Please ensure that a game or another application is not using the VPK." );
607
646
return ;
@@ -617,3 +656,26 @@ void Window::writeEntryToFile(const QString& path, const VPKEntry& entry) {
617
656
}
618
657
file.close ();
619
658
}
659
+
660
+ void ExtractVPKWorker::run (Window* window, const QString& saveDir, const std::function<bool (const QString&)>& predicate) {
661
+ int currentEntry = 0 ;
662
+ for (const auto & [directory, entries] : window->vpk ->getEntries ()) {
663
+ QString dir (directory.c_str ());
664
+ if (!predicate (dir)) {
665
+ continue ;
666
+ }
667
+
668
+ QDir qDir;
669
+ if (!qDir.mkpath (saveDir + ' /' + dir)) {
670
+ QMessageBox::critical (window, tr (" Error" ), " Failed to create directory." );
671
+ return ;
672
+ }
673
+
674
+ for (const auto & entry : entries) {
675
+ auto filePath = saveDir + ' /' + dir + ' /' + entry.filename .c_str ();
676
+ window->writeEntryToFile (filePath, entry);
677
+ emit progressUpdated (++currentEntry);
678
+ }
679
+ }
680
+ emit taskFinished ();
681
+ }
0 commit comments