From b08d994e3224728c36b7bfe05d764d3d89381655 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 10 May 2026 17:27:30 -0600 Subject: [PATCH 1/8] Update aurora --- extern/aurora | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/aurora b/extern/aurora index 449e7bf9b9..aff6edf9de 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 449e7bf9b934a61b7c5736d8b87310a1f92829f0 +Subproject commit aff6edf9de39b3c211ccb252e76331abaa9e5e01 From 93c6106770adf2559cd69c5e9c734311586690ec Mon Sep 17 00:00:00 2001 From: qwertyquerty Date: Sun, 10 May 2026 16:30:39 -0700 Subject: [PATCH 2/8] cherry pick actor spawner (#920) --- files.cmake | 1 + src/dusk/imgui/ImGuiActorSpawner.cpp | 142 +++++++++++++++++++++++++++ src/dusk/imgui/ImGuiConsole.cpp | 1 + src/dusk/imgui/ImGuiMenuTools.cpp | 1 + src/dusk/imgui/ImGuiMenuTools.hpp | 2 + 5 files changed, 147 insertions(+) create mode 100644 src/dusk/imgui/ImGuiActorSpawner.cpp diff --git a/files.cmake b/files.cmake index 53aa7a2636..d0bcad2d30 100644 --- a/files.cmake +++ b/files.cmake @@ -1450,6 +1450,7 @@ set(DUSK_FILES src/dusk/imgui/ImGuiBloomWindow.hpp src/dusk/imgui/ImGuiMenuTools.cpp src/dusk/imgui/ImGuiMenuTools.hpp + src/dusk/imgui/ImGuiActorSpawner.cpp src/dusk/imgui/ImGuiProcessOverlay.cpp src/dusk/imgui/ImGuiCameraOverlay.cpp src/dusk/imgui/ImGuiHeapOverlay.cpp diff --git a/src/dusk/imgui/ImGuiActorSpawner.cpp b/src/dusk/imgui/ImGuiActorSpawner.cpp new file mode 100644 index 0000000000..f3aefd4c41 --- /dev/null +++ b/src/dusk/imgui/ImGuiActorSpawner.cpp @@ -0,0 +1,142 @@ +#include "imgui.h" + +#include "ImGuiMenuTools.hpp" +#include "d/actor/d_a_alink.h" +#include "d/d_com_inf_game.h" +#include "f_op/f_op_actor_mng.h" +#include "SSystem/SComponent/c_sxyz.h" +#include "SSystem/SComponent/c_xyz.h" + +namespace dusk { +namespace { + +struct ActorSpawnerState { + int actorId = 0; + int params = -1; + int argument = -1; + int angleX = 0; + int angleY = 0; + int angleZ = 0; + float scaleX = 1.0f; + float scaleY = 1.0f; + float scaleZ = 1.0f; + bool usePlayerRoom = true; + int manualRoom = 0; + int spawnCount = 1; + bool hasResult = false; + unsigned int lastResult = 0; + int lastAttempted = 0; +}; + +ActorSpawnerState s_state; + +} // namespace + +void ImGuiMenuTools::ShowActorSpawner() { + if (!m_showActorSpawner) { + return; + } + + if (!ImGui::Begin("Actor Spawner", &m_showActorSpawner)) { + ImGui::End(); + return; + } + + daAlink_c* player = (daAlink_c*)dComIfGp_getPlayer(0); + + ImGui::SeparatorText("Actor"); + ImGui::InputInt("Actor ID", &s_state.actorId); + ImGui::InputInt("Params (hex)", &s_state.params, 0, 0, ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputInt("Argument", &s_state.argument); + s_state.argument = (s_state.argument < -128) ? -128 : (s_state.argument > 127) ? 127 : s_state.argument; + + ImGui::SeparatorText("Angle"); + ImGui::InputInt("Angle X", &s_state.angleX); + ImGui::InputInt("Angle Y", &s_state.angleY); + ImGui::InputInt("Angle Z", &s_state.angleZ); + + ImGui::SeparatorText("Scale"); + ImGui::InputFloat("Scale X", &s_state.scaleX, 0.1f, 1.0f); + ImGui::InputFloat("Scale Y", &s_state.scaleY, 0.1f, 1.0f); + ImGui::InputFloat("Scale Z", &s_state.scaleZ, 0.1f, 1.0f); + + ImGui::SeparatorText("Spawn"); + ImGui::InputInt("Count", &s_state.spawnCount); + if (s_state.spawnCount < 1) { + s_state.spawnCount = 1; + } + + ImGui::SeparatorText("Position"); + ImGui::Checkbox("Use player room", &s_state.usePlayerRoom); + if (!s_state.usePlayerRoom) { + ImGui::InputInt("Room No", &s_state.manualRoom); + } + + if (player != nullptr) { + ImGui::Text("Spawn pos: %.2f, %.2f, %.2f", + player->current.pos.x, player->current.pos.y, player->current.pos.z); + } else { + ImGui::TextDisabled("Player not available"); + } + + ImGui::Separator(); + + bool canSpawn = player != nullptr; + if (!canSpawn) { + ImGui::BeginDisabled(); + } + + if (ImGui::Button("Spawn", ImVec2(-1, 0))) { + cXyz pos = player->current.pos; + csXyz angle; + angle.set((s16)s_state.angleX, (s16)s_state.angleY, (s16)s_state.angleZ); + cXyz scale(s_state.scaleX, s_state.scaleY, s_state.scaleZ); + int roomNo = s_state.usePlayerRoom ? player->current.roomNo : s_state.manualRoom; + + layer_class* savedLayer = fpcLy_CurrentLayer(); + base_process_class* playScene = fpcM_SearchByName(fpcNm_PLAY_SCENE_e); + if (playScene != nullptr) { + fpcLy_SetCurrentLayer(&((process_node_class*)playScene)->layer); + } + + s_state.lastResult = 0; + s_state.lastAttempted = s_state.spawnCount; + for (int i = 0; i < s_state.spawnCount; ++i) { + unsigned int result = fopAcM_create( + (s16)s_state.actorId, + (u32)s_state.params, + &pos, + roomNo, + &angle, + &scale, + (s8)s_state.argument + ); + if (result != 0) { + s_state.lastResult = result; + } + } + s_state.hasResult = true; + + fpcLy_SetCurrentLayer(savedLayer); + } + + if (!canSpawn) { + ImGui::EndDisabled(); + } + + if (s_state.hasResult) { + if (s_state.lastResult != 0) { + if (s_state.lastAttempted == 1) { + ImGui::Text("Spawned: proc ID %u", s_state.lastResult); + } else { + ImGui::Text("Spawned %d (last proc ID %u)", s_state.lastAttempted, s_state.lastResult); + } + } else { + ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "Spawn failed (returned 0)"); + } + } + + ImGui::End(); +} + +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index dca2a88d79..de62596308 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -375,6 +375,7 @@ namespace dusk { m_menuTools.ShowAudioDebug(); m_menuTools.ShowSaveEditor(); m_menuTools.ShowStateShare(); + m_menuTools.ShowActorSpawner(); } // Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds. diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 306298c28d..1cb0ec474c 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -107,6 +107,7 @@ namespace dusk { ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug); ImGui::MenuItem("Bloom", nullptr, &m_showBloomWindow); ImGui::MenuItem("Stub Log", nullptr, &m_showStubLog); + ImGui::MenuItem("Actor Spawner", nullptr, &m_showActorSpawner); if (!dusk::IsGameLaunched) { ImGui::EndDisabled(); diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index 6b925b7bf3..1c17ddbbe9 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -28,6 +28,7 @@ namespace dusk { void ShowSaveEditor(); void ShowStateShare(); void ShowInputViewer(); + void ShowActorSpawner(); private: bool m_showDebugOverlay = false; @@ -70,6 +71,7 @@ namespace dusk { bool m_showInputViewer = false; bool m_showInputViewerGyro = false; + bool m_showActorSpawner = false; int m_inputOverlayCorner = 3; std::string m_controllerName; }; From 3a02e129e74ad361eac22223212b90e823bb1c28 Mon Sep 17 00:00:00 2001 From: qwertyquerty Date: Sun, 10 May 2026 16:35:29 -0700 Subject: [PATCH 3/8] Fix keyboard not binding maybe (idk i cant repro) (#901) * fixkb * restore comment --- src/dusk/ui/controller_config.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/dusk/ui/controller_config.cpp b/src/dusk/ui/controller_config.cpp index 52c6f6c702..88baa7b29c 100644 --- a/src/dusk/ui/controller_config.cpp +++ b/src/dusk/ui/controller_config.cpp @@ -208,7 +208,15 @@ bool input_neutral(int port) { bool keyboard_escape_pressed() { int keyCount = 0; const bool* keys = SDL_GetKeyboardState(&keyCount); - return keys != nullptr && SDL_SCANCODE_ESCAPE < keyCount && keys[SDL_SCANCODE_ESCAPE]; + if (keys == nullptr || SDL_SCANCODE_ESCAPE >= keyCount || !keys[SDL_SCANCODE_ESCAPE]) { + return false; + } + for (int i = 0; i < keyCount; ++i) { + if (i != SDL_SCANCODE_ESCAPE && keys[i]) { + return false; + } + } + return true; } Rml::String keyboard_key_name(s32 scancode) { From e7ab978a30ac82afeb0759cbdbf9dcfe0e4973b6 Mon Sep 17 00:00:00 2001 From: SuperDude88 <82904174+SuperDude88@users.noreply.github.com> Date: Sun, 10 May 2026 20:37:22 -0400 Subject: [PATCH 4/8] Crash Reporting Popup (#879) * Initial Draft - Add draft crash report window on startup If you want to disable them before/during startup, there is a command line option to force it * Fixes - Update language to be more precise, consistent with settings menu - Actually shut down reporting properly if you disable it - Fix my silly syntax errors * Update text & use Sentry consent --------- Co-authored-by: Luke Street --- CMakeLists.txt | 6 ++ files.cmake | 2 + include/dusk/crash_reporting.h | 17 ++++- include/dusk/settings.h | 1 - src/dusk/crash_reporting.cpp | 131 +++++++++++++++++---------------- src/dusk/settings.cpp | 2 - src/dusk/ui/reporting.cpp | 113 ++++++++++++++++++++++++++++ src/dusk/ui/reporting.hpp | 28 +++++++ src/dusk/ui/settings.cpp | 32 +++++--- src/m_Do/m_Do_main.cpp | 18 ++++- 10 files changed, 264 insertions(+), 86 deletions(-) create mode 100644 src/dusk/ui/reporting.cpp create mode 100644 src/dusk/ui/reporting.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c4f3180f1..df0afc5b86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -444,6 +444,12 @@ target_link_libraries(dusk PRIVATE aurora::main ${GAME_LIBS} ${JSYSTEM_LINK_LIBR target_precompile_headers(dusk PRIVATE "$<$:${CMAKE_SOURCE_DIR}/include/dusk_pch.hpp>") if (TARGET crashpad_handler) add_dependencies(dusk crashpad_handler) + add_custom_command(TARGET dusk POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "$" + COMMENT "Copying crashpad handler" + ) endif () if (ANDROID) diff --git a/files.cmake b/files.cmake index d0bcad2d30..d35d95b8a2 100644 --- a/files.cmake +++ b/files.cmake @@ -1495,6 +1495,8 @@ set(DUSK_FILES src/dusk/ui/prelaunch.hpp src/dusk/ui/preset.cpp src/dusk/ui/preset.hpp + src/dusk/ui/reporting.cpp + src/dusk/ui/reporting.hpp src/dusk/ui/select_button.cpp src/dusk/ui/select_button.hpp src/dusk/ui/settings.cpp diff --git a/include/dusk/crash_reporting.h b/include/dusk/crash_reporting.h index dfc5bde817..c42079a6f6 100644 --- a/include/dusk/crash_reporting.h +++ b/include/dusk/crash_reporting.h @@ -1,8 +1,17 @@ #pragma once -namespace dusk { +namespace dusk::crash_reporting { -void InitializeCrashReporting(); -void ShutdownCrashReporting(); +enum class Consent { + Unavailable, + Unknown, + Given, + Revoked, +}; -} // namespace dusk +void initialize(); +void shutdown(); +Consent get_consent(); +void set_consent(bool enabled); + +} // namespace dusk::crash_reporting diff --git a/include/dusk/settings.h b/include/dusk/settings.h index f74a4bf61f..709c3eb329 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -184,7 +184,6 @@ struct UserSettings { ConfigVar skipPreLaunchUI; ConfigVar showPipelineCompilation; ConfigVar wasPresetChosen; - ConfigVar enableCrashReporting; ConfigVar checkForUpdates; ConfigVar cardFileType; ConfigVar enableAdvancedSettings; diff --git a/src/dusk/crash_reporting.cpp b/src/dusk/crash_reporting.cpp index 657e5ef5d2..5c75388361 100644 --- a/src/dusk/crash_reporting.cpp +++ b/src/dusk/crash_reporting.cpp @@ -4,7 +4,6 @@ #include "dusk/dusk.h" #include "dusk/logging.h" #include "dusk/main.h" -#include "dusk/settings.h" #include "version.h" #include @@ -13,114 +12,83 @@ #include #include -#include "SDL3/SDL_filesystem.h" - #if DUSK_ENABLE_SENTRY_NATIVE #include #endif -namespace dusk { +namespace dusk::crash_reporting { namespace { #if DUSK_ENABLE_SENTRY_NATIVE bool g_sentryInitialized = false; -bool IsTruthy(std::string_view value) { - return value == "1" || value == "true" || value == "TRUE" || value == "yes" - || value == "YES" || value == "on" || value == "ON"; +bool truthy(std::string_view value) { + return value == "1" || value == "true" || value == "TRUE" || value == "yes" || value == "YES" || + value == "on" || value == "ON"; } -std::string GetEnvOrEmpty(const char* name) { +std::string env_or_empty(const char* name) { if (const char* value = std::getenv(name)) { return value; } return {}; } -bool GetEffectiveEnabled() { - const std::string env = GetEnvOrEmpty("DUSK_SENTRY_ENABLED"); - if (!env.empty()) { - return IsTruthy(env); - } - return getSettings().backend.enableCrashReporting; +bool disabled_by_env() { + const std::string env = env_or_empty("DUSK_SENTRY_ENABLED"); + return !env.empty() && !truthy(env); } -std::string GetEffectiveDsn() { - const std::string env = GetEnvOrEmpty("DUSK_SENTRY_DSN"); +std::string effective_dsn() { + const std::string env = env_or_empty("DUSK_SENTRY_DSN"); if (!env.empty()) { return env; } return DUSK_SENTRY_DSN; } -bool GetEffectiveDebug() { - const std::string env = GetEnvOrEmpty("DUSK_SENTRY_DEBUG"); +bool effective_debug() { + const std::string env = env_or_empty("DUSK_SENTRY_DEBUG"); if (!env.empty()) { - return IsTruthy(env); + return truthy(env); } return false; } -std::string GetReleaseName() { +std::string release_name() { return std::string(AppName) + "@" DUSK_WC_DESCRIBE; } -std::filesystem::path GetSentryDatabasePath() { +std::filesystem::path sentry_database_path() { return dusk::ConfigPath / "sentry"; } -std::filesystem::path GetLogAttachmentPath() { +std::filesystem::path log_attachment_path() { if (const char* logPath = GetLogFilePath()) { return logPath; } return {}; } -std::filesystem::path GetCrashpadHandlerPath() { - const char* basePath = SDL_GetBasePath(); - if (!basePath) { - return {}; - } - - const std::filesystem::path handlerDir(basePath); -#if _WIN32 - return handlerDir / "crashpad_handler.exe"; -#else - return handlerDir / "crashpad_handler"; -#endif -} - -void ConfigurePathOptions(sentry_options_t* options) { - const auto databasePath = GetSentryDatabasePath(); +void configure_path_options(sentry_options_t* options) { + const auto databasePath = sentry_database_path(); std::error_code ec; std::filesystem::create_directories(databasePath, ec); if (ec) { - DuskLog.warn("Unable to create Sentry database path '{}': {}", - databasePath.string(), ec.message()); + DuskLog.warn( + "Unable to create Sentry database path '{}': {}", databasePath.string(), ec.message()); } #if _WIN32 const std::wstring databasePathWide = databasePath.wstring(); sentry_options_set_database_pathw(options, databasePathWide.c_str()); - - const auto handlerPath = GetCrashpadHandlerPath(); - if (!handlerPath.empty()) { - const std::wstring handlerPathWide = handlerPath.wstring(); - sentry_options_set_handler_pathw(options, handlerPathWide.c_str()); - } #else const std::string databasePathUtf8 = databasePath.string(); sentry_options_set_database_path(options, databasePathUtf8.c_str()); - - const auto handlerPath = GetCrashpadHandlerPath(); - if (!handlerPath.empty()) { - const std::string handlerPathUtf8 = handlerPath.string(); - sentry_options_set_handler_path(options, handlerPathUtf8.c_str()); - } #endif - const auto logPath = GetLogAttachmentPath(); + const auto logPath = log_attachment_path(); if (!logPath.empty()) { #if _WIN32 sentry_options_add_attachmentw(options, logPath.wstring().c_str()); @@ -133,32 +101,29 @@ void ConfigurePathOptions(sentry_options_t* options) { } // namespace -void InitializeCrashReporting() { +void initialize() { #if DUSK_ENABLE_SENTRY_NATIVE - if (g_sentryInitialized) { + if (g_sentryInitialized || disabled_by_env()) { return; } - if (!GetEffectiveEnabled()) { - return; - } - - const std::string dsn = GetEffectiveDsn(); + const std::string dsn = effective_dsn(); if (dsn.empty()) { DuskLog.warn("Crash reporting is enabled but no Sentry DSN is configured"); return; } - const std::string release = GetReleaseName(); + const std::string release = release_name(); sentry_options_t* options = sentry_options_new(); sentry_options_set_dsn(options, dsn.c_str()); sentry_options_set_release(options, release.c_str()); sentry_options_set_environment(options, DUSK_SENTRY_ENVIRONMENT); - sentry_options_set_debug(options, GetEffectiveDebug() ? 1 : 0); + sentry_options_set_debug(options, effective_debug() ? 1 : 0); + sentry_options_set_require_user_consent(options, 1); sentry_options_set_cache_keep(options, 1); sentry_options_set_max_breadcrumbs(options, 100); - ConfigurePathOptions(options); + configure_path_options(options); if (sentry_init(options) != 0) { DuskLog.warn("Failed to initialize Sentry crash reporting"); @@ -173,7 +138,7 @@ void InitializeCrashReporting() { #endif } -void ShutdownCrashReporting() { +void shutdown() { #if DUSK_ENABLE_SENTRY_NATIVE if (!g_sentryInitialized) { return; @@ -184,4 +149,40 @@ void ShutdownCrashReporting() { #endif } -} // namespace dusk +Consent get_consent() { +#if DUSK_ENABLE_SENTRY_NATIVE + if (!g_sentryInitialized) { + return Consent::Unavailable; + } + + switch (sentry_user_consent_get()) { + case SENTRY_USER_CONSENT_GIVEN: + return Consent::Given; + case SENTRY_USER_CONSENT_REVOKED: + return Consent::Revoked; + case SENTRY_USER_CONSENT_UNKNOWN: + default: + return Consent::Unknown; + } +#else + return Consent::Unavailable; +#endif +} + +void set_consent(bool enabled) { +#if DUSK_ENABLE_SENTRY_NATIVE + if (!g_sentryInitialized) { + return; + } + + if (enabled) { + sentry_user_consent_give(); + } else { + sentry_user_consent_revoke(); + } +#else + (void)enabled; +#endif +} + +} // namespace dusk::crash_reporting diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index f0d10d4226..6880b407f7 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -121,7 +121,6 @@ UserSettings g_userSettings = { .skipPreLaunchUI {"backend.skipPreLaunchUI", false}, .showPipelineCompilation {"backend.showPipelineCompilation", false}, .wasPresetChosen {"backend.wasPresetChosen", false}, - .enableCrashReporting {"backend.enableCrashReporting", true}, .checkForUpdates {"backend.checkForUpdates", true}, .cardFileType {"backend.cardFileType", static_cast(CARD_GCIFOLDER)}, .enableAdvancedSettings {"backend.enableAdvancedSettings", false}, @@ -228,7 +227,6 @@ void registerSettings() { Register(g_userSettings.backend.skipPreLaunchUI); Register(g_userSettings.backend.showPipelineCompilation); Register(g_userSettings.backend.wasPresetChosen); - Register(g_userSettings.backend.enableCrashReporting); Register(g_userSettings.backend.checkForUpdates); Register(g_userSettings.backend.cardFileType); Register(g_userSettings.backend.enableAdvancedSettings); diff --git a/src/dusk/ui/reporting.cpp b/src/dusk/ui/reporting.cpp new file mode 100644 index 0000000000..e32a9cbc20 --- /dev/null +++ b/src/dusk/ui/reporting.cpp @@ -0,0 +1,113 @@ +#if DUSK_ENABLE_SENTRY_NATIVE + +#include "reporting.hpp" + +#include "button.hpp" +#include "dusk/crash_reporting.h" +#include "ui.hpp" + +#include + +namespace dusk::ui { + +CrashReportWindow::CrashReportWindow() : WindowSmall("modal", "modal-dialog") { + mDialog->SetClass("modal-dialog", true); + + auto* header = append(mDialog, "div"); + header->SetClass("modal-header", true); + + auto* title = append(header, "div"); + title->SetClass("modal-title", true); + title->SetInnerRML("Send Crash Reports"); + + auto* headIcon = append(header, "icon"); + headIcon->SetClass("question-mark", true); + + auto* intro = append(mDialog, "div"); + intro->SetClass("modal-body", true); + intro->SetInnerRML( + "Dusk can automatically send crash reports to the developers. Crash reports contain the " + "following:" + "
• Operating system version
• CPU architecture
• GPU model & driver version" + "
• File paths (may include account username)
• Stack trace

" + "This can be changed in the Settings menu at any time."); + + auto* grid = append(mDialog, "div"); + grid->SetClass("preset-grid", true); + + struct OptionInfo { + const char* name; + const char* desc; + void (*apply)(); + }; + + static constexpr OptionInfo kOptions[] = { + {"Enable", + "Send crash reports to Dusk developers. Reports will include the information described " + "above.", + [] { crash_reporting::set_consent(true); }}, + {"Disable", + "Do not send crash reports. This may make it more difficult to resolve issues you " + "encounter.", + [] { crash_reporting::set_consent(false); }}, + }; + + for (const auto& option : kOptions) { + auto* col = append(grid, "div"); + col->SetClass("preset-col", true); + + auto btn = std::make_unique