From 8b3a945b5f8c8700b809348c2d25aa22bc7af7c7 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Sat, 28 Dec 2024 22:04:21 -0600 Subject: [PATCH] misc: Dirty Hacks Enable this settings screen via a boolean in Config.json First one is the xb2 menu softlock fix --- .../Configuration/DirtyHacks.cs | 11 ++++ src/Ryujinx.Common/TitleIDs.cs | 2 + src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs | 6 --- .../Shader/ShaderCache.cs | 5 +- src/Ryujinx.HLE/HLEConfiguration.cs | 9 +++- .../Services/Fs/FileSystemProxy/IStorage.cs | 12 +++++ .../Extensions/FileSystemExtensions.cs | 3 +- .../Loaders/Processes/ProcessLoader.cs | 3 +- src/Ryujinx.HLE/Switch.cs | 3 ++ .../Configuration/ConfigurationFileFormat.cs | 14 +++++- .../ConfigurationState.Migration.cs | 3 ++ .../Configuration/ConfigurationState.Model.cs | 50 +++++++++++++++++++ .../Configuration/ConfigurationState.cs | 2 + src/Ryujinx/AppHost.cs | 6 ++- src/Ryujinx/Ryujinx.csproj | 6 +++ src/Ryujinx/UI/ViewModels/BaseModel.cs | 3 +- .../UI/ViewModels/SettingsViewModel.cs | 31 +++++++++--- .../UI/Views/Settings/SettingsHacksView.axaml | 48 ++++++++++++++++++ .../Views/Settings/SettingsHacksView.axaml.cs | 17 +++++++ src/Ryujinx/UI/Windows/SettingsWindow.axaml | 6 +++ .../UI/Windows/SettingsWindow.axaml.cs | 4 ++ 21 files changed, 222 insertions(+), 22 deletions(-) create mode 100644 src/Ryujinx.Common/Configuration/DirtyHacks.cs create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml create mode 100644 src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml.cs diff --git a/src/Ryujinx.Common/Configuration/DirtyHacks.cs b/src/Ryujinx.Common/Configuration/DirtyHacks.cs new file mode 100644 index 000000000..6a6d4949c --- /dev/null +++ b/src/Ryujinx.Common/Configuration/DirtyHacks.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.Common.Configuration +{ + [Flags] + public enum DirtyHacks + { + None = 0, + Xc2MenuSoftlockFix = 1 << 10 + } +} diff --git a/src/Ryujinx.Common/TitleIDs.cs b/src/Ryujinx.Common/TitleIDs.cs index b75ee1299..2d4068e4e 100644 --- a/src/Ryujinx.Common/TitleIDs.cs +++ b/src/Ryujinx.Common/TitleIDs.cs @@ -8,6 +8,8 @@ namespace Ryujinx.Common { public static class TitleIDs { + public static Optional CurrentApplication; + public static GraphicsBackend SelectGraphicsBackend(string titleId, GraphicsBackend currentBackend) { switch (currentBackend) diff --git a/src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs b/src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs index fbb7399ca..6b2a57ad9 100644 --- a/src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs +++ b/src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs @@ -47,12 +47,6 @@ namespace Ryujinx.Graphics.Gpu /// public static bool EnableMacroHLE = true; - /// - /// Title id of the current running game. - /// Used by the shader cache. - /// - public static string TitleId; - /// /// Enables or disables the shader cache. /// diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 0924c60f8..2f4d98b9b 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; @@ -116,8 +117,8 @@ namespace Ryujinx.Graphics.Gpu.Shader /// private static string GetDiskCachePath() { - return GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null - ? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId, "cache", "shader") + return GraphicsConfig.EnableShaderCache && TitleIDs.CurrentApplication.HasValue + ? Path.Combine(AppDataManager.GamesDirPath, TitleIDs.CurrentApplication, "cache", "shader") : null; } diff --git a/src/Ryujinx.HLE/HLEConfiguration.cs b/src/Ryujinx.HLE/HLEConfiguration.cs index 52c2b3da4..3bfab0be4 100644 --- a/src/Ryujinx.HLE/HLEConfiguration.cs +++ b/src/Ryujinx.HLE/HLEConfiguration.cs @@ -188,6 +188,11 @@ namespace Ryujinx.HLE /// An action called when HLE force a refresh of output after docked mode changed. /// public Action RefreshInputConfig { internal get; set; } + + /** + * The desired hacky workarounds. + */ + public DirtyHacks Hacks { internal get; set; } public HLEConfiguration(VirtualFileSystem virtualFileSystem, LibHacHorizonManager libHacHorizonManager, @@ -218,7 +223,8 @@ namespace Ryujinx.HLE bool multiplayerDisableP2p, string multiplayerLdnPassphrase, string multiplayerLdnServer, - int customVSyncInterval) + int customVSyncInterval, + DirtyHacks dirtyHacks = DirtyHacks.None) { VirtualFileSystem = virtualFileSystem; LibHacHorizonManager = libHacHorizonManager; @@ -250,6 +256,7 @@ namespace Ryujinx.HLE MultiplayerDisableP2p = multiplayerDisableP2p; MultiplayerLdnPassphrase = multiplayerLdnPassphrase; MultiplayerLdnServer = multiplayerLdnServer; + Hacks = dirtyHacks; } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs index 4299a6c74..6b542c16a 100644 --- a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs @@ -1,6 +1,9 @@ using LibHac; using LibHac.Common; using LibHac.Sf; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using System.Threading; namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy { @@ -13,6 +16,8 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy _baseStorage = SharedRef.CreateMove(ref baseStorage); } + private const string Xc2TitleId = "0100e95004038000"; + [CommandCmif(0)] // Read(u64 offset, u64 length) -> buffer buffer public ResultCode Read(ServiceCtx context) @@ -33,6 +38,13 @@ 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.HasFlag(DirtyHacks.Xc2MenuSoftlockFix) && TitleIDs.CurrentApplication == Xc2TitleId) + { + // Add a load-bearing sleep to avoid XC2 softlock + // https://web.archive.org/web/20240728045136/https://github.com/Ryujinx/Ryujinx/issues/2357 + Thread.Sleep(2); + } return (ResultCode)result.Value; } diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs index 97284f3bb..e4a5a547d 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs @@ -4,6 +4,7 @@ using LibHac.Fs.Fsa; using LibHac.Loader; using LibHac.Ns; using LibHac.Tools.FsSystem; +using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.HLE.Loaders.Executables; @@ -102,7 +103,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions } // Initialize GPU. - Graphics.Gpu.GraphicsConfig.TitleId = programId.ToString("X16"); + TitleIDs.CurrentApplication = programId.ToString("X16"); device.Gpu.HostInitalized.Set(); if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible)) diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs index fe8360f04..3e1929204 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs @@ -6,6 +6,7 @@ using LibHac.Ns; using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Processes.Extensions; @@ -204,7 +205,7 @@ namespace Ryujinx.HLE.Loaders.Processes } // Explicitly null TitleId to disable the shader cache. - Graphics.Gpu.GraphicsConfig.TitleId = null; + TitleIDs.CurrentApplication = default; _device.Gpu.HostInitalized.Set(); ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device, diff --git a/src/Ryujinx.HLE/Switch.cs b/src/Ryujinx.HLE/Switch.cs index d0afdf173..e0f94274c 100644 --- a/src/Ryujinx.HLE/Switch.cs +++ b/src/Ryujinx.HLE/Switch.cs @@ -37,6 +37,8 @@ namespace Ryujinx.HLE public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable; + public DirtyHacks DirtyHacks { get; } + public Switch(HLEConfiguration configuration) { ArgumentNullException.ThrowIfNull(configuration.GpuRenderer); @@ -72,6 +74,7 @@ namespace Ryujinx.HLE System.EnablePtc = Configuration.EnablePtc; System.FsIntegrityCheckLevel = Configuration.FsIntegrityCheckLevel; System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode; + DirtyHacks = Configuration.Hacks; UpdateVSyncInterval(); #pragma warning restore IDE0055 } diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs index 027e1052b..f5ce265ee 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs @@ -17,7 +17,7 @@ namespace Ryujinx.UI.Common.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 57; + public const int CurrentVersion = 58; /// /// Version of the configuration file format @@ -429,7 +429,17 @@ namespace Ryujinx.UI.Common.Configuration /// Uses Hypervisor over JIT if available /// public bool UseHypervisor { get; set; } - + + /** + * Show toggles for dirty hacks in the UI. + */ + public bool ShowDirtyHacks { get; set; } + + /** + * The packed value of the enabled dirty hacks. + */ + public int EnabledDirtyHacks { get; set; } + /// /// Loads a configuration file from disk /// diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Migration.cs index a41ea2cd7..8652b4331 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Migration.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Migration.cs @@ -735,6 +735,9 @@ namespace Ryujinx.UI.Common.Configuration Multiplayer.DisableP2p.Value = configurationFileFormat.MultiplayerDisableP2p; Multiplayer.LdnPassphrase.Value = configurationFileFormat.MultiplayerLdnPassphrase; Multiplayer.LdnServer.Value = configurationFileFormat.LdnServer; + + Hacks.ShowDirtyHacks.Value = configurationFileFormat.ShowDirtyHacks; + Hacks.Xc2MenuSoftlockFix.Value = ((DirtyHacks)configurationFileFormat.EnabledDirtyHacks).HasFlag(DirtyHacks.Xc2MenuSoftlockFix); if (configurationFileUpdated) { diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Model.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Model.cs index f28ce0348..c07ff3d71 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.Model.cs @@ -1,4 +1,5 @@ using ARMeilleure; +using Gommon; using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; @@ -617,6 +618,49 @@ namespace Ryujinx.UI.Common.Configuration } } + public class HacksSection + { + /// + /// Show toggles for dirty hacks in the UI. + /// + public ReactiveObject ShowDirtyHacks { get; private set; } + + public ReactiveObject Xc2MenuSoftlockFix { get; private set; } + + public HacksSection() + { + ShowDirtyHacks = new ReactiveObject(); + Xc2MenuSoftlockFix = new ReactiveObject(); + Xc2MenuSoftlockFix.Event += HackChanged; + } + + private void HackChanged(object sender, ReactiveEventArgs rxe) + { + Ryujinx.Common.Logging.Logger.Info?.Print(LogClass.Configuration, $"EnabledDirtyHacks set to: {EnabledHacks}", "LogValueChange"); + } + + public DirtyHacks EnabledHacks + { + get + { + DirtyHacks dirtyHacks = DirtyHacks.None; + + if (Xc2MenuSoftlockFix) + Apply(DirtyHacks.Xc2MenuSoftlockFix); + + return dirtyHacks; + + void Apply(DirtyHacks hack) + { + if (dirtyHacks is not DirtyHacks.None) + dirtyHacks |= hack; + else + dirtyHacks = hack; + } + } + } + } + /// /// The default configuration instance /// @@ -651,6 +695,11 @@ namespace Ryujinx.UI.Common.Configuration /// The Multiplayer section /// public MultiplayerSection Multiplayer { get; private set; } + + /** + * The Dirty Hacks section + */ + public HacksSection Hacks { get; private set; } /// /// Enables or disables Discord Rich Presence @@ -700,6 +749,7 @@ namespace Ryujinx.UI.Common.Configuration Graphics = new GraphicsSection(); Hid = new HidSection(); Multiplayer = new MultiplayerSection(); + Hacks = new HacksSection(); EnableDiscordIntegration = new ReactiveObject(); CheckUpdatesOnStart = new ReactiveObject(); ShowConfirmExit = new ReactiveObject(); diff --git a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs index 90bdc3409..8ae76ecc5 100644 --- a/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs +++ b/src/Ryujinx.UI.Common/Configuration/ConfigurationState.cs @@ -138,6 +138,8 @@ namespace Ryujinx.UI.Common.Configuration MultiplayerDisableP2p = Multiplayer.DisableP2p, MultiplayerLdnPassphrase = Multiplayer.LdnPassphrase, LdnServer = Multiplayer.LdnServer, + ShowDirtyHacks = Hacks.ShowDirtyHacks, + EnabledDirtyHacks = (int)Hacks.EnabledHacks, }; return configurationFile; diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index 909eb05d5..0f8b8030c 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -311,6 +311,7 @@ namespace Ryujinx.Ava Device.VSyncMode = e.NewValue; Device.UpdateVSyncInterval(); } + _renderer.Window?.ChangeVSyncMode(e.NewValue); _viewModel.ShowCustomVSyncIntervalPicker = (e.NewValue == VSyncMode.Custom); @@ -923,7 +924,7 @@ namespace Ryujinx.Ava // Initialize Configuration. var memoryConfiguration = ConfigurationState.Instance.System.DramSize.Value; - Device = new HLE.Switch(new HLEConfiguration( + Device = new Switch(new HLEConfiguration( VirtualFileSystem, _viewModel.LibHacHorizonManager, ContentManager, @@ -953,7 +954,8 @@ namespace Ryujinx.Ava ConfigurationState.Instance.Multiplayer.DisableP2p, ConfigurationState.Instance.Multiplayer.LdnPassphrase, ConfigurationState.Instance.Multiplayer.LdnServer, - ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value)); + ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value, + ConfigurationState.Instance.Hacks.ShowDirtyHacks ? ConfigurationState.Instance.Hacks.EnabledHacks : DirtyHacks.None)); } private static IHardwareDeviceDriver InitializeAudio() diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index d98e499c2..5e5adf2a0 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -139,4 +139,10 @@ + + + SettingsHacksView.axaml + Code + + \ No newline at end of file diff --git a/src/Ryujinx/UI/ViewModels/BaseModel.cs b/src/Ryujinx/UI/ViewModels/BaseModel.cs index d8f2e9096..e27c52867 100644 --- a/src/Ryujinx/UI/ViewModels/BaseModel.cs +++ b/src/Ryujinx/UI/ViewModels/BaseModel.cs @@ -13,8 +13,9 @@ namespace Ryujinx.Ava.UI.ViewModels PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - protected void OnPropertiesChanged(params ReadOnlySpan propertyNames) + protected void OnPropertiesChanged(string firstPropertyName, params ReadOnlySpan propertyNames) { + OnPropertyChanged(firstPropertyName); foreach (var propertyName in propertyNames) { OnPropertyChanged(propertyName); diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 9feaaba9b..aa4b994ef 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -62,7 +62,9 @@ namespace Ryujinx.Ava.UI.ViewModels private int _networkInterfaceIndex; private int _multiplayerModeIndex; private string _ldnPassphrase; - private string _LdnServer; + private string _ldnServer; + + private bool _xc2MenuSoftlockFix = ConfigurationState.Instance.Hacks.Xc2MenuSoftlockFix; public int ResolutionScale { @@ -162,9 +164,7 @@ namespace Ryujinx.Ava.UI.ViewModels get => _vSyncMode; set { - if (value == VSyncMode.Custom || - value == VSyncMode.Switch || - value == VSyncMode.Unbounded) + if (value is VSyncMode.Custom or VSyncMode.Switch or VSyncMode.Unbounded) { _vSyncMode = value; OnPropertyChanged(); @@ -258,6 +258,8 @@ namespace Ryujinx.Ava.UI.ViewModels public bool UseHypervisor { get; set; } public bool DisableP2P { get; set; } + public bool ShowDirtyHacks => ConfigurationState.Instance.Hacks.ShowDirtyHacks; + public string TimeZone { get; set; } public string ShaderDumpPath { get; set; } @@ -274,6 +276,17 @@ namespace Ryujinx.Ava.UI.ViewModels } } + public bool Xc2MenuSoftlockFixEnabled + { + get => _xc2MenuSoftlockFix; + set + { + _xc2MenuSoftlockFix = value; + + OnPropertyChanged(); + } + } + public int Language { get; set; } public int Region { get; set; } public int FsGlobalAccessLogMode { get; set; } @@ -374,10 +387,10 @@ namespace Ryujinx.Ava.UI.ViewModels public string LdnServer { - get => _LdnServer; + get => _ldnServer; set { - _LdnServer = value; + _ldnServer = value; OnPropertyChanged(); } } @@ -746,6 +759,9 @@ namespace Ryujinx.Ava.UI.ViewModels config.Multiplayer.DisableP2p.Value = DisableP2P; config.Multiplayer.LdnPassphrase.Value = LdnPassphrase; config.Multiplayer.LdnServer.Value = LdnServer; + + // Dirty Hacks + config.Hacks.Xc2MenuSoftlockFix.Value = Xc2MenuSoftlockFixEnabled; config.ToFileFormat().SaveConfig(Program.ConfigurationPath); @@ -779,5 +795,8 @@ namespace Ryujinx.Ava.UI.ViewModels RevertIfNotSaved(); CloseWindow?.Invoke(); } + + public static string Xc2MenuFixTooltip => + "From the issue on GitHub:\n\nWhen clicking very fast from game main menu to 2nd submenu, there is a low chance that the game will softlock, the submenu won't show up, while background music is still there."; } } diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml new file mode 100644 index 000000000..b7817f064 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml.cs new file mode 100644 index 000000000..f9e0958ca --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.UI.Common.Configuration; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsHacksView : UserControl + { + public SettingsViewModel ViewModel; + + public SettingsHacksView() + { + InitializeComponent(); + } + } +} diff --git a/src/Ryujinx/UI/Windows/SettingsWindow.axaml b/src/Ryujinx/UI/Windows/SettingsWindow.axaml index 2bf5b55e7..59302b6fc 100644 --- a/src/Ryujinx/UI/Windows/SettingsWindow.axaml +++ b/src/Ryujinx/UI/Windows/SettingsWindow.axaml @@ -37,6 +37,7 @@ + +