From a02256099164ef8e080505bc958aa6fcfbe7b459 Mon Sep 17 00:00:00 2001 From: SchweGELBin Date: Sat, 7 Mar 2026 20:08:47 +0100 Subject: [PATCH 1/3] [desktop] tas: add option to disable file overwrite dialog (#3657) Hello everybody, thank you for letting me participate in the development of the Eden emulator! I've been playing around with the TAS functionality and didn't want to always click "Yes" in the dialog that askes if I want to "Overwrite file of player 1?" after recording the inputs. So I can't record and play TAS files with keybinds only, because I'd still need to switching from my contoller to my keyboard and back. So I added the option "Show recording dialog" into the configure_tas screen. (The final naming and string can be changed of course.) It's a checkbox that is enabled by default (so no changes if ignored), but can be unchecked to disable the popup. The change has been tested on top of the current master branch. I've also created a commit to add the relevant translation data, where german is translated and the rest unfinished. I'm not sure how this would be handled as this project uses transifex for it localization, so I can remove this commit if preferred. Have a great day! - Michi Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3657 Reviewed-by: crueter Reviewed-by: CamilleLaVey Co-authored-by: SchweGELBin Co-committed-by: SchweGELBin --- src/common/settings.h | 1 + src/yuzu/configuration/configure_tas.cpp | 4 +++- src/yuzu/configuration/configure_tas.ui | 7 +++++++ src/yuzu/main_window.cpp | 14 +++++++++----- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/common/settings.h b/src/common/settings.h index 7c6c0d062f..ac04d26fc5 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -706,6 +706,7 @@ struct Values { Setting pause_tas_on_load{linkage, true, "pause_tas_on_load", Category::Controls}; Setting tas_enable{linkage, false, "tas_enable", Category::Controls}; Setting tas_loop{linkage, false, "tas_loop", Category::Controls}; + Setting tas_show_recording_dialog{linkage, true, "tas_show_recording_dialog", Category::Controls}; Setting mouse_panning{ linkage, false, "mouse_panning", Category::Controls, Specialization::Default, false}; diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp index 8bdb987426..75d5a5eeaf 100644 --- a/src/yuzu/configuration/configure_tas.cpp +++ b/src/yuzu/configuration/configure_tas.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -35,6 +35,7 @@ void ConfigureTasDialog::LoadConfiguration() { ui->tas_enable->setChecked(Settings::values.tas_enable.GetValue()); ui->tas_loop_script->setChecked(Settings::values.tas_loop.GetValue()); ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load.GetValue()); + ui->tas_show_recording_dialog->setChecked(Settings::values.tas_show_recording_dialog.GetValue()); } void ConfigureTasDialog::ApplyConfiguration() { @@ -42,6 +43,7 @@ void ConfigureTasDialog::ApplyConfiguration() { Settings::values.tas_enable.SetValue(ui->tas_enable->isChecked()); Settings::values.tas_loop.SetValue(ui->tas_loop_script->isChecked()); Settings::values.pause_tas_on_load.SetValue(ui->tas_pause_on_load->isChecked()); + Settings::values.tas_show_recording_dialog.SetValue(ui->tas_show_recording_dialog->isChecked()); } void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) { diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui index da8f2a86c5..5b4bba53b6 100644 --- a/src/yuzu/configuration/configure_tas.ui +++ b/src/yuzu/configuration/configure_tas.ui @@ -78,6 +78,13 @@ + + + + Show recording dialog + + + diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index e02e02b413..688078385a 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -3665,13 +3665,17 @@ void MainWindow::OnTasRecord() { const bool is_recording = input_subsystem->GetTas()->Record(); if (!is_recording) { - is_tas_recording_dialog_active = true; + if (Settings::values.tas_show_recording_dialog.GetValue()) { + is_tas_recording_dialog_active = true; - bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); - input_subsystem->GetTas()->SaveRecording(answer); - is_tas_recording_dialog_active = false; + input_subsystem->GetTas()->SaveRecording(answer); + is_tas_recording_dialog_active = false; + } else { + input_subsystem->GetTas()->SaveRecording(true); + } } OnTasStateChanged(); } From afec66f598d2327946b869a1b744e4b4cbc1066c Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 7 Mar 2026 23:44:58 +0100 Subject: [PATCH 2/3] core/hle/services/audio] Fix audioctl being started before audout/audin (which are required for ctor) (#3695) `audctl` depends on `audout`, simply start `audout` and `audin` before! Signed-off-by: lizzie Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3695 Reviewed-by: MaranBr Reviewed-by: DraVee Co-authored-by: lizzie Co-committed-by: lizzie --- src/core/hle/service/audio/audio.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp index 331176bf7f..7637ed5bf5 100644 --- a/src/core/hle/service/audio/audio.cpp +++ b/src/core/hle/service/audio/audio.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -18,17 +21,14 @@ namespace Service::Audio { void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService("audctl", std::make_shared(system)); server_manager->RegisterNamedService("audin:u", std::make_shared(system)); server_manager->RegisterNamedService("audout:u", std::make_shared(system)); - server_manager->RegisterNamedService( - "audrec:a", std::make_shared(system)); - server_manager->RegisterNamedService("audrec:u", - std::make_shared(system)); - server_manager->RegisterNamedService("audren:u", - std::make_shared(system)); - server_manager->RegisterNamedService("hwopus", - std::make_shared(system)); + // Depends on audout:u and audin:u on ctor! + server_manager->RegisterNamedService("audctl", std::make_shared(system)); + server_manager->RegisterNamedService("audrec:a", std::make_shared(system)); + server_manager->RegisterNamedService("audrec:u", std::make_shared(system)); + server_manager->RegisterNamedService("audren:u", std::make_shared(system)); + server_manager->RegisterNamedService("hwopus", std::make_shared(system)); ServerManager::RunServer(std::move(server_manager)); } From 9423a33fc2192a6ff1fb613468c764f82d8d1ec7 Mon Sep 17 00:00:00 2001 From: xbzk Date: Sun, 8 Mar 2026 00:41:40 +0100 Subject: [PATCH 3/3] [android,ui] fix addons list not refresh upon rotation (#3687) Fixes a bug reported by Pavel, in which an RC as returning empty patchList after phone rotated. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3687 Reviewed-by: DraVee Reviewed-by: MaranBr Co-authored-by: xbzk Co-committed-by: xbzk --- .../yuzu/yuzu_emu/fragments/AddonsFragment.kt | 13 ++-- .../org/yuzu/yuzu_emu/model/AddonViewModel.kt | 76 ++++++++++++++----- .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 2 +- 3 files changed, 67 insertions(+), 24 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt index 573549d84b..96b7a8cce2 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments @@ -19,7 +19,6 @@ import androidx.navigation.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.transition.MaterialSharedAxis -import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.adapters.AddonAdapter import org.yuzu.yuzu_emu.databinding.FragmentAddonsBinding @@ -42,7 +41,7 @@ class AddonsFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - addonViewModel.onOpenAddons(args.game) + addonViewModel.onAddonsViewCreated(args.game) enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) @@ -122,12 +121,14 @@ class AddonsFragment : Fragment() { override fun onResume() { super.onResume() - addonViewModel.refreshAddons() + addonViewModel.onAddonsViewStarted(args.game) } override fun onDestroy() { + if (activity?.isChangingConfigurations != true) { + addonViewModel.onCloseAddons() + } super.onDestroy() - addonViewModel.onCloseAddons() } val installAddon = @@ -167,7 +168,7 @@ class AddonsFragment : Fragment() { } catch (_: Exception) { return@newInstance errorMessage } - addonViewModel.refreshAddons() + addonViewModel.refreshAddons(force = true) return@newInstance getString(R.string.addon_installed_successfully) }.show(parentFragmentManager, ProgressDialogFragment.TAG) } else { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt index 2a0e72be26..2331630c4e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt @@ -18,7 +18,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig import java.util.concurrent.atomic.AtomicBoolean class AddonViewModel : ViewModel() { - private val _patchList = MutableStateFlow(mutableListOf()) + private val _patchList = MutableStateFlow>(emptyList()) val addonList get() = _patchList.asStateFlow() private val _showModInstallPicker = MutableStateFlow(false) @@ -31,34 +31,62 @@ class AddonViewModel : ViewModel() { val addonToDelete = _addonToDelete.asStateFlow() var game: Game? = null + private var loadedGameKey: String? = null private val isRefreshing = AtomicBoolean(false) + private val pendingRefresh = AtomicBoolean(false) - fun onOpenAddons(game: Game) { + fun onAddonsViewCreated(game: Game) { this.game = game - refreshAddons() + refreshAddons(commitEmpty = false) } - fun refreshAddons() { - if (isRefreshing.get() || game == null) { + fun onAddonsViewStarted(game: Game) { + this.game = game + val hasLoadedCurrentGame = loadedGameKey == gameKey(game) + refreshAddons(force = !hasLoadedCurrentGame) + } + + fun refreshAddons(force: Boolean = false, commitEmpty: Boolean = true) { + val currentGame = game ?: return + val currentGameKey = gameKey(currentGame) + if (!force && loadedGameKey == currentGameKey) { return } - isRefreshing.set(true) + if (!isRefreshing.compareAndSet(false, true)) { + if (force) { + pendingRefresh.set(true) + } + return + } + viewModelScope.launch { - withContext(Dispatchers.IO) { - val patchList = ( - NativeLibrary.getPatchesForFile(game!!.path, game!!.programId) - ?: emptyArray() - ).toMutableList() + try { + val patches = withContext(Dispatchers.IO) { + NativeLibrary.getPatchesForFile(currentGame.path, currentGame.programId) + } ?: return@launch + + val patchList = patches.toMutableList() patchList.sortBy { it.name } // Ensure only one update is enabled ensureSingleUpdateEnabled(patchList) removeDuplicates(patchList) + if (patchList.isEmpty() && !commitEmpty) { + return@launch + } + if (gameKey(game ?: return@launch) != currentGameKey) { + return@launch + } - _patchList.value = patchList + _patchList.value = patchList.toList() + loadedGameKey = currentGameKey + } finally { isRefreshing.set(false) + if (pendingRefresh.compareAndSet(true, false)) { + refreshAddons(force = true) + } } } } @@ -119,17 +147,26 @@ class AddonViewModel : ViewModel() { PatchType.DLC -> NativeLibrary.removeDLC(patch.programId) PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name) } - refreshAddons() + refreshAddons(force = true) } fun onCloseAddons() { - if (_patchList.value.isEmpty()) { + val currentGame = game ?: run { + _patchList.value = emptyList() + loadedGameKey = null + return + } + val currentList = _patchList.value + if (currentList.isEmpty()) { + _patchList.value = emptyList() + loadedGameKey = null + game = null return } NativeConfig.setDisabledAddons( - game!!.programId, - _patchList.value.mapNotNull { + currentGame.programId, + currentList.mapNotNull { if (it.enabled) { null } else { @@ -148,7 +185,8 @@ class AddonViewModel : ViewModel() { }.toTypedArray() ) NativeConfig.saveGlobalConfig() - _patchList.value.clear() + _patchList.value = emptyList() + loadedGameKey = null game = null } @@ -159,4 +197,8 @@ class AddonViewModel : ViewModel() { fun showModNoticeDialog(show: Boolean) { _showModNoticeDialog.value = show } + + private fun gameKey(game: Game): String { + return "${game.programId}|${game.path}" + } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index db4cc0f60e..74a171cf1f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt @@ -642,7 +642,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } } - addonViewModel.refreshAddons() + addonViewModel.refreshAddons(force = true) val separator = System.lineSeparator() ?: "\n" val installResult = StringBuilder()