From 65b481ed29285e62fa48778092ca2dc0fa80fd2b Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Mon, 9 Feb 2026 10:23:24 +0900 Subject: [PATCH] [Emulator] Fix crashes with in-process relaunch Gracefully shutdown dispatch thread and ensure we don't persist alternate xex paths as separate discs --- src/xenia/emulator.cc | 12 +++++++++--- src/xenia/kernel/kernel_state.cc | 18 +++++++++--------- src/xenia/kernel/kernel_state.h | 4 ++++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 4c7d2b99b..f7fc8abb6 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -1461,8 +1461,11 @@ void Emulator::RelaunchTitle(const std::string& host_path, // Tell WaitUntilExit not to fire on_exit when main thread dies. relaunching_ = true; - // Force-terminate all threads. Cooperative shutdown isn't possible since - // workers may be stuck in processor_->Execute(). + // Stop the dispatch thread gracefully before force-terminating threads, + // otherwise TerminateThread corrupts the CV it's blocked on. + kernel_state_->ShutdownDispatchThread(); + + // Force-terminate remaining threads. { auto threads = kernel_state()->object_table()->GetObjectsByType( @@ -2012,7 +2015,10 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path, game_info_database_ = std::make_unique(db.get()); - kernel_state_->xam_state()->LoadSpaInfo(db.get(), path); + // Don't persist disc path during in-process relaunch; only for + // user-initiated file opens. + kernel_state_->xam_state()->LoadSpaInfo( + db.get(), relaunching_ ? std::filesystem::path{} : path); // AddTitleToPlayedList is now called inside LoadSpaInfo/UpdateSpaInfo diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index a47a2d346..ea5d53ffb 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -80,15 +80,7 @@ KernelState::KernelState(Emulator* emulator) KernelState::~KernelState() { SetExecutableModule(nullptr); - if (dispatch_thread_running_) { - dispatch_thread_running_ = false; - if (dispatch_thread_ && dispatch_thread_->is_running()) { - dispatch_cond_.notify_all(); - dispatch_thread_->Wait(0, 0, 0, nullptr); - } - // Skip notify/Wait if already force-terminated — mutex may be abandoned. - dispatch_thread_.reset(); - } + ShutdownDispatchThread(); executable_module_.reset(); user_modules_.clear(); @@ -103,6 +95,14 @@ KernelState::~KernelState() { shared_kernel_state_ = nullptr; } +void KernelState::ShutdownDispatchThread() { + if (dispatch_thread_running_) { + dispatch_thread_running_ = false; + dispatch_cond_.notify_all(); + dispatch_thread_->Wait(0, 0, 0, nullptr); + } +} + KernelState* KernelState::shared() { return shared_kernel_state_; } uint32_t KernelState::title_id() const { diff --git a/src/xenia/kernel/kernel_state.h b/src/xenia/kernel/kernel_state.h index 0d0a6ac37..013025b74 100644 --- a/src/xenia/kernel/kernel_state.h +++ b/src/xenia/kernel/kernel_state.h @@ -262,6 +262,10 @@ class KernelState { // This DOES NOT RETURN if called from a guest thread! void TerminateTitle(); + // Gracefully stops the dispatch thread. Call before force-terminating + // threads to avoid corrupting the CV it's blocked on. + void ShutdownDispatchThread(); + void RegisterThread(XThread* thread); void UnregisterThread(XThread* thread); void OnThreadExecute(XThread* thread);