summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-x.ci/scripts/linux/upload.sh25
-rwxr-xr-x.ci/scripts/windows/docker.sh1
-rw-r--r--.ci/scripts/windows/upload.ps14
-rw-r--r--.ci/templates/build-msvc.yml2
-rw-r--r--.ci/yuzu-patreon-step2.yml25
-rw-r--r--.github/workflows/verify.yml2
-rw-r--r--CMakeLists.txt12
-rw-r--r--src/audio_core/audio_in_manager.cpp2
-rw-r--r--src/audio_core/audio_out_manager.cpp2
-rw-r--r--src/audio_core/renderer/audio_device.cpp34
-rw-r--r--src/audio_core/renderer/audio_device.h22
-rw-r--r--src/common/CMakeLists.txt6
-rw-r--r--src/common/settings.h1
-rw-r--r--src/core/CMakeLists.txt6
-rw-r--r--src/core/hle/service/audio/audout_u.cpp3
-rw-r--r--src/core/hle/service/audio/audren_u.cpp4
-rw-r--r--src/yuzu/CMakeLists.txt10
-rw-r--r--src/yuzu/applets/qt_controller.cpp2
-rw-r--r--src/yuzu/configuration/config.cpp6
-rw-r--r--src/yuzu/configuration/config.h4
-rw-r--r--src/yuzu/configuration/configure_debug.cpp21
-rw-r--r--src/yuzu/configuration/configure_debug.h2
-rw-r--r--src/yuzu/configuration/configure_debug.ui122
-rw-r--r--src/yuzu/configuration/configure_input.cpp2
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp3
-rw-r--r--src/yuzu/configuration/input_profiles.cpp9
-rw-r--r--src/yuzu/configuration/input_profiles.h4
-rw-r--r--src/yuzu/main.cpp34
-rw-r--r--src/yuzu/main.h2
-rw-r--r--src/yuzu/mini_dump.cpp202
-rw-r--r--src/yuzu/mini_dump.h19
-rw-r--r--src/yuzu/startup_checks.cpp29
-rw-r--r--src/yuzu/startup_checks.h5
-rw-r--r--vcpkg.json4
34 files changed, 492 insertions, 139 deletions
diff --git a/.ci/scripts/linux/upload.sh b/.ci/scripts/linux/upload.sh
index 8173c5728..e0f336427 100755
--- a/.ci/scripts/linux/upload.sh
+++ b/.ci/scripts/linux/upload.sh
@@ -5,21 +5,24 @@
. .ci/scripts/common/pre-upload.sh
-APPIMAGE_NAME="yuzu-${GITDATE}-${GITREV}.AppImage"
-REV_NAME="yuzu-linux-${GITDATE}-${GITREV}"
+APPIMAGE_NAME="yuzu-${RELEASE_NAME}-${GITDATE}-${GITREV}.AppImage"
+BASE_NAME="yuzu-linux"
+REV_NAME="${BASE_NAME}-${GITDATE}-${GITREV}"
ARCHIVE_NAME="${REV_NAME}.tar.xz"
COMPRESSION_FLAGS="-cJvf"
-if [ "${RELEASE_NAME}" = "mainline" ]; then
- DIR_NAME="${REV_NAME}"
+if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then
+ DIR_NAME="${BASE_NAME}-${RELEASE_NAME}"
else
- DIR_NAME="${REV_NAME}_${RELEASE_NAME}"
+ DIR_NAME="${REV_NAME}-${RELEASE_NAME}"
fi
mkdir "$DIR_NAME"
cp build/bin/yuzu-cmd "$DIR_NAME"
-cp build/bin/yuzu "$DIR_NAME"
+if [ "${RELEASE_NAME}" != "early-access" ] && [ "${RELEASE_NAME}" != "mainline" ]; then
+ cp build/bin/yuzu "$DIR_NAME"
+fi
# Build an AppImage
cd build
@@ -32,6 +35,11 @@ if ! ./appimagetool-x86_64.AppImage --version; then
export APPIMAGE_EXTRACT_AND_RUN=1
fi
+# Don't let AppImageLauncher ask to integrate EA
+if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then
+ echo "X-AppImage-Integrate=false" >> AppDir/org.yuzu_emu.yuzu.desktop
+fi
+
if [ "${RELEASE_NAME}" = "mainline" ]; then
# Generate update information if releasing to mainline
./appimagetool-x86_64.AppImage -u "gh-releases-zsync|yuzu-emu|yuzu-${RELEASE_NAME}|latest|yuzu-*.AppImage.zsync" AppDir "${APPIMAGE_NAME}"
@@ -46,4 +54,9 @@ if [ -f "build/${APPIMAGE_NAME}.zsync" ]; then
cp "build/${APPIMAGE_NAME}.zsync" "${ARTIFACTS_DIR}/"
fi
+# Copy the AppImage to the general release directory and remove git revision info
+if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then
+ cp "build/${APPIMAGE_NAME}" "${DIR_NAME}/yuzu-${RELEASE_NAME}.AppImage"
+fi
+
. .ci/scripts/common/post-upload.sh
diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh
index 790ba8218..6f522feed 100755
--- a/.ci/scripts/windows/docker.sh
+++ b/.ci/scripts/windows/docker.sh
@@ -21,6 +21,7 @@ cmake .. \
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
-DENABLE_QT_TRANSLATION=ON \
-DUSE_CCACHE=ON \
+ -DYUZU_CRASH_DUMPS=ON \
-DYUZU_USE_BUNDLED_SDL2=OFF \
-DYUZU_USE_EXTERNAL_SDL2=OFF \
-DYUZU_TESTS=OFF \
diff --git a/.ci/scripts/windows/upload.ps1 b/.ci/scripts/windows/upload.ps1
index d463281de..21abcd752 100644
--- a/.ci/scripts/windows/upload.ps1
+++ b/.ci/scripts/windows/upload.ps1
@@ -65,8 +65,8 @@ if ("$env:GITHUB_ACTIONS" -eq "true") {
# None of the other GHA builds are including source, so commenting out today
#Copy-Item $MSVC_SOURCE_TARXZ -Destination "artifacts"
- # Are debug symbols important?
- # cp .\build\bin\yuzu*.pdb .\pdb\
+ # Debugging symbols
+ cp .\build\bin\yuzu*.pdb .\artifacts\
# Write out a tag BUILD_TAG to environment for the Upload step
# We're getting ${{ github.event.number }} as $env:PR_NUMBER"
diff --git a/.ci/templates/build-msvc.yml b/.ci/templates/build-msvc.yml
index a2ee71bd8..ea405e5dc 100644
--- a/.ci/templates/build-msvc.yml
+++ b/.ci/templates/build-msvc.yml
@@ -9,7 +9,7 @@ parameters:
steps:
- script: choco install vulkan-sdk
displayName: 'Install vulkan-sdk'
-- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release .. && cd ..
+- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_CRASH_DUMPS=ON .. && cd ..
displayName: 'Configure CMake'
- task: MSBuild@1
displayName: 'Build'
diff --git a/.ci/yuzu-patreon-step2.yml b/.ci/yuzu-patreon-step2.yml
index 5d5b140fd..71a23ebe6 100644
--- a/.ci/yuzu-patreon-step2.yml
+++ b/.ci/yuzu-patreon-step2.yml
@@ -11,9 +11,30 @@ stages:
- stage: build
displayName: 'build'
jobs:
- - job: build
+ - job: linux
timeoutInMinutes: 120
- displayName: 'windows-msvc'
+ displayName: 'linux'
+ pool:
+ vmImage: ubuntu-latest
+ strategy:
+ maxParallel: 10
+ matrix:
+ linux:
+ BuildSuffix: 'linux'
+ ScriptFolder: 'linux'
+ steps:
+ - template: ./templates/sync-source.yml
+ parameters:
+ artifactSource: $(parameters.artifactSource)
+ needSubmodules: 'true'
+ - template: ./templates/build-single.yml
+ parameters:
+ artifactSource: 'false'
+ cache: $(parameters.cache)
+ version: $(DisplayVersion)
+ - job: msvc
+ timeoutInMinutes: 120
+ displayName: 'windows'
pool:
vmImage: windows-2022
steps:
diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml
index 733a52764..7cde8380b 100644
--- a/.github/workflows/verify.yml
+++ b/.github/workflows/verify.yml
@@ -104,7 +104,7 @@ jobs:
run: |
glslangValidator --version
mkdir build
- cmake . -B build -GNinja -DCMAKE_TOOLCHAIN_FILE="CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DGIT_BRANCH=pr-verify
+ cmake . -B build -GNinja -DCMAKE_TOOLCHAIN_FILE="CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DGIT_BRANCH=pr-verify -DYUZU_CRASH_DUMPS=ON
- name: Build
run: cmake --build build
- name: Cache Summary
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2ab0ea589..20dd1383f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,6 +38,8 @@ option(YUZU_USE_BUNDLED_OPUS "Compile bundled opus" ON)
option(YUZU_TESTS "Compile tests" ON)
+CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF)
+
option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}")
option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON)
@@ -46,6 +48,9 @@ if (YUZU_USE_BUNDLED_VCPKG)
if (YUZU_TESTS)
list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests")
endif()
+ if (YUZU_CRASH_DUMPS)
+ list(APPEND VCPKG_MANIFEST_FEATURES "dbghelp")
+ endif()
include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake)
elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "")
@@ -447,6 +452,13 @@ elseif (WIN32)
# PSAPI is the Process Status API
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
endif()
+
+ if (YUZU_CRASH_DUMPS)
+ find_library(DBGHELP_LIBRARY dbghelp)
+ if ("${DBGHELP_LIBRARY}" STREQUAL "DBGHELP_LIBRARY-NOTFOUND")
+ message(FATAL_ERROR "YUZU_CRASH_DUMPS enabled but dbghelp library not found")
+ endif()
+ endif()
elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
set(PLATFORM_LIBRARIES rt)
endif()
diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp
index 4aadb7fd6..f39fb4002 100644
--- a/src/audio_core/audio_in_manager.cpp
+++ b/src/audio_core/audio_in_manager.cpp
@@ -82,7 +82,7 @@ u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceN
auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)};
if (input_devices.size() > 1) {
- names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac"));
+ names.emplace_back("Uac");
return 1;
}
return 0;
diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp
index 71d67de64..1766efde1 100644
--- a/src/audio_core/audio_out_manager.cpp
+++ b/src/audio_core/audio_out_manager.cpp
@@ -74,7 +74,7 @@ void Manager::BufferReleaseAndRegister() {
u32 Manager::GetAudioOutDeviceNames(
std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const {
- names.push_back({"DeviceOut"});
+ names.emplace_back("DeviceOut");
return 1;
}
diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp
index d5886e55e..0d9d8f6ce 100644
--- a/src/audio_core/renderer/audio_device.cpp
+++ b/src/audio_core/renderer/audio_device.cpp
@@ -1,6 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <array>
+#include <span>
+
#include "audio_core/audio_core.h"
#include "audio_core/common/feature_support.h"
#include "audio_core/renderer/audio_device.h"
@@ -9,14 +12,33 @@
namespace AudioCore::AudioRenderer {
+constexpr std::array usb_device_names{
+ AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
+ AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
+ AudioDevice::AudioDeviceName{"AudioTvOutput"},
+ AudioDevice::AudioDeviceName{"AudioUsbDeviceOutput"},
+};
+
+constexpr std::array device_names{
+ AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
+ AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
+ AudioDevice::AudioDeviceName{"AudioTvOutput"},
+};
+
+constexpr std::array output_device_names{
+ AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
+ AudioDevice::AudioDeviceName{"AudioTvOutput"},
+ AudioDevice::AudioDeviceName{"AudioExternalOutput"},
+};
+
AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_,
const u32 revision)
: output_sink{system.AudioCore().GetOutputSink()},
applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {}
u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
- const size_t max_count) {
- std::span<AudioDeviceName> names{};
+ const size_t max_count) const {
+ std::span<const AudioDeviceName> names{};
if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) {
names = usb_device_names;
@@ -24,7 +46,7 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
names = device_names;
}
- u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
+ const u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
for (u32 i = 0; i < out_count; i++) {
out_buffer.push_back(names[i]);
}
@@ -32,8 +54,8 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
}
u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer,
- const size_t max_count) {
- u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
+ const size_t max_count) const {
+ const u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
for (u32 i = 0; i < out_count; i++) {
out_buffer.push_back(output_device_names[i]);
@@ -45,7 +67,7 @@ void AudioDevice::SetDeviceVolumes(const f32 volume) {
output_sink.SetDeviceVolume(volume);
}
-f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) {
+f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const {
return output_sink.GetDeviceVolume();
}
diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h
index 1f449f261..dd6be70ee 100644
--- a/src/audio_core/renderer/audio_device.h
+++ b/src/audio_core/renderer/audio_device.h
@@ -3,7 +3,7 @@
#pragma once
-#include <span>
+#include <string_view>
#include "audio_core/audio_render_manager.h"
@@ -23,21 +23,13 @@ namespace AudioRenderer {
class AudioDevice {
public:
struct AudioDeviceName {
- std::array<char, 0x100> name;
+ std::array<char, 0x100> name{};
- AudioDeviceName(const char* name_) {
- std::strncpy(name.data(), name_, name.size());
+ constexpr AudioDeviceName(std::string_view name_) {
+ name_.copy(name.data(), name.size() - 1);
}
};
- std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput",
- "AudioBuiltInSpeakerOutput", "AudioTvOutput",
- "AudioUsbDeviceOutput"};
- std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput",
- "AudioBuiltInSpeakerOutput", "AudioTvOutput"};
- std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput",
- "AudioExternalOutput"};
-
explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision);
/**
@@ -47,7 +39,7 @@ public:
* @param max_count - Maximum number of devices to write (count of out_buffer).
* @return Number of device names written.
*/
- u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count);
+ u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
/**
* Get a list of the available output devices.
@@ -57,7 +49,7 @@ public:
* @param max_count - Maximum number of devices to write (count of out_buffer).
* @return Number of device names written.
*/
- u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count);
+ u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
/**
* Set the volume of all streams in the backend sink.
@@ -73,7 +65,7 @@ public:
* @param name - Name of the device to check. Unused.
* @return Volume of the device.
*/
- f32 GetDeviceVolume(std::string_view name);
+ f32 GetDeviceVolume(std::string_view name) const;
private:
/// Backend output sink for the device
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index b1e0ba6cc..68436a4bc 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -19,7 +19,7 @@ find_package(Git QUIET)
add_custom_command(OUTPUT scm_rev.cpp
COMMAND ${CMAKE_COMMAND}
- -DSRC_DIR=${CMAKE_SOURCE_DIR}
+ -DSRC_DIR=${PROJECT_SOURCE_DIR}
-DBUILD_REPOSITORY=${BUILD_REPOSITORY}
-DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE}
-DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING}
@@ -31,13 +31,13 @@ add_custom_command(OUTPUT scm_rev.cpp
-DGIT_BRANCH=${GIT_BRANCH}
-DBUILD_FULLNAME=${BUILD_FULLNAME}
-DGIT_EXECUTABLE=${GIT_EXECUTABLE}
- -P ${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake
+ -P ${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake
DEPENDS
# Check that the scm_rev files haven't changed
"${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in"
"${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h"
# technically we should regenerate if the git version changed, but its not worth the effort imo
- "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
+ "${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
VERBATIM
)
diff --git a/src/common/settings.h b/src/common/settings.h
index 13651de57..851812f28 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -530,6 +530,7 @@ struct Values {
Setting<bool> use_debug_asserts{false, "use_debug_asserts"};
Setting<bool> use_auto_stub{false, "use_auto_stub"};
Setting<bool> enable_all_controllers{false, "enable_all_controllers"};
+ Setting<bool> create_crash_dumps{false, "create_crash_dumps"};
// Miscellaneous
Setting<std::string> log_filter{"*:Info", "log_filter"};
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 806e7ff6c..405a2f993 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -4,12 +4,6 @@
add_library(core STATIC
arm/arm_interface.h
arm/arm_interface.cpp
- arm/dynarmic/arm_dynarmic_32.cpp
- arm/dynarmic/arm_dynarmic_32.h
- arm/dynarmic/arm_dynarmic_64.cpp
- arm/dynarmic/arm_dynarmic_64.h
- arm/dynarmic/arm_dynarmic_cp15.cpp
- arm/dynarmic/arm_dynarmic_cp15.h
arm/dynarmic/arm_exclusive_monitor.cpp
arm/dynarmic/arm_exclusive_monitor.h
arm/exclusive_monitor.cpp
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index a44dd842a..49c092301 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -246,9 +246,8 @@ void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
const auto write_count =
static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName));
std::vector<AudioDevice::AudioDeviceName> device_names{};
- std::string print_names{};
if (write_count > 0) {
- device_names.push_back(AudioDevice::AudioDeviceName("DeviceOut"));
+ device_names.emplace_back("DeviceOut");
LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut");
} else {
LOG_DEBUG(Service_Audio, "called. Empty buffer passed in.");
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index bc69117c6..6fb07c37d 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -252,7 +252,7 @@ private:
std::vector<AudioDevice::AudioDeviceName> out_names{};
- u32 out_count = impl->ListAudioDeviceName(out_names, in_count);
+ const u32 out_count = impl->ListAudioDeviceName(out_names, in_count);
std::string out{};
for (u32 i = 0; i < out_count; i++) {
@@ -365,7 +365,7 @@ private:
std::vector<AudioDevice::AudioDeviceName> out_names{};
- u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count);
+ const u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count);
std::string out{};
for (u32 i = 0; i < out_count; i++) {
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 50007338f..29d506c47 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -208,6 +208,16 @@ add_executable(yuzu
yuzu.rc
)
+if (WIN32 AND YUZU_CRASH_DUMPS)
+ target_sources(yuzu PRIVATE
+ mini_dump.cpp
+ mini_dump.h
+ )
+
+ target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY})
+ target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP)
+endif()
+
file(GLOB COMPAT_LIST
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index 8be311fcb..1d8072243 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -63,7 +63,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
InputCommon::InputSubsystem* input_subsystem_, Core::System& system_)
: QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
parameters(std::move(parameters_)), input_subsystem{input_subsystem_},
- input_profiles(std::make_unique<InputProfiles>(system_)), system{system_} {
+ input_profiles(std::make_unique<InputProfiles>()), system{system_} {
ui->setupUi(this);
player_widgets = {
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 8ecd87150..a4ed68422 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -15,8 +15,7 @@
namespace FS = Common::FS;
-Config::Config(Core::System& system_, const std::string& config_name, ConfigType config_type)
- : type(config_type), system{system_} {
+Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) {
global = config_type == ConfigType::GlobalConfig;
Initialize(config_name);
@@ -546,6 +545,7 @@ void Config::ReadDebuggingValues() {
ReadBasicSetting(Settings::values.use_debug_asserts);
ReadBasicSetting(Settings::values.use_auto_stub);
ReadBasicSetting(Settings::values.enable_all_controllers);
+ ReadBasicSetting(Settings::values.create_crash_dumps);
qt_config->endGroup();
}
@@ -1161,6 +1161,7 @@ void Config::SaveDebuggingValues() {
WriteBasicSetting(Settings::values.use_debug_asserts);
WriteBasicSetting(Settings::values.disable_macro_jit);
WriteBasicSetting(Settings::values.enable_all_controllers);
+ WriteBasicSetting(Settings::values.create_crash_dumps);
qt_config->endGroup();
}
@@ -1547,7 +1548,6 @@ void Config::Reload() {
ReadValues();
// To apply default value changes
SaveValues();
- system.ApplySettings();
}
void Config::Save() {
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 486ceea94..06fa7d2d0 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -25,7 +25,7 @@ public:
InputProfile,
};
- explicit Config(Core::System& system_, const std::string& config_name = "qt-config",
+ explicit Config(const std::string& config_name = "qt-config",
ConfigType config_type = ConfigType::GlobalConfig);
~Config();
@@ -194,8 +194,6 @@ private:
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;
bool global;
-
- Core::System& system;
};
// These metatype declarations cannot be in common/settings.h because core is devoid of QT
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 04d397750..622808e94 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QDesktopServices>
+#include <QMessageBox>
#include <QUrl>
#include "common/fs/path_util.h"
#include "common/logging/backend.h"
@@ -26,6 +27,16 @@ 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;
@@ -71,7 +82,14 @@ void ConfigureDebug::SetConfiguration() {
ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue());
#else
ui->disable_web_applet->setEnabled(false);
- ui->disable_web_applet->setText(QString::fromUtf8("Web applet not compiled"));
+ 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
}
@@ -84,6 +102,7 @@ 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.h b/src/yuzu/configuration/configure_debug.h
index 42d30f170..030a0b7f7 100644
--- a/src/yuzu/configuration/configure_debug.h
+++ b/src/yuzu/configuration/configure_debug.h
@@ -32,4 +32,6 @@ private:
std::unique_ptr<Ui::ConfigureDebug> ui;
const Core::System& system;
+
+ bool crash_dump_warning_shown{false};
};
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 47b8b80f1..314d47af5 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -7,60 +7,60 @@
</property>
<widget class="QWidget">
<layout class="QVBoxLayout" name="verticalLayout_1">
- <item>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string>Debugger</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Debugger</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <item>
+ <widget class="QCheckBox" name="toggle_gdbstub">
+ <property name="text">
+ <string>Enable GDB Stub</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_11">
+ <property name="text">
+ <string>Port:</string>
+ </property>
+ </widget>
+ </item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_11">
- <item>
- <widget class="QCheckBox" name="toggle_gdbstub">
- <property name="text">
- <string>Enable GDB Stub</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QLabel" name="label_11">
- <property name="text">
- <string>Port:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QSpinBox" name="gdbport_spinbox">
- <property name="minimum">
- <number>1024</number>
- </property>
- <property name="maximum">
- <number>65535</number>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="QSpinBox" name="gdbport_spinbox">
+ <property name="minimum">
+ <number>1024</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
</item>
</layout>
- </widget>
- </item>
- </layout>
+ </item>
+ </layout>
+ </widget>
</item>
+ </layout>
+ </item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
@@ -231,6 +231,13 @@
<string>Debugging</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="reporting_services">
+ <property name="text">
+ <string>Enable Verbose Reporting Services**</string>
+ </property>
+ </widget>
+ </item>
<item row="0" column="0">
<widget class="QCheckBox" name="fs_access_log">
<property name="text">
@@ -238,20 +245,20 @@
</property>
</widget>
</item>
- <item row="1" column="0">
+ <item row="0" column="1">
<widget class="QCheckBox" name="dump_audio_commands">
- <property name="text">
- <string>Dump Audio Commands To Console**</string>
- </property>
<property name="toolTip">
<string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string>
</property>
+ <property name="text">
+ <string>Dump Audio Commands To Console**</string>
+ </property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QCheckBox" name="reporting_services">
+ <item row="2" column="1">
+ <widget class="QCheckBox" name="create_crash_dumps">
<property name="text">
- <string>Enable Verbose Reporting Services**</string>
+ <string>Create Minidump After Crash</string>
</property>
</widget>
</item>
@@ -340,7 +347,6 @@
<tabstop>disable_loop_safety_checks</tabstop>
<tabstop>fs_access_log</tabstop>
<tabstop>reporting_services</tabstop>
- <tabstop>dump_audio_commands</tabstop>
<tabstop>quest_flag</tabstop>
<tabstop>enable_cpu_debugging</tabstop>
<tabstop>use_debug_asserts</tabstop>
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 16fba3deb..cb55472c9 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -65,7 +65,7 @@ void OnDockedModeChanged(bool last_state, bool new_state, Core::System& system)
ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
- profiles(std::make_unique<InputProfiles>(system_)), system{system_} {
+ profiles(std::make_unique<InputProfiles>()), system{system_} {
ui->setupUi(this);
}
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index af8343b2e..c3cb8f61d 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -42,8 +42,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name));
const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename())
: fmt::format("{:016X}", title_id);
- game_config =
- std::make_unique<Config>(system, config_file_name, Config::ConfigType::PerGameConfig);
+ game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig);
addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this);
audio_tab = std::make_unique<ConfigureAudio>(system_, this);
diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp
index 20b22e7de..807afbeb2 100644
--- a/src/yuzu/configuration/input_profiles.cpp
+++ b/src/yuzu/configuration/input_profiles.cpp
@@ -27,7 +27,7 @@ std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) {
} // namespace
-InputProfiles::InputProfiles(Core::System& system_) : system{system_} {
+InputProfiles::InputProfiles() {
const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input";
if (!FS::IsDir(input_profile_loc)) {
@@ -43,8 +43,8 @@ InputProfiles::InputProfiles(Core::System& system_) : system{system_} {
if (IsINI(filename) && IsProfileNameValid(name_without_ext)) {
map_profiles.insert_or_assign(
- name_without_ext, std::make_unique<Config>(system, name_without_ext,
- Config::ConfigType::InputProfile));
+ name_without_ext,
+ std::make_unique<Config>(name_without_ext, Config::ConfigType::InputProfile));
}
return true;
@@ -80,8 +80,7 @@ bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t p
}
map_profiles.insert_or_assign(
- profile_name,
- std::make_unique<Config>(system, profile_name, Config::ConfigType::InputProfile));
+ profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile));
return SaveProfile(profile_name, player_index);
}
diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h
index 65fc9e62c..2bf3e4250 100644
--- a/src/yuzu/configuration/input_profiles.h
+++ b/src/yuzu/configuration/input_profiles.h
@@ -15,7 +15,7 @@ class Config;
class InputProfiles {
public:
- explicit InputProfiles(Core::System& system_);
+ explicit InputProfiles();
virtual ~InputProfiles();
std::vector<std::string> GetInputProfileNames();
@@ -31,6 +31,4 @@ private:
bool ProfileExistsInMap(const std::string& profile_name) const;
std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles;
-
- Core::System& system;
};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index a85adc072..bda9986e1 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -138,6 +138,10 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/uisettings.h"
#include "yuzu/util/clickable_label.h"
+#ifdef YUZU_DBGHELP
+#include "yuzu/mini_dump.h"
+#endif
+
using namespace Common::Literals;
#ifdef USE_DISCORD_PRESENCE
@@ -269,10 +273,9 @@ bool GMainWindow::CheckDarkMode() {
#endif // __linux__
}
-GMainWindow::GMainWindow(bool has_broken_vulkan)
+GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan)
: ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()},
- input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
- config{std::make_unique<Config>(*system)},
+ input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::move(config_)},
vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
provider{std::make_unique<FileSys::ManualContentProvider>()} {
#ifdef __linux__
@@ -1637,7 +1640,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
const auto config_file_name = title_id == 0
? Common::FS::PathToUTF8String(file_path.filename())
: fmt::format("{:016X}", title_id);
- Config per_game_config(*system, config_file_name, Config::ConfigType::PerGameConfig);
+ Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
+ system->ApplySettings();
}
// Save configurations
@@ -2981,7 +2985,7 @@ void GMainWindow::OnConfigure() {
Settings::values.disabled_addons.clear();
- config = std::make_unique<Config>(*system);
+ config = std::make_unique<Config>();
UISettings::values.reset_to_defaults = false;
UISettings::values.game_dirs = std::move(old_game_dirs);
@@ -3042,6 +3046,7 @@ void GMainWindow::OnConfigure() {
UpdateStatusButtons();
controller_dialog->refreshConfiguration();
+ system->ApplySettings();
}
void GMainWindow::OnConfigureTas() {
@@ -4082,7 +4087,24 @@ void GMainWindow::changeEvent(QEvent* event) {
#endif
int main(int argc, char* argv[]) {
+ std::unique_ptr<Config> config = std::make_unique<Config>();
bool has_broken_vulkan = false;
+ bool is_child = false;
+ if (CheckEnvVars(&is_child)) {
+ 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)) {
return 0;
}
@@ -4135,7 +4157,7 @@ int main(int argc, char* argv[]) {
// generating shaders
setlocale(LC_ALL, "C");
- GMainWindow main_window{has_broken_vulkan};
+ GMainWindow main_window{std::move(config), has_broken_vulkan};
// After settings have been loaded by GMainWindow, apply the filter
main_window.show();
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 1ae2b93d9..716aef063 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -120,7 +120,7 @@ class GMainWindow : public QMainWindow {
public:
void filterBarSetChecked(bool state);
void UpdateUITheme();
- explicit GMainWindow(bool has_broken_vulkan);
+ explicit GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan);
~GMainWindow() override;
bool DropAction(QDropEvent* event);
diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp
new file mode 100644
index 000000000..a34dc6a9c
--- /dev/null
+++ b/src/yuzu/mini_dump.cpp
@@ -0,0 +1,202 @@
+// 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
new file mode 100644
index 000000000..d6b6cca84
--- /dev/null
+++ b/src/yuzu/mini_dump.h
@@ -0,0 +1,19 @@
+// 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/startup_checks.cpp b/src/yuzu/startup_checks.cpp
index 8421280bf..29b87da05 100644
--- a/src/yuzu/startup_checks.cpp
+++ b/src/yuzu/startup_checks.cpp
@@ -31,19 +31,36 @@ void CheckVulkan() {
}
}
-bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
+bool CheckEnvVars(bool* is_child) {
#ifdef _WIN32
// Check environment variable to see if we are the child
char variable_contents[8];
const DWORD startup_check_var =
GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8);
- if (startup_check_var > 0 && std::strncmp(variable_contents, "ON", 8) == 0) {
+ if (startup_check_var > 0 && std::strncmp(variable_contents, ENV_VAR_ENABLED_TEXT, 8) == 0) {
CheckVulkan();
return true;
}
+ // Don't perform startup checks if we are a child process
+ char is_child_s[8];
+ const DWORD is_child_len = GetEnvironmentVariableA(IS_CHILD_ENV_VAR, is_child_s, 8);
+ if (is_child_len > 0 && std::strncmp(is_child_s, ENV_VAR_ENABLED_TEXT, 8) == 0) {
+ *is_child = true;
+ return false;
+ } else if (!SetEnvironmentVariableA(IS_CHILD_ENV_VAR, ENV_VAR_ENABLED_TEXT)) {
+ std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n",
+ IS_CHILD_ENV_VAR, GetLastError());
+ return true;
+ }
+#endif
+ return false;
+}
+
+bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
+#ifdef _WIN32
// Set the startup variable for child processes
- const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, "ON");
+ const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, ENV_VAR_ENABLED_TEXT);
if (!env_var_set) {
std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n",
STARTUP_CHECK_ENV_VAR, GetLastError());
@@ -53,7 +70,7 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
PROCESS_INFORMATION process_info;
std::memset(&process_info, '\0', sizeof(process_info));
- if (!SpawnChild(arg0, &process_info)) {
+ if (!SpawnChild(arg0, &process_info, 0)) {
return false;
}
@@ -106,7 +123,7 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
}
#ifdef _WIN32
-bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) {
+bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags) {
STARTUPINFOA startup_info;
std::memset(&startup_info, '\0', sizeof(startup_info));
@@ -120,7 +137,7 @@ bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) {
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
false, // bInheritHandles
- 0, // dwCreationFlags
+ flags, // dwCreationFlags
nullptr, // lpEnvironment
nullptr, // lpCurrentDirectory
&startup_info, // lpStartupInfo
diff --git a/src/yuzu/startup_checks.h b/src/yuzu/startup_checks.h
index 096dd54a8..f2fc2d9d4 100644
--- a/src/yuzu/startup_checks.h
+++ b/src/yuzu/startup_checks.h
@@ -7,11 +7,14 @@
#include <windows.h>
#endif
+constexpr char IS_CHILD_ENV_VAR[] = "YUZU_IS_CHILD";
constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS";
+constexpr char ENV_VAR_ENABLED_TEXT[] = "ON";
void CheckVulkan();
+bool CheckEnvVars(bool* is_child);
bool StartupChecks(const char* arg0, bool* has_broken_vulkan);
#ifdef _WIN32
-bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi);
+bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags);
#endif
diff --git a/vcpkg.json b/vcpkg.json
index c4413e22a..3c92510d6 100644
--- a/vcpkg.json
+++ b/vcpkg.json
@@ -31,6 +31,10 @@
"yuzu-tests": {
"description": "Compile tests",
"dependencies": [ "catch2" ]
+ },
+ "dbghelp": {
+ "description": "Compile Windows crash dump (Minidump) support",
+ "dependencies": [ "dbghelp" ]
}
},
"overrides": [