From f3a0d0abc433ab1b147efa055b6692a3151fa1b9 Mon Sep 17 00:00:00 2001 From: troglodyte <122689783+troglodyte9@users.noreply.github.com> Date: Sat, 23 May 2026 20:06:55 +0300 Subject: [PATCH] gui-qt: don't crash when deleting an archive locked by another process (#3956) * gui-qt: don't crash on install file delete failures when you install a .zip, .vpk, .pkg, or firmware .pup and tick the matching "delete after install" option, vita3k tries to remove the installer file from disk. the archive and pkg dialogs use the throwing fs::remove overload, which raises a filesystem_error when the file is locked by another process. since the call runs inside a qt slot, the exception escapes into the qt event loop and the whole emulator goes down with it. the firmware dialog uses QFile::remove instead, which returns a bool that the code was ignoring, so a locked firmware file silently stuck around with no feedback to the user. leaving the installer open in something like 7-zip while installing is enough to trigger either case. this swaps fs::remove for the non-throwing error_code overload in the archive and pkg dialogs, and checks the bool from QFile::remove in the firmware dialog. anything that could not be removed gets shown in a warning so the user knows what stuck around, and the install itself still finishes normally. * gui-qt: route firmware drops through the install dialog dropping a .pup onto the main window used to run install_pup inline with a bare QProgressDialog and its own success/failure handling, bypassing FirmwareInstallDialog entirely. that path duplicated the progress UI and never offered the "delete firmware file after install?" option that the menu-triggered flow exposes. route the drop through FirmwareInstallDialog instead, matching the existing .pkg and .vpk drop routing right next to it. this adds a constructor that takes a pre-supplied firmware path so the dialog skips its file picker when invoked from a drop. dropped firmware installs now share the threaded progress UI, the warning when the file is locked, and the optional cleanup checkbox with the menu trigger. --- .../include/gui-qt/firmware_install_dialog.h | 1 + vita3k/gui-qt/src/archive_install_dialog.cpp | 15 +++++++-- vita3k/gui-qt/src/firmware_install_dialog.cpp | 21 +++++++++++-- vita3k/gui-qt/src/main_window.cpp | 31 +++---------------- vita3k/gui-qt/src/pkg_install_dialog.cpp | 23 +++++++++++--- 5 files changed, 57 insertions(+), 34 deletions(-) diff --git a/vita3k/gui-qt/include/gui-qt/firmware_install_dialog.h b/vita3k/gui-qt/include/gui-qt/firmware_install_dialog.h index ff4bed1e2..bb2b3f644 100644 --- a/vita3k/gui-qt/include/gui-qt/firmware_install_dialog.h +++ b/vita3k/gui-qt/include/gui-qt/firmware_install_dialog.h @@ -52,6 +52,7 @@ class FirmwareInstallDialog : public QDialog { Q_OBJECT public: explicit FirmwareInstallDialog(EmuEnvState &emuenv, QWidget *parent = nullptr); + FirmwareInstallDialog(EmuEnvState &emuenv, const QString &path, QWidget *parent = nullptr); Q_SIGNALS: void install_complete(); diff --git a/vita3k/gui-qt/src/archive_install_dialog.cpp b/vita3k/gui-qt/src/archive_install_dialog.cpp index 1b49a8ba0..cd7087cab 100644 --- a/vita3k/gui-qt/src/archive_install_dialog.cpp +++ b/vita3k/gui-qt/src/archive_install_dialog.cpp @@ -431,9 +431,20 @@ void ArchiveInstallDialog::run_install(const std::vector &archives) { [this, del_check, results]() { if (del_check->isChecked()) { std::set deleted; + QStringList failed; for (const auto &r : results) { - if (!r.archive_full_path.empty() && deleted.insert(r.archive_full_path).second) - fs::remove(r.archive_full_path); + if (r.archive_full_path.empty() || !deleted.insert(r.archive_full_path).second) + continue; + // fs::remove throws and crashes the app when the archive is locked so use the non throwing overload + boost::system::error_code error; + fs::remove(r.archive_full_path, error); + if (error) + failed.push_back(r.archive_name); + } + + if (!failed.isEmpty()) { + QMessageBox::warning(this, tr("Could Not Delete Archives"), + tr("The following archive files could not be deleted. They may still be open in another program:\n\n%1").arg(failed.join(QStringLiteral("\n")))); } } Q_EMIT install_complete(results); diff --git a/vita3k/gui-qt/src/firmware_install_dialog.cpp b/vita3k/gui-qt/src/firmware_install_dialog.cpp index 1291ea479..9408b202d 100644 --- a/vita3k/gui-qt/src/firmware_install_dialog.cpp +++ b/vita3k/gui-qt/src/firmware_install_dialog.cpp @@ -60,6 +60,20 @@ FirmwareInstallDialog::FirmwareInstallDialog(EmuEnvState &emuenv, QWidget *paren run_install(path); } +FirmwareInstallDialog::FirmwareInstallDialog(EmuEnvState &emuenv, const QString &path, QWidget *parent) + : QDialog(parent) + , m_emuenv(emuenv) { + setWindowTitle(tr("Install Firmware")); + setModal(true); + + if (path.isEmpty()) { + QMetaObject::invokeMethod(this, &QDialog::reject, Qt::QueuedConnection); + return; + } + + run_install(path); +} + void FirmwareInstallDialog::run_install(const QString &path) { auto *main_layout = new QVBoxLayout(this); @@ -122,8 +136,11 @@ void FirmwareInstallDialog::run_install(const QString &path) { layout()->addWidget(buttons); connect(buttons, &QDialogButtonBox::accepted, this, [this, del_check, path]() { - if (del_check->isChecked()) - QFile::remove(path); + // QFile::remove returns false silently when the firmware file is locked, so surface that to the user + if (del_check->isChecked() && !QFile::remove(path)) { + QMessageBox::warning(this, tr("Could Not Delete Firmware"), + tr("The firmware file could not be deleted. It may still be open in another program:\n\n%1").arg(path)); + } Q_EMIT install_complete(); accept(); }); diff --git a/vita3k/gui-qt/src/main_window.cpp b/vita3k/gui-qt/src/main_window.cpp index b7cd41335..62af5c9ca 100644 --- a/vita3k/gui-qt/src/main_window.cpp +++ b/vita3k/gui-qt/src/main_window.cpp @@ -93,7 +93,6 @@ #include #include #include -#include #include #include #include @@ -715,31 +714,11 @@ void MainWindow::handle_drop(const QString &path) { const std::string ext = string_utils::tolower(drop_path.extension().string()); if (ext == ".pup") { - QProgressDialog prog(tr("Installing firmware\u2026"), - /*cancel=*/QString(), 0, 100, this); - prog.setWindowTitle(tr("Install Firmware")); - prog.setWindowModality(Qt::WindowModal); - prog.setMinimumDuration(0); - prog.setValue(0); - - const std::string fw_version = install_pup( - emuenv.pref_path, - drop_path, - [&prog](uint32_t pct) { - prog.setValue(static_cast(pct)); - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - }); - - prog.setValue(100); - - if (!fw_version.empty()) { - LOG_INFO("Firmware {} installed successfully via drop!", fw_version); - on_install_finished(); - } else { - QMessageBox::critical(this, tr("Install Failed"), - tr("Failed to install the dropped firmware file.\n" - "Check the log for details.")); - } + auto *dlg = new FirmwareInstallDialog(emuenv, path, this); + connect(dlg, &FirmwareInstallDialog::install_complete, + this, &MainWindow::on_install_finished); + connect(dlg, &QDialog::finished, dlg, &QObject::deleteLater); + dlg->exec(); } else if (ext == ".vpk" || ext == ".zip") { auto *dlg = new ArchiveInstallDialog(emuenv, { drop_path }, this); diff --git a/vita3k/gui-qt/src/pkg_install_dialog.cpp b/vita3k/gui-qt/src/pkg_install_dialog.cpp index de52eac62..6a52306d4 100644 --- a/vita3k/gui-qt/src/pkg_install_dialog.cpp +++ b/vita3k/gui-qt/src/pkg_install_dialog.cpp @@ -322,10 +322,25 @@ void PkgInstallDialog::run_install(const fs::path &pkg_path, connect(buttons, &QDialogButtonBox::accepted, this, [this, del_pkg, del_bin, pkg_path, title_id]() { - if (del_pkg->isChecked()) - fs::remove(pkg_path); - if (del_bin->isChecked() && !m_license_path.empty()) - fs::remove(m_license_path); + QStringList failed; + // fs::remove throws and crashes the app when the file is locked so use the non throwing overload + if (del_pkg->isChecked()) { + boost::system::error_code error; + fs::remove(pkg_path, error); + if (error) + failed.push_back(QString::fromStdString(pkg_path.filename().string())); + } + if (del_bin->isChecked() && !m_license_path.empty()) { + boost::system::error_code error; + fs::remove(m_license_path, error); + if (error) + failed.push_back(QString::fromStdString(m_license_path.filename().string())); + } + + if (!failed.isEmpty()) { + QMessageBox::warning(this, tr("Could Not Delete Files"), + tr("The following files could not be deleted. They may still be open in another program:\n\n%1").arg(failed.join(QStringLiteral("\n")))); + } Q_EMIT install_complete(title_id); accept();