diff options
Diffstat (limited to 'src/yuzu')
-rw-r--r-- | src/yuzu/CMakeLists.txt | 10 | ||||
-rw-r--r-- | src/yuzu/applets/qt_controller.cpp | 52 | ||||
-rw-r--r-- | src/yuzu/applets/qt_controller.h | 4 | ||||
-rw-r--r-- | src/yuzu/breakpad.cpp | 77 | ||||
-rw-r--r-- | src/yuzu/breakpad.h | 10 | ||||
-rw-r--r-- | src/yuzu/configuration/configure_debug.cpp | 18 | ||||
-rw-r--r-- | src/yuzu/configuration/configure_debug.ui | 7 | ||||
-rw-r--r-- | src/yuzu/configuration/configure_input.cpp | 59 | ||||
-rw-r--r-- | src/yuzu/configuration/configure_input.h | 7 | ||||
-rw-r--r-- | src/yuzu/configuration/configure_input_player.h | 5 | ||||
-rw-r--r-- | src/yuzu/configuration/shared_translation.cpp | 1 | ||||
-rw-r--r-- | src/yuzu/debugger/wait_tree.cpp | 2 | ||||
-rw-r--r-- | src/yuzu/game_list.cpp | 26 | ||||
-rw-r--r-- | src/yuzu/game_list.h | 10 | ||||
-rw-r--r-- | src/yuzu/game_list_worker.cpp | 102 | ||||
-rw-r--r-- | src/yuzu/game_list_worker.h | 35 | ||||
-rw-r--r-- | src/yuzu/main.cpp | 127 | ||||
-rw-r--r-- | src/yuzu/main.h | 25 | ||||
-rw-r--r-- | src/yuzu/mini_dump.cpp | 202 | ||||
-rw-r--r-- | src/yuzu/mini_dump.h | 19 | ||||
-rw-r--r-- | src/yuzu/uisettings.h | 4 |
21 files changed, 423 insertions, 379 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 34208ed74..33e1fb663 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -227,14 +227,14 @@ add_executable(yuzu yuzu.rc ) -if (WIN32 AND YUZU_CRASH_DUMPS) +if (YUZU_CRASH_DUMPS) target_sources(yuzu PRIVATE - mini_dump.cpp - mini_dump.h + breakpad.cpp + breakpad.h ) - target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY}) - target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP) + target_link_libraries(yuzu PRIVATE libbreakpad_client) + target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS) endif() if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp index ca0e14fad..515cb7ce6 100644 --- a/src/yuzu/applets/qt_controller.cpp +++ b/src/yuzu/applets/qt_controller.cpp @@ -155,18 +155,27 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( UpdateBorderColor(i); connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { - if (checked) { - // Hide eventual error message about number of controllers - ui->labelError->setVisible(false); - for (std::size_t index = 0; index <= i; ++index) { - connected_controller_checkboxes[index]->setChecked(checked); - } - } else { - for (std::size_t index = i; index < NUM_PLAYERS; ++index) { - connected_controller_checkboxes[index]->setChecked(checked); - } + // Reconnect current controller if it was the last one checked + // (player number was reduced by more than one) + const bool reconnect_first = !checked && i < player_groupboxes.size() - 1 && + player_groupboxes[i + 1]->isChecked(); + + // Ensures that connecting a controller changes the number of players + if (connected_controller_checkboxes[i]->isChecked() != checked) { + // Ensures that the players are always connected in sequential order + PropagatePlayerNumberChanged(i, checked, reconnect_first); } }); + connect(connected_controller_checkboxes[i], &QCheckBox::clicked, [this, i](bool checked) { + // Reconnect current controller if it was the last one checked + // (player number was reduced by more than one) + const bool reconnect_first = !checked && + i < connected_controller_checkboxes.size() - 1 && + connected_controller_checkboxes[i + 1]->isChecked(); + + // Ensures that the players are always connected in sequential order + PropagatePlayerNumberChanged(i, checked, reconnect_first); + }); connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged), [this, i](int) { @@ -668,6 +677,29 @@ void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) { } } +void QtControllerSelectorDialog::PropagatePlayerNumberChanged(size_t player_index, bool checked, + bool reconnect_current) { + connected_controller_checkboxes[player_index]->setChecked(checked); + // Hide eventual error message about number of controllers + ui->labelError->setVisible(false); + + if (checked) { + // Check all previous buttons when checked + if (player_index > 0) { + PropagatePlayerNumberChanged(player_index - 1, checked); + } + } else { + // Unchecked all following buttons when unchecked + if (player_index < connected_controller_checkboxes.size() - 1) { + PropagatePlayerNumberChanged(player_index + 1, checked); + } + } + + if (reconnect_current) { + connected_controller_checkboxes[player_index]->setCheckState(Qt::Checked); + } +} + void QtControllerSelectorDialog::DisableUnsupportedPlayers() { const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h index 7f0673d06..e5372495d 100644 --- a/src/yuzu/applets/qt_controller.h +++ b/src/yuzu/applets/qt_controller.h @@ -100,6 +100,10 @@ private: // Updates the console mode. void UpdateDockedState(bool is_handheld); + // Enable preceding controllers or disable following ones + void PropagatePlayerNumberChanged(size_t player_index, bool checked, + bool reconnect_current = false); + // Disables and disconnects unsupported players based on the given parameters. void DisableUnsupportedPlayers(); diff --git a/src/yuzu/breakpad.cpp b/src/yuzu/breakpad.cpp new file mode 100644 index 000000000..0f6a71ab0 --- /dev/null +++ b/src/yuzu/breakpad.cpp @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> +#include <ranges> + +#if defined(_WIN32) +#include <client/windows/handler/exception_handler.h> +#elif defined(__linux__) +#include <client/linux/handler/exception_handler.h> +#else +#error Minidump creation not supported on this platform +#endif + +#include "common/fs/fs_paths.h" +#include "common/fs/path_util.h" +#include "yuzu/breakpad.h" + +namespace Breakpad { + +static void PruneDumpDirectory(const std::filesystem::path& dump_path) { + // Code in this function should be exception-safe. + struct Entry { + std::filesystem::path path; + std::filesystem::file_time_type last_write_time; + }; + std::vector<Entry> existing_dumps; + + // Get existing entries. + std::error_code ec; + std::filesystem::directory_iterator dir(dump_path, ec); + for (auto& entry : dir) { + if (entry.is_regular_file()) { + existing_dumps.push_back(Entry{ + .path = entry.path(), + .last_write_time = entry.last_write_time(ec), + }); + } + } + + // Sort descending by creation date. + std::ranges::stable_sort(existing_dumps, [](const auto& a, const auto& b) { + return a.last_write_time > b.last_write_time; + }); + + // Delete older dumps. + for (size_t i = 5; i < existing_dumps.size(); i++) { + std::filesystem::remove(existing_dumps[i].path, ec); + } +} + +#if defined(__linux__) +[[noreturn]] bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, + bool succeeded) { + // Prevent time- and space-consuming core dumps from being generated, as we have + // already generated a minidump and a core file will not be useful anyway. + _exit(1); +} +#endif + +void InstallCrashHandler() { + // Write crash dumps to profile directory. + const auto dump_path = GetYuzuPath(Common::FS::YuzuPath::CrashDumpsDir); + PruneDumpDirectory(dump_path); + +#if defined(_WIN32) + // TODO: If we switch to MinGW builds for Windows, this needs to be wrapped in a C API. + static google_breakpad::ExceptionHandler eh{dump_path, nullptr, nullptr, nullptr, + google_breakpad::ExceptionHandler::HANDLER_ALL}; +#elif defined(__linux__) + static google_breakpad::MinidumpDescriptor descriptor{dump_path}; + static google_breakpad::ExceptionHandler eh{descriptor, nullptr, DumpCallback, + nullptr, true, -1}; +#endif +} + +} // namespace Breakpad diff --git a/src/yuzu/breakpad.h b/src/yuzu/breakpad.h new file mode 100644 index 000000000..0f911aa9c --- /dev/null +++ b/src/yuzu/breakpad.h @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Breakpad { + +void InstallCrashHandler(); + +} diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index b22fda746..ef421c754 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -27,16 +27,6 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent) connect(ui->toggle_gdbstub, &QCheckBox::toggled, [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); }); - - connect(ui->create_crash_dumps, &QCheckBox::stateChanged, [&](int) { - if (crash_dump_warning_shown) { - return; - } - QMessageBox::warning(this, tr("Restart Required"), - tr("yuzu is required to restart in order to apply this setting."), - QMessageBox::Ok, QMessageBox::Ok); - crash_dump_warning_shown = true; - }); } ConfigureDebug::~ConfigureDebug() = default; @@ -89,13 +79,6 @@ void ConfigureDebug::SetConfiguration() { ui->disable_web_applet->setEnabled(false); ui->disable_web_applet->setText(tr("Web applet not compiled")); #endif - -#ifdef YUZU_DBGHELP - ui->create_crash_dumps->setChecked(Settings::values.create_crash_dumps.GetValue()); -#else - ui->create_crash_dumps->setEnabled(false); - ui->create_crash_dumps->setText(tr("MiniDump creation not compiled")); -#endif } void ConfigureDebug::ApplyConfiguration() { @@ -107,7 +90,6 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); Settings::values.reporting_services = ui->reporting_services->isChecked(); Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked(); - Settings::values.create_crash_dumps = ui->create_crash_dumps->isChecked(); Settings::values.quest_flag = ui->quest_flag->isChecked(); Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 66b8b7459..76fe98924 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -471,13 +471,6 @@ </property> </widget> </item> - <item row="4" column="0"> - <widget class="QCheckBox" name="create_crash_dumps"> - <property name="text"> - <string>Create Minidump After Crash</string> - </property> - </widget> - </item> <item row="3" column="0"> <widget class="QCheckBox" name="dump_audio_commands"> <property name="toolTip"> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 5a48e388b..3dcad2701 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -101,13 +101,13 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, ui->tabPlayer5, ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8, }; - player_connected = { + connected_controller_checkboxes = { ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected, ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected, ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, }; - std::array<QLabel*, 8> player_connected_labels = { + std::array<QLabel*, 8> connected_controller_labels = { ui->label, ui->label_3, ui->label_4, ui->label_5, ui->label_6, ui->label_7, ui->label_8, ui->label_9, }; @@ -115,23 +115,37 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, for (std::size_t i = 0; i < player_tabs.size(); ++i) { player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); player_tabs[i]->layout()->addWidget(player_controllers[i]); - connect(player_connected[i], &QCheckBox::clicked, [this, i](int checked) { - // Ensures that the controllers are always connected in sequential order - this->propagateMouseClickOnPlayers(i, checked, true); + connect(player_controllers[i], &ConfigureInputPlayer::Connected, [this, i](bool checked) { + // Ensures that connecting a controller changes the number of players + if (connected_controller_checkboxes[i]->isChecked() != checked) { + // Ensures that the players are always connected in sequential order + PropagatePlayerNumberChanged(i, checked); + } + }); + connect(connected_controller_checkboxes[i], &QCheckBox::clicked, [this, i](bool checked) { + // Reconnect current controller if it was the last one checked + // (player number was reduced by more than one) + const bool reconnect_first = !checked && + i < connected_controller_checkboxes.size() - 1 && + connected_controller_checkboxes[i + 1]->isChecked(); + + // Ensures that the players are always connected in sequential order + PropagatePlayerNumberChanged(i, checked, reconnect_first); }); connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this, &ConfigureInput::UpdateAllInputDevices); connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this, &ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection); - connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) { + connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) { + // Keep activated controllers synced with the "Connected Controllers" checkboxes player_controllers[i]->ConnectPlayer(state == Qt::Checked); }); // Remove/hide all the elements that exceed max_players, if applicable. if (i >= max_players) { ui->tabWidget->removeTab(static_cast<int>(max_players)); - player_connected[i]->hide(); - player_connected_labels[i]->hide(); + connected_controller_checkboxes[i]->hide(); + connected_controller_labels[i]->hide(); } } // Only the first player can choose handheld mode so connect the signal just to player 1 @@ -175,28 +189,25 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem, LoadConfiguration(); } -void ConfigureInput::propagateMouseClickOnPlayers(size_t player_index, bool checked, bool origin) { - // Origin has already been toggled - if (!origin) { - player_connected[player_index]->setChecked(checked); - } +void ConfigureInput::PropagatePlayerNumberChanged(size_t player_index, bool checked, + bool reconnect_current) { + connected_controller_checkboxes[player_index]->setChecked(checked); if (checked) { // Check all previous buttons when checked if (player_index > 0) { - propagateMouseClickOnPlayers(player_index - 1, checked, false); + PropagatePlayerNumberChanged(player_index - 1, checked); } } else { // Unchecked all following buttons when unchecked - if (player_index < player_tabs.size() - 1) { - // Reconnect current player if it was the last one checked - // (player number was reduced by more than one) - if (origin && player_connected[player_index + 1]->checkState() == Qt::Checked) { - player_connected[player_index]->setCheckState(Qt::Checked); - } - propagateMouseClickOnPlayers(player_index + 1, checked, false); + if (player_index < connected_controller_checkboxes.size() - 1) { + PropagatePlayerNumberChanged(player_index + 1, checked); } } + + if (reconnect_current) { + connected_controller_checkboxes[player_index]->setCheckState(Qt::Checked); + } } QList<QWidget*> ConfigureInput::GetSubTabs() const { @@ -249,17 +260,17 @@ void ConfigureInput::LoadConfiguration() { } void ConfigureInput::LoadPlayerControllerIndices() { - for (std::size_t i = 0; i < player_connected.size(); ++i) { + for (std::size_t i = 0; i < connected_controller_checkboxes.size(); ++i) { if (i == 0) { auto* handheld = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); if (handheld->IsConnected()) { - player_connected[i]->setChecked(true); + connected_controller_checkboxes[i]->setChecked(true); continue; } } const auto* controller = system.HIDCore().GetEmulatedControllerByIndex(i); - player_connected[i]->setChecked(controller->IsConnected()); + connected_controller_checkboxes[i]->setChecked(controller->IsConnected()); } } diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index abb7f7089..136cd3a0a 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -56,7 +56,9 @@ private: void UpdateDockedState(bool is_handheld); void UpdateAllInputDevices(); void UpdateAllInputProfiles(std::size_t player_index); - void propagateMouseClickOnPlayers(size_t player_index, bool origin, bool checked); + // Enable preceding controllers or disable following ones + void PropagatePlayerNumberChanged(size_t player_index, bool checked, + bool reconnect_current = false); /// Load configuration settings. void LoadConfiguration(); @@ -71,7 +73,8 @@ private: std::array<ConfigureInputPlayer*, 8> player_controllers; std::array<QWidget*, 8> player_tabs; - std::array<QCheckBox*, 8> player_connected; + // Checkboxes representing the "Connected Controllers". + std::array<QCheckBox*, 8> connected_controller_checkboxes; ConfigureInputAdvanced* advanced; Core::System& system; diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h index d4df43d73..d3255d2b4 100644 --- a/src/yuzu/configuration/configure_input_player.h +++ b/src/yuzu/configuration/configure_input_player.h @@ -75,7 +75,7 @@ public: void ClearAll(); signals: - /// Emitted when this controller is connected by the user. + /// Emitted when this controller is (dis)connected by the user. void Connected(bool connected); /// Emitted when the Handheld mode is selected (undocked with dual joycons attached). void HandheldStateChanged(bool is_handheld); @@ -183,9 +183,6 @@ private: /// Stores a pair of "Connected Controllers" combobox index and Controller Type enum. std::vector<std::pair<int, Core::HID::NpadStyleIndex>> index_controller_type_pairs; - static constexpr int PLAYER_COUNT = 8; - std::array<QCheckBox*, PLAYER_COUNT> player_connected_checkbox; - /// This will be the the setting function when an input is awaiting configuration. std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp index 3fe448f27..1434b1a56 100644 --- a/src/yuzu/configuration/shared_translation.cpp +++ b/src/yuzu/configuration/shared_translation.cpp @@ -156,7 +156,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) { // Ui General INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", ""); INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", ""); - INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", ""); INSERT(UISettings, confirm_before_stopping, "Confirm before stopping emulation", ""); INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", ""); INSERT(UISettings, controller_applet_disabled, "Disable controller applet", ""); diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 0783a2430..7049c57b6 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -127,7 +127,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons return list; } - if (thread.GetOwnerProcess() == nullptr || !thread.GetOwnerProcess()->Is64BitProcess()) { + if (thread.GetOwnerProcess() == nullptr || !thread.GetOwnerProcess()->Is64Bit()) { return list; } diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 90433e245..f294dc23d 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -380,7 +380,6 @@ void GameList::UnloadController() { GameList::~GameList() { UnloadController(); - emit ShouldCancelWorker(); } void GameList::SetFilterFocus() { @@ -397,6 +396,10 @@ void GameList::ClearFilter() { search_field->clear(); } +void GameList::WorkerEvent() { + current_worker->ProcessEvents(this); +} + void GameList::AddDirEntry(GameListDir* entry_items) { item_model->invisibleRootItem()->appendRow(entry_items); tree_view->setExpanded( @@ -828,28 +831,21 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); - // Before deleting rows, cancel the worker so that it is not using them - emit ShouldCancelWorker(); + // Cancel any existing worker. + current_worker.reset(); // Delete any rows that might already exist if we're repopulating item_model->removeRows(0, item_model->rowCount()); search_field->clear(); - GameListWorker* worker = - new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system); + current_worker = std::make_unique<GameListWorker>(vfs, provider, game_dirs, compatibility_list, + play_time_manager, system); - connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); - connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, - Qt::QueuedConnection); - connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, + // Get events from the worker as data becomes available + connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent, Qt::QueuedConnection); - // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to - // cancel without delay. - connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel, - Qt::DirectConnection); - QThreadPool::globalInstance()->start(worker); - current_worker = std::move(worker); + QThreadPool::globalInstance()->start(current_worker.get()); } void GameList::SaveInterfaceLayout() { diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 712570cea..563a3a35b 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -109,7 +109,6 @@ signals: void BootGame(const QString& game_path, u64 program_id, std::size_t program_index, StartGameType type, AmLaunchType launch_type); void GameChosen(const QString& game_path, const u64 title_id = 0); - void ShouldCancelWorker(); void OpenFolderRequested(u64 program_id, GameListOpenTarget target, const std::string& game_path); void OpenTransferableShaderCacheRequested(u64 program_id); @@ -138,11 +137,16 @@ private slots: void OnUpdateThemedIcons(); private: + friend class GameListWorker; + void WorkerEvent(); + void AddDirEntry(GameListDir* entry_items); void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent); - void ValidateEntry(const QModelIndex& item); void DonePopulating(const QStringList& watch_list); +private: + void ValidateEntry(const QModelIndex& item); + void RefreshGameDirectory(); void ToggleFavorite(u64 program_id); @@ -165,7 +169,7 @@ private: QVBoxLayout* layout = nullptr; QTreeView* tree_view = nullptr; QStandardItemModel* item_model = nullptr; - GameListWorker* current_worker = nullptr; + std::unique_ptr<GameListWorker> current_worker; QFileSystemWatcher* watcher = nullptr; ControllerNavigation* controller_navigation = nullptr; CompatibilityList compatibility_list; diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 077ced12b..69be21027 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -233,10 +233,53 @@ GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, const PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_) : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, - compatibility_list{compatibility_list_}, - play_time_manager{play_time_manager_}, system{system_} {} + compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_}, system{ + system_} { + // We want the game list to manage our lifetime. + setAutoDelete(false); +} + +GameListWorker::~GameListWorker() { + this->disconnect(); + stop_requested.store(true); + processing_completed.Wait(); +} + +void GameListWorker::ProcessEvents(GameList* game_list) { + while (true) { + std::function<void(GameList*)> func; + { + // Lock queue to protect concurrent modification. + std::scoped_lock lk(lock); + + // If we can't pop a function, return. + if (queued_events.empty()) { + return; + } + + // Pop a function. + func = std::move(queued_events.back()); + queued_events.pop_back(); + } + + // Run the function. + func(game_list); + } +} + +template <typename F> +void GameListWorker::RecordEvent(F&& func) { + { + // Lock queue to protect concurrent modification. + std::scoped_lock lk(lock); -GameListWorker::~GameListWorker() = default; + // Add the function into the front of the queue. + queued_events.emplace_front(std::move(func)); + } + + // Data now available. + emit DataAvailable(); +} void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { using namespace FileSys; @@ -284,9 +327,9 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { GetMetadataFromControlNCA(patch, *control, icon, name); } - emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, - program_id, compatibility_list, play_time_manager, patch), - parent_dir); + auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, + program_id, compatibility_list, play_time_manager, patch); + RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); } } @@ -360,11 +403,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa const FileSys::PatchManager patch{id, system.GetFileSystemController(), system.GetContentProvider()}; - emit EntryReady(MakeGameListEntry(physical_name, name, - Common::FS::GetSize(physical_name), icon, - *loader, id, compatibility_list, - play_time_manager, patch), - parent_dir); + auto entry = MakeGameListEntry( + physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, + id, compatibility_list, play_time_manager, patch); + + RecordEvent( + [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); } } else { std::vector<u8> icon; @@ -376,11 +420,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), system.GetContentProvider()}; - emit EntryReady(MakeGameListEntry(physical_name, name, - Common::FS::GetSize(physical_name), icon, - *loader, program_id, compatibility_list, - play_time_manager, patch), - parent_dir); + auto entry = MakeGameListEntry( + physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, + program_id, compatibility_list, play_time_manager, patch); + + RecordEvent( + [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); } } } else if (is_dir) { @@ -399,25 +444,34 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa } void GameListWorker::run() { + watch_list.clear(); provider->ClearAllEntries(); + const auto DirEntryReady = [&](GameListDir* game_list_dir) { + RecordEvent([=](GameList* game_list) { game_list->AddDirEntry(game_list_dir); }); + }; + for (UISettings::GameDir& game_dir : game_dirs) { + if (stop_requested) { + break; + } + if (game_dir.path == QStringLiteral("SDMC")) { auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); - emit DirEntryReady(game_list_dir); + DirEntryReady(game_list_dir); AddTitlesToGameList(game_list_dir); } else if (game_dir.path == QStringLiteral("UserNAND")) { auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); - emit DirEntryReady(game_list_dir); + DirEntryReady(game_list_dir); AddTitlesToGameList(game_list_dir); } else if (game_dir.path == QStringLiteral("SysNAND")) { auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); - emit DirEntryReady(game_list_dir); + DirEntryReady(game_list_dir); AddTitlesToGameList(game_list_dir); } else { watch_list.append(game_dir.path); auto* const game_list_dir = new GameListDir(game_dir); - emit DirEntryReady(game_list_dir); + DirEntryReady(game_list_dir); ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), game_dir.deep_scan, game_list_dir); ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), @@ -425,12 +479,6 @@ void GameListWorker::run() { } } - emit Finished(watch_list); + RecordEvent([=](GameList* game_list) { game_list->DonePopulating(watch_list); }); processing_completed.Set(); } - -void GameListWorker::Cancel() { - this->disconnect(); - stop_requested.store(true); - processing_completed.Wait(); -} diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 54dc05e30..d5990fcde 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h @@ -4,6 +4,7 @@ #pragma once #include <atomic> +#include <deque> #include <memory> #include <string> @@ -20,6 +21,7 @@ namespace Core { class System; } +class GameList; class QStandardItem; namespace FileSys { @@ -46,24 +48,22 @@ public: /// Starts the processing of directory tree information. void run() override; - /// Tells the worker that it should no longer continue processing. Thread-safe. - void Cancel(); - -signals: +public: /** - * The `EntryReady` signal is emitted once an entry has been prepared and is ready - * to be added to the game list. - * @param entry_items a list with `QStandardItem`s that make up the columns of the new - * entry. + * Synchronously processes any events queued by the worker. + * + * AddDirEntry is called on the game list for every discovered directory. + * AddEntry is called on the game list for every discovered program. + * DonePopulating is called on the game list when processing completes. */ - void DirEntryReady(GameListDir* entry_items); - void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir); + void ProcessEvents(GameList* game_list); - /** - * After the worker has traversed the game directory looking for entries, this signal is - * emitted with a list of folders that should be watched for changes as well. - */ - void Finished(QStringList watch_list); +signals: + void DataAvailable(); + +private: + template <typename F> + void RecordEvent(F&& func); private: void AddTitlesToGameList(GameListDir* parent_dir); @@ -84,8 +84,11 @@ private: QStringList watch_list; - Common::Event processing_completed; + std::mutex lock; + std::condition_variable cv; + std::deque<std::function<void(GameList*)>> queued_events; std::atomic_bool stop_requested = false; + Common::Event processing_completed; Core::System& system; }; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 73cd06478..c8efd1917 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -159,8 +159,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/util/clickable_label.h" #include "yuzu/vk_device_info.h" -#ifdef YUZU_DBGHELP -#include "yuzu/mini_dump.h" +#ifdef YUZU_CRASH_DUMPS +#include "yuzu/breakpad.h" #endif using namespace Common::Literals; @@ -1072,7 +1072,7 @@ void GMainWindow::InitializeWidgets() { }); volume_popup->layout()->addWidget(volume_slider); - volume_button = new QPushButton(); + volume_button = new VolumeButton(); volume_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); volume_button->setFocusPolicy(Qt::NoFocus); volume_button->setCheckable(true); @@ -1103,6 +1103,8 @@ void GMainWindow::InitializeWidgets() { context_menu.exec(volume_button->mapToGlobal(menu_location)); volume_button->repaint(); }); + connect(volume_button, &VolumeButton::VolumeChanged, this, &GMainWindow::UpdateVolumeUI); + statusBar()->insertPermanentWidget(0, volume_button); // setup AA button @@ -2019,7 +2021,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())} .filename()); } - const bool is_64bit = system->Kernel().ApplicationProcess()->Is64BitProcess(); + const bool is_64bit = system->Kernel().ApplicationProcess()->Is64Bit(); const auto instruction_set_suffix = is_64bit ? tr("(64-bit)") : tr("(32-bit)"); title_name = tr("%1 %2", "%1 is the title name. %2 indicates if the title is 64-bit or 32-bit") .arg(QString::fromStdString(title_name), instruction_set_suffix) @@ -2172,6 +2174,7 @@ void GMainWindow::ShutdownGame() { return; } + play_time_manager->Stop(); OnShutdownBegin(); OnEmulationStopTimeExpired(); OnEmulationStopped(); @@ -2735,7 +2738,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa return; } - const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); + const auto extracted = FileSys::ExtractRomFS(romfs); if (extracted == nullptr) { failed(); return; @@ -2980,7 +2983,7 @@ bool GMainWindow::MakeShortcutIcoPath(const u64 program_id, const std::string_vi #elif defined(__linux__) || defined(__FreeBSD__) out_icon_path = Common::FS::GetDataDirectory("XDG_DATA_HOME") / "icons/hicolor/256x256"; #endif - + // Create icons directory if it doesn't exist if (!Common::FS::CreateDirs(out_icon_path)) { QMessageBox::critical( @@ -3571,7 +3574,7 @@ void GMainWindow::OnExecuteProgram(std::size_t program_index) { } void GMainWindow::OnExit() { - OnStopGame(); + ShutdownGame(); } void GMainWindow::OnSaveConfig() { @@ -4087,6 +4090,66 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file } } +bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::string& title, + const std::string& comment, const std::string& icon_path, + const std::string& command, const std::string& arguments, + const std::string& categories, const std::string& keywords) { +#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) + // This desktop file template was writing referencing + // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html + std::string shortcut_contents{}; + shortcut_contents.append("[Desktop Entry]\n"); + shortcut_contents.append("Type=Application\n"); + shortcut_contents.append("Version=1.0\n"); + shortcut_contents.append(fmt::format("Name={:s}\n", title)); + shortcut_contents.append(fmt::format("Comment={:s}\n", comment)); + shortcut_contents.append(fmt::format("Icon={:s}\n", icon_path)); + shortcut_contents.append(fmt::format("TryExec={:s}\n", command)); + shortcut_contents.append(fmt::format("Exec={:s} {:s}\n", command, arguments)); + shortcut_contents.append(fmt::format("Categories={:s}\n", categories)); + shortcut_contents.append(fmt::format("Keywords={:s}\n", keywords)); + + std::ofstream shortcut_stream(shortcut_path); + if (!shortcut_stream.is_open()) { + LOG_WARNING(Common, "Failed to create file {:s}", shortcut_path); + return false; + } + shortcut_stream << shortcut_contents; + shortcut_stream.close(); + + return true; +#elif defined(WIN32) + IShellLinkW* shell_link; + auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, + (void**)&shell_link); + if (FAILED(hres)) { + return false; + } + shell_link->SetPath( + Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to + shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data()); + shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data()); + shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0); + + IPersistFile* persist_file; + hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file); + if (FAILED(hres)) { + return false; + } + + hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE); + if (FAILED(hres)) { + return false; + } + + persist_file->Release(); + shell_link->Release(); + + return true; +#endif + return false; +} + void GMainWindow::OnLoadAmiibo() { if (emu_thread == nullptr || !emu_thread->IsRunning()) { return; @@ -4873,7 +4936,12 @@ bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installe } bool GMainWindow::ConfirmClose() { - if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) { + if (emu_thread == nullptr || + UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Never) { + return true; + } + if (!system->GetExitLocked() && + UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Based_On_Game) { return true; } const auto text = tr("Are you sure you want to close yuzu?"); @@ -4978,7 +5046,7 @@ bool GMainWindow::ConfirmChangeGame() { } bool GMainWindow::ConfirmForceLockedExit() { - if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) { + if (emu_thread == nullptr) { return true; } const auto text = tr("The currently running application has requested yuzu to not exit.\n\n" @@ -5154,6 +5222,32 @@ void GMainWindow::changeEvent(QEvent* event) { QWidget::changeEvent(event); } +void VolumeButton::wheelEvent(QWheelEvent* event) { + + int num_degrees = event->angleDelta().y() / 8; + int num_steps = (num_degrees / 15) * scroll_multiplier; + // Stated in QT docs: Most mouse types work in steps of 15 degrees, in which case the delta + // value is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees. + + if (num_steps > 0) { + Settings::values.volume.SetValue( + std::min(200, Settings::values.volume.GetValue() + num_steps)); + } else { + Settings::values.volume.SetValue( + std::max(0, Settings::values.volume.GetValue() + num_steps)); + } + + scroll_multiplier = std::min(MaxMultiplier, scroll_multiplier * 2); + scroll_timer.start(100); // reset the multiplier if no scroll event occurs within 100 ms + + emit VolumeChanged(); + event->accept(); +} + +void VolumeButton::ResetMultiplier() { + scroll_multiplier = 1; +} + #ifdef main #undef main #endif @@ -5215,22 +5309,15 @@ int main(int argc, char* argv[]) { return 0; } -#ifdef YUZU_DBGHELP - PROCESS_INFORMATION pi; - if (!is_child && Settings::values.create_crash_dumps.GetValue() && - MiniDump::SpawnDebuggee(argv[0], pi)) { - // Delete the config object so that it doesn't save when the program exits - config.reset(nullptr); - MiniDump::DebugDebuggee(pi); - return 0; - } -#endif - if (StartupChecks(argv[0], &has_broken_vulkan, Settings::values.perform_vulkan_check.GetValue())) { return 0; } +#ifdef YUZU_CRASH_DUMPS + Breakpad::InstallCrashHandler(); +#endif + Common::DetachedTasks detached_tasks; MicroProfileOnThreadCreate("Frontend"); SCOPE_EXIT({ MicroProfileShutdown(); }); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index d203e5301..f67c4cfda 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -9,6 +9,7 @@ #include <filesystem> #include <QMainWindow> #include <QMessageBox> +#include <QPushButton> #include <QTimer> #include <QTranslator> @@ -138,6 +139,28 @@ namespace VkDeviceInfo { class Record; } +class VolumeButton : public QPushButton { + Q_OBJECT +public: + explicit VolumeButton(QWidget* parent = nullptr) : QPushButton(parent), scroll_multiplier(1) { + connect(&scroll_timer, &QTimer::timeout, this, &VolumeButton::ResetMultiplier); + } + +signals: + void VolumeChanged(); + +protected: + void wheelEvent(QWheelEvent* event) override; + +private slots: + void ResetMultiplier(); + +private: + int scroll_multiplier; + QTimer scroll_timer; + constexpr static int MaxMultiplier = 8; +}; + class GMainWindow : public QMainWindow { Q_OBJECT @@ -492,7 +515,7 @@ private: QPushButton* dock_status_button = nullptr; QPushButton* filter_status_button = nullptr; QPushButton* aa_status_button = nullptr; - QPushButton* volume_button = nullptr; + VolumeButton* volume_button = nullptr; QWidget* volume_popup = nullptr; QSlider* volume_slider = nullptr; QTimer status_bar_update_timer; diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp deleted file mode 100644 index a34dc6a9c..000000000 --- a/src/yuzu/mini_dump.cpp +++ /dev/null @@ -1,202 +0,0 @@ -// SPDX-FileCopyrightText: 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <cstdio> -#include <cstring> -#include <ctime> -#include <filesystem> -#include <fmt/format.h> -#include <windows.h> -#include "yuzu/mini_dump.h" -#include "yuzu/startup_checks.h" - -// dbghelp.h must be included after windows.h -#include <dbghelp.h> - -namespace MiniDump { - -void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, - EXCEPTION_POINTERS* pep) { - char file_name[255]; - const std::time_t the_time = std::time(nullptr); - std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time)); - - // Open the file - HANDLE file_handle = CreateFileA(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - - if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) { - fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError()); - return; - } - - // Create the minidump - const MINIDUMP_TYPE dump_type = MiniDumpNormal; - - const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle, - dump_type, (pep != 0) ? info : 0, 0, 0); - - if (write_dump_status) { - fmt::print(stderr, "MiniDump created: {}", file_name); - } else { - fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError()); - } - - // Close the file - CloseHandle(file_handle); -} - -void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) { - EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; - - HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId); - if (thread_handle == nullptr) { - fmt::print(stderr, "OpenThread failed ({})", GetLastError()); - return; - } - - // Get child process context - CONTEXT context = {}; - context.ContextFlags = CONTEXT_ALL; - if (!GetThreadContext(thread_handle, &context)) { - fmt::print(stderr, "GetThreadContext failed ({})", GetLastError()); - return; - } - - // Create exception pointers for minidump - EXCEPTION_POINTERS ep; - ep.ExceptionRecord = &record; - ep.ContextRecord = &context; - - MINIDUMP_EXCEPTION_INFORMATION info; - info.ThreadId = deb_ev.dwThreadId; - info.ExceptionPointers = &ep; - info.ClientPointers = false; - - CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep); - - if (CloseHandle(thread_handle) == 0) { - fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError()); - } -} - -bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) { - std::memset(&pi, 0, sizeof(pi)); - - // Don't debug if we are already being debugged - if (IsDebuggerPresent()) { - return false; - } - - if (!SpawnChild(arg0, &pi, 0)) { - fmt::print(stderr, "warning: continuing without crash dumps"); - return false; - } - - const bool can_debug = DebugActiveProcess(pi.dwProcessId); - if (!can_debug) { - fmt::print(stderr, - "warning: DebugActiveProcess failed ({}), continuing without crash dumps", - GetLastError()); - return false; - } - - return true; -} - -static const char* ExceptionName(DWORD exception) { - switch (exception) { - case EXCEPTION_ACCESS_VIOLATION: - return "EXCEPTION_ACCESS_VIOLATION"; - case EXCEPTION_DATATYPE_MISALIGNMENT: - return "EXCEPTION_DATATYPE_MISALIGNMENT"; - case EXCEPTION_BREAKPOINT: - return "EXCEPTION_BREAKPOINT"; - case EXCEPTION_SINGLE_STEP: - return "EXCEPTION_SINGLE_STEP"; - case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: - return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; - case EXCEPTION_FLT_DENORMAL_OPERAND: - return "EXCEPTION_FLT_DENORMAL_OPERAND"; - case EXCEPTION_FLT_DIVIDE_BY_ZERO: - return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; - case EXCEPTION_FLT_INEXACT_RESULT: - return "EXCEPTION_FLT_INEXACT_RESULT"; - case EXCEPTION_FLT_INVALID_OPERATION: - return "EXCEPTION_FLT_INVALID_OPERATION"; - case EXCEPTION_FLT_OVERFLOW: - return "EXCEPTION_FLT_OVERFLOW"; - case EXCEPTION_FLT_STACK_CHECK: - return "EXCEPTION_FLT_STACK_CHECK"; - case EXCEPTION_FLT_UNDERFLOW: - return "EXCEPTION_FLT_UNDERFLOW"; - case EXCEPTION_INT_DIVIDE_BY_ZERO: - return "EXCEPTION_INT_DIVIDE_BY_ZERO"; - case EXCEPTION_INT_OVERFLOW: - return "EXCEPTION_INT_OVERFLOW"; - case EXCEPTION_PRIV_INSTRUCTION: - return "EXCEPTION_PRIV_INSTRUCTION"; - case EXCEPTION_IN_PAGE_ERROR: - return "EXCEPTION_IN_PAGE_ERROR"; - case EXCEPTION_ILLEGAL_INSTRUCTION: - return "EXCEPTION_ILLEGAL_INSTRUCTION"; - case EXCEPTION_NONCONTINUABLE_EXCEPTION: - return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; - case EXCEPTION_STACK_OVERFLOW: - return "EXCEPTION_STACK_OVERFLOW"; - case EXCEPTION_INVALID_DISPOSITION: - return "EXCEPTION_INVALID_DISPOSITION"; - case EXCEPTION_GUARD_PAGE: - return "EXCEPTION_GUARD_PAGE"; - case EXCEPTION_INVALID_HANDLE: - return "EXCEPTION_INVALID_HANDLE"; - default: - return "unknown exception type"; - } -} - -void DebugDebuggee(PROCESS_INFORMATION& pi) { - DEBUG_EVENT deb_ev = {}; - - while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) { - const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE); - if (!wait_success) { - fmt::print(stderr, "error: WaitForDebugEvent failed ({})", GetLastError()); - return; - } - - switch (deb_ev.dwDebugEventCode) { - case OUTPUT_DEBUG_STRING_EVENT: - case CREATE_PROCESS_DEBUG_EVENT: - case CREATE_THREAD_DEBUG_EVENT: - case EXIT_PROCESS_DEBUG_EVENT: - case EXIT_THREAD_DEBUG_EVENT: - case LOAD_DLL_DEBUG_EVENT: - case RIP_EVENT: - case UNLOAD_DLL_DEBUG_EVENT: - // Continue on all other debug events - ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE); - break; - case EXCEPTION_DEBUG_EVENT: - EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; - - // We want to generate a crash dump if we are seeing the same exception again. - if (!deb_ev.u.Exception.dwFirstChance) { - fmt::print(stderr, "Creating MiniDump on ExceptionCode: 0x{:08x} {}\n", - record.ExceptionCode, ExceptionName(record.ExceptionCode)); - DumpFromDebugEvent(deb_ev, pi); - } - - // Continue without handling the exception. - // Lets the debuggee use its own exception handler. - // - If one does not exist, we will see the exception once more where we make a minidump - // for. Then when it reaches here again, yuzu will probably crash. - // - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an - // infinite loop of exceptions. - ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); - break; - } - } -} - -} // namespace MiniDump diff --git a/src/yuzu/mini_dump.h b/src/yuzu/mini_dump.h deleted file mode 100644 index d6b6cca84..000000000 --- a/src/yuzu/mini_dump.h +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <windows.h> - -#include <dbghelp.h> - -namespace MiniDump { - -void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, - EXCEPTION_POINTERS* pep); - -void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi); -bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi); -void DebugDebuggee(PROCESS_INFORMATION& pi); - -} // namespace MiniDump diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index b62ff620c..77d992c54 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -93,10 +93,6 @@ struct Values { Setting<bool> show_filter_bar{linkage, true, "showFilterBar", Category::Ui}; Setting<bool> show_status_bar{linkage, true, "showStatusBar", Category::Ui}; - Setting<bool> confirm_before_closing{ - linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default, - true, true}; - SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage, ConfirmStop::Ask_Always, "confirmStop", |