diff --git a/assets/locales.json b/assets/locales.json index af11978f7..a6ee055ee 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -267,6 +267,31 @@ "zh_TW": "使用 Hypervisor" } }, + { + "ID": "SettingsTabSystemHvForceOrderedAtomics", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Force Ordered Atomics on Hypervisor", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, { "ID": "MenuBarFile", "Translations": { @@ -16518,6 +16543,31 @@ "zh_TW": "使用 Hypervisor 取代 JIT。使用時可大幅提高效能,但在目前狀態下可能不穩定。" } }, + { + "ID": "HvForceOrderedAtomicsTooltip", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Force all exclusive memory accesses to be ordered on Hypervisor.\n\nSome games like The Legend of Zelda: Breath of the Wild and Splatoon 2 require this setting to be ON when using Hypervisor to prevent softlocks and crashes.\n\nLeave OFF if unsure.", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, { "ID": "DRamTooltip", "Translations": { diff --git a/src/Ryujinx.Cpu/AppleHv/HvCodePatcher.cs b/src/Ryujinx.Cpu/AppleHv/HvCodePatcher.cs new file mode 100644 index 000000000..876597b78 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/HvCodePatcher.cs @@ -0,0 +1,62 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace Ryujinx.Cpu.AppleHv +{ + static class HvCodePatcher + { + private const uint XMask = 0x3f808000u; + private const uint XValue = 0x8000000u; + + private const uint ZrIndex = 31u; + + public static void RewriteUnorderedExclusiveInstructions(Span code) + { + Span codeUint = MemoryMarshal.Cast(code); + Span> codeVector = MemoryMarshal.Cast>(code); + + Vector128 mask = Vector128.Create(XMask); + Vector128 value = Vector128.Create(XValue); + + for (int index = 0; index < codeVector.Length; index++) + { + Vector128 v = codeVector[index]; + + if (Vector128.EqualsAny(Vector128.BitwiseAnd(v, mask), value)) + { + int baseIndex = index * 4; + + for (int instIndex = baseIndex; instIndex < baseIndex + 4; instIndex++) + { + ref uint inst = ref codeUint[instIndex]; + + if ((inst & XMask) != XValue) + { + continue; + } + + bool isPair = (inst & (1u << 21)) != 0; + bool isLoad = (inst & (1u << 22)) != 0; + + uint rt2 = (inst >> 10) & 0x1fu; + uint rs = (inst >> 16) & 0x1fu; + + if (isLoad && rs != ZrIndex) + { + continue; + } + + if (!isPair && rt2 != ZrIndex) + { + continue; + } + + // Set the ordered flag. + inst |= 1u << 15; + } + } + } + } + } +} diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs index dd5547274..05b9d3701 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs @@ -25,6 +25,8 @@ namespace Ryujinx.Cpu.AppleHv private readonly MemoryBlock _backingMemory; private readonly PageTable _pageTable; + private readonly bool _forceOrderedAtomics; + private readonly ManagedPageFlags _pages; public bool UsesPrivateAllocations => false; @@ -46,11 +48,13 @@ namespace Ryujinx.Cpu.AppleHv /// /// Physical backing memory where virtual memory will be mapped to /// Size of the address space + /// Force all exclusive memory accesses to be ordered /// Optional function to handle invalid memory accesses - public HvMemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler = null) + public HvMemoryManager(MemoryBlock backingMemory, ulong addressSpaceSize, bool forceOrderedAtomics, InvalidAccessHandler invalidAccessHandler = null) { _backingMemory = backingMemory; _pageTable = new PageTable(); + _forceOrderedAtomics = forceOrderedAtomics; _invalidAccessHandler = invalidAccessHandler; AddressSpaceSize = addressSpaceSize; @@ -317,6 +321,18 @@ namespace Ryujinx.Cpu.AppleHv public void Reprotect(ulong va, ulong size, MemoryPermission protection) { + if (_forceOrderedAtomics && protection.HasFlag(MemoryPermission.Execute)) + { + // Some applications use unordered exclusive memory access instructions + // where it is not valid to do so, leading to memory re-ordering that + // makes the code behave incorrectly on some CPUs. + // To work around this, we force all such accesses to be ordered. + + using WritableRegion writableRegion = GetWritableRegion(va, (int)size); + + HvCodePatcher.RewriteUnorderedExclusiveInstructions(writableRegion.Memory.Span); + } + // TODO } diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs index 28f7ef25f..f98dffc34 100644 --- a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs +++ b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs @@ -55,7 +55,7 @@ namespace Ryujinx.HLE.HOS if (OperatingSystem.IsMacOS() && isArm64Host && for64Bit && context.Device.Configuration.UseHypervisor) { HvEngine cpuEngine = new(_tickSource); - HvMemoryManager memoryManager = new(context.Memory, addressSpaceSize, invalidAccessHandler); + HvMemoryManager memoryManager = new(context.Memory, addressSpaceSize, context.Device.Configuration.HvForceOrderedAtomics, invalidAccessHandler); processContext = new ArmProcessContext(pid, cpuEngine, _gpu, memoryManager, addressSpaceSize, for64Bit); } else diff --git a/src/Ryujinx.HLE/HleConfiguration.cs b/src/Ryujinx.HLE/HleConfiguration.cs index e2f95ede7..0ee02015c 100644 --- a/src/Ryujinx.HLE/HleConfiguration.cs +++ b/src/Ryujinx.HLE/HleConfiguration.cs @@ -164,6 +164,11 @@ namespace Ryujinx.HLE /// internal readonly bool UseHypervisor; + /// + /// Force all exclusive memory accesses to be ordered on Hypervisor. + /// + internal readonly bool HvForceOrderedAtomics; + /// /// Multiplayer LAN Interface ID (device GUID) /// @@ -232,6 +237,7 @@ namespace Ryujinx.HLE AspectRatio aspectRatio, float audioVolume, bool useHypervisor, + bool hvForceOrderedAtomics, string multiplayerLanInterfaceId, MultiplayerMode multiplayerMode, bool multiplayerDisableP2p, @@ -261,6 +267,7 @@ namespace Ryujinx.HLE AspectRatio = aspectRatio; AudioVolume = audioVolume; UseHypervisor = useHypervisor; + HvForceOrderedAtomics = hvForceOrderedAtomics; MultiplayerLanInterfaceId = multiplayerLanInterfaceId; MultiplayerMode = multiplayerMode; MultiplayerDisableP2p = multiplayerDisableP2p; diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs b/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs index e2fa68397..068482b96 100644 --- a/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs +++ b/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs @@ -333,6 +333,7 @@ namespace Ryujinx.Headless options.AspectRatio, options.AudioVolume, options.UseHypervisor ?? true, + options.HvForceOrderedAtomics, options.MultiplayerLanInterfaceId, Common.Configuration.Multiplayer.MultiplayerMode.Disabled, false, diff --git a/src/Ryujinx/Headless/Options.cs b/src/Ryujinx/Headless/Options.cs index 88c74eee5..605ef0d7f 100644 --- a/src/Ryujinx/Headless/Options.cs +++ b/src/Ryujinx/Headless/Options.cs @@ -79,6 +79,9 @@ namespace Ryujinx.Headless if (NeedsOverride(nameof(UseHypervisor)) && OperatingSystem.IsMacOS()) UseHypervisor = configurationState.System.UseHypervisor; + if (NeedsOverride(nameof(HvForceOrderedAtomics)) && OperatingSystem.IsMacOS()) + HvForceOrderedAtomics = configurationState.System.HvForceOrderedAtomics; + if (NeedsOverride(nameof(MultiplayerLanInterfaceId))) MultiplayerLanInterfaceId = configurationState.Multiplayer.LanInterfaceId; @@ -339,6 +342,9 @@ namespace Ryujinx.Headless [Option("use-hypervisor", Required = false, Default = true, HelpText = "Uses Hypervisor over JIT if available.")] public bool? UseHypervisor { get; set; } + [Option("hv-force-ordered-atomics", Required = false, Default = false, HelpText = "Forces all exclusive memory accesses to be ordered on Hypervisor.")] + public bool HvForceOrderedAtomics { get; set; } + [Option("lan-interface-id", Required = false, Default = "0", HelpText = "GUID for the network interface used by LAN.")] public string MultiplayerLanInterfaceId { get; set; } diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs index 26ea73f73..7ae718bad 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationFileFormat.cs @@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Systems.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 70; + public const int CurrentVersion = 71; /// /// Version of the configuration file format @@ -464,6 +464,11 @@ namespace Ryujinx.Ava.Systems.Configuration /// public bool UseHypervisor { get; set; } + /// + /// Force all exclusive memory accesses to be ordered on Hypervisor. + /// + public bool HvForceOrderedAtomics { get; set; } + /// /// Enables or disables the GDB stub /// diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs index a91a74711..862464fae 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Migration.cs @@ -107,6 +107,7 @@ namespace Ryujinx.Ava.Systems.Configuration System.IgnoreControllerApplet.Value = cff.IgnoreApplet; System.SkipUserProfilesManager.Value = cff.SkipUserProfiles; System.UseHypervisor.Value = cff.UseHypervisor; + System.HvForceOrderedAtomics.Value = cff.HvForceOrderedAtomics; UI.GuiColumns.FavColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value; UI.GuiColumns.IconColumn.Value = shouldLoadFromFile ? cff.GuiColumns.IconColumn : UI.GuiColumns.IconColumn.Value; @@ -484,7 +485,8 @@ namespace Ryujinx.Ava.Systems.Configuration }; } ), - (69, static cff => cff.SkipUserProfiles = false) + (69, static cff => cff.SkipUserProfiles = false), + (70, static cff => cff.HvForceOrderedAtomics = false) ); } } diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs index 405400b81..f0a44c695 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.Model.cs @@ -412,6 +412,11 @@ namespace Ryujinx.Ava.Systems.Configuration /// public ReactiveObject UseHypervisor { get; private set; } + /// + /// Force all exclusive memory accesses to be ordered on Hypervisor. + /// + public ReactiveObject HvForceOrderedAtomics { get; private set; } + public SystemSection() { Language = new ReactiveObject(); @@ -465,6 +470,8 @@ namespace Ryujinx.Ava.Systems.Configuration AudioVolume.LogChangesToValue(nameof(AudioVolume)); UseHypervisor = new ReactiveObject(); UseHypervisor.LogChangesToValue(nameof(UseHypervisor)); + HvForceOrderedAtomics = new ReactiveObject(); + HvForceOrderedAtomics.LogChangesToValue(nameof(HvForceOrderedAtomics)); } } @@ -936,6 +943,7 @@ namespace Ryujinx.Ava.Systems.Configuration Graphics.AspectRatio, System.AudioVolume, System.UseHypervisor, + System.HvForceOrderedAtomics, Multiplayer.LanInterfaceId, Multiplayer.Mode, Multiplayer.DisableP2p, diff --git a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs index 185aedf64..ea3cc94b0 100644 --- a/src/Ryujinx/Systems/Configuration/ConfigurationState.cs +++ b/src/Ryujinx/Systems/Configuration/ConfigurationState.cs @@ -86,6 +86,7 @@ namespace Ryujinx.Ava.Systems.Configuration IgnoreApplet = System.IgnoreControllerApplet, SkipUserProfiles = System.SkipUserProfilesManager, UseHypervisor = System.UseHypervisor, + HvForceOrderedAtomics = System.HvForceOrderedAtomics, GuiColumns = new GuiColumns { FavColumn = UI.GuiColumns.FavColumn, @@ -215,6 +216,7 @@ namespace Ryujinx.Ava.Systems.Configuration System.IgnoreControllerApplet.Value = false; System.SkipUserProfilesManager.Value = false; System.UseHypervisor.Value = true; + System.HvForceOrderedAtomics.Value = false; Multiplayer.LanInterfaceId.Value = "0"; Multiplayer.Mode.Value = MultiplayerMode.Disabled; Multiplayer.DisableP2p.Value = false; diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index acf7517d8..e6618f61a 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -276,6 +276,7 @@ namespace Ryujinx.Ava.UI.ViewModels public bool IsVulkanSelected => GraphicsBackendIndex == 1 || (GraphicsBackendIndex == 0 && !OperatingSystem.IsMacOS()); public bool UseHypervisor { get; set; } + public bool HvForceOrderedAtomics { get; set; } public bool DisableP2P { get; set; } public bool ShowDirtyHacks => ConfigurationState.Instance.Hacks.ShowDirtyHacks; @@ -673,6 +674,7 @@ namespace Ryujinx.Ava.UI.ViewModels EnableLowPowerPptc = config.System.EnableLowPowerPtc; MemoryMode = (int)config.System.MemoryManagerMode.Value; UseHypervisor = config.System.UseHypervisor; + HvForceOrderedAtomics = config.System.HvForceOrderedAtomics; TurboMultiplier = config.System.TickScalar; // Graphics @@ -784,6 +786,7 @@ namespace Ryujinx.Ava.UI.ViewModels config.System.EnableLowPowerPtc.Value = EnableLowPowerPptc; config.System.MemoryManagerMode.Value = (MemoryManagerMode)MemoryMode; config.System.UseHypervisor.Value = UseHypervisor; + config.System.HvForceOrderedAtomics.Value = HvForceOrderedAtomics; config.System.TickScalar.Value = TurboMultiplier; // Graphics diff --git a/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml index 2424aca97..ab65bd464 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsCPUView.axaml @@ -71,6 +71,12 @@ + + +