summaryrefslogtreecommitdiffstats
path: root/src/yuzu
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu')
-rw-r--r--src/yuzu/CMakeLists.txt10
-rw-r--r--src/yuzu/applets/qt_controller.cpp52
-rw-r--r--src/yuzu/applets/qt_controller.h4
-rw-r--r--src/yuzu/breakpad.cpp77
-rw-r--r--src/yuzu/breakpad.h10
-rw-r--r--src/yuzu/configuration/configure_debug.cpp18
-rw-r--r--src/yuzu/configuration/configure_debug.ui7
-rw-r--r--src/yuzu/configuration/configure_input.cpp59
-rw-r--r--src/yuzu/configuration/configure_input.h7
-rw-r--r--src/yuzu/configuration/configure_input_player.h5
-rw-r--r--src/yuzu/configuration/shared_translation.cpp1
-rw-r--r--src/yuzu/debugger/wait_tree.cpp2
-rw-r--r--src/yuzu/game_list.cpp26
-rw-r--r--src/yuzu/game_list.h10
-rw-r--r--src/yuzu/game_list_worker.cpp102
-rw-r--r--src/yuzu/game_list_worker.h35
-rw-r--r--src/yuzu/main.cpp127
-rw-r--r--src/yuzu/main.h25
-rw-r--r--src/yuzu/mini_dump.cpp202
-rw-r--r--src/yuzu/mini_dump.h19
-rw-r--r--src/yuzu/uisettings.h4
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",