Просмотр исходного кода

misc: Dirty Hacks
Enable this settings screen via a boolean in Config.json
First one is the xb2 menu softlock fix

Evan Husted 1 год назад
Родитель
Сommit
8b3a945b5f

+ 11 - 0
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
+    }
+}

+ 2 - 0
src/Ryujinx.Common/TitleIDs.cs

@@ -8,6 +8,8 @@ namespace Ryujinx.Common
 {
     public static class TitleIDs
     {
+        public static Optional<string> CurrentApplication;
+        
         public static GraphicsBackend SelectGraphicsBackend(string titleId, GraphicsBackend currentBackend)
         {
             switch (currentBackend)

+ 0 - 6
src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs

@@ -47,12 +47,6 @@ namespace Ryujinx.Graphics.Gpu
         /// </summary>
         public static bool EnableMacroHLE = true;
 
-        /// <summary>
-        /// Title id of the current running game.
-        /// Used by the shader cache.
-        /// </summary>
-        public static string TitleId;
-
         /// <summary>
         /// Enables or disables the shader cache.
         /// </summary>

+ 3 - 2
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
         /// </summary>
         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;
         }
 

+ 8 - 1
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.
         /// </summary>
         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;
         }
     }
 }

+ 12 - 0
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<LibHac.FsSrv.Sf.IStorage>.CreateMove(ref baseStorage);
         }
 
+        private const string Xc2TitleId = "0100e95004038000";
+
         [CommandCmif(0)]
         // Read(u64 offset, u64 length) -> buffer<u8, 0x46, 0> 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;
             }

+ 2 - 1
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))

+ 2 - 1
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,

+ 3 - 0
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
         }

+ 12 - 2
src/Ryujinx.UI.Common/Configuration/ConfigurationFileFormat.cs

@@ -17,7 +17,7 @@ namespace Ryujinx.UI.Common.Configuration
         /// <summary>
         /// The current version of the file format
         /// </summary>
-        public const int CurrentVersion = 57;
+        public const int CurrentVersion = 58;
 
         /// <summary>
         /// Version of the configuration file format
@@ -429,7 +429,17 @@ namespace Ryujinx.UI.Common.Configuration
         /// Uses Hypervisor over JIT if available
         /// </summary>
         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; }
+        
         /// <summary>
         /// Loads a configuration file from disk
         /// </summary>

+ 3 - 0
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)
             {

+ 50 - 0
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
+        {
+            /// <summary>
+            /// Show toggles for dirty hacks in the UI.
+            /// </summary>
+            public ReactiveObject<bool> ShowDirtyHacks { get; private set; }
+            
+            public ReactiveObject<bool> Xc2MenuSoftlockFix { get; private set; }
+
+            public HacksSection()
+            {
+                ShowDirtyHacks = new ReactiveObject<bool>();
+                Xc2MenuSoftlockFix = new ReactiveObject<bool>();
+                Xc2MenuSoftlockFix.Event += HackChanged;
+            }
+
+            private void HackChanged(object sender, ReactiveEventArgs<bool> 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;
+                    }
+                }
+            }
+        }
+
         /// <summary>
         /// The default configuration instance
         /// </summary>
@@ -651,6 +695,11 @@ namespace Ryujinx.UI.Common.Configuration
         /// The Multiplayer section
         /// </summary>
         public MultiplayerSection Multiplayer { get; private set; }
+        
+        /**
+         * The Dirty Hacks section
+         */
+        public HacksSection Hacks { get; private set; }
 
         /// <summary>
         /// 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<bool>();
             CheckUpdatesOnStart = new ReactiveObject<bool>();
             ShowConfirmExit = new ReactiveObject<bool>();

+ 2 - 0
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;

+ 4 - 2
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()

+ 6 - 0
src/Ryujinx/Ryujinx.csproj

@@ -139,4 +139,10 @@
   <ItemGroup>
     <AdditionalFiles Include="Assets\locales.json" />
   </ItemGroup>
+  <ItemGroup>
+    <Compile Update="UI\Views\Settings\SettingsHacksView.axaml.cs">
+      <DependentUpon>SettingsHacksView.axaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+  </ItemGroup>
 </Project>

+ 2 - 1
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<string> propertyNames)
+        protected void OnPropertiesChanged(string firstPropertyName, params ReadOnlySpan<string> propertyNames)
         {
+            OnPropertyChanged(firstPropertyName);
             foreach (var propertyName in propertyNames)
             {
                 OnPropertyChanged(propertyName);

+ 25 - 6
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.";
     }
 }

+ 48 - 0
src/Ryujinx/UI/Views/Settings/SettingsHacksView.axaml

@@ -0,0 +1,48 @@
+<UserControl
+    x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsHacksView"
+    xmlns="https://github.com/avaloniaui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
+    mc:Ignorable="d"
+    x:DataType="viewModels:SettingsViewModel">
+    <Design.DataContext>
+        <viewModels:SettingsViewModel />
+    </Design.DataContext>
+    <ScrollViewer
+        Name="HacksPage"
+        HorizontalAlignment="Stretch"
+        VerticalAlignment="Stretch"
+        HorizontalScrollBarVisibility="Disabled"
+        VerticalScrollBarVisibility="Auto">
+        <Border Classes="settings">
+            <StackPanel
+                Margin="10"
+                HorizontalAlignment="Center"
+                Orientation="Vertical"
+                Spacing="5">
+                <TextBlock
+                    HorizontalAlignment="Center"
+                    Classes="h1"
+                    Text="Dirty Hacks" />
+                <TextBlock
+                    Foreground="{DynamicResource SecondaryTextColor}"
+                    TextDecorations="Underline"
+                    Text="Game-specific hacks &amp; tricks to alleviate performance issues or crashing. May cause issues." />
+                <StackPanel
+                    Margin="0,10,0,0"
+                    Orientation="Horizontal"
+                    HorizontalAlignment="Center"
+                    ToolTip.Tip="{Binding Xc2MenuFixTooltip}">
+                    <CheckBox
+                        Margin="0"
+                        IsChecked="{Binding Xc2MenuSoftlockFixEnabled}"/>
+                    <TextBlock
+                        VerticalAlignment="Center"
+                        Text="Xenoblade Chronicles 2 Menu Softlock Fix" />
+                </StackPanel>
+            </StackPanel>
+        </Border>
+    </ScrollViewer>
+</UserControl>

+ 17 - 0
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();
+        }
+    }
+}

+ 6 - 0
src/Ryujinx/UI/Windows/SettingsWindow.axaml

@@ -37,6 +37,7 @@
             <settings:SettingsAudioView Name="AudioPage" />
             <settings:SettingsNetworkView Name="NetworkPage" />
             <settings:SettingsLoggingView Name="LoggingPage" />
+            <settings:SettingsHacksView Name="HacksPage" />
         </Grid>
         <ui:NavigationView
             Grid.Row="1"
@@ -91,6 +92,11 @@
                     Content="{ext:Locale SettingsTabLogging}"
                     Tag="LoggingPage"
                     IconSource="Document" />
+                <ui:NavigationViewItem
+                    IsVisible="{Binding ShowDirtyHacks}"
+                    Content="Dirty Hacks"
+                    Tag="HacksPage"
+                    IconSource="Code" />
             </ui:NavigationView.MenuItems>
             <ui:NavigationView.Styles>
                 <Style Selector="Grid#PlaceholderGrid">

+ 4 - 0
src/Ryujinx/UI/Windows/SettingsWindow.axaml.cs

@@ -86,6 +86,10 @@ namespace Ryujinx.Ava.UI.Windows
                     case nameof(LoggingPage):
                         NavPanel.Content = LoggingPage;
                         break;
+                    case nameof(HacksPage):
+                        HacksPage.ViewModel = ViewModel;
+                        NavPanel.Content = HacksPage;
+                        break;
                     default:
                         throw new NotImplementedException();
                 }