diff options
| author | PastaLaPate <pastalapate@proton.me> | 2026-11-02 20:34:15 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-11-02 20:34:15 +0100 |
| commit | 6f002badce45fa2fdb414043544c829e834a7e27 (patch) | |
| tree | 51c21a851fbc8218ebe7febb69fb710a891cc4fc /src/yuzu/main_window.cpp | |
| parent | 396f011ea875dfe4fdb1b0f489651ab74a505b74 (diff) | |
| parent | 046646e3514ca4950622d75609e68498f889989e (diff) | |
Merge branch 'nx2' into ci/qt-6.12.0
Diffstat (limited to 'src/yuzu/main_window.cpp')
| -rw-r--r-- | src/yuzu/main_window.cpp | 213 |
1 files changed, 139 insertions, 74 deletions
diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 2b43bcd2d0..e5a8f81424 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -42,6 +42,7 @@ #include "set_play_time_dialog.h" #include "util/util.h" #include "yuzu/game/game_list.h" +#include "yuzu/nup_dialogs.h" #include "applets/qt_amiibo_settings.h" #include "applets/qt_controller.h" @@ -73,8 +74,11 @@ #include <QClipboard> #include <QDesktopServices> #include <QDir> +#include <QElapsedTimer> +#include <QEventLoop> #include <QFileDialog> #include <QFileInfo> +#include <QFutureWatcher> #include <QGuiApplication> #include <QInputDialog> #include <QMimeData> @@ -451,7 +455,7 @@ MainWindow::MainWindow(bool has_broken_vulkan) QtCommon::SetupContentProviders(); // Gen keys if necessary - OnCheckFirmwareDecryption(); + OnCheckFirmwareDecryption(false); #ifdef __unix__ OnCheckGraphicsBackend(); @@ -1293,7 +1297,6 @@ void MainWindow::InitializeHotkeys() { hotkey_registry.LoadHotkeys(); LinkActionShortcut(ui->action_Load_File, QStringLiteral("Load File")); - LinkActionShortcut(ui->action_Load_Amiibo, QStringLiteral("Load/Remove Amiibo")); LinkActionShortcut(ui->action_Exit, QStringLiteral("Exit Eglantine")); LinkActionShortcut(ui->action_Restart, QStringLiteral("Restart Emulation")); LinkActionShortcut(ui->action_Pause, QStringLiteral("Continue/Pause Emulation")); @@ -1833,24 +1836,6 @@ bool MainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPar const Core::SystemResultStatus result{ QtCommon::system->Load(*render_window, filename.toStdString(), params)}; - const auto drd_callout = (UISettings::values.callout_flags.GetValue() & - static_cast<u32>(CalloutFlag::DRDDeprecation)) == 0; - - if (result == Core::SystemResultStatus::Success && - QtCommon::system->GetAppLoader().GetFileType() == - Loader::FileType::DeconstructedRomDirectory && - drd_callout) { - UISettings::values.callout_flags = UISettings::values.callout_flags.GetValue() | - static_cast<u32>(CalloutFlag::DRDDeprecation); - QMessageBox::warning( - this, tr("Warning: Outdated Game Format"), - tr("You are using the deconstructed ROM directory format for this game, which is an " - "outdated format that has been superseded by others such as NCA, NAX, XCI, or " - "NSP. Deconstructed ROM directories lack icons, metadata, and update " - "support.<br>For an explanation of the various Switch formats Eglantine supports, " - "out our user handbook. This message will not be shown again.")); - } - if (result != Core::SystemResultStatus::Success) { switch (result) { case Core::SystemResultStatus::ErrorGetLoader: @@ -1920,6 +1905,31 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa "converted.")); return; } + if (boot_in_progress || close_pending) { + return; + } + boot_in_progress = true; + game_list->SetScanEnabled(false); + QElapsedTimer boot_timer; + boot_timer.start(); + SCOPE_EXIT { + boot_in_progress = false; + LOG_INFO(Frontend, "Launch finished: elapsed_ms={} running={}", boot_timer.elapsed(), + emulation_running); + if (!emulation_running && !close_pending) { + game_list->SetScanEnabled(true); + game_list->PopulateAsync(UISettings::values.game_dirs); + } + if (close_pending) { + QTimer::singleShot(0, this, &QWidget::close); + } + }; + if (emulation_running) { + ShutdownGame(); + } + if (close_pending) { + return; + } LOG_INFO(Frontend, "Eglantine starting..."); if (params.program_id == 0 || @@ -1973,9 +1983,43 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa // behavior of asking. user_flag_cmd_line = false; + FileSys::VirtualFile prepared_romfs; + if (loader && loader->GetFileType() == Loader::FileType::DeconstructedRomDirectory) { + QProgressDialog progress(tr("Preparing game files..."), QString{}, 0, 0, this); + progress.setWindowModality(Qt::ApplicationModal); + progress.setWindowFlag(Qt::WindowCloseButtonHint, false); + progress.setCancelButton(nullptr); + progress.setMinimumDuration(0); + QFutureWatcher<FileSys::VirtualFile> watcher; + QEventLoop loop; + connect(&watcher, &QFutureWatcher<FileSys::VirtualFile>::finished, &loop, &QEventLoop::quit); + watcher.setFuture(QtConcurrent::run([&loader] { + FileSys::VirtualFile image; + loader->ReadRomFS(image); + return image; + })); + progress.show(); + loop.exec(); + try { + prepared_romfs = watcher.result(); + } catch (const std::exception& error) { + LOG_ERROR(Frontend, "Loose RomFS preparation failed: {}", error.what()); + QMessageBox::critical(this, tr("Unable to prepare game files"), + tr("Game preparation failed. See the log for details.")); + return; + } + if (close_pending) { + return; + } + } + + QElapsedTimer load_timer; + load_timer.start(); if (!LoadROM(filename, params)) { + LOG_ERROR(Frontend, "Core load failed: elapsed_ms={}", load_timer.elapsed()); return; } + LOG_INFO(Frontend, "Core load completed: elapsed_ms={}", load_timer.elapsed()); QtCommon::system->SetShuttingDown(false); game_list->setDisabled(true); @@ -2110,6 +2154,9 @@ bool MainWindow::OnShutdownBegin() { Settings::values.current_speed_mode = Settings::SpeedMode::Standard; if (QtCommon::system->IsShuttingDown()) { + status_bar_update_timer.stop(); + connect(QtCommon::emu_thread.get(), &QThread::finished, this, + &MainWindow::OnEmulationStopped, Qt::UniqueConnection); return false; } @@ -2120,6 +2167,8 @@ bool MainWindow::OnShutdownBegin() { } QtCommon::system->SetShuttingDown(true); + status_bar_update_timer.stop(); + LOG_INFO(Frontend, "Shutdown requested; waiting for emulation thread"); discord_rpc->Pause(); RequestGameExit(); @@ -2139,7 +2188,8 @@ bool MainWindow::OnShutdownBegin() { shutdown_timer.setSingleShot(true); shutdown_timer.start(shutdown_time); - connect(&shutdown_timer, &QTimer::timeout, this, &MainWindow::OnEmulationStopTimeExpired); + connect(&shutdown_timer, &QTimer::timeout, this, &MainWindow::OnEmulationStopTimeExpired, + Qt::UniqueConnection); // Disable everything to prevent anything from being triggered here ui->action_Pause->setEnabled(false); @@ -2163,14 +2213,16 @@ void MainWindow::OnEmulationStopTimeExpired() { } void MainWindow::OnEmulationStopped() { + if (!emulation_running) { + return; + } + if (QtCommon::emu_thread && QtCommon::emu_thread->isRunning()) { + return; + } shutdown_timer.stop(); if (QtCommon::emu_thread) { QtCommon::emu_thread->disconnect(); - if (!QtCommon::emu_thread->wait(QDeadlineTimer(10'000))) { - LOG_ERROR(Frontend, "Emulation thread failed to stop, terminating it"); - QtCommon::emu_thread->terminate(); - QtCommon::emu_thread->wait(); - } + QtCommon::emu_thread->wait(); QtCommon::emu_thread.reset(); } @@ -2239,6 +2291,13 @@ void MainWindow::OnEmulationStopped() { Settings::RestoreGlobalState(QtCommon::system->IsPoweredOn()); QtCommon::system->HIDCore().ReloadInputDevices(); UpdateStatusButtons(); + LOG_INFO(Frontend, "Emulation cleanup completed"); + if (close_pending) { + QTimer::singleShot(0, this, &QWidget::close); + } else if (!boot_in_progress) { + game_list->SetScanEnabled(true); + game_list->PopulateAsync(UISettings::values.game_dirs); + } } void MainWindow::ShutdownGame() { @@ -2250,6 +2309,13 @@ void MainWindow::ShutdownGame() { play_time_manager->Stop(); OnShutdownBegin(); OnEmulationStopTimeExpired(); + if (QtCommon::emu_thread) { + QEventLoop loop; + connect(QtCommon::emu_thread.get(), &QThread::finished, &loop, &QEventLoop::quit); + if (QtCommon::emu_thread->isRunning()) { + loop.exec(QEventLoop::ExcludeUserInputEvents); + } + } OnEmulationStopped(); } @@ -3082,8 +3148,6 @@ void MainWindow::OnPauseContinueGame() { void MainWindow::OnStopGame() { if (ConfirmShutdownGame()) { play_time_manager->Stop(); - // Update game list to show new play time - game_list->PopulateAsync(UISettings::values.game_dirs); if (OnShutdownBegin()) { OnShutdownBeginDialog(); } else { @@ -3998,21 +4062,15 @@ void MainWindow::OnGameListConvertToNup(const std::string& game_path) { const bool resume = FileSys::Nup::HasResumableJournal(archive); bool delete_sources = resume; + FileSys::Nup::Metadata metadata; + QString progress_title; + QPixmap progress_icon; if (resume) { if (QMessageBox::question(this, tr("Resume Conversion"), tr("Resume the interrupted conversion?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) return; } else { - QMessageBox prompt(QMessageBox::Question, convert_title, - tr("Source folder:\n%1\n\nTarget archive:\n%2") - .arg(QString::fromStdString(Common::FS::PathToUTF8String(*root)), - QString::fromStdString(Common::FS::PathToUTF8String(archive))), - QMessageBox::Yes | QMessageBox::No, this); - // QMessageBox takes ownership of its check box. - auto* low_storage = new QCheckBox(tr("Low storage: delete the original files as they " - "are packed (cannot be undone)")); - prompt.setCheckBox(low_storage); u64 needed{}; { std::error_code ec; @@ -4024,38 +4082,31 @@ void MainWindow::OnGameListConvertToNup(const std::string& game_path) { } std::error_code space_error; const auto space = std::filesystem::space(archive.parent_path(), space_error); - const auto to_mb = [](u64 bytes) { return bytes / (1024 * 1024); }; - const QString space_info = tr("\n\nApproximate space needed: %1 MB\nFree space on target " - "volume: %2 MB") - .arg(to_mb(needed)) - .arg(space_error ? 0 : to_mb(space.available)); - prompt.setInformativeText(space_info); - if (!space_error && space.available < needed) { - prompt.setText(prompt.text() + tr("\n\nWarning: free space is less than the " - "estimated space needed.")); - } - if (prompt.exec() != QMessageBox::Yes) + NupMetadataDialog dialog(*root, archive, needed, space_error ? 0 : space.available, this); + if (dialog.exec() != QDialog::Accepted) return; - delete_sources = low_storage->isChecked(); + delete_sources = dialog.DeleteSources(); + metadata = dialog.Metadata(); + progress_title = dialog.DisplayName(); + progress_icon = dialog.CurrentIcon(); } nup_running = true; nup_source_path = game_path; game_list->SetNupConversionRunning(true); nup_cancel = std::make_shared<std::atomic_bool>(false); - nup_progress = - new QProgressDialog(tr("Preparing conversion..."), tr("Cancel"), 0, 1000, this); - nup_progress->setWindowTitle(tr("Converting to NUP")); - nup_progress->setMinimumDuration(0); - nup_progress->setAutoClose(false); - nup_progress->setAutoReset(false); - connect(nup_progress, &QProgressDialog::canceled, this, - [cancel = nup_cancel] { cancel->store(true); }); + nup_progress = new NupProgressDialog(progress_title, progress_icon, this); + connect(nup_progress, &NupProgressDialog::CancelRequested, this, + [this, cancel = nup_cancel] { + cancel->store(true); + if (nup_progress) + nup_progress->SetCancelling(); + }); nup_progress->show(); const auto cancel = nup_cancel; auto last_update = std::make_shared<std::chrono::steady_clock::time_point>(); - nup_watcher.setFuture(QtConcurrent::run([this, root = *root, archive, delete_sources, + nup_watcher.setFuture(QtConcurrent::run([this, root = *root, archive, delete_sources, metadata, cancel, last_update] { std::string error; const auto on_progress = [this, last_update]( @@ -4069,22 +4120,13 @@ void MainWindow::OnGameListConvertToNup(const std::string& game_path) { [this, progress] { if (!nup_progress) return; - const auto percent = progress.bytes_total == 0 - ? 0 - : static_cast<int>(progress.bytes_done * 1000 / - progress.bytes_total); - nup_progress->setValue(percent); - nup_progress->setLabelText( - tr("%1 / %2 MB\n%3 / %4 files") - .arg(progress.bytes_done / (1024 * 1024)) - .arg(progress.bytes_total / (1024 * 1024)) - .arg(progress.files_done) - .arg(progress.files_total)); + nup_progress->SetProgress(progress); }, Qt::QueuedConnection); }; const auto result = FileSys::Nup::PackDump( - root, archive, {.delete_sources = delete_sources}, on_progress, cancel.get(), &error); + root, archive, {.delete_sources = delete_sources, .metadata = metadata}, on_progress, + cancel.get(), &error); QMetaObject::invokeMethod( this, [this, result, delete_sources, error] { @@ -4092,7 +4134,7 @@ void MainWindow::OnGameListConvertToNup(const std::string& game_path) { nup_source_path.clear(); game_list->SetNupConversionRunning(false); if (nup_progress) { - nup_progress->close(); + nup_progress->Finish(); nup_progress->deleteLater(); nup_progress = nullptr; } @@ -4550,8 +4592,13 @@ void MainWindow::OnMouseActivity() { } } -void MainWindow::OnCheckFirmwareDecryption() { - if (!ContentManager::AreKeysPresent()) { +void MainWindow::OnCheckFirmwareDecryption(bool prompt) { + if (!ContentManager::AreKeysPresent() && !prompt) { + // At startup only log it: loose ExeFS/RomFS dumps (all Switch 2 games so far) need no + // keys, and a modal question blocked every unattended launch. Tools > Install Decryption + // Keys still installs them. + LOG_WARNING(Frontend, "Decryption keys are missing: NCA, NSP and XCI games need them"); + } else if (!ContentManager::AreKeysPresent()) { const auto res = QtCommon::Frontend::Warning( tr("Derivation Components Missing"), tr("Decryption keys are missing. Install them now?"), @@ -4734,10 +4781,28 @@ bool MainWindow::ConfirmClose() { } void MainWindow::closeEvent(QCloseEvent* event) { - if (!ConfirmClose()) { + if (!close_pending && !ConfirmClose()) { event->ignore(); return; } + close_pending = true; + game_list->SetScanEnabled(false); + if (boot_in_progress) { + event->ignore(); + return; + } + if (QtCommon::emu_thread && emulation_running) { + event->ignore(); + if (!QtCommon::emu_thread->isRunning()) { + OnEmulationStopped(); + return; + } + if (OnShutdownBegin()) { + OnShutdownBeginDialog(); + } + return; + } + LOG_INFO(Frontend, "Closing frontend after emulation cleanup"); UpdateUISettings(); game_list->SaveInterfaceLayout(); |
