From 6286501550b97424ad1ab5a628d528b17da513ce Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Tue, 31 Dec 2024 20:11:44 -0600 Subject: [PATCH 01/14] misc: do not log dirty hack changes if ShowDirtyHacks is disabled --- .../Utilities/Configuration/ConfigurationState.Model.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs index 4fc25addb..fe5f2c3ad 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs @@ -645,6 +645,9 @@ namespace Ryujinx.Ava.Utilities.Configuration private void HackChanged(object sender, ReactiveEventArgs rxe) { + if (!ShowDirtyHacks) + return; + var newHacks = EnabledHacks.Select(x => x.Hack) .JoinToString(", "); From 3525d5ecd4bbd7b14893d03cf8018570478a7dca Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Tue, 31 Dec 2024 20:11:49 -0600 Subject: [PATCH 02/14] UI: clean up slider UI for shader translation delay --- .../UI/ViewModels/SettingsHacksViewModel.cs | 4 +-- .../UI/Views/Settings/SettingsHacksView.axaml | 36 +++++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs index b93cdd6dc..4cfbc8957 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs @@ -40,7 +40,7 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public string ShaderTranslationDelayTooltipText => $"Current value: {ShaderTranslationDelay}"; + public string ShaderTranslationDelayValueText => $"{ShaderTranslationDelay}ms"; public int ShaderTranslationDelay { @@ -49,7 +49,7 @@ namespace Ryujinx.Ava.UI.ViewModels { _shaderTranslationSleepDelay = value; - OnPropertiesChanged(nameof(ShaderTranslationDelay), nameof(ShaderTranslationDelayTooltipText)); + OnPropertiesChanged(nameof(ShaderTranslationDelay), nameof(ShaderTranslationDelayValueText)); } } diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml index 087112368..2ef0cc74f 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml @@ -54,21 +54,27 @@ - + + + + From 27c5cba10b679e75f12bcae50b36a1cb3bc49acd Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Tue, 31 Dec 2024 21:11:57 -0600 Subject: [PATCH 03/14] misc: More Mvvm usage instead of writing out the observable properties --- .../UI/ViewModels/AboutWindowViewModel.cs | 40 ++------- .../DownloadableContentManagerViewModel.cs | 39 ++------- .../UI/ViewModels/ModManagerViewModel.cs | 31 ++----- .../UI/ViewModels/SettingsHacksViewModel.cs | 31 ++----- .../UI/ViewModels/SettingsViewModel.cs | 2 +- .../UI/ViewModels/TitleUpdateViewModel.cs | 70 ++++------------ .../UserFirmwareAvatarSelectorViewModel.cs | 33 ++------ .../UserProfileImageSelectorViewModel.cs | 17 +--- .../UI/ViewModels/UserSaveManagerViewModel.cs | 82 +++++-------------- .../UI/Views/Settings/SettingsHacksView.axaml | 2 +- .../UI/Windows/TitleUpdateWindow.axaml | 2 +- 11 files changed, 72 insertions(+), 277 deletions(-) diff --git a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs index 6bc1e1f03..979ae8253 100644 --- a/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/AboutWindowViewModel.cs @@ -1,6 +1,7 @@ using Avalonia.Media.Imaging; using Avalonia.Styling; using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; using Ryujinx.Ava.Common; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Utilities.Configuration; @@ -8,42 +9,11 @@ using System; namespace Ryujinx.Ava.UI.ViewModels { - public class AboutWindowViewModel : BaseModel, IDisposable + public partial class AboutWindowViewModel : BaseModel, IDisposable { - private Bitmap _githubLogo; - private Bitmap _discordLogo; - - private string _version; - - public Bitmap GithubLogo - { - get => _githubLogo; - set - { - _githubLogo = value; - OnPropertyChanged(); - } - } - - public Bitmap DiscordLogo - { - get => _discordLogo; - set - { - _discordLogo = value; - OnPropertyChanged(); - } - } - - public string Version - { - get => _version; - set - { - _version = value; - OnPropertyChanged(); - } - } + [ObservableProperty] private Bitmap _githubLogo; + [ObservableProperty] private Bitmap _discordLogo; + [ObservableProperty] private string _version; public string Developers => "GreemDev"; diff --git a/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs index acc26decb..658568909 100644 --- a/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs @@ -2,6 +2,7 @@ using Avalonia.Collections; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Platform.Storage; using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; using DynamicData; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common.Locale; @@ -17,13 +18,13 @@ using Application = Avalonia.Application; namespace Ryujinx.Ava.UI.ViewModels { - public class DownloadableContentManagerViewModel : BaseModel + public partial class DownloadableContentManagerViewModel : BaseModel { private readonly ApplicationLibrary _applicationLibrary; private AvaloniaList _downloadableContents = new(); - private AvaloniaList _selectedDownloadableContents = new(); - private AvaloniaList _views = new(); - private bool _showBundledContentNotice = false; + [ObservableProperty] private AvaloniaList _selectedDownloadableContents = new(); + [ObservableProperty] private AvaloniaList _views = new(); + [ObservableProperty] private bool _showBundledContentNotice = false; private string _search; private readonly ApplicationData _applicationData; @@ -41,26 +42,6 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public AvaloniaList Views - { - get => _views; - set - { - _views = value; - OnPropertyChanged(); - } - } - - public AvaloniaList SelectedDownloadableContents - { - get => _selectedDownloadableContents; - set - { - _selectedDownloadableContents = value; - OnPropertyChanged(); - } - } - public string Search { get => _search; @@ -77,16 +58,6 @@ namespace Ryujinx.Ava.UI.ViewModels get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count); } - public bool ShowBundledContentNotice - { - get => _showBundledContentNotice; - set - { - _showBundledContentNotice = value; - OnPropertyChanged(); - } - } - public DownloadableContentManagerViewModel(ApplicationLibrary applicationLibrary, ApplicationData applicationData) { _applicationLibrary = applicationLibrary; diff --git a/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs index 9c26376ce..ce40ce16c 100644 --- a/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/ModManagerViewModel.cs @@ -1,8 +1,7 @@ -using Avalonia; using Avalonia.Collections; -using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Platform.Storage; using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; using DynamicData; using Gommon; using Ryujinx.Ava.Common.Locale; @@ -18,13 +17,13 @@ using System.Linq; namespace Ryujinx.Ava.UI.ViewModels { - public class ModManagerViewModel : BaseModel + public partial class ModManagerViewModel : BaseModel { private readonly string _modJsonPath; private AvaloniaList _mods = new(); - private AvaloniaList _views = new(); - private AvaloniaList _selectedMods = new(); + [ObservableProperty] private AvaloniaList _views = new(); + [ObservableProperty] private AvaloniaList _selectedMods = new(); private string _search; private readonly ulong _applicationId; @@ -44,26 +43,6 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public AvaloniaList Views - { - get => _views; - set - { - _views = value; - OnPropertyChanged(); - } - } - - public AvaloniaList SelectedMods - { - get => _selectedMods; - set - { - _selectedMods = value; - OnPropertyChanged(); - } - } - public string Search { get => _search; @@ -143,8 +122,10 @@ namespace Ryujinx.Ava.UI.ViewModels .Filter(Filter) .Bind(out var view).AsObservableList(); +#pragma warning disable MVVMTK0034 // Event to update is fired below _views.Clear(); _views.AddRange(view); +#pragma warning restore MVVMTK0034 SelectedMods = new(Views.Where(x => x.Enabled)); diff --git a/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs index 4cfbc8957..5096a716d 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsHacksViewModel.cs @@ -1,9 +1,10 @@ -using Gommon; +using CommunityToolkit.Mvvm.ComponentModel; +using Gommon; using Ryujinx.Ava.Utilities.Configuration; namespace Ryujinx.Ava.UI.ViewModels { - public class SettingsHacksViewModel : BaseModel + public partial class SettingsHacksViewModel : BaseModel { private readonly SettingsViewModel _baseViewModel; @@ -14,31 +15,9 @@ namespace Ryujinx.Ava.UI.ViewModels _baseViewModel = settingsVm; } - private bool _xc2MenuSoftlockFix = ConfigurationState.Instance.Hacks.Xc2MenuSoftlockFix; - private bool _shaderTranslationThreadSleep = ConfigurationState.Instance.Hacks.EnableShaderTranslationDelay; + [ObservableProperty] private bool _xc2MenuSoftlockFix = ConfigurationState.Instance.Hacks.Xc2MenuSoftlockFix; + [ObservableProperty] private bool _shaderTranslationDelayEnabled = ConfigurationState.Instance.Hacks.EnableShaderTranslationDelay; private int _shaderTranslationSleepDelay = ConfigurationState.Instance.Hacks.ShaderTranslationDelay; - - public bool Xc2MenuSoftlockFixEnabled - { - get => _xc2MenuSoftlockFix; - set - { - _xc2MenuSoftlockFix = value; - - OnPropertyChanged(); - } - } - - public bool ShaderTranslationDelayEnabled - { - get => _shaderTranslationThreadSleep; - set - { - _shaderTranslationThreadSleep = value; - - OnPropertyChanged(); - } - } public string ShaderTranslationDelayValueText => $"{ShaderTranslationDelay}ms"; diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index a5bdd2f88..39df76aa4 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -756,7 +756,7 @@ namespace Ryujinx.Ava.UI.ViewModels config.Multiplayer.LdnServer.Value = LdnServer; // Dirty Hacks - config.Hacks.Xc2MenuSoftlockFix.Value = DirtyHacks.Xc2MenuSoftlockFixEnabled; + config.Hacks.Xc2MenuSoftlockFix.Value = DirtyHacks.Xc2MenuSoftlockFix; config.Hacks.EnableShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelayEnabled; config.Hacks.ShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelay; diff --git a/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs b/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs index 0748efeb4..86d59d6b4 100644 --- a/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/TitleUpdateViewModel.cs @@ -1,74 +1,32 @@ using Avalonia.Collections; -using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Platform.Storage; using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Models; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.Utilities.AppLibrary; -using Ryujinx.HLE.FileSystem; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -using Application = Avalonia.Application; namespace Ryujinx.Ava.UI.ViewModels { - public record TitleUpdateViewNoUpdateSentinal(); + public record TitleUpdateViewModelNoUpdate; - public class TitleUpdateViewModel : BaseModel + public partial class TitleUpdateViewModel : BaseModel { private ApplicationLibrary ApplicationLibrary { get; } private ApplicationData ApplicationData { get; } - private AvaloniaList _titleUpdates = new(); - private AvaloniaList _views = new(); - private object _selectedUpdate = new TitleUpdateViewNoUpdateSentinal(); - private bool _showBundledContentNotice = false; + [ObservableProperty] private AvaloniaList _titleUpdates = new(); + [ObservableProperty] private AvaloniaList _views = new(); + [ObservableProperty] private object _selectedUpdate = new TitleUpdateViewModelNoUpdate(); + [ObservableProperty] private bool _showBundledContentNotice; - public AvaloniaList TitleUpdates - { - get => _titleUpdates; - set - { - _titleUpdates = value; - OnPropertyChanged(); - } - } - - public AvaloniaList Views - { - get => _views; - set - { - _views = value; - OnPropertyChanged(); - } - } - - public object SelectedUpdate - { - get => _selectedUpdate; - set - { - _selectedUpdate = value; - OnPropertyChanged(); - } - } - - public bool ShowBundledContentNotice - { - get => _showBundledContentNotice; - set - { - _showBundledContentNotice = value; - OnPropertyChanged(); - } - } - - public IStorageProvider StorageProvider; + private readonly IStorageProvider _storageProvider; public TitleUpdateViewModel(ApplicationLibrary applicationLibrary, ApplicationData applicationData) { @@ -76,7 +34,7 @@ namespace Ryujinx.Ava.UI.ViewModels ApplicationData = applicationData; - StorageProvider = RyujinxApp.MainWindow.StorageProvider; + _storageProvider = RyujinxApp.MainWindow.StorageProvider; LoadUpdates(); } @@ -87,7 +45,7 @@ namespace Ryujinx.Ava.UI.ViewModels .Where(it => it.TitleUpdate.TitleIdBase == ApplicationData.IdBase); bool hasBundledContent = false; - SelectedUpdate = new TitleUpdateViewNoUpdateSentinal(); + SelectedUpdate = new TitleUpdateViewModelNoUpdate(); foreach ((TitleUpdateModel update, bool isSelected) in updates) { TitleUpdates.Add(update); @@ -113,12 +71,12 @@ namespace Ryujinx.Ava.UI.ViewModels var selected = SelectedUpdate; Views.Clear(); - Views.Add(new TitleUpdateViewNoUpdateSentinal()); + Views.Add(new TitleUpdateViewModelNoUpdate()); Views.AddRange(sortedUpdates); SelectedUpdate = selected; - if (SelectedUpdate is TitleUpdateViewNoUpdateSentinal) + if (SelectedUpdate is TitleUpdateViewModelNoUpdate) { SelectedUpdate = Views[0]; } @@ -176,7 +134,7 @@ namespace Ryujinx.Ava.UI.ViewModels } else if (update == SelectedUpdate as TitleUpdateModel) { - SelectedUpdate = new TitleUpdateViewNoUpdateSentinal(); + SelectedUpdate = new TitleUpdateViewModelNoUpdate(); } SortUpdates(); @@ -184,7 +142,7 @@ namespace Ryujinx.Ava.UI.ViewModels public async Task Add() { - var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + var result = await _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { AllowMultiple = true, FileTypeFilter = new List diff --git a/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs index b07bf78b9..29c81308b 100644 --- a/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs @@ -1,4 +1,5 @@ using Avalonia.Media; +using CommunityToolkit.Mvvm.ComponentModel; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; @@ -20,12 +21,12 @@ using Image = SkiaSharp.SKImage; namespace Ryujinx.Ava.UI.ViewModels { - internal class UserFirmwareAvatarSelectorViewModel : BaseModel + internal partial class UserFirmwareAvatarSelectorViewModel : BaseModel { private static readonly Dictionary _avatarStore = new(); - private ObservableCollection _images; - private Color _backgroundColor = Colors.White; + [ObservableProperty] private ObservableCollection _images; + [ObservableProperty] private Color _backgroundColor = Colors.White; private int _selectedIndex; @@ -34,27 +35,11 @@ namespace Ryujinx.Ava.UI.ViewModels _images = new ObservableCollection(); LoadImagesFromStore(); - } - - public Color BackgroundColor - { - get => _backgroundColor; - set + PropertyChanged += (_, args) => { - _backgroundColor = value; - OnPropertyChanged(); - ChangeImageBackground(); - } - } - - public ObservableCollection Images - { - get => _images; - set - { - _images = value; - OnPropertyChanged(); - } + if (args.PropertyName == nameof(BackgroundColor)) + ChangeImageBackground(); + }; } public int SelectedIndex @@ -70,7 +55,7 @@ namespace Ryujinx.Ava.UI.ViewModels } else { - SelectedImage = _images[_selectedIndex].Data; + SelectedImage = Images[_selectedIndex].Data; } OnPropertyChanged(); diff --git a/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs b/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs index 8e7d41a55..36a9a62f9 100644 --- a/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/UserProfileImageSelectorViewModel.cs @@ -1,18 +1,9 @@ +using CommunityToolkit.Mvvm.ComponentModel; + namespace Ryujinx.Ava.UI.ViewModels { - internal class UserProfileImageSelectorViewModel : BaseModel + internal partial class UserProfileImageSelectorViewModel : BaseModel { - private bool _firmwareFound; - - public bool FirmwareFound - { - get => _firmwareFound; - - set - { - _firmwareFound = value; - OnPropertyChanged(); - } - } + [ObservableProperty] private bool _firmwareFound; } } diff --git a/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs index 85adef005..187df0449 100644 --- a/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/UserSaveManagerViewModel.cs @@ -1,3 +1,4 @@ +using CommunityToolkit.Mvvm.ComponentModel; using DynamicData; using DynamicData.Binding; using Ryujinx.Ava.Common.Locale; @@ -8,74 +9,31 @@ using System.Collections.ObjectModel; namespace Ryujinx.Ava.UI.ViewModels { - public class UserSaveManagerViewModel : BaseModel + public partial class UserSaveManagerViewModel : BaseModel { - private int _sortIndex; - private int _orderIndex; - private string _search; - private ObservableCollection _saves = new(); - private ObservableCollection _views = new(); + [ObservableProperty] private int _sortIndex; + [ObservableProperty] private int _orderIndex; + [ObservableProperty] private string _search; + [ObservableProperty] private ObservableCollection _saves = new(); + [ObservableProperty] private ObservableCollection _views = new(); private readonly AccountManager _accountManager; public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId); - public int SortIndex - { - get => _sortIndex; - set - { - _sortIndex = value; - OnPropertyChanged(); - Sort(); - } - } - - public int OrderIndex - { - get => _orderIndex; - set - { - _orderIndex = value; - OnPropertyChanged(); - Sort(); - } - } - - public string Search - { - get => _search; - set - { - _search = value; - OnPropertyChanged(); - Sort(); - } - } - - public ObservableCollection Saves - { - get => _saves; - set - { - _saves = value; - OnPropertyChanged(); - Sort(); - } - } - - public ObservableCollection Views - { - get => _views; - set - { - _views = value; - OnPropertyChanged(); - } - } - public UserSaveManagerViewModel(AccountManager accountManager) { _accountManager = accountManager; + PropertyChanged += (_, evt) => + { + if (evt.PropertyName is + nameof(SortIndex) or + nameof(OrderIndex) or + nameof(Search) or + nameof(Saves)) + { + Sort(); + } + }; } public void Sort() @@ -85,8 +43,10 @@ namespace Ryujinx.Ava.UI.ViewModels .Sort(GetComparer()) .Bind(out var view).AsObservableList(); +#pragma warning disable MVVMTK0034 _views.Clear(); _views.AddRange(view); +#pragma warning restore MVVMTK0034 OnPropertyChanged(nameof(Views)); } @@ -94,7 +54,7 @@ namespace Ryujinx.Ava.UI.ViewModels { if (arg is SaveModel save) { - return string.IsNullOrWhiteSpace(_search) || save.Title.ToLower().Contains(_search.ToLower()); + return string.IsNullOrWhiteSpace(Search) || save.Title.ToLower().Contains(Search.ToLower()); } return false; diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml index 2ef0cc74f..f1900a69a 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml @@ -37,7 +37,7 @@ ToolTip.Tip="{Binding DirtyHacks.Xc2MenuFixTooltip}"> + IsChecked="{Binding DirtyHacks.Xc2MenuSoftlockFix}"/> diff --git a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml index 0ba9bc7d8..a8ec5d29a 100644 --- a/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml +++ b/src/Ryujinx/UI/Windows/TitleUpdateWindow.axaml @@ -95,7 +95,7 @@ + DataType="viewModels:TitleUpdateViewModelNoUpdate"> From 7c01633f13f952790ab92e3f1ad9b5a633f3e9e7 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Tue, 31 Dec 2024 21:15:50 -0600 Subject: [PATCH 04/14] UI: Show the path of the mod on the folder button --- src/Ryujinx/UI/Windows/ModManagerWindow.axaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ryujinx/UI/Windows/ModManagerWindow.axaml b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml index 3a1c4e6dd..a8fd3a2e7 100644 --- a/src/Ryujinx/UI/Windows/ModManagerWindow.axaml +++ b/src/Ryujinx/UI/Windows/ModManagerWindow.axaml @@ -93,6 +93,7 @@ Padding="10" MinWidth="0" MinHeight="0" + ToolTip.Tip="{Binding Path}" Click="OpenLocation"> Date: Tue, 31 Dec 2024 21:21:54 -0600 Subject: [PATCH 05/14] misc: DateTimeOffset Extract extension from Gommon --- Directory.Packages.props | 2 +- src/Ryujinx/UI/ViewModels/SettingsViewModel.cs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 7054dcd7d..ab3bc39b8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -42,7 +42,7 @@ - + diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 39df76aa4..03e3d44e9 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -466,11 +466,10 @@ namespace Ryujinx.Ava.UI.ViewModels public void MatchSystemTime() { - var dto = DateTimeOffset.Now; - - CurrentDate = new DateTimeOffset(dto.Year, dto.Month, dto.Day, 0, 0, 0, dto.Offset); + (DateTimeOffset dto, TimeSpan timeOfDay) = DateTimeOffset.Now.Extract(); - CurrentTime = dto.TimeOfDay; + CurrentDate = dto; + CurrentTime = timeOfDay; OnPropertyChanged(nameof(CurrentDate)); OnPropertyChanged(nameof(CurrentTime)); From 732aafd3bb9a18b341aa9cbc8ce8cdc78e2616e6 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Tue, 31 Dec 2024 22:23:08 -0600 Subject: [PATCH 06/14] misc: Prevent value change logging when the value is changed to the same thing it was before the value change. --- src/Ryujinx.Common/ReactiveObject.cs | 11 +++ .../UI/ViewModels/SettingsViewModel.cs | 79 +++---------------- 2 files changed, 24 insertions(+), 66 deletions(-) diff --git a/src/Ryujinx.Common/ReactiveObject.cs b/src/Ryujinx.Common/ReactiveObject.cs index 8df1e20fe..7ff16f0cb 100644 --- a/src/Ryujinx.Common/ReactiveObject.cs +++ b/src/Ryujinx.Common/ReactiveObject.cs @@ -53,6 +53,17 @@ namespace Ryujinx.Common { public static void LogValueChange(LogClass logClass, ReactiveEventArgs eventArgs, string valueName) { + if ((eventArgs.NewValue == null || eventArgs.OldValue == null)) + { + if (!(eventArgs.NewValue == null && eventArgs.OldValue == null)) + goto Log; + } + else if (!eventArgs.NewValue!.Equals(eventArgs.OldValue)) + goto Log; + + return; + + Log: string message = string.Create(CultureInfo.InvariantCulture, $"{valueName} set to: {eventArgs.NewValue}"); Logger.Info?.Print(logClass, message); diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 03e3d44e9..2678bbf98 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -1,6 +1,7 @@ using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; using Gommon; using LibHac.Tools.FsSystem; using Ryujinx.Audio.Backends.OpenAL; @@ -46,9 +47,9 @@ namespace Ryujinx.Ava.UI.ViewModels private int _resolutionScale; private int _graphicsBackendMultithreadingIndex; private float _volume; - private bool _isVulkanAvailable = true; - private bool _gameDirectoryChanged; - private bool _autoloadDirectoryChanged; + [ObservableProperty] private bool _isVulkanAvailable = true; + [ObservableProperty] private bool _gameDirectoryChanged; + [ObservableProperty] private bool _autoloadDirectoryChanged; private readonly List _gpuIds = new(); private int _graphicsBackendIndex; private int _scalingFilter; @@ -63,7 +64,7 @@ namespace Ryujinx.Ava.UI.ViewModels private int _networkInterfaceIndex; private int _multiplayerModeIndex; private string _ldnPassphrase; - private string _ldnServer; + [ObservableProperty] private string _ldnServer; public SettingsHacksViewModel DirtyHacks { get; } @@ -111,43 +112,10 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public bool IsVulkanAvailable - { - get => _isVulkanAvailable; - set - { - _isVulkanAvailable = value; - - OnPropertyChanged(); - } - } - public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS(); public bool IsAppleSiliconMac => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64; - public bool GameDirectoryChanged - { - get => _gameDirectoryChanged; - set - { - _gameDirectoryChanged = value; - - OnPropertyChanged(); - } - } - - public bool AutoloadDirectoryChanged - { - get => _autoloadDirectoryChanged; - set - { - _autoloadDirectoryChanged = value; - - OnPropertyChanged(); - } - } - public bool IsMacOS => OperatingSystem.IsMacOS(); public bool EnableDiscordIntegration { get; set; } @@ -182,19 +150,12 @@ namespace Ryujinx.Ava.UI.ViewModels _customVSyncInterval = newInterval; _customVSyncIntervalPercentageProxy = value; OnPropertiesChanged( - nameof(CustomVSyncInterval), + nameof(CustomVSyncInterval), nameof(CustomVSyncIntervalPercentageText)); } } - public string CustomVSyncIntervalPercentageText - { - get - { - string text = CustomVSyncIntervalPercentageProxy + "%"; - return text; - } - } + public string CustomVSyncIntervalPercentageText => CustomVSyncIntervalPercentageProxy + "%"; public bool EnableCustomVSyncInterval { @@ -356,7 +317,6 @@ namespace Ryujinx.Ava.UI.ViewModels set { _networkInterfaceIndex = value != -1 ? value : 0; - ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[_networkInterfaceIndex]]; } } @@ -366,7 +326,6 @@ namespace Ryujinx.Ava.UI.ViewModels set { _multiplayerModeIndex = value; - ConfigurationState.Instance.Multiplayer.Mode.Value = (MultiplayerMode)_multiplayerModeIndex; } } @@ -375,16 +334,6 @@ namespace Ryujinx.Ava.UI.ViewModels public bool IsInvalidLdnPassphraseVisible { get; set; } - public string LdnServer - { - get => _ldnServer; - set - { - _ldnServer = value; - OnPropertyChanged(); - } - } - public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() { _virtualFileSystem = virtualFileSystem; @@ -647,16 +596,14 @@ namespace Ryujinx.Ava.UI.ViewModels config.ShowTitleBar.Value = ShowTitleBar; config.HideCursor.Value = (HideCursorMode)HideCursor; - if (_gameDirectoryChanged) + if (GameDirectoryChanged) { - List gameDirs = new(GameDirectories); - config.UI.GameDirs.Value = gameDirs; + config.UI.GameDirs.Value = [..GameDirectories]; } - if (_autoloadDirectoryChanged) + if (AutoloadDirectoryChanged) { - List autoloadDirs = new(AutoloadDirectories); - config.UI.AutoloadDirs.Value = autoloadDirs; + config.UI.AutoloadDirs.Value = [..AutoloadDirectories]; } config.UI.BaseStyle.Value = BaseStyleIndex switch @@ -766,8 +713,8 @@ namespace Ryujinx.Ava.UI.ViewModels SaveSettingsEvent?.Invoke(); - _gameDirectoryChanged = false; - _autoloadDirectoryChanged = false; + GameDirectoryChanged = false; + AutoloadDirectoryChanged = false; } private static void RevertIfNotSaved() From 5d63706cea0306c6e40174c9d5490d2279dba48a Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Tue, 31 Dec 2024 22:34:14 -0600 Subject: [PATCH 07/14] misc: Bake in ValueEqual logic into ReactiveEventArgs [ci skip] --- src/Ryujinx.Common/ReactiveObject.cs | 29 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Ryujinx.Common/ReactiveObject.cs b/src/Ryujinx.Common/ReactiveObject.cs index 7ff16f0cb..bb2ece81c 100644 --- a/src/Ryujinx.Common/ReactiveObject.cs +++ b/src/Ryujinx.Common/ReactiveObject.cs @@ -53,17 +53,9 @@ namespace Ryujinx.Common { public static void LogValueChange(LogClass logClass, ReactiveEventArgs eventArgs, string valueName) { - if ((eventArgs.NewValue == null || eventArgs.OldValue == null)) - { - if (!(eventArgs.NewValue == null && eventArgs.OldValue == null)) - goto Log; - } - else if (!eventArgs.NewValue!.Equals(eventArgs.OldValue)) - goto Log; + if (eventArgs.AreValuesEqual) + return; - return; - - Log: string message = string.Create(CultureInfo.InvariantCulture, $"{valueName} set to: {eventArgs.NewValue}"); Logger.Info?.Print(logClass, message); @@ -76,5 +68,22 @@ namespace Ryujinx.Common { public T OldValue { get; } = oldValue; public T NewValue { get; } = newValue; + + public bool AreValuesEqual + { + get + { + if (OldValue == null && NewValue == null) + return true; + + if (OldValue == null && NewValue != null) + return false; + + if (OldValue != null && NewValue == null) + return false; + + return OldValue!.Equals(NewValue); + } + } } } From 978d2c132b27a401997215a716a68b1583c407d7 Mon Sep 17 00:00:00 2001 From: jozz024 <74272560+jozz024@users.noreply.github.com> Date: Tue, 31 Dec 2024 22:45:52 -0600 Subject: [PATCH 08/14] add a keyboard shortcut for opening amiibo .bin files (#461) --- src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml | 1 + src/Ryujinx/UI/Windows/MainWindow.axaml | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml index 2c07bd8ef..78848e89b 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -247,6 +247,7 @@ Header="{ext:Locale MenuBarActionsScanAmiiboBin}" Icon="{ext:Icon mdi-cube-scan}" IsVisible="{Binding CanScanAmiiboBinaries}" + InputGesture="Ctrl + B" IsEnabled="{Binding IsAmiiboBinRequested}" /> + From 003a6d322beed205e9d068a7bf73f90f68833136 Mon Sep 17 00:00:00 2001 From: Daenorth Date: Wed, 1 Jan 2025 07:15:21 +0100 Subject: [PATCH 09/14] Update to no_NO Norwegian Translation (#475) Updated for time resync & auto graphics backend --- src/Ryujinx/Assets/locales.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index b3a7a51b8..6f22e7d06 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -1235,7 +1235,7 @@ "it_IT": "", "ja_JP": "", "ko_KR": "자주 묻는 질문(FAQ) 및 안내", - "no_NO": "", + "no_NO": "Vanlige spørsmål og veiledninger", "pl_PL": "", "pt_BR": "", "ru_RU": "FAQ и Руководства", @@ -1460,7 +1460,7 @@ "it_IT": "Preferito", "ja_JP": "お気に入り", "ko_KR": "즐겨찾기", - "no_NO": "", + "no_NO": "Favoritter", "pl_PL": "Ulubione", "pt_BR": "Favorito", "ru_RU": "Избранное", @@ -2610,7 +2610,7 @@ "it_IT": "", "ja_JP": "", "ko_KR": "펌웨어 버전 : {0}", - "no_NO": "", + "no_NO": "Fastvareversjon: {0}", "pl_PL": "", "pt_BR": "Versão do firmware: {0}", "ru_RU": "Версия прошивки: {0}", @@ -3460,7 +3460,7 @@ "it_IT": "Corea", "ja_JP": "韓国", "ko_KR": "한국", - "no_NO": "", + "no_NO": "Koreansk", "pl_PL": "", "pt_BR": "Coreia", "ru_RU": "Корея", @@ -4010,7 +4010,7 @@ "it_IT": "", "ja_JP": "", "ko_KR": "PC 날짜와 시간에 동기화", - "no_NO": "", + "no_NO": "Resynkroniser til PC-dato og -klokkeslett", "pl_PL": "", "pt_BR": "", "ru_RU": "Повторная синхронизация с датой и временем на компьютере", @@ -15260,7 +15260,7 @@ "it_IT": "", "ja_JP": "", "ko_KR": "시스템 시간을 PC의 현재 날짜 및 시간과 일치하도록 다시 동기화합니다.\n\n이 설정은 활성 설정이 아니므로 여전히 동기화되지 않을 수 있으며, 이 경우 이 버튼을 다시 클릭하면 됩니다.", - "no_NO": "", + "no_NO": "Resynkroniser systemtiden slik at den samsvarer med PC-ens gjeldende dato og klokkeslett. \\Dette er ikke en aktiv innstilling, men den kan likevel komme ut av synkronisering; i så fall er det bare å klikke på denne knappen igjen.", "pl_PL": "", "pt_BR": "", "ru_RU": "Повторно синхронизирует системное время, чтобы оно соответствовало текущей дате и времени вашего компьютера.\n\nЭто не активная настройка, она все еще может рассинхронизироваться; в этом случае просто нажмите эту кнопку еще раз.", @@ -20535,7 +20535,7 @@ "it_IT": "", "ja_JP": "", "ko_KR": "Vulkan을 사용합니다.\nARM 맥에서 해당 플랫폼에서 잘 실행되는 게임을 플레이하는 경우 Metal 후단부를 사용합니다.", - "no_NO": "", + "no_NO": "Bruker Vulkan \nPå en ARM Mac, og når du spiller et spill som kjører bra under den, bruker du Metal-backend.", "pl_PL": "", "pt_BR": "", "ru_RU": "Использует Vulkan.\nНа Mac с ARM процессорами используется Metal, если игра с ним совместима и хорошо работает.", @@ -22598,4 +22598,4 @@ } } ] -} \ No newline at end of file +} From 37c165e9fc79ab442d868bec40580797a80421c8 Mon Sep 17 00:00:00 2001 From: Otozinclus <58051309+Otozinclus@users.noreply.github.com> Date: Wed, 1 Jan 2025 07:18:17 +0100 Subject: [PATCH 10/14] Only delay shader translation on Metal (#480) This way the Arbitrary Shader Translation Delay hack will no longer affect shader loading when using Vulkan. --- .../Shader/DiskCache/ParallelDiskCacheLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs index 910e9aea0..eb0f72af1 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs @@ -367,7 +367,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache { try { - if (_context.DirtyHacks.IsEnabled(DirtyHack.ShaderTranslationDelay)) + if (_context.Capabilities.Api == TargetApi.Metal && _context.DirtyHacks.IsEnabled(DirtyHack.ShaderTranslationDelay)) Thread.Sleep(_context.DirtyHacks[DirtyHack.ShaderTranslationDelay]); AsyncProgramTranslation asyncTranslation = new(guestShaders, specState, programIndex, isCompute); From fd2b5a7fc1e60d2355f3aba488fef9138b67e3b4 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Wed, 1 Jan 2025 01:12:00 -0600 Subject: [PATCH 11/14] misc: Remove RendererHost AXAML --- src/Ryujinx/UI/Renderer/RendererHost.axaml | 12 ------------ .../{RendererHost.axaml.cs => RendererHost.cs} | 13 ++++++------- 2 files changed, 6 insertions(+), 19 deletions(-) delete mode 100644 src/Ryujinx/UI/Renderer/RendererHost.axaml rename src/Ryujinx/UI/Renderer/{RendererHost.axaml.cs => RendererHost.cs} (94%) diff --git a/src/Ryujinx/UI/Renderer/RendererHost.axaml b/src/Ryujinx/UI/Renderer/RendererHost.axaml deleted file mode 100644 index e0b586b45..000000000 --- a/src/Ryujinx/UI/Renderer/RendererHost.axaml +++ /dev/null @@ -1,12 +0,0 @@ - - diff --git a/src/Ryujinx/UI/Renderer/RendererHost.axaml.cs b/src/Ryujinx/UI/Renderer/RendererHost.cs similarity index 94% rename from src/Ryujinx/UI/Renderer/RendererHost.axaml.cs rename to src/Ryujinx/UI/Renderer/RendererHost.cs index fa9aec0c5..7dfec8d62 100644 --- a/src/Ryujinx/UI/Renderer/RendererHost.axaml.cs +++ b/src/Ryujinx/UI/Renderer/RendererHost.cs @@ -1,16 +1,15 @@ -using Avalonia; +using Avalonia; using Avalonia.Controls; -using Gommon; +using Avalonia.Media; using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using System; -using System.Runtime.InteropServices; namespace Ryujinx.Ava.UI.Renderer { - public partial class RendererHost : UserControl, IDisposable + public class RendererHost : UserControl, IDisposable { public readonly EmbeddedWindow EmbeddedWindow; @@ -19,7 +18,8 @@ namespace Ryujinx.Ava.UI.Renderer public RendererHost() { - InitializeComponent(); + Focusable = true; + FlowDirection = FlowDirection.LeftToRight; EmbeddedWindow = ConfigurationState.Instance.Graphics.GraphicsBackend.Value switch { @@ -43,8 +43,6 @@ namespace Ryujinx.Ava.UI.Renderer public RendererHost(string titleId) { - InitializeComponent(); - switch (TitleIDs.SelectGraphicsBackend(titleId, ConfigurationState.Instance.Graphics.GraphicsBackend)) { case GraphicsBackend.OpenGl: @@ -109,3 +107,4 @@ namespace Ryujinx.Ava.UI.Renderer } } } + From 391f57bdd20f6e89f2e5fae14e052ef5595f1896 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Wed, 1 Jan 2025 01:54:59 -0600 Subject: [PATCH 12/14] misc: Headless: Inherit main input config --- src/Ryujinx/Headless/HeadlessRyujinx.cs | 32 +++++++++++++++------ src/Ryujinx/Headless/Options.cs | 37 +++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.cs b/src/Ryujinx/Headless/HeadlessRyujinx.cs index 3cb0afca3..c99e5409c 100644 --- a/src/Ryujinx/Headless/HeadlessRyujinx.cs +++ b/src/Ryujinx/Headless/HeadlessRyujinx.cs @@ -162,6 +162,11 @@ namespace Ryujinx.Headless } ReloadConfig(); + + if (option.InheritConfig) + { + option.InheritMainConfigInput(originalArgs, ConfigurationState.Instance); + } _virtualFileSystem = VirtualFileSystem.CreateInstance(); _libHacHorizonManager = new LibHacHorizonManager(); @@ -224,15 +229,7 @@ namespace Ryujinx.Headless _enableKeyboard = option.EnableKeyboard; _enableMouse = option.EnableMouse; - static void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index) - { - InputConfig inputConfig = HandlePlayerConfiguration(inputProfileName, inputId, index); - if (inputConfig != null) - { - _inputConfiguration.Add(inputConfig); - } - } LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1); LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2); @@ -244,7 +241,6 @@ namespace Ryujinx.Headless LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8); LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld); - if (_inputConfiguration.Count == 0) { return; @@ -306,6 +302,24 @@ namespace Ryujinx.Headless } _inputManager.Dispose(); + + return; + + void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index) + { + if (index == PlayerIndex.Handheld && _inputConfiguration.Count > 0) + { + Logger.Info?.Print(LogClass.Configuration, "Skipping handheld configuration as there are already other players configured."); + return; + } + + InputConfig inputConfig = option.InheritedInputConfigs[index] ?? HandlePlayerConfiguration(inputProfileName, inputId, index); + + if (inputConfig != null) + { + _inputConfiguration.Add(inputConfig); + } + } } private static void SetupProgressHandler() diff --git a/src/Ryujinx/Headless/Options.cs b/src/Ryujinx/Headless/Options.cs index c0def95c1..11deea3a5 100644 --- a/src/Ryujinx/Headless/Options.cs +++ b/src/Ryujinx/Headless/Options.cs @@ -154,10 +154,39 @@ namespace Ryujinx.Headless return; bool NeedsOverride(string argKey) => originalArgs.None(arg => arg.TrimStart('-').EqualsIgnoreCase(OptionName(argKey))); - - string OptionName(string propertyName) => - typeof(Options)!.GetProperty(propertyName)!.GetCustomAttribute()!.LongName; } + + public void InheritMainConfigInput(string[] originalArgs, ConfigurationState configurationState) + { + Dictionary indicesToProperties = new() + { + { PlayerIndex.Handheld, (nameof(InputIdHandheld), nameof(InputProfileHandheldName)) }, + { PlayerIndex.Player1, (nameof(InputId1), nameof(InputProfile1Name)) }, + { PlayerIndex.Player2, (nameof(InputId2), nameof(InputProfile2Name)) }, + { PlayerIndex.Player3, (nameof(InputId3), nameof(InputProfile3Name)) }, + { PlayerIndex.Player4, (nameof(InputId4), nameof(InputProfile4Name)) }, + { PlayerIndex.Player5, (nameof(InputId5), nameof(InputProfile5Name)) }, + { PlayerIndex.Player6, (nameof(InputId6), nameof(InputProfile6Name)) }, + { PlayerIndex.Player7, (nameof(InputId7), nameof(InputProfile7Name)) }, + { PlayerIndex.Player8, (nameof(InputId8), nameof(InputProfile8Name)) } + }; + + foreach ((PlayerIndex playerIndex, (string id, string profile)) in indicesToProperties) + { + if (NeedsOverride(id) && NeedsOverride(profile)) + { + configurationState.Hid.InputConfig.Value.FindFirst(x => x.PlayerIndex == playerIndex) + .IfPresent(ic => InheritedInputConfigs[playerIndex] = ic); + } + } + + return; + + bool NeedsOverride(string argKey) => originalArgs.None(arg => arg.TrimStart('-').EqualsIgnoreCase(OptionName(argKey))); + } + + private static string OptionName(string propertyName) => + typeof(Options)!.GetProperty(propertyName)!.GetCustomAttribute()!.LongName; // General @@ -391,5 +420,7 @@ namespace Ryujinx.Headless [Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)] public string InputPath { get; set; } + + public SafeDictionary InheritedInputConfigs = new(); } } From 88d11d3d8dd07c3026eabcb3a35a4e535f72ea50 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Wed, 1 Jan 2025 02:14:59 -0600 Subject: [PATCH 13/14] misc: some cleanups and fix compile warnings --- src/Ryujinx/Headless/HeadlessRyujinx.cs | 2 +- src/Ryujinx/Headless/Options.cs | 10 ++++------ .../ViewModels/DownloadableContentManagerViewModel.cs | 6 +++--- src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs | 6 +++++- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.cs b/src/Ryujinx/Headless/HeadlessRyujinx.cs index c99e5409c..5730254f7 100644 --- a/src/Ryujinx/Headless/HeadlessRyujinx.cs +++ b/src/Ryujinx/Headless/HeadlessRyujinx.cs @@ -52,7 +52,7 @@ namespace Ryujinx.Headless // Make process DPI aware for proper window sizing on high-res screens. ForceDpiAware.Windows(); - Console.Title = $"Ryujinx Console {Program.Version} (Headless)"; + Console.Title = $"HeadlessRyujinx Console {Program.Version}"; if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) { diff --git a/src/Ryujinx/Headless/Options.cs b/src/Ryujinx/Headless/Options.cs index 11deea3a5..0d7e46285 100644 --- a/src/Ryujinx/Headless/Options.cs +++ b/src/Ryujinx/Headless/Options.cs @@ -171,13 +171,11 @@ namespace Ryujinx.Headless { PlayerIndex.Player8, (nameof(InputId8), nameof(InputProfile8Name)) } }; - foreach ((PlayerIndex playerIndex, (string id, string profile)) in indicesToProperties) + foreach ((PlayerIndex playerIndex, _) in indicesToProperties + .Where(it => NeedsOverride(it.Value.InputId) && NeedsOverride(it.Value.InputProfileName))) { - if (NeedsOverride(id) && NeedsOverride(profile)) - { - configurationState.Hid.InputConfig.Value.FindFirst(x => x.PlayerIndex == playerIndex) - .IfPresent(ic => InheritedInputConfigs[playerIndex] = ic); - } + configurationState.Hid.InputConfig.Value.FindFirst(x => x.PlayerIndex == playerIndex) + .IfPresent(ic => InheritedInputConfigs[playerIndex] = ic); } return; diff --git a/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs index 658568909..52f97cf02 100644 --- a/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/DownloadableContentManagerViewModel.cs @@ -106,9 +106,9 @@ namespace Ryujinx.Ava.UI.ViewModels // NOTE(jpr): this works around a bug where calling _views.Clear also clears SelectedDownloadableContents for // some reason. so we save the items here and add them back after var items = SelectedDownloadableContents.ToArray(); - - _views.Clear(); - _views.AddRange(view); + + Views.Clear(); + Views.AddRange(view); foreach (DownloadableContentModel item in items) { diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 6df1f76ad..b7a43ccaf 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -182,7 +182,11 @@ namespace Ryujinx.Ava.UI.ViewModels Applications.ToObservableChangeSet() .Filter(Filter) .Sort(GetComparer()) + .OnItemAdded(_ => OnPropertyChanged(nameof(AppsObservableList))) + .OnItemRemoved(_ => OnPropertyChanged(nameof(AppsObservableList))) +#pragma warning disable MVVMTK0034 // Event to update is fired below .Bind(out _appsObservableList) +#pragma warning restore MVVMTK0034 .AsObservableList(); _rendererWaitEvent = new AutoResetEvent(false); @@ -192,8 +196,8 @@ namespace Ryujinx.Ava.UI.ViewModels LoadConfigurableHotKeys(); Volume = ConfigurationState.Instance.System.AudioVolume; + CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value; } - CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value; } public void Initialize( From f43442f774a4af8d1b41cae6dd93793f8f3f2069 Mon Sep 17 00:00:00 2001 From: WilliamWsyHK Date: Wed, 1 Jan 2025 16:15:14 +0800 Subject: [PATCH 14/14] Include Hack for XC2 JP Edition (#481) XC2 has 2 editions, one JP and one global. I own the JP version and suffered from the soft-lock, meanwhile the current hack only works for global edition, so PR is simply include JP edition from the hack. --- .../HOS/Services/Fs/FileSystemProxy/IStorage.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs index 3d197ac19..ad4cccc44 100644 --- a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs @@ -15,8 +15,10 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy { _baseStorage = SharedRef.CreateMove(ref baseStorage); } - - private const string Xc2TitleId = "0100e95004038000"; + + private const string Xc2JpTitleId = "0100f3400332c000"; + private const string Xc2GlobalTitleId = "0100e95004038000"; + private static bool IsXc2 => TitleIDs.CurrentApplication.Value.OrDefault() is Xc2GlobalTitleId or Xc2JpTitleId; [CommandCmif(0)] // Read(u64 offset, u64 length) -> buffer buffer @@ -39,7 +41,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy using var region = context.Memory.GetWritableRegion(bufferAddress, (int)bufferLen, true); Result result = _baseStorage.Get.Read((long)offset, new OutBuffer(region.Memory.Span), (long)size); - if (context.Device.DirtyHacks.IsEnabled(DirtyHack.Xc2MenuSoftlockFix) && TitleIDs.CurrentApplication.Value == Xc2TitleId) + if (context.Device.DirtyHacks.IsEnabled(DirtyHack.Xc2MenuSoftlockFix) && IsXc2) { // Add a load-bearing sleep to avoid XC2 softlock // https://web.archive.org/web/20240728045136/https://github.com/Ryujinx/Ryujinx/issues/2357