diff --git a/aurora-main b/aurora-main index 96bf4ac..41a9d64 160000 --- a/aurora-main +++ b/aurora-main @@ -1 +1 @@ -Subproject commit 96bf4ac5021df76b0ace0d66b17ffcf153802d92 +Subproject commit 41a9d64811bf55961832483b6b3c8a08f38ca498 diff --git a/runtime/include/audio_backend.h b/runtime/include/audio_backend.h index ae05827..6ff3248 100644 --- a/runtime/include/audio_backend.h +++ b/runtime/include/audio_backend.h @@ -22,6 +22,8 @@ public: // Applied to the final host output, covering both AX and direct AI DMA. void SetMasterVolume(float volume); void SetMuted(bool muted); + // Mutes output without touching the user's persisted mute setting. + void SetSilenced(bool silenced); private: AudioBackend() = default; @@ -43,6 +45,7 @@ private: bool m_initialized = false; float m_masterVolume = 1.0f; bool m_muted = false; + bool m_silenced = false; bool m_reportedDroppedBlock = false; std::vector m_convertBuffer; }; diff --git a/runtime/include/boot_race.h b/runtime/include/boot_race.h index 18623e6..0eb161f 100644 --- a/runtime/include/boot_race.h +++ b/runtime/include/boot_race.h @@ -7,4 +7,5 @@ namespace BootRace { // False when `arg` is not a boot-race option; throws on a malformed one. bool ParseArgument(std::string_view arg); +bool Requested(); } // namespace BootRace diff --git a/runtime/src/audio_backend.cpp b/runtime/src/audio_backend.cpp index 7ceda4c..fcb4fde 100644 --- a/runtime/src/audio_backend.cpp +++ b/runtime/src/audio_backend.cpp @@ -15,7 +15,7 @@ AudioBackend& AudioBackend::Instance() { } float AudioBackend::EffectiveGainLocked() const { - return m_muted ? 0.0f : m_masterVolume; + return m_muted || m_silenced ? 0.0f : m_masterVolume; } void AudioBackend::ApplyGainLocked() { @@ -36,6 +36,12 @@ void AudioBackend::SetMuted(bool muted) { ApplyGainLocked(); } +void AudioBackend::SetSilenced(bool silenced) { + std::lock_guard lock(m_mutex); + m_silenced = silenced; + ApplyGainLocked(); +} + bool AudioBackend::EnsureInitializedLocked(uint32_t sampleRate, uint32_t channels) { if (m_initialized && m_sampleRate == sampleRate && m_channels == channels) { return true; diff --git a/runtime/src/hle/boot_race.cpp b/runtime/src/hle/boot_race.cpp index dd5228e..6d592fc 100644 --- a/runtime/src/hle/boot_race.cpp +++ b/runtime/src/hle/boot_race.cpp @@ -125,6 +125,10 @@ bool ParseArgument(std::string_view arg) { return true; } +bool Requested() { + return g_course.has_value(); +} + } // namespace BootRace // ArgumentParser::parse, picks the boot section in SectionMgr::Init. diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 929723f..6d0dd6b 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -57,6 +57,7 @@ #endif #include "abi_bridge.h" +#include "audio_backend.h" #include "boot_race.h" #include "guest_abi/guest_region.h" #include "guest_flat_memory.h" @@ -167,6 +168,7 @@ std::atomic_bool g_auroraInitialized{false}; std::atomic_flag g_abortSignalHandled = ATOMIC_FLAG_INIT; std::atomic_bool g_fatalErrorReported{false}; std::atomic_bool g_fatalPopupShown{false}; +bool g_headless = false; std::atomic g_lastExitCode{0}; std::atomic_bool g_exitCodeSet{false}; @@ -773,7 +775,7 @@ void MarkFatalErrorReported() { } void ShowRuntimeFatalPopup(std::string_view category, std::string_view details) noexcept { - if (g_fatalPopupShown.exchange(true, std::memory_order_acq_rel)) { + if (g_headless || g_fatalPopupShown.exchange(true, std::memory_order_acq_rel)) { return; } @@ -1345,15 +1347,25 @@ int RuntimeMain(int argc, char** argv) { std::string currentEntryLabel; + // `--headless`: with a boot race, run without a window, sound or dialogs. + g_headless = std::any_of(argv + 1, argv + argc, + [](const char* arg) { return std::string_view(arg) == "--headless"; }); + try { for (int i = 1; i < argc; ++i) { - if (!BootRace::ParseArgument(argv[i])) { + if (std::string_view(argv[i]) != "--headless" && !BootRace::ParseArgument(argv[i])) { throw std::invalid_argument(std::string("Unknown command-line option '") + argv[i] + - "'; the game runtime only accepts --race[=] and --race-cpu, use Config.toml through the installed host."); + "'; the game runtime only accepts --race[=], --race-cpu and --headless, use Config.toml through the installed host."); } } + if (g_headless && !BootRace::Requested()) { + throw std::invalid_argument("--headless needs --race[=] or --race-cpu"); + } + if (g_headless) { + AudioBackend::Instance().SetSilenced(true); + } RuntimeConfigFile::LogLoadedConfig(); - if (RuntimeConfigFile::DiscordPresenceEnabled()) { + if (!g_headless && RuntimeConfigFile::DiscordPresenceEnabled()) { DiscordPresence::Initialize(RuntimeConfigFile::DiscordClientId(), "Mario Kart Wii"); } SystemBridge::Initialize(); @@ -1387,7 +1399,8 @@ int RuntimeMain(int argc, char** argv) { auroraConfig.windowHeight = RuntimeConfigFile::WindowHeight(auroraConfig.windowHeight); auroraConfig.hasWindowPosition = RuntimeConfigFile::WindowPosition( auroraConfig.windowPosX, auroraConfig.windowPosY); - auroraConfig.allowJoystickBackgroundEvents = true; + auroraConfig.allowJoystickBackgroundEvents = !g_headless; + auroraConfig.startHidden = g_headless; auroraConfig.disableCopyFilter = RuntimeConfigFile::DisableCopyFilter(true); // Dolphin-style custom textures. Aurora indexes /texture_replacements // once during aurora_initialize, so both knobs only take effect on the next launch. @@ -1466,6 +1479,9 @@ int RuntimeMain(int argc, char** argv) { UpdateMkwDynamicAspectSurface(auroraInfo.windowSize.native_fb_width, auroraInfo.windowSize.native_fb_height); settings_overlay::InitializeRuntimeSettings(); + if (BootRace::Requested()) { + aurora_set_skip_unready_pipelines(false); + } RT_LOG(RT_TAG_CONFIG) << "video.widescreen=" << (configWidescreen ? "true" : "false") << " force_16_9=" << (forceAspect169 ? "true" : "false") << " SCGetAspectRatio=" << (configWidescreen || forceAspect169 ? 1 : 0)