From 09748b140a87ef30e40c963449a4e9254e440909 Mon Sep 17 00:00:00 2001 From: GreemDev Date: Sun, 16 Nov 2025 19:02:03 -0600 Subject: [PATCH 01/16] Use the new C# 14 null propagation setter --- src/ARMeilleure/Translation/IntervalTree.cs | 20 ++------- .../Collections/IntervalTree.cs | 5 +-- .../Collections/IntrusiveRedBlackTree.cs | 21 +++------ .../Collections/IntrusiveRedBlackTreeImpl.cs | 15 ++----- .../Collections/TreeDictionary.cs | 5 +-- .../Memory/CounterCache.cs | 5 +-- .../Optimizations/BranchElimination.cs | 6 +-- .../HOS/Diagnostics/Demangler/Demangler.cs | 45 ++++--------------- .../HOS/Kernel/Threading/KScheduler.cs | 5 +-- .../HOS/Services/Sockets/Bsd/IClient.cs | 4 +- .../Tracking/SmartMultiRegionHandle.cs | 10 +---- src/Ryujinx/Systems/AppHost.cs | 10 +---- .../UI/Applet/SwkbdAppletDialog.axaml.cs | 5 +-- 13 files changed, 33 insertions(+), 123 deletions(-) diff --git a/src/ARMeilleure/Translation/IntervalTree.cs b/src/ARMeilleure/Translation/IntervalTree.cs index f4abe3bd5..5af487e28 100644 --- a/src/ARMeilleure/Translation/IntervalTree.cs +++ b/src/ARMeilleure/Translation/IntervalTree.cs @@ -361,10 +361,7 @@ namespace ARMeilleure.Translation IntervalTreeNode tmp = LeftOf(replacementNode) ?? RightOf(replacementNode); - if (tmp != null) - { - tmp.Parent = ParentOf(replacementNode); - } + tmp?.Parent = ParentOf(replacementNode); if (ParentOf(replacementNode) == null) { @@ -582,10 +579,7 @@ namespace ARMeilleure.Translation { IntervalTreeNode right = RightOf(node); node.Right = LeftOf(right); - if (node.Right != null) - { - node.Right.Parent = node; - } + node.Right?.Parent = node; IntervalTreeNode nodeParent = ParentOf(node); right.Parent = nodeParent; @@ -615,10 +609,7 @@ namespace ARMeilleure.Translation { IntervalTreeNode left = LeftOf(node); node.Left = RightOf(left); - if (node.Left != null) - { - node.Left.Parent = node; - } + node.Left?.Parent = node; IntervalTreeNode nodeParent = ParentOf(node); left.Parent = nodeParent; @@ -667,10 +658,7 @@ namespace ARMeilleure.Translation /// Color (Boolean) private static void SetColor(IntervalTreeNode node, bool color) { - if (node != null) - { - node.Color = color; - } + node?.Color = color; } /// diff --git a/src/Ryujinx.Common/Collections/IntervalTree.cs b/src/Ryujinx.Common/Collections/IntervalTree.cs index a094a5a61..cc2062d48 100644 --- a/src/Ryujinx.Common/Collections/IntervalTree.cs +++ b/src/Ryujinx.Common/Collections/IntervalTree.cs @@ -386,10 +386,7 @@ namespace Ryujinx.Common.Collections IntervalTreeNode tmp = LeftOf(replacementNode) ?? RightOf(replacementNode); - if (tmp != null) - { - tmp.Parent = ParentOf(replacementNode); - } + tmp?.Parent = ParentOf(replacementNode); if (ParentOf(replacementNode) == null) { diff --git a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs index d951a6024..55bfe0019 100644 --- a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs +++ b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs @@ -235,10 +235,7 @@ namespace Ryujinx.Common.Collections parent = ParentOf(element); color = ColorOf(element); - if (child != null) - { - child.Parent = parent; - } + child?.Parent = parent; if (parent == null) { @@ -258,8 +255,7 @@ namespace Ryujinx.Common.Collections element.Right = old.Right; element.Parent = old.Parent; element.Predecessor = old.Predecessor; - if (element.Predecessor != null) - element.Predecessor.Successor = element; + element.Predecessor?.Successor = element; if (ParentOf(old) == null) { @@ -292,10 +288,7 @@ namespace Ryujinx.Common.Collections parent = ParentOf(nodeToDelete); color = ColorOf(nodeToDelete); - if (child != null) - { - child.Parent = parent; - } + child?.Parent = parent; if (parent == null) { @@ -314,11 +307,9 @@ namespace Ryujinx.Common.Collections { RestoreBalanceAfterRemoval(child); } - - if (old.Successor != null) - old.Successor.Predecessor = old.Predecessor; - if (old.Predecessor != null) - old.Predecessor.Successor = old.Successor; + + old.Successor?.Predecessor = old.Predecessor; + old.Predecessor?.Successor = old.Successor; return old; } diff --git a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs index abd3723fe..58bcc4fe5 100644 --- a/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs +++ b/src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs @@ -250,10 +250,7 @@ namespace Ryujinx.Common.Collections { T right = RightOf(node); node.Right = LeftOf(right); - if (node.Right != null) - { - node.Right.Parent = node; - } + node.Right?.Parent = node; T nodeParent = ParentOf(node); right.Parent = nodeParent; @@ -281,10 +278,7 @@ namespace Ryujinx.Common.Collections { T left = LeftOf(node); node.Left = RightOf(left); - if (node.Left != null) - { - node.Left.Parent = node; - } + node.Left?.Parent = node; T nodeParent = ParentOf(node); left.Parent = nodeParent; @@ -329,10 +323,7 @@ namespace Ryujinx.Common.Collections /// Color (Boolean) protected static void SetColor(T node, bool color) { - if (node != null) - { - node.Color = color; - } + node?.Color = color; } /// diff --git a/src/Ryujinx.Common/Collections/TreeDictionary.cs b/src/Ryujinx.Common/Collections/TreeDictionary.cs index 453f128d3..ef818167d 100644 --- a/src/Ryujinx.Common/Collections/TreeDictionary.cs +++ b/src/Ryujinx.Common/Collections/TreeDictionary.cs @@ -328,10 +328,7 @@ namespace Ryujinx.Common.Collections Node tmp = LeftOf(replacementNode) ?? RightOf(replacementNode); - if (tmp != null) - { - tmp.Parent = ParentOf(replacementNode); - } + tmp?.Parent = ParentOf(replacementNode); if (ParentOf(replacementNode) == null) { diff --git a/src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs index 2d67c2c28..33381dd55 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs @@ -84,10 +84,7 @@ namespace Ryujinx.Graphics.Gpu.Memory for (int i = 0; i < count; i++) { ICounterEvent evt = _items[index + i].Event; - if (evt != null) - { - evt.Invalid = true; - } + evt?.Invalid = true; } _items.RemoveRange(index, count); diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs index bd2eceda5..ab988f70e 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs @@ -26,12 +26,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations // - Both branches are jumping to the same location. // In this case, the branch on the current block can be removed, // as the next block is going to jump to the same place anyway. - if (nextBlock == null) - { - return false; - } - if (nextBlock.Operations.First?.Value is not Operation next) + if (nextBlock?.Operations.First?.Value is not Operation next) { return false; } diff --git a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs index 5424342fb..98f122e37 100644 --- a/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs +++ b/src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs @@ -891,10 +891,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler result = new NestedName(name, prev); } - if (context != null) - { - context.FinishWithTemplateArguments = false; - } + context?.FinishWithTemplateArguments = false; return result; } @@ -1074,10 +1071,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler return null; } - if (context != null) - { - context.CtorDtorConversion = true; - } + context?.CtorDtorConversion = true; return new ConversionOperatorType(type); default: @@ -1349,10 +1343,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler _position++; - if (context != null) - { - context.CtorDtorConversion = true; - } + context?.CtorDtorConversion = true; if (isInherited && ParseName(context) == null) { @@ -1372,10 +1363,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler _position++; - if (context != null) - { - context.CtorDtorConversion = true; - } + context?.CtorDtorConversion = true; return new CtorDtorNameType(prev, true); } @@ -3005,16 +2993,10 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler BaseNode result = null; CvType cv = new(ParseCvQualifiers(), null); - if (context != null) - { - context.Cv = cv; - } + context?.Cv = cv; SimpleReferenceType Ref = ParseRefQualifiers(); - if (context != null) - { - context.Ref = Ref; - } + context?.Ref = Ref; if (ConsumeIf("St")) { @@ -3060,10 +3042,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler } result = new NameTypeWithTemplateArguments(result, templateArgument); - if (context != null) - { - context.FinishWithTemplateArguments = true; - } + context?.FinishWithTemplateArguments = true; _substitutionList.Add(result); continue; @@ -3256,10 +3235,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler return null; } - if (context != null) - { - context.FinishWithTemplateArguments = true; - } + context?.FinishWithTemplateArguments = true; return new NameTypeWithTemplateArguments(substitution, templateArguments); } @@ -3279,10 +3255,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler return null; } - if (context != null) - { - context.FinishWithTemplateArguments = true; - } + context?.FinishWithTemplateArguments = true; return new NameTypeWithTemplateArguments(result, templateArguments); } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs index b97ba705c..330624b2b 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs @@ -174,10 +174,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading if (previousThread != nextThread) { - if (previousThread != null) - { - previousThread.LastScheduledTime = PerformanceCounter.ElapsedTicks; - } + previousThread?.LastScheduledTime = PerformanceCounter.ElapsedTicks; _state.SelectedThread = nextThread; _state.NeedsScheduling = true; diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs index f22b1eb14..ef3b68b27 100644 --- a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs +++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs @@ -1169,9 +1169,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd public override void DestroyAtExit() { - if (_context != null) { - _context.Dispose(); - } + _context?.Dispose(); } } } diff --git a/src/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs b/src/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs index ccd60e8da..7e350712b 100644 --- a/src/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs +++ b/src/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs @@ -58,10 +58,7 @@ namespace Ryujinx.Memory.Tracking { foreach (RegionHandle handle in _handles) { - if (handle != null) - { - handle?.RegisterAction((address, size) => action(handle.Address, handle.Size)); - } + handle?.RegisterAction((address, size) => action(handle.Address, handle.Size)); } } @@ -69,10 +66,7 @@ namespace Ryujinx.Memory.Tracking { foreach (RegionHandle handle in _handles) { - if (handle != null) - { - handle?.RegisterPreciseAction((address, size, write) => action(handle.Address, handle.Size, write)); - } + handle?.RegisterPreciseAction((address, size, write) => action(handle.Address, handle.Size, write)); } } diff --git a/src/Ryujinx/Systems/AppHost.cs b/src/Ryujinx/Systems/AppHost.cs index f9e99c62b..2eba0d26b 100644 --- a/src/Ryujinx/Systems/AppHost.cs +++ b/src/Ryujinx/Systems/AppHost.cs @@ -501,18 +501,12 @@ namespace Ryujinx.Ava.Systems private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs args) { - if (Device != null) - { - Device.Configuration.IgnoreMissingServices = args.NewValue; - } + Device?.Configuration.IgnoreMissingServices = args.NewValue; } private void UpdateAspectRatioState(object sender, ReactiveEventArgs args) { - if (Device != null) - { - Device.Configuration.AspectRatio = args.NewValue; - } + Device?.Configuration.AspectRatio = args.NewValue; } private void UpdateAntiAliasing(object sender, ReactiveEventArgs e) diff --git a/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs index dd5b7d9f1..bef6fc47d 100644 --- a/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs +++ b/src/Ryujinx/UI/Applet/SwkbdAppletDialog.axaml.cs @@ -167,10 +167,7 @@ namespace Ryujinx.Ava.UI.Controls private void Message_TextInput(object sender, TextInputEventArgs e) { - if (_host != null) - { - _host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message); - } + _host?.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message); } private void Message_KeyUp(object sender, KeyEventArgs e) From e8751e1c40be5bfd343121608dab542f816aff99 Mon Sep 17 00:00:00 2001 From: GreemDev Date: Sun, 16 Nov 2025 19:13:58 -0600 Subject: [PATCH 02/16] more C# 14 partial properties --- .../UI/Models/Input/GamepadInputConfig.cs | 144 ++++++++++++------ src/Ryujinx/UI/Models/Input/HotkeyConfig.cs | 39 +++-- .../UI/Models/Input/KeyboardInputConfig.cs | 103 +++++++++---- src/Ryujinx/UI/Models/ModModel.cs | 4 +- src/Ryujinx/UI/Models/ProfileImageModel.cs | 3 +- src/Ryujinx/UI/Models/TempProfile.cs | 14 +- src/Ryujinx/UI/Models/UserProfile.cs | 21 ++- .../UI/ViewModels/SettingsViewModel.cs | 5 +- 8 files changed, 230 insertions(+), 103 deletions(-) diff --git a/src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs b/src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs index fa08156af..0eeef45f5 100644 --- a/src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs +++ b/src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs @@ -27,82 +27,136 @@ namespace Ryujinx.Ava.UI.Models.Input public ControllerType ControllerType { get; set; } public PlayerIndex PlayerIndex { get; set; } - [ObservableProperty] private StickInputId _leftJoystick; - [ObservableProperty] private bool _leftInvertStickX; - [ObservableProperty] private bool _leftInvertStickY; - [ObservableProperty] private bool _leftRotate90; - [ObservableProperty] private GamepadInputId _leftStickButton; + [ObservableProperty] + public partial StickInputId LeftJoystick { get; set; } - [ObservableProperty] private StickInputId _rightJoystick; - [ObservableProperty] private bool _rightInvertStickX; - [ObservableProperty] private bool _rightInvertStickY; - [ObservableProperty] private bool _rightRotate90; - [ObservableProperty] private GamepadInputId _rightStickButton; + [ObservableProperty] + public partial bool LeftInvertStickX { get; set; } - [ObservableProperty] private GamepadInputId _dpadUp; - [ObservableProperty] private GamepadInputId _dpadDown; - [ObservableProperty] private GamepadInputId _dpadLeft; - [ObservableProperty] private GamepadInputId _dpadRight; + [ObservableProperty] + public partial bool LeftInvertStickY { get; set; } - [ObservableProperty] private GamepadInputId _buttonMinus; - [ObservableProperty] private GamepadInputId _buttonPlus; + [ObservableProperty] + public partial bool LeftRotate90 { get; set; } - [ObservableProperty] private GamepadInputId _buttonA; - [ObservableProperty] private GamepadInputId _buttonB; - [ObservableProperty] private GamepadInputId _buttonX; - [ObservableProperty] private GamepadInputId _buttonY; + [ObservableProperty] + public partial GamepadInputId LeftStickButton { get; set; } - [ObservableProperty] private GamepadInputId _buttonZl; - [ObservableProperty] private GamepadInputId _buttonZr; + [ObservableProperty] + public partial StickInputId RightJoystick { get; set; } - [ObservableProperty] private GamepadInputId _buttonL; - [ObservableProperty] private GamepadInputId _buttonR; + [ObservableProperty] + public partial bool RightInvertStickX { get; set; } - [ObservableProperty] private GamepadInputId _leftButtonSl; - [ObservableProperty] private GamepadInputId _leftButtonSr; + [ObservableProperty] + public partial bool RightInvertStickY { get; set; } - [ObservableProperty] private GamepadInputId _rightButtonSl; - [ObservableProperty] private GamepadInputId _rightButtonSr; + [ObservableProperty] + public partial bool RightRotate90 { get; set; } - [ObservableProperty] private float _deadzoneLeft; - [ObservableProperty] private float _deadzoneRight; + [ObservableProperty] + public partial GamepadInputId RightStickButton { get; set; } - [ObservableProperty] private float _rangeLeft; - [ObservableProperty] private float _rangeRight; + [ObservableProperty] + public partial GamepadInputId DpadUp { get; set; } - [ObservableProperty] private float _triggerThreshold; + [ObservableProperty] + public partial GamepadInputId DpadDown { get; set; } - [ObservableProperty] private bool _enableMotion; + [ObservableProperty] + public partial GamepadInputId DpadLeft { get; set; } - [ObservableProperty] private bool _enableRumble; + [ObservableProperty] + public partial GamepadInputId DpadRight { get; set; } - [ObservableProperty] private bool _enableLedChanging; + [ObservableProperty] + public partial GamepadInputId ButtonMinus { get; set; } - [ObservableProperty] private Color _ledColor; + [ObservableProperty] + public partial GamepadInputId ButtonPlus { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonA { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonB { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonX { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonY { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonZl { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonZr { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonL { get; set; } + + [ObservableProperty] + public partial GamepadInputId ButtonR { get; set; } + + [ObservableProperty] + public partial GamepadInputId LeftButtonSl { get; set; } + + [ObservableProperty] + public partial GamepadInputId LeftButtonSr { get; set; } + + [ObservableProperty] + public partial GamepadInputId RightButtonSl { get; set; } + + [ObservableProperty] + public partial GamepadInputId RightButtonSr { get; set; } + + [ObservableProperty] + public partial float DeadzoneLeft { get; set; } + + [ObservableProperty] + public partial float DeadzoneRight { get; set; } + + [ObservableProperty] + public partial float RangeLeft { get; set; } + + [ObservableProperty] + public partial float RangeRight { get; set; } + + [ObservableProperty] + public partial float TriggerThreshold { get; set; } + + [ObservableProperty] + public partial bool EnableMotion { get; set; } + + [ObservableProperty] + public partial bool EnableRumble { get; set; } + + [ObservableProperty] + public partial bool EnableLedChanging { get; set; } + + [ObservableProperty] + public partial Color LedColor { get; set; } public bool ShowLedColorPicker => !TurnOffLed && !UseRainbowLed; - private bool _turnOffLed; - public bool TurnOffLed { - get => _turnOffLed; + get; set { - _turnOffLed = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(ShowLedColorPicker)); } } - private bool _useRainbowLed; - public bool UseRainbowLed { - get => _useRainbowLed; + get; set { - _useRainbowLed = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(ShowLedColorPicker)); } diff --git a/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs b/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs index 9e557d7b1..545af7876 100644 --- a/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs +++ b/src/Ryujinx/UI/Models/Input/HotkeyConfig.cs @@ -6,31 +6,44 @@ namespace Ryujinx.Ava.UI.Models.Input { public partial class HotkeyConfig : BaseModel { - [ObservableProperty] private Key _toggleVSyncMode; + [ObservableProperty] + public partial Key ToggleVSyncMode { get; set; } - [ObservableProperty] private Key _screenshot; + [ObservableProperty] + public partial Key Screenshot { get; set; } - [ObservableProperty] private Key _showUI; + [ObservableProperty] + public partial Key ShowUI { get; set; } - [ObservableProperty] private Key _pause; + [ObservableProperty] + public partial Key Pause { get; set; } - [ObservableProperty] private Key _toggleMute; + [ObservableProperty] + public partial Key ToggleMute { get; set; } - [ObservableProperty] private Key _resScaleUp; + [ObservableProperty] + public partial Key ResScaleUp { get; set; } - [ObservableProperty] private Key _resScaleDown; + [ObservableProperty] + public partial Key ResScaleDown { get; set; } - [ObservableProperty] private Key _volumeUp; + [ObservableProperty] + public partial Key VolumeUp { get; set; } - [ObservableProperty] private Key _volumeDown; + [ObservableProperty] + public partial Key VolumeDown { get; set; } - [ObservableProperty] private Key _customVSyncIntervalIncrement; + [ObservableProperty] + public partial Key CustomVSyncIntervalIncrement { get; set; } - [ObservableProperty] private Key _customVSyncIntervalDecrement; + [ObservableProperty] + public partial Key CustomVSyncIntervalDecrement { get; set; } - [ObservableProperty] private Key _turboMode; + [ObservableProperty] + public partial Key TurboMode { get; set; } - [ObservableProperty] private bool _turboModeWhileHeld; + [ObservableProperty] + public partial bool TurboModeWhileHeld { get; set; } public HotkeyConfig(KeyboardHotkeys config) { diff --git a/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs b/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs index c8169b6d5..de51d9d70 100644 --- a/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs +++ b/src/Ryujinx/UI/Models/Input/KeyboardInputConfig.cs @@ -12,42 +12,89 @@ namespace Ryujinx.Ava.UI.Models.Input public ControllerType ControllerType { get; set; } public PlayerIndex PlayerIndex { get; set; } - [ObservableProperty] private Key _leftStickUp; - [ObservableProperty] private Key _leftStickDown; - [ObservableProperty] private Key _leftStickLeft; - [ObservableProperty] private Key _leftStickRight; - [ObservableProperty] private Key _leftStickButton; + [ObservableProperty] + public partial Key LeftStickUp { get; set; } - [ObservableProperty] private Key _rightStickUp; - [ObservableProperty] private Key _rightStickDown; - [ObservableProperty] private Key _rightStickLeft; - [ObservableProperty] private Key _rightStickRight; - [ObservableProperty] private Key _rightStickButton; + [ObservableProperty] + public partial Key LeftStickDown { get; set; } - [ObservableProperty] private Key _dpadUp; - [ObservableProperty] private Key _dpadDown; - [ObservableProperty] private Key _dpadLeft; - [ObservableProperty] private Key _dpadRight; + [ObservableProperty] + public partial Key LeftStickLeft { get; set; } - [ObservableProperty] private Key _buttonMinus; - [ObservableProperty] private Key _buttonPlus; + [ObservableProperty] + public partial Key LeftStickRight { get; set; } - [ObservableProperty] private Key _buttonA; - [ObservableProperty] private Key _buttonB; - [ObservableProperty] private Key _buttonX; - [ObservableProperty] private Key _buttonY; + [ObservableProperty] + public partial Key LeftStickButton { get; set; } - [ObservableProperty] private Key _buttonL; - [ObservableProperty] private Key _buttonR; + [ObservableProperty] + public partial Key RightStickUp { get; set; } - [ObservableProperty] private Key _buttonZl; - [ObservableProperty] private Key _buttonZr; + [ObservableProperty] + public partial Key RightStickDown { get; set; } - [ObservableProperty] private Key _leftButtonSl; - [ObservableProperty] private Key _leftButtonSr; + [ObservableProperty] + public partial Key RightStickLeft { get; set; } - [ObservableProperty] private Key _rightButtonSl; - [ObservableProperty] private Key _rightButtonSr; + [ObservableProperty] + public partial Key RightStickRight { get; set; } + + [ObservableProperty] + public partial Key RightStickButton { get; set; } + + [ObservableProperty] + public partial Key DpadUp { get; set; } + + [ObservableProperty] + public partial Key DpadDown { get; set; } + + [ObservableProperty] + public partial Key DpadLeft { get; set; } + + [ObservableProperty] + public partial Key DpadRight { get; set; } + + [ObservableProperty] + public partial Key ButtonMinus { get; set; } + + [ObservableProperty] + public partial Key ButtonPlus { get; set; } + + [ObservableProperty] + public partial Key ButtonA { get; set; } + + [ObservableProperty] + public partial Key ButtonB { get; set; } + + [ObservableProperty] + public partial Key ButtonX { get; set; } + + [ObservableProperty] + public partial Key ButtonY { get; set; } + + [ObservableProperty] + public partial Key ButtonL { get; set; } + + [ObservableProperty] + public partial Key ButtonR { get; set; } + + [ObservableProperty] + public partial Key ButtonZl { get; set; } + + [ObservableProperty] + public partial Key ButtonZr { get; set; } + + [ObservableProperty] + public partial Key LeftButtonSl { get; set; } + + [ObservableProperty] + public partial Key LeftButtonSr { get; set; } + + [ObservableProperty] + public partial Key RightButtonSl { get; set; } + + [ObservableProperty] + public partial Key RightButtonSr { get; set; } public KeyboardInputConfig(InputConfig config) { diff --git a/src/Ryujinx/UI/Models/ModModel.cs b/src/Ryujinx/UI/Models/ModModel.cs index 91804d365..307674d81 100644 --- a/src/Ryujinx/UI/Models/ModModel.cs +++ b/src/Ryujinx/UI/Models/ModModel.cs @@ -6,8 +6,8 @@ namespace Ryujinx.Ava.UI.Models { public partial class ModModel : BaseModel { - [ObservableProperty] private bool _enabled; - + [ObservableProperty] + public partial bool Enabled { get; set; } public bool InSd { get; } public string Path { get; } public string Name { get; } diff --git a/src/Ryujinx/UI/Models/ProfileImageModel.cs b/src/Ryujinx/UI/Models/ProfileImageModel.cs index f12aa7bd4..ef313d225 100644 --- a/src/Ryujinx/UI/Models/ProfileImageModel.cs +++ b/src/Ryujinx/UI/Models/ProfileImageModel.cs @@ -15,6 +15,7 @@ namespace Ryujinx.Ava.UI.Models public string Name { get; set; } public byte[] Data { get; set; } - [ObservableProperty] private SolidColorBrush _backgroundColor = new(Colors.White); + [ObservableProperty] + public partial SolidColorBrush BackgroundColor { get; set; } = new(Colors.White); } } diff --git a/src/Ryujinx/UI/Models/TempProfile.cs b/src/Ryujinx/UI/Models/TempProfile.cs index 51e86fb7f..a4a4fe32f 100644 --- a/src/Ryujinx/UI/Models/TempProfile.cs +++ b/src/Ryujinx/UI/Models/TempProfile.cs @@ -7,24 +7,26 @@ namespace Ryujinx.Ava.UI.Models { public partial class TempProfile : BaseModel { - [ObservableProperty] private byte[] _image; - [ObservableProperty] private string _name = String.Empty; - private UserId _userId; + [ObservableProperty] + public partial byte[] Image { get; set; } + + [ObservableProperty] + public partial string Name { get; set; } = string.Empty; public static uint MaxProfileNameLength => 0x20; public UserId UserId { - get => _userId; + get; set { - _userId = value; + field = value; OnPropertyChanged(); OnPropertyChanged(nameof(UserIdString)); } } - public string UserIdString => _userId.ToString(); + public string UserIdString => UserId.ToString(); public TempProfile(UserProfile profile) { diff --git a/src/Ryujinx/UI/Models/UserProfile.cs b/src/Ryujinx/UI/Models/UserProfile.cs index f14e74d07..9ebecf8d4 100644 --- a/src/Ryujinx/UI/Models/UserProfile.cs +++ b/src/Ryujinx/UI/Models/UserProfile.cs @@ -13,11 +13,20 @@ namespace Ryujinx.Ava.UI.Models { private readonly Profile _profile; private readonly NavigationDialogHost _owner; - [ObservableProperty] private byte[] _image; - [ObservableProperty] private string _name; - [ObservableProperty] private UserId _userId; - [ObservableProperty] private bool _isPointerOver; - [ObservableProperty] private IBrush _backgroundColor; + [ObservableProperty] + public partial byte[] Image { get; set; } + + [ObservableProperty] + public partial string Name { get; set; } + + [ObservableProperty] + public partial UserId UserId { get; set; } + + [ObservableProperty] + public partial bool IsPointerOver { get; set; } + + [ObservableProperty] + public partial IBrush BackgroundColor { get; set; } public UserProfile(Profile profile, NavigationDialogHost owner) { @@ -39,7 +48,7 @@ namespace Ryujinx.Ava.UI.Models private void UpdateBackground() { - Application currentApplication = Avalonia.Application.Current; + Application currentApplication = Application.Current; currentApplication.Styles.TryGetResource("ControlFillColorSecondary", currentApplication.ActualThemeVariant, out object color); if (color is not null) diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 894f06969..233c30bad 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -359,8 +359,9 @@ namespace Ryujinx.Ava.UI.ViewModels } } - [ObservableProperty] private bool _matchSystemTime; - + [ObservableProperty] + public partial bool MatchSystemTime { get; set; } + public DateTimeOffset CurrentDate { get; set; } public TimeSpan CurrentTime { get; set; } From 862a686c5e8dc62d5fab3d3785b5a57d8d38c4c9 Mon Sep 17 00:00:00 2001 From: GreemDev Date: Mon, 17 Nov 2025 00:15:58 -0600 Subject: [PATCH 03/16] UI: Improve "Show Changelog" button in the updater Now it no longer causes the dialog to disappear (which then promptly re-appears so you can click yes/no to accept/deny the update) --- src/Ryujinx/Systems/Updater/Updater.GitLab.cs | 10 +- src/Ryujinx/Systems/Updater/Updater.cs | 18 +-- src/Ryujinx/UI/Helpers/ContentDialogHelper.cs | 139 +++++++++++++++--- src/Ryujinx/UI/Helpers/ControlExtensions.cs | 40 +++++ 4 files changed, 164 insertions(+), 43 deletions(-) create mode 100644 src/Ryujinx/UI/Helpers/ControlExtensions.cs diff --git a/src/Ryujinx/Systems/Updater/Updater.GitLab.cs b/src/Ryujinx/Systems/Updater/Updater.GitLab.cs index 17f01c136..deb515797 100644 --- a/src/Ryujinx/Systems/Updater/Updater.GitLab.cs +++ b/src/Ryujinx/Systems/Updater/Updater.GitLab.cs @@ -88,14 +88,10 @@ namespace Ryujinx.Ava.Systems { if (showVersionUpToDate) { - UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( + await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - string.Empty); - - if (userResult is UserResult.Ok) - { - OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion)); - } + string.Empty, + _versionResponse.ReleaseUrlFormat.Format(currentVersion)); } Logger.Info?.Print(LogClass.Application, "Up to date."); diff --git a/src/Ryujinx/Systems/Updater/Updater.cs b/src/Ryujinx/Systems/Updater/Updater.cs index a02d3c940..bc45f8ff6 100644 --- a/src/Ryujinx/Systems/Updater/Updater.cs +++ b/src/Ryujinx/Systems/Updater/Updater.cs @@ -60,14 +60,10 @@ namespace Ryujinx.Ava.Systems { if (showVersionUpToDate) { - UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( + await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - string.Empty); - - if (userResult is UserResult.Ok) - { - OpenHelper.OpenUrl(_versionResponse.ReleaseUrlFormat.Format(currentVersion)); - } + string.Empty, + changelogUrl: _versionResponse.ReleaseUrlFormat.Format(currentVersion)); } Logger.Info?.Print(LogClass.Application, "Up to date."); @@ -106,22 +102,18 @@ namespace Ryujinx.Ava.Systems Logger.Info?.Print(LogClass.Application, $"Version found: {newVersionString.Replace("→", "->")}"); - RequestUserToUpdate: // Show a message asking the user if they want to update UserResult shouldUpdate = await ContentDialogHelper.CreateUpdaterChoiceDialog( LocaleManager.Instance[LocaleKeys.RyujinxUpdater], LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage], - newVersionString); + newVersionString, + ReleaseInformation.GetChangelogUrl(currentVersion, newVersion)); switch (shouldUpdate) { case UserResult.Yes: await UpdateRyujinx(_versionResponse.ArtifactUrl); break; - // Secondary button maps to no, which in this case is the show changelog button. - case UserResult.No: - OpenHelper.OpenUrl(ReleaseInformation.GetChangelogUrl(currentVersion, newVersion)); - goto RequestUserToUpdate; default: _running = false; break; diff --git a/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs b/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs index e8730913c..65de07e6e 100644 --- a/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs +++ b/src/Ryujinx/UI/Helpers/ContentDialogHelper.cs @@ -10,6 +10,7 @@ using FluentAvalonia.Core; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common.Helper; using Ryujinx.Common.Logging; using System; using System.Threading; @@ -102,6 +103,25 @@ namespace Ryujinx.Ava.UI.Helpers return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction); } + + public async static Task ShowTextDialogWithButton( + string title, + string primaryText, + string secondaryText, + string primaryButton, + string secondaryButton, + string closeButton, + int iconSymbol, + string buttonText, + Action onClick, + UserResult primaryButtonResult = UserResult.Ok, + ManualResetEvent deferResetEvent = null, + TypedEventHandler deferCloseAction = null) + { + Grid content = CreateTextDialogContentWithButton(primaryText, secondaryText, iconSymbol, buttonText, onClick); + + return await ShowContentDialog(title, content, primaryButton, secondaryButton, closeButton, primaryButtonResult, deferResetEvent, deferCloseAction); + } public static async Task ShowDeferredContentDialog( Window window, @@ -173,43 +193,109 @@ namespace Ryujinx.Ava.UI.Helpers MinHeight = 80, }; - SymbolIcon icon = new() + content.Children.Add(new SymbolIcon { Symbol = (Symbol)symbol, Margin = new Thickness(10), FontSize = 40, FlowDirection = FlowDirection.LeftToRight, VerticalAlignment = VerticalAlignment.Center, - }; + GridColumn = 0, + GridRow = 0, + GridRowSpan = 2 + }); - Grid.SetColumn(icon, 0); - Grid.SetRowSpan(icon, 2); - Grid.SetRow(icon, 0); - - TextBlock primaryLabel = new() + content.Children.Add(new TextBlock { Text = primaryText, Margin = new Thickness(5), TextWrapping = TextWrapping.Wrap, MaxWidth = 450, - }; + GridColumn = 1, + GridRow = 0 + }); - TextBlock secondaryLabel = new() + content.Children.Add(new TextBlock { Text = secondaryText, Margin = new Thickness(5), TextWrapping = TextWrapping.Wrap, MaxWidth = 450, + GridColumn = 1, + GridRow = 1 + }); + + return content; + } + + private static Grid CreateTextDialogContentWithButton(string primaryText, string secondaryText, int symbol, string buttonName, Action onClick) + { + Grid content = new() + { + RowDefinitions = [new(), new(), new(GridLength.Star), new()], + ColumnDefinitions = [new(GridLength.Auto), new()], + + MinHeight = 80, }; - Grid.SetColumn(primaryLabel, 1); - Grid.SetColumn(secondaryLabel, 1); - Grid.SetRow(primaryLabel, 0); - Grid.SetRow(secondaryLabel, 1); + content.Children.Add(new SymbolIcon + { + Symbol = (Symbol)symbol, + Margin = new Thickness(10), + FontSize = 40, + FlowDirection = FlowDirection.LeftToRight, + VerticalAlignment = VerticalAlignment.Center, + GridColumn = 0, + GridRow = 0, + GridRowSpan = 2 + }); - content.Children.Add(icon); - content.Children.Add(primaryLabel); - content.Children.Add(secondaryLabel); + StackPanel buttonContent = new() + { + Orientation = Orientation.Horizontal, + Spacing = 2 + }; + + buttonContent.Children.Add(new TextBlock + { + Text = buttonName, + Margin = new Thickness(2) + }); + + buttonContent.Children.Add(new SymbolIcon + { + FlowDirection = FlowDirection.LeftToRight, + Symbol = Symbol.Open + }); + + content.Children.Add(new TextBlock + { + Text = primaryText, + Margin = new Thickness(5), + TextWrapping = TextWrapping.Wrap, + MaxWidth = 450, + GridColumn = 1, + GridRow = 0 + }); + + content.Children.Add(new TextBlock + { + Text = secondaryText, + Margin = new Thickness(5), + TextWrapping = TextWrapping.Wrap, + MaxWidth = 450, + GridColumn = 1, + GridRow = 1 + }); + + content.Children.Add(new Button + { + Content = buttonContent, + HorizontalAlignment = HorizontalAlignment.Center, + Command = Commands.Create(onClick), + GridRow = 2, + GridColumnSpan = 2, + }); return content; } @@ -282,15 +368,20 @@ namespace Ryujinx.Ava.UI.Helpers LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Important); - internal static async Task CreateUpdaterUpToDateInfoDialog(string primary, string secondaryText) - => await ShowTextDialog( + internal static async Task CreateUpdaterUpToDateInfoDialog(string primary, string secondaryText, + string changelogUrl) + { + await ShowTextDialogWithButton( LocaleManager.Instance[LocaleKeys.DialogUpdaterTitle], primary, secondaryText, - LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage], + string.Empty, string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], - (int)Symbol.Important); + (int)Symbol.Important, + LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage], + () => OpenHelper.OpenUrl(changelogUrl)); + } internal static async Task CreateWarningDialog(string primary, string secondaryText) => await ShowTextDialog( @@ -340,7 +431,7 @@ namespace Ryujinx.Ava.UI.Helpers return response == UserResult.Yes; } - internal static async Task CreateUpdaterChoiceDialog(string title, string primary, string secondaryText) + internal static async Task CreateUpdaterChoiceDialog(string title, string primary, string secondaryText, string changelogUrl) { if (_isChoiceDialogOpen) { @@ -349,14 +440,16 @@ namespace Ryujinx.Ava.UI.Helpers _isChoiceDialogOpen = true; - UserResult response = await ShowTextDialog( + UserResult response = await ShowTextDialogWithButton( title, primary, secondaryText, LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage], + string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogNo], (int)Symbol.Help, + LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage], + () => OpenHelper.OpenUrl(changelogUrl), UserResult.Yes); _isChoiceDialogOpen = false; diff --git a/src/Ryujinx/UI/Helpers/ControlExtensions.cs b/src/Ryujinx/UI/Helpers/ControlExtensions.cs new file mode 100644 index 000000000..734128757 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/ControlExtensions.cs @@ -0,0 +1,40 @@ +using Avalonia.Controls; + +namespace Ryujinx.Ava.UI.Helpers +{ + public static class ControlExtensions + { + extension(Control ctrl) + { + public int GridRow + { + get => Grid.GetRow(ctrl); + set => Grid.SetRow(ctrl, value); + } + + public int GridColumn + { + get => Grid.GetColumn(ctrl); + set => Grid.SetColumn(ctrl, value); + } + + public int GridRowSpan + { + get => Grid.GetRowSpan(ctrl); + set => Grid.SetRowSpan(ctrl, value); + } + + public int GridColumnSpan + { + get => Grid.GetColumnSpan(ctrl); + set => Grid.SetColumnSpan(ctrl, value); + } + + public bool GridIsSharedSizeScope + { + get => Grid.GetIsSharedSizeScope(ctrl); + set => Grid.SetIsSharedSizeScope(ctrl, value); + } + } + } +} From 6c7dc7646b62196a50a5dbb0e7f61fe4eaa3fb55 Mon Sep 17 00:00:00 2001 From: Babib3l Date: Tue, 18 Nov 2025 05:34:54 +0100 Subject: [PATCH 04/16] Translation updates (ryubing/ryujinx!221) See merge request ryubing/ryujinx!221 --- assets/locales.json | 48 ++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/assets/locales.json b/assets/locales.json index 9e9b070f6..8899bf692 100644 --- a/assets/locales.json +++ b/assets/locales.json @@ -225,7 +225,7 @@ "el_GR": "Χωρίς Ελέγχους (γρηγορότερο, μη ασφαλές)", "en_US": "Host Unchecked (Fastest, Unsafe)", "es_ES": "Host Sin Verificación (Más rápido, Inseguro)", - "fr_FR": "Hôte Non Vérifié (plus rapide, non sécurisé)", + "fr_FR": "Hôte Non Vérifié (Plus Rapide, Non Sécurisé)", "he_IL": "מארח לא מבוקר (המהיר ביותר, לא בטוח)", "it_IT": "Host senza verifica (più veloce, non sicura)", "ja_JP": "ホスト, チェックなし (最高速, 安全でない)", @@ -3350,7 +3350,7 @@ "el_GR": "Ερώτημα", "en_US": "Prompt", "es_ES": "Al Inicio", - "fr_FR": "Demande", + "fr_FR": "Demander", "he_IL": "הודעה", "it_IT": "Domanda", "ja_JP": "プロンプト", @@ -4800,7 +4800,7 @@ "el_GR": "Backend Ήχου:", "en_US": "Audio Backend:", "es_ES": "Motor de Audio:", - "fr_FR": "Bibliothèque Audio :", + "fr_FR": "Moteur Audio :", "he_IL": "אחראי שמע:", "it_IT": "Backend audio:", "ja_JP": "音声バックエンド:", @@ -5700,7 +5700,7 @@ "el_GR": "Έκταση σε όλο το παράθυρο", "en_US": "Stretch to Fit Window", "es_ES": "Estirar a la Ventana", - "fr_FR": "Ajuster à la Taille de la Fenêtre", + "fr_FR": "Adapter à la Taille de la Fenêtre", "he_IL": "מתח לגודל חלון", "it_IT": "Adatta alla finestra", "ja_JP": "ウインドウサイズに合わせる", @@ -5750,7 +5750,7 @@ "el_GR": "Τοποθεσία Shaders Γραφικών:", "en_US": "Graphics Shader Dump Path:", "es_ES": "Directorio de Volcado de Sombreadores:", - "fr_FR": "Chemin du Dossier de Copie des Shaders :", + "fr_FR": "Chemin du dump des shaders graphiques :", "he_IL": "", "it_IT": "Percorso di dump degli shader:", "ja_JP": "グラフィックス シェーダー ダンプパス:", @@ -15000,7 +15000,7 @@ "el_GR": "Πολυνηματική Επεξεργασία Γραφικών:", "en_US": "Graphics Backend Multithreading:", "es_ES": "Multihilado del Motor Gráfico:", - "fr_FR": "Interface graphique multithread :", + "fr_FR": "Interface Graphique Multithread :", "he_IL": "אחראי גרפיקה רב-תהליכי:", "it_IT": "Multithreading del backend grafico:", "ja_JP": "グラフィックスバックエンドのマルチスレッド実行:", @@ -16598,9 +16598,9 @@ "ar_SA": "", "de_DE": "Diese Option überspringt den Dialog 'Benutzerprofile verwalten' während des Spiels und verwendet ein voreingestelltes Profil.\n\nDie Profilumschaltung finden Sie unter 'Einstellungen' - 'Benutzerprofile verwalten'. Wählen Sie das gewünschte Profil aus, bevor Sie das Spiel laden.", "el_GR": "Αυτή η επιλογή παρακάμπτει το παράθυρο διαλόγου 'Διαχειριστής Προφίλ Χρήστη' κατά τη διάρκεια του παιχνιδιού, χρησιμοποιώντας ένα προεπιλεγμένο προφίλ.\n\nΗ εναλλαγή προφίλ βρίσκεται στις 'Ρυθμίσεις' - 'Διαχειριστής Προφίλ Χρήστη'. Επιλέξτε το επιθυμητό προφίλ πριν φορτώσετε το παιχνίδι.", - "en_US": "This option skips the 'Manage User Profiles' dialog during gameplay, using a pre-selected profile.\n\nProfile switching is found in 'Settings' - 'Manager User Profiles'. Select the desired profile before loading the game.", - "es_ES": "Esta opción omite el diálogo de 'Gestionar perfiles de usuario' durante el juego, utilizando un perfil preseleccionado.\n\nEl cambio de perfil se encuentra en 'Configuración' - 'Gestionar perfiles de usuario'. Seleccione el perfil deseado antes de cargar el juego.", - "fr_FR": "Cette option permet d'éviter le dialogue du 'Gérer les profils d'utilisateurs' pendant le jeu, en utilisant un profil pré-sélectionné.\n\nLa sélection du profil se trouve dans 'Paramètres' - 'Gérer les profils d'utilisateurs'. Sélectionnez le profil souhaité avant de lancer le jeu.", + "en_US": "This option skips the 'Manage User Profiles' dialog during gameplay, using a pre-selected profile.\n\nProfile switching is found in 'Options' - 'User Profiles'. Select the desired profile before loading the game.", + "es_ES": "Esta opción omite el diálogo de 'Gestionar perfiles de usuario' durante el juego, utilizando un perfil preseleccionado.\n\nEl cambio de perfil se encuentra en 'Opciones' - 'Perfiles de Usuario'. Seleccione el perfil deseado antes de cargar el juego.", + "fr_FR": "Cette option permet de passer le dialogue 'Gérer les profils d'utilisateurs' pendant le jeu, en utilisant un profil pré-sélectionné.\n\nLa sélection du profil se trouve dans 'Options' - 'Profils d'Utilisateurs'. Sélectionnez le profil souhaité avant de lancer le jeu.", "he_IL": "", "it_IT": "Questa opzione salta la finestra di dialogo 'Gestisci i profili utente' durante il gioco, utilizzando un profilo pre-selezionato.\n\nIl cambio del profilo si trova in 'Impostazioni' - 'Gestisci i profili utente'. Seleziona il profilo desiderato prima di caricare il gioco.", "ja_JP": "このオプションは、ゲームプレイ中に「ユーザプロファイルを管理」ダイアログをスキップし、事前に選択されたプロファイルを使用します。\n\nプロファイルの切り替えは、「設定」-「ユーザプロファイルを管理」で見つけることができます。ゲームのロード前に目的のプロファイルをを選択してください。", @@ -22520,26 +22520,26 @@ { "ID": "MultiplayerModeTooltip", "Translations": { - "ar_SA": "تغيير وضع LDN متعدد اللاعبين.\n\nسوف يقوم LdnMitm بتعديل وظيفة اللعب المحلية/اللاسلكية المحلية في الألعاب لتعمل كما لو كانت شبكة LAN، مما يسمح باتصالات الشبكة المحلية نفسها مع محاكيات ريوجينكس الأخرى وأجهزة نينتندو سويتش المخترقة التي تم تثبيت وحدة ldn_mitm عليها.\n\nيتطلب وضع اللاعبين المتعددين أن يكون جميع اللاعبين على نفس إصدار اللعبة (على سبيل المثال، يتعذر على الإصدار 13.0.1 من سوبر سماش برذرز ألتميت الاتصال بالإصدار 13.0.0).\n\nاتركه معطلا إذا لم تكن متأكدا.", - "de_DE": "Ändert den LDN-Mehrspielermodus.\n\nLdnMitm ändert die lokale drahtlose/lokale Spielfunktionalität in Spielen so, dass sie wie ein LAN funktioniert und lokale, netzwerkgleiche Verbindungen mit anderen Ryujinx-Instanzen und gehackten Nintendo Switch-Konsolen ermöglicht, auf denen das ldn_mitm-Modul installiert ist.\n\nMultiplayer erfordert, dass alle Spieler die gleiche Spielversion verwenden (d.h. Super Smash Bros. Ultimate v13.0.1 kann sich nicht mit v13.0.0 verbinden).\n\nIm Zweifelsfall auf DISABLED lassen.", + "ar_SA": "تغيير وضع LDN متعدد اللاعبين.\n\nسوف يقوم ldn_mitm بتعديل وظيفة اللعب المحلية/اللاسلكية المحلية في الألعاب لتعمل كما لو كانت شبكة LAN، مما يسمح باتصالات الشبكة المحلية نفسها مع محاكيات ريوجينكس الأخرى وأجهزة نينتندو سويتش المخترقة التي تم تثبيت وحدة ldn_mitm عليها.\n\nيتطلب وضع اللاعبين المتعددين أن يكون جميع اللاعبين على نفس إصدار اللعبة (على سبيل المثال، يتعذر على الإصدار 13.0.1 من سوبر سماش برذرز ألتميت الاتصال بالإصدار 13.0.0).\n\nاتركه معطلا إذا لم تكن متأكدا.", + "de_DE": "Ändert den LDN-Mehrspielermodus.\n\nldn_mitm ändert die lokale drahtlose/lokale Spielfunktionalität in Spielen so, dass sie wie ein LAN funktioniert und lokale, netzwerkgleiche Verbindungen mit anderen Ryujinx-Instanzen und gehackten Nintendo Switch-Konsolen ermöglicht, auf denen das ldn_mitm-Modul installiert ist.\n\nMultiplayer erfordert, dass alle Spieler die gleiche Spielversion verwenden (d.h. Super Smash Bros. Ultimate v13.0.1 kann sich nicht mit v13.0.0 verbinden).\n\nIm Zweifelsfall auf DISABLED lassen.", "el_GR": "", - "en_US": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", - "es_ES": "Cambiar modo LDN multijugador.\n\nLdnMitm modificará la funcionalidad local de juego inalámbrico para funcionar como si fuera LAN, permitiendo locales conexiones de la misma red con otras instancias de Ryujinx y consolas hackeadas de Nintendo Switch que tienen instalado el módulo ldn_mitm.\n\nMultijugador requiere que todos los jugadores estén en la misma versión del juego (por ejemplo, Super Smash Bros. Ultimate v13.0.1 no se puede conectar a v13.0.0).\n\nDejar DESACTIVADO si no está seguro.", - "fr_FR": "Change le mode multijoueur LDN.\n\nLdnMitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", + "en_US": "Change LDN multiplayer mode.\n\nldn_mitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.", + "es_ES": "Cambiar modo LDN multijugador.\n\nldn_mitm modificará la funcionalidad local de juego inalámbrico para funcionar como si fuera LAN, permitiendo locales conexiones de la misma red con otras instancias de Ryujinx y consolas hackeadas de Nintendo Switch que tienen instalado el módulo ldn_mitm.\n\nMultijugador requiere que todos los jugadores estén en la misma versión del juego (por ejemplo, Super Smash Bros. Ultimate v13.0.1 no se puede conectar a v13.0.0).\n\nDejar DESACTIVADO si no está seguro.", + "fr_FR": "Change le mode multijoueur LDN.\n\nldn_mitm modifiera la fonctionnalité de jeu sans fil local/jeu local dans les jeux pour fonctionner comme s'il s'agissait d'un LAN, permettant des connexions locales sur le même réseau avec d'autres instances de Ryujinx et des consoles Nintendo Switch piratées ayant le module ldn_mitm installé.\n\nLe multijoueur nécessite que tous les joueurs soient sur la même version du jeu (par exemple, Super Smash Bros. Ultimate v13.0.1 ne peut pas se connecter à v13.0.0).\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", "he_IL": "", - "it_IT": "Cambia la modalità multigiocatore LDN.\n\nLdnMitm modificherà la funzionalità locale wireless/local play nei giochi per funzionare come se fosse in modalità LAN, consentendo connessioni locali sulla stessa rete con altre istanze di Ryujinx e console Nintendo Switch modificate che hanno il modulo ldn_mitm installato.\n\nLa modalità multigiocatore richiede che tutti i giocatori usino la stessa versione del gioco (es. Super Smash Bros. Ultimate v13.0.1 non può connettersi con la v13.0.0).\n\nNel dubbio, lascia l'opzione su Disabilitato.", + "it_IT": "Cambia la modalità multigiocatore LDN.\n\nldn_mitm modificherà la funzionalità locale wireless/local play nei giochi per funzionare come se fosse in modalità LAN, consentendo connessioni locali sulla stessa rete con altre istanze di Ryujinx e console Nintendo Switch modificate che hanno il modulo ldn_mitm installato.\n\nLa modalità multigiocatore richiede che tutti i giocatori usino la stessa versione del gioco (es. Super Smash Bros. Ultimate v13.0.1 non può connettersi con la v13.0.0).\n\nNel dubbio, lascia l'opzione su Disabilitato.", "ja_JP": "LDNマルチプレイヤーモードを変更します.\n\nldn_mitmモジュールがインストールされた, 他のRyujinxインスタンスや,ハックされたNintendo Switchコンソールとのローカル/同一ネットワーク接続を可能にします.\n\nマルチプレイでは, すべてのプレイヤーが同じゲームバージョンである必要があります(例:Super Smash Bros. Ultimate v13.0.1はv13.0.0に接続できません).\n\n不明な場合は「無効」のままにしてください.", - "ko_KR": "LDN 멀티플레이어 모드를 변경합니다.\n\nLdnMitm은 게임의 로컬 무선/로컬 플레이 기능을 LAN처럼 작동하도록 수정하여 다른 Ryujinx 인스턴스나 ldn_mitm 모듈이 설치된 해킹된 Nintendo Switch 콘솔과 로컬, 동일 네트워크 연결이 가능합니다.\n\n멀티플레이어는 모든 플레이어가 동일한 게임 버전을 사용해야 합니다(예 : 슈퍼 스매시브라더스 얼티밋 v13.0.1은 v13.0.0에 연결할 수 없음).\n\n모르면 비활성화 상태로 두세요.", - "no_NO": "Endre LDN flerspillermodus.\n\nLdnMitm vil endre lokal trådløst/lokal spillfunksjonalitet i spill som skal fungere som om den var LAN, noe som tillater lokal, samme nettverk forbindelser med andre Ryujinx instanser og hacket Nintendo Switch konsoller som har installert ldn_mitm-modulen.\n\nFlerspiller krever at alle spillerne er på samme versjon (dvs. Super Smash Bros. Ultimat v13.0.1 kan ikke koble til v13.0.0).\n\nForlat DEAKTIVERT hvis usikker.", + "ko_KR": "LDN 멀티플레이어 모드를 변경합니다.\n\nldn_mitm은 게임의 로컬 무선/로컬 플레이 기능을 LAN처럼 작동하도록 수정하여 다른 Ryujinx 인스턴스나 ldn_mitm 모듈이 설치된 해킹된 Nintendo Switch 콘솔과 로컬, 동일 네트워크 연결이 가능합니다.\n\n멀티플레이어는 모든 플레이어가 동일한 게임 버전을 사용해야 합니다(예 : 슈퍼 스매시브라더스 얼티밋 v13.0.1은 v13.0.0에 연결할 수 없음).\n\n모르면 비활성화 상태로 두세요.", + "no_NO": "Endre LDN flerspillermodus.\n\nldn_mitm vil endre lokal trådløst/lokal spillfunksjonalitet i spill som skal fungere som om den var LAN, noe som tillater lokal, samme nettverk forbindelser med andre Ryujinx instanser og hacket Nintendo Switch konsoller som har installert ldn_mitm-modulen.\n\nFlerspiller krever at alle spillerne er på samme versjon (dvs. Super Smash Bros. Ultimat v13.0.1 kan ikke koble til v13.0.0).\n\nForlat DEAKTIVERT hvis usikker.", "pl_PL": "", - "pt_BR": "Alterar o modo multiplayer LDN.\n\nLdnMitm modificará a funcionalidade de jogo sem fio/local nos jogos para funcionar como se fosse LAN, permitindo conexões locais, na mesma rede, com outras instâncias do Ryujinx e consoles Nintendo Switch hackeados que possuem o módulo ldn_mitm instalado.\n\nO multiplayer exige que todos os jogadores estejam na mesma versão do jogo (ex.: Super Smash Bros. Ultimate v13.0.1 não consegue se conectar à v13.0.0).\n\nDeixe DESATIVADO se estiver em dúvida.", - "ru_RU": "Меняет многопользовательский режим LDN.\n\nLdnMitm модифицирует функциональность локальной беспроводной игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить выключенным.", - "sv_SE": "Ändra LDN-flerspelarläge\n\nLdnMitm kommer att ändra lokal funktionalitet för trådlös/lokalt spel att fungera som om det vore ett LAN, vilket ger stöd för anslutningar med local och same-network med andra Ryujinx-instanser och hackade Nintendo Switch-konsoller som har modulen ldn_mitm installerad.\n\nFlerspelare kräver att alla spelare har samma spelversion (t.ex. Super Smash Bros. Ultimate v13.0.1 kan inte ansluta till v13.0.0).\n\nLämna INAKTIVERAD om du är osäker.", - "th_TH": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nLdnMitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ", + "pt_BR": "Alterar o modo multiplayer LDN.\n\nldn_mitm modificará a funcionalidade de jogo sem fio/local nos jogos para funcionar como se fosse LAN, permitindo conexões locais, na mesma rede, com outras instâncias do Ryujinx e consoles Nintendo Switch hackeados que possuem o módulo ldn_mitm instalado.\n\nO multiplayer exige que todos os jogadores estejam na mesma versão do jogo (ex.: Super Smash Bros. Ultimate v13.0.1 não consegue se conectar à v13.0.0).\n\nDeixe DESATIVADO se estiver em dúvida.", + "ru_RU": "Меняет многопользовательский режим LDN.\n\nldn_mitm модифицирует функциональность локальной беспроводной игры на одном устройстве в играх, позволяя играть с другими пользователями Ryujinx или взломанными консолями Nintendo Switch с установленным модулем ldn_mitm, находящимися в одной локальной сети друг с другом.\n\nМногопользовательская игра требует наличия у всех игроков одной и той же версии игры (т.е. Super Smash Bros. Ultimate v13.0.1 не может подключиться к v13.0.0).\n\nРекомендуется оставить выключенным.", + "sv_SE": "Ändra LDN-flerspelarläge\n\nldn_mitm kommer att ändra lokal funktionalitet för trådlös/lokalt spel att fungera som om det vore ett LAN, vilket ger stöd för anslutningar med local och same-network med andra Ryujinx-instanser och hackade Nintendo Switch-konsoller som har modulen ldn_mitm installerad.\n\nFlerspelare kräver att alla spelare har samma spelversion (t.ex. Super Smash Bros. Ultimate v13.0.1 kan inte ansluta till v13.0.0).\n\nLämna INAKTIVERAD om du är osäker.", + "th_TH": "เปลี่ยนโหมดผู้เล่นหลายคนของ LDN\n\nldn_mitm จะปรับเปลี่ยนฟังก์ชันการเล่นแบบไร้สาย/ภายใน จะให้เกมทำงานเหมือนกับว่าเป็น LAN ช่วยให้สามารถเชื่อมต่อภายในเครือข่ายเดียวกันกับอินสแตนซ์ Ryujinx อื่น ๆ และคอนโซล Nintendo Switch ที่ถูกแฮ็กซึ่งมีโมดูล ldn_mitm ติดตั้งอยู่\n\nผู้เล่นหลายคนต้องการให้ผู้เล่นทุกคนอยู่ในเกมเวอร์ชันเดียวกัน (เช่น Super Smash Bros. Ultimate v13.0.1 ไม่สามารถเชื่อมต่อกับ v13.0.0)\n\nปล่อยให้ปิดการใช้งานหากไม่แน่ใจ", "tr_TR": "", - "uk_UA": "Змінити LDN мультиплеєру.\n\nLdnMitm змінить функціонал бездротової/локальної гри в іграх, щоб вони працювали так, ніби це LAN, що дозволяє локальні підключення в тій самій мережі з іншими екземплярами Ryujinx та хакнутими консолями Nintendo Switch, які мають встановлений модуль ldn_mitm.\n\nМультиплеєр вимагає, щоб усі гравці були на одній і тій же версії гри (наприклад Super Smash Bros. Ultimate v13.0.1 не зможе під'єднатися до v13.0.0).\n\nЗалиште на \"Вимкнено\", якщо не впевнені.", + "uk_UA": "Змінити LDN мультиплеєру.\n\nldn_mitm змінить функціонал бездротової/локальної гри в іграх, щоб вони працювали так, ніби це LAN, що дозволяє локальні підключення в тій самій мережі з іншими екземплярами Ryujinx та хакнутими консолями Nintendo Switch, які мають встановлений модуль ldn_mitm.\n\nМультиплеєр вимагає, щоб усі гравці були на одній і тій же версії гри (наприклад Super Smash Bros. Ultimate v13.0.1 не зможе під'єднатися до v13.0.0).\n\nЗалиште на \"Вимкнено\", якщо не впевнені.", "zh_CN": "修改 LDN 多人联机游玩模式。\n\nldn_mitm 联机插件将修改游戏中的本地无线和本地游玩功能,使其表现得像局域网一样,允许和其他安装了 ldn_mitm 插件的 Ryujinx 模拟器和破解的任天堂 Switch 主机在同一网络下进行本地连接,实现多人联机游玩。\n\n多人联机游玩要求所有玩家必须运行相同的游戏版本(例如,游戏版本 v13.0.1 无法与 v13.0.0 联机)。\n\n如果不确定,请保持为“禁用”。", - "zh_TW": "變更 LDN 多人遊戲模式。\n\nLdnMitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。" + "zh_TW": "變更 LDN 多人遊戲模式。\n\nldn_mitm 將修改遊戲中的本機無線/本機遊戲功能,使其如同區域網路一樣執行,允許與其他安裝了 ldn_mitm 模組的 Ryujinx 實例和已破解的 Nintendo Switch 遊戲機進行本機同網路連線。\n\n多人遊戲要求所有玩家使用相同的遊戲版本 (例如,Super Smash Bros. Ultimate v13.0.1 無法連接 v13.0.0)。\n\n如果不確定,請保持 Disabled (停用) 狀態。" } }, { @@ -22800,7 +22800,7 @@ "el_GR": "", "en_US": "Generates a new passphrase, which can be shared with other players.", "es_ES": "Genera una nueva frase de contraseña, que puede ser compartida con otros jugadores.", - "fr_FR": "Génére un nouveau mot de passe, qui peut être partagé avec les autres.", + "fr_FR": "Génère un nouveau mot de passe, qui peut être partagé avec les autres.", "he_IL": "", "it_IT": "Genera una nuova passphrase, che può essere condivisa con altri giocatori.", "ja_JP": "", From e1c829f91d4755c940dbbff8fe0fb37c9956c096 Mon Sep 17 00:00:00 2001 From: GreemDev Date: Fri, 14 Nov 2025 21:55:08 -0600 Subject: [PATCH 05/16] We no longer offer a dedicated headless build. This script could have been deleted like a year ago lol --- .../macos/create_macos_build_headless.sh | 124 ------------------ 1 file changed, 124 deletions(-) delete mode 100755 distribution/macos/create_macos_build_headless.sh diff --git a/distribution/macos/create_macos_build_headless.sh b/distribution/macos/create_macos_build_headless.sh deleted file mode 100755 index e7843f438..000000000 --- a/distribution/macos/create_macos_build_headless.sh +++ /dev/null @@ -1,124 +0,0 @@ -#!/bin/bash - -set -e - -if [ "$#" -lt 8 ]; then - echo "usage " - exit 1 -fi - -mkdir -p "$1" -mkdir -p "$2" -mkdir -p "$3" - -BASE_DIR=$(readlink -f "$1") -TEMP_DIRECTORY=$(readlink -f "$2") -OUTPUT_DIRECTORY=$(readlink -f "$3") -ENTITLEMENTS_FILE_PATH=$(readlink -f "$4") -VERSION=$5 -SOURCE_REVISION_ID=$6 -CONFIGURATION=$7 -CANARY=$8 - -if [[ "$(uname)" == "Darwin" ]]; then - echo "Clearing xattr on all dot undercsore files" - find "$BASE_DIR" -type f -name "._*" -exec sh -c ' - for f; do - dir=$(dirname "$f") - base=$(basename "$f") - orig="$dir/${base#._}" - [ -f "$orig" ] && xattr -c "$orig" || true - done - ' sh {} + -fi - -if [ "$CANARY" == "1" ]; then - RELEASE_TAR_FILE_NAME=nogui-ryujinx-canary-$VERSION-macos_universal.tar -elif [ "$VERSION" == "1.1.0" ]; then - RELEASE_TAR_FILE_NAME=nogui-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar -else - RELEASE_TAR_FILE_NAME=nogui-ryujinx-$VERSION-macos_universal.tar -fi - -ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64" -X64_OUTPUT="$TEMP_DIRECTORY/publish_x64" -UNIVERSAL_OUTPUT="$OUTPUT_DIRECTORY/publish" -EXECUTABLE_SUB_PATH=Ryujinx.Headless.SDL3 - -rm -rf "$TEMP_DIRECTORY" -mkdir -p "$TEMP_DIRECTORY" - -DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS) - -dotnet restore -dotnet build -c "$CONFIGURATION" src/Ryujinx.Headless.SDL3 -dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL3 -dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL3 - -# Get rid of the support library for ARMeilleure for x64 (that's only for arm64) -rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib" - -# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant -# TODO: remove this once done -rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib" - -rm -rf "$OUTPUT_DIRECTORY" -mkdir -p "$OUTPUT_DIRECTORY" - -# Let's copy one of the two different outputs and remove the executable -cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT" -rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" - -# Make its libraries universal -python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.dylib" - -if ! [ -x "$(command -v lipo)" ]; -then - if ! [ -x "$(command -v llvm-lipo-17)" ]; - then - LIPO=llvm-lipo - else - LIPO=llvm-lipo-17 - fi -else - LIPO=lipo -fi - -# Make the executable universal -$LIPO "$ARM64_OUTPUT/$EXECUTABLE_SUB_PATH" "$X64_OUTPUT/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" -create - -# Now sign it -if ! [ -x "$(command -v codesign)" ]; -then - if ! [ -x "$(command -v rcodesign)" ]; - then - echo "Cannot find rcodesign on your system, please install rcodesign." - exit 1 - fi - - # NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes. - # cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign" - echo "Using rcodesign for ad-hoc signing" - for FILE in "$UNIVERSAL_OUTPUT"/*; do - if [[ $(file "$FILE") == *"Mach-O"* ]]; then - rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$FILE" - fi - done -else - echo "Using codesign for ad-hoc signing" - for FILE in "$UNIVERSAL_OUTPUT"/*; do - if [[ $(file "$FILE") == *"Mach-O"* ]]; then - codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$FILE" - fi - done -fi - -echo "Creating archive" -pushd "$OUTPUT_DIRECTORY" -tar --exclude "publish/Ryujinx.Headless.SDL3" -cvf "$RELEASE_TAR_FILE_NAME" publish 1> /dev/null -python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "publish/Ryujinx.Headless.SDL3" "publish/Ryujinx.Headless.SDL3" -gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz" -rm "$RELEASE_TAR_FILE_NAME" -popd - -echo "Done" From 6126e3dc1e1e324735f21372335c083fcf3cce51 Mon Sep 17 00:00:00 2001 From: Goodfeat Date: Wed, 19 Nov 2025 20:33:35 -0600 Subject: [PATCH 06/16] fix: UI: Custom setting was reset to global when changed during gameplay. (ryubing/ryujinx!164) See merge request ryubing/ryujinx!164 --- src/Ryujinx/Program.cs | 24 ++++++++++------- .../UI/ViewModels/SettingsViewModel.cs | 26 +++++++++---------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index abab3daca..4904b8464 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -196,22 +196,26 @@ namespace Ryujinx.Ava return gameDir; } - public static void ReloadConfig() + public static void ReloadConfig(bool isRunGameWithCustomConfig = false) { string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); - // Now load the configuration as the other subsystems are now registered - if (File.Exists(localConfigurationPath)) - { - ConfigurationPath = localConfigurationPath; - } - else if (File.Exists(appDataConfigurationPath)) - { - ConfigurationPath = appDataConfigurationPath; - } + if (!isRunGameWithCustomConfig) // To return settings from the game folder if the user configuration exists + { + // Now load the configuration as the other subsystems are now registered + if (File.Exists(localConfigurationPath)) + { + ConfigurationPath = localConfigurationPath; + } + else if (File.Exists(appDataConfigurationPath)) + { + ConfigurationPath = appDataConfigurationPath; + } + } + if (ConfigurationPath == null) { // No configuration, we load the default values and save it to disk diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 233c30bad..d5d9b8218 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -88,6 +88,11 @@ namespace Ryujinx.Ava.UI.ViewModels get; } + public bool IsCustomConfig + { + get; + } + public bool IsGameTitleNotNull => !string.IsNullOrEmpty(GameTitle); public double PanelOpacity => IsGameTitleNotNull ? 0.5 : 1; @@ -459,7 +464,7 @@ namespace Ryujinx.Ava.UI.ViewModels using MemoryStream ms = new(gameIconData); GameIcon = new Bitmap(ms); } - + IsCustomConfig = customConfig; IsGameRunning = gameRunning; GamePath = gamePath; GameTitle = gameName; @@ -869,16 +874,11 @@ namespace Ryujinx.Ava.UI.ViewModels GameListNeedsRefresh = false; } - private static void RevertIfNotSaved() + private static void RevertIfNotSaved(bool isCustomConfig = false, bool isGameRunning = false) { - /* - maybe this is an unnecessary check(all options need to be tested) - if (string.IsNullOrEmpty(Program.GlobalConfigurationPath)) - { - Program.ReloadConfig(); - } - */ - Program.ReloadConfig(); + // Restores settings for a custom configuration during a game, if the condition is met. + // If the condition is not met (parameter is false), restores global (default) configuration instead. + Program.ReloadConfig(isCustomConfig && isGameRunning); } public void ApplyButton() @@ -895,14 +895,14 @@ namespace Ryujinx.Ava.UI.ViewModels File.Delete(gameDir); } - RevertIfNotSaved(); + RevertIfNotSaved(IsCustomConfig, IsGameRunning); CloseWindow?.Invoke(); } public void SaveUserConfig() { SaveSettings(); - RevertIfNotSaved(); // Revert global configuration after saving user configuration + RevertIfNotSaved(IsCustomConfig, IsGameRunning); // Revert global or custom configuration after saving user configuration CloseWindow?.Invoke(); } @@ -934,7 +934,7 @@ namespace Ryujinx.Ava.UI.ViewModels public void CancelButton() { - RevertIfNotSaved(); + RevertIfNotSaved(IsCustomConfig, IsGameRunning); CloseWindow?.Invoke(); } } From 39f55b2af3603bbbda9d7f9bb60f4d0a9ad8cb3b Mon Sep 17 00:00:00 2001 From: KeatonTheBot Date: Wed, 19 Nov 2025 20:50:23 -0600 Subject: [PATCH 07/16] cpu: Protect against stack overflow caused by deep recursion (ryubing/ryujinx!111) See merge request ryubing/ryujinx!111 --- .../Instructions/InstEmitException.cs | 4 +- .../Instructions/InstEmitException32.cs | 2 +- src/ARMeilleure/Instructions/InstEmitFlow.cs | 2 +- .../Instructions/InstEmitFlowHelper.cs | 60 +++++++++++++++++-- src/ARMeilleure/Optimizations.cs | 1 + src/ARMeilleure/State/ExecutionContext.cs | 5 ++ src/ARMeilleure/State/NativeContext.cs | 8 +++ src/ARMeilleure/Translation/PTC/Ptc.cs | 2 +- src/ARMeilleure/Translation/Translator.cs | 2 + .../Translation/TranslatorStubs.cs | 8 +++ 10 files changed, 83 insertions(+), 11 deletions(-) diff --git a/src/ARMeilleure/Instructions/InstEmitException.cs b/src/ARMeilleure/Instructions/InstEmitException.cs index d30fb2fbd..a91716c64 100644 --- a/src/ARMeilleure/Instructions/InstEmitException.cs +++ b/src/ARMeilleure/Instructions/InstEmitException.cs @@ -19,7 +19,7 @@ namespace ARMeilleure.Instructions context.LoadFromContext(); - context.Return(Const(op.Address)); + InstEmitFlowHelper.EmitReturn(context, Const(op.Address)); } public static void Svc(ArmEmitterContext context) @@ -49,7 +49,7 @@ namespace ARMeilleure.Instructions context.LoadFromContext(); - context.Return(Const(op.Address)); + InstEmitFlowHelper.EmitReturn(context, Const(op.Address)); } } } diff --git a/src/ARMeilleure/Instructions/InstEmitException32.cs b/src/ARMeilleure/Instructions/InstEmitException32.cs index 57af1522b..e5bad56ef 100644 --- a/src/ARMeilleure/Instructions/InstEmitException32.cs +++ b/src/ARMeilleure/Instructions/InstEmitException32.cs @@ -33,7 +33,7 @@ namespace ARMeilleure.Instructions context.LoadFromContext(); - context.Return(Const(context.CurrOp.Address)); + InstEmitFlowHelper.EmitReturn(context, Const(context.CurrOp.Address)); } } } diff --git a/src/ARMeilleure/Instructions/InstEmitFlow.cs b/src/ARMeilleure/Instructions/InstEmitFlow.cs index a986bf66f..cb214d3d5 100644 --- a/src/ARMeilleure/Instructions/InstEmitFlow.cs +++ b/src/ARMeilleure/Instructions/InstEmitFlow.cs @@ -66,7 +66,7 @@ namespace ARMeilleure.Instructions { OpCodeBReg op = (OpCodeBReg)context.CurrOp; - context.Return(GetIntOrZR(context, op.Rn)); + EmitReturn(context, GetIntOrZR(context, op.Rn)); } public static void Tbnz(ArmEmitterContext context) => EmitTb(context, onNotZero: true); diff --git a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs index d0871b29f..74866f982 100644 --- a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs +++ b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs @@ -13,6 +13,10 @@ namespace ARMeilleure.Instructions { static class InstEmitFlowHelper { + // How many calls we can have in our call stack before we give up and return to the dispatcher. + // This prevents stack overflows caused by deep recursive calls. + private const int MaxCallDepth = 200; + public static void EmitCondBranch(ArmEmitterContext context, Operand target, Condition cond) { if (cond != Condition.Al) @@ -182,12 +186,7 @@ namespace ARMeilleure.Instructions { if (isReturn || context.IsSingleStep) { - if (target.Type == OperandType.I32) - { - target = context.ZeroExtend32(OperandType.I64, target); - } - - context.Return(target); + EmitReturn(context, target); } else { @@ -195,6 +194,19 @@ namespace ARMeilleure.Instructions } } + public static void EmitReturn(ArmEmitterContext context, Operand target) + { + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + DecreaseCallDepth(context, nativeContext); + + if (target.Type == OperandType.I32) + { + target = context.ZeroExtend32(OperandType.I64, target); + } + + context.Return(target); + } + private static void EmitTableBranch(ArmEmitterContext context, Operand guestAddress, bool isJump) { context.StoreToContext(); @@ -257,6 +269,8 @@ namespace ARMeilleure.Instructions if (isJump) { + DecreaseCallDepth(context, nativeContext); + context.Tailcall(hostAddress, nativeContext); } else @@ -278,8 +292,42 @@ namespace ARMeilleure.Instructions Operand lblContinue = context.GetLabel(nextAddr.Value); context.BranchIf(lblContinue, returnAddress, nextAddr, Comparison.Equal, BasicBlockFrequency.Cold); + DecreaseCallDepth(context, nativeContext); + context.Return(returnAddress); } } + + public static void EmitCallDepthCheckAndIncrement(EmitterContext context, Operand guestAddress) + { + if (!Optimizations.EnableDeepCallRecursionProtection) + { + return; + } + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand callDepthAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset())); + Operand currentCallDepth = context.Load(OperandType.I32, callDepthAddr); + Operand lblDoCall = Label(); + + context.BranchIf(lblDoCall, currentCallDepth, Const(MaxCallDepth), Comparison.LessUI); + context.Store(callDepthAddr, context.Subtract(currentCallDepth, Const(1))); + context.Return(guestAddress); + + context.MarkLabel(lblDoCall); + context.Store(callDepthAddr, context.Add(currentCallDepth, Const(1))); + } + + private static void DecreaseCallDepth(EmitterContext context, Operand nativeContext) + { + if (!Optimizations.EnableDeepCallRecursionProtection) + { + return; + } + + Operand callDepthAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset())); + Operand currentCallDepth = context.Load(OperandType.I32, callDepthAddr); + context.Store(callDepthAddr, context.Subtract(currentCallDepth, Const(1))); + } } } diff --git a/src/ARMeilleure/Optimizations.cs b/src/ARMeilleure/Optimizations.cs index 6dd7befe7..3a76b6d93 100644 --- a/src/ARMeilleure/Optimizations.cs +++ b/src/ARMeilleure/Optimizations.cs @@ -13,6 +13,7 @@ namespace ARMeilleure public static bool AllowLcqInFunctionTable { get; set; } = true; public static bool UseUnmanagedDispatchLoop { get; set; } = true; public static bool EnableDebugging { get; set; } = false; + public static bool EnableDeepCallRecursionProtection { get; set; } = true; public static bool UseAdvSimdIfAvailable { get; set; } = true; public static bool UseArm64AesIfAvailable { get; set; } = true; diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs index fa1a4a032..365805e45 100644 --- a/src/ARMeilleure/State/ExecutionContext.cs +++ b/src/ARMeilleure/State/ExecutionContext.cs @@ -134,6 +134,11 @@ namespace ARMeilleure.State public bool GetFPstateFlag(FPState flag) => _nativeContext.GetFPStateFlag(flag); public void SetFPstateFlag(FPState flag, bool value) => _nativeContext.SetFPStateFlag(flag, value); + internal void ResetCallDepth() + { + _nativeContext.ResetCallDepth(); + } + internal void CheckInterrupt() { if (Interrupted) diff --git a/src/ARMeilleure/State/NativeContext.cs b/src/ARMeilleure/State/NativeContext.cs index a9f1c3dab..bc9e31888 100644 --- a/src/ARMeilleure/State/NativeContext.cs +++ b/src/ARMeilleure/State/NativeContext.cs @@ -22,6 +22,7 @@ namespace ARMeilleure.State public ulong ExclusiveValueHigh; public int Running; public long Tpidr2El0; + public int CallDepth; /// /// Precise PC value used for debugging. @@ -199,6 +200,8 @@ namespace ARMeilleure.State public bool GetRunning() => GetStorage().Running != 0; public void SetRunning(bool value) => GetStorage().Running = value ? 1 : 0; + public void ResetCallDepth() => GetStorage().CallDepth = 0; + public unsafe static int GetRegisterOffset(Register reg) { if (reg.Type == RegisterType.Integer) @@ -284,6 +287,11 @@ namespace ARMeilleure.State return StorageOffset(ref _dummyStorage, ref _dummyStorage.DebugPrecisePc); } + public static int GetCallDepthOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.CallDepth); + } + private static int StorageOffset(ref NativeCtxStorage storage, ref T target) { return (int)Unsafe.ByteOffset(ref Unsafe.As(ref storage), ref target); diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs index c69ebcadb..cfa4377db 100644 --- a/src/ARMeilleure/Translation/PTC/Ptc.cs +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -33,7 +33,7 @@ namespace ARMeilleure.Translation.PTC private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0"; - private const uint InternalVersion = 7009; //! To be incremented manually for each change to the ARMeilleure project. + private const uint InternalVersion = 7010; //! To be incremented manually for each change to the ARMeilleure project. private const string ActualDir = "0"; private const string BackupDir = "1"; diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs index 0ebb705a4..b098ff4cf 100644 --- a/src/ARMeilleure/Translation/Translator.cs +++ b/src/ARMeilleure/Translation/Translator.cs @@ -186,6 +186,7 @@ namespace ARMeilleure.Translation Statistics.StartTimer(); + context.ResetCallDepth(); ulong nextAddr = func.Execute(Stubs.ContextWrapper, context); Statistics.StopTimer(address); @@ -260,6 +261,7 @@ namespace ARMeilleure.Translation Logger.StartPass(PassName.Translation); + InstEmitFlowHelper.EmitCallDepthCheckAndIncrement(context, Const(address)); EmitSynchronization(context); if (blocks[0].Address != address) diff --git a/src/ARMeilleure/Translation/TranslatorStubs.cs b/src/ARMeilleure/Translation/TranslatorStubs.cs index 458a42434..2d95ceb99 100644 --- a/src/ARMeilleure/Translation/TranslatorStubs.cs +++ b/src/ARMeilleure/Translation/TranslatorStubs.cs @@ -262,10 +262,18 @@ namespace ARMeilleure.Translation Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset())); Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())); + Operand callDepthAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetCallDepthOffset())); EmitSyncFpContext(context, nativeContext, true); context.MarkLabel(beginLbl); + + if (Optimizations.EnableDeepCallRecursionProtection) + { + // Reset the call depth counter, since this is our first guest function call. + context.Store(callDepthAddress, Const(0)); + } + context.Store(dispatchAddress, guestAddress); context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext)); context.BranchIfFalse(endLbl, guestAddress); From d522bfef62c00d38e08ac00d573201cc8143e94a Mon Sep 17 00:00:00 2001 From: GreemDev Date: Tue, 2 Dec 2025 21:22:55 -0600 Subject: [PATCH 08/16] fix: Force the key install helper to delete key files before copying (not sure why the overwrite boolean does nothing for File.Copy) --- src/Ryujinx.HLE/FileSystem/ContentManager.cs | 72 +++++++++++++------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs index 9f38de42b..d0fe0f1a7 100644 --- a/src/Ryujinx.HLE/FileSystem/ContentManager.cs +++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs @@ -483,10 +483,29 @@ namespace Ryujinx.HLE.FileSystem { if (Directory.Exists(keysSource)) { - foreach (string filePath in Directory.EnumerateFiles(keysSource, "*.keys")) + string[] keyPaths = Directory.EnumerateFiles(keysSource, "*.keys").ToArray(); + + if (keyPaths.Length is 0) + throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files."); + + foreach (string filePath in keyPaths) { - VerifyKeysFile(filePath); - File.Copy(filePath, Path.Combine(installDirectory, Path.GetFileName(filePath)), true); + try + { + VerifyKeysFile(filePath); + } + catch (Exception e) + { + Logger.Error?.Print(LogClass.Application, e.Message); + continue; + } + + string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath)); + + if (File.Exists(destPath)) + File.Delete(destPath); + + File.Copy(filePath, destPath, true); } return; @@ -501,13 +520,25 @@ namespace Ryujinx.HLE.FileSystem using FileStream file = File.OpenRead(keysSource); - if (info.Extension is ".keys") + if (info.Extension is not ".keys") + throw new InvalidFirmwarePackageException("Input file extension is not .keys"); + + try { VerifyKeysFile(keysSource); - File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true); - } - else + } + catch + { throw new InvalidFirmwarePackageException("Input file is not a valid key package"); + } + + string dest = Path.Combine(installDirectory, info.Name); + + if (File.Exists(dest)) + File.Delete(dest); + + // overwrite: true seems to not work on its own? https://github.com/Ryubing/Issues/issues/189 + File.Copy(keysSource, dest, true); } private void FinishInstallation(string temporaryDirectory, string registeredDirectory) @@ -985,8 +1016,8 @@ namespace Ryujinx.HLE.FileSystem public static void VerifyKeysFile(string filePath) { // Verify the keys file format refers to https://github.com/Thealexbarney/LibHac/blob/master/KEYS.md - string genericPattern = @"^[a-z0-9_]+ = [a-z0-9]+$"; - string titlePattern = @"^[a-z0-9]{32} = [a-z0-9]{32}$"; + string genericPattern = "^[a-z0-9_]+ = [a-z0-9]+$"; + string titlePattern = "^[a-z0-9]{32} = [a-z0-9]{32}$"; if (File.Exists(filePath)) { @@ -994,24 +1025,13 @@ namespace Ryujinx.HLE.FileSystem string fileName = Path.GetFileName(filePath); string[] lines = File.ReadAllLines(filePath); - bool verified; - switch (fileName) + bool verified = fileName switch { - case "prod.keys": - verified = VerifyKeys(lines, genericPattern); - break; - case "title.keys": - verified = VerifyKeys(lines, titlePattern); - break; - case "console.keys": - verified = VerifyKeys(lines, genericPattern); - break; - case "dev.keys": - verified = VerifyKeys(lines, genericPattern); - break; - default: - throw new FormatException($"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported."); - } + "prod.keys" or "console.keys" or "dev.keys" => VerifyKeys(lines, genericPattern), + "title.keys" => VerifyKeys(lines, titlePattern), + _ => throw new FormatException( + $"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.") + }; if (!verified) { From b018a44ff0a4f902d1cd05da549c4230a478fd21 Mon Sep 17 00:00:00 2001 From: Alula Date: Thu, 4 Dec 2025 17:32:26 -0600 Subject: [PATCH 09/16] Disable coredumps by default on Linux + other minor fixes (ryubing/ryujinx!204) See merge request ryubing/ryujinx!204 --- src/Ryujinx.Common/Utilities/OsUtils.cs | 16 +++++++++++ src/Ryujinx/Program.cs | 35 ++++++++++++++++++++----- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/Ryujinx.Common/Utilities/OsUtils.cs b/src/Ryujinx.Common/Utilities/OsUtils.cs index a0791b092..29c6e187c 100644 --- a/src/Ryujinx.Common/Utilities/OsUtils.cs +++ b/src/Ryujinx.Common/Utilities/OsUtils.cs @@ -20,5 +20,21 @@ namespace Ryujinx.Common.Utilities Debug.Assert(res != -1); } } + + // "dumpable" attribute of the calling process + private const int PR_SET_DUMPABLE = 4; + + [DllImport("libc", SetLastError = true)] + private static extern int prctl(int option, int arg2); + + public static void SetCoreDumpable(bool dumpable) + { + if (OperatingSystem.IsLinux()) + { + int dumpableInt = dumpable ? 1 : 0; + int result = prctl(PR_SET_DUMPABLE, dumpableInt); + Debug.Assert(result == 0); + } + } } } diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index 4904b8464..d77e79756 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -17,6 +17,7 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.Logging; using Ryujinx.Common.SystemInterop; +using Ryujinx.Common.Utilities; using Ryujinx.Graphics.Vulkan.MoltenVK; using Ryujinx.Headless; using Ryujinx.SDL3.Common; @@ -46,7 +47,7 @@ namespace Ryujinx.Ava public static int Main(string[] args) { Version = ReleaseInformation.Version; - + if (OperatingSystem.IsWindows()) { if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041)) @@ -55,8 +56,11 @@ namespace Ryujinx.Ava return 0; } - if (Environment.CurrentDirectory.StartsWithIgnoreCase("C:\\Program Files") || - Environment.CurrentDirectory.StartsWithIgnoreCase("C:\\Program Files (x86)")) + var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); + + if (Environment.CurrentDirectory.StartsWithIgnoreCase(programFiles) || + Environment.CurrentDirectory.StartsWithIgnoreCase(programFilesX86)) { _ = Win32NativeInterop.MessageBoxA(nint.Zero, "Ryujinx is not intended to be run from the Program Files folder. Please move it out and relaunch.", $"Ryujinx {Version}", MbIconwarning); return 0; @@ -73,11 +77,23 @@ namespace Ryujinx.Ava } } + bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui"); + bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps"); + + // TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception. + // This is undesirable and causes very odd behavior during development (the process stops responding, + // the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user. + // This needs to be investigated, but calling prctl() is better than modifying system-wide settings or leaving this be. + if (!coreDumpArg) + { + OsUtils.SetCoreDumpable(false); + } + PreviewerDetached = true; - if (args.Length > 0 && args[0] is "--no-gui" or "nogui") + if (noGuiArg) { - HeadlessRyujinx.Entrypoint(args[1..]); + HeadlessRyujinx.Entrypoint(args); return 0; } @@ -112,6 +128,14 @@ namespace Ryujinx.Ava : [Win32RenderingMode.Software] }); + private static bool ConsumeCommandLineArgument(ref string[] args, string targetArgument) + { + List argList = [.. args]; + bool found = argList.Remove(targetArgument); + args = argList.ToArray(); + return found; + } + private static void Initialize(string[] args) { // Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched @@ -177,7 +201,6 @@ namespace Ryujinx.Ava } } - public static string GetDirGameUserConfig(string gameId, bool changeFolderForGame = false) { if (string.IsNullOrEmpty(gameId)) From 52700f71dc0f2abb8fcf5fb564b66c713e335550 Mon Sep 17 00:00:00 2001 From: Princess Piplup Date: Thu, 4 Dec 2025 23:35:17 +0000 Subject: [PATCH 10/16] Fix SaveCurrentScreenshot (ryubing/ryujinx!230) See merge request ryubing/ryujinx!230 --- .../SystemAppletProxy/ISelfController.cs | 2 +- src/Ryujinx.HLE/UI/IHostUIHandler.cs | 5 +++++ src/Ryujinx/Headless/Windows/WindowBase.cs | 5 +++++ src/Ryujinx/UI/Applet/AvaHostUIHandler.cs | 5 +++++ src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs | 5 ++++- 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs index 8e0f515ba..7aac6f3ea 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs @@ -416,7 +416,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys return ResultCode.InvalidParameters; } - Logger.Stub?.PrintStub(LogClass.ServiceAm, new { albumReportOption }); + context.Device.UIHandler.TakeScreenshot(); return ResultCode.Success; } diff --git a/src/Ryujinx.HLE/UI/IHostUIHandler.cs b/src/Ryujinx.HLE/UI/IHostUIHandler.cs index b5c5cb168..79b479d8a 100644 --- a/src/Ryujinx.HLE/UI/IHostUIHandler.cs +++ b/src/Ryujinx.HLE/UI/IHostUIHandler.cs @@ -68,5 +68,10 @@ namespace Ryujinx.HLE.UI /// Displays the player select dialog and returns the selected profile. /// UserProfile ShowPlayerSelectDialog(); + + /// + /// Takes a screenshot from the current renderer and saves it in the screenshots folder. + /// + void TakeScreenshot(); } } diff --git a/src/Ryujinx/Headless/Windows/WindowBase.cs b/src/Ryujinx/Headless/Windows/WindowBase.cs index 8e06a3f20..49b2a389a 100644 --- a/src/Ryujinx/Headless/Windows/WindowBase.cs +++ b/src/Ryujinx/Headless/Windows/WindowBase.cs @@ -580,5 +580,10 @@ namespace Ryujinx.Headless { return AccountSaveDataManager.GetLastUsedUser(); } + + public void TakeScreenshot() + { + throw new NotImplementedException(); + } } } diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs index 38670e5d5..45235ee3f 100644 --- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs @@ -327,5 +327,10 @@ namespace Ryujinx.Ava.UI.Applet return profile; } + + public void TakeScreenshot() + { + _parent.ViewModel.AppHost.ScreenshotRequested = true; + } } } diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 2236b27f6..651dc901c 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -1333,7 +1333,10 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public void TakeScreenshot() => AppHost.ScreenshotRequested = true; + public void TakeScreenshot() + { + AppHost.ScreenshotRequested = true; + } public void HideUi() => ShowMenuAndStatusBar = false; From fd7554425a77de67d1b23fd07f0322eaa27970ef Mon Sep 17 00:00:00 2001 From: LotP <22-lotp@users.noreply.git.ryujinx.app> Date: Fri, 5 Dec 2025 07:53:09 -0600 Subject: [PATCH 11/16] Update BiquadFilterEffectParameter2.cs (ryubing/ryujinx!233) See merge request ryubing/ryujinx!233 --- .../Parameter/Effect/BiquadFilterEffectParameter2.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter2.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter2.cs index 0c74f1e7b..b1e61f6c1 100644 --- a/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter2.cs +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter2.cs @@ -19,6 +19,11 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect /// The output channel indices that will be used by the . /// public Array6 Output; + + /// + /// Reserved/unused. + /// + private readonly uint _padding; /// /// Biquad filter numerator (b0, b1, b2). From c3155fcadb4b1ec0322bf93b7d27621198af7d75 Mon Sep 17 00:00:00 2001 From: LotP <22-lotp@users.noreply.git.ryujinx.app> Date: Sat, 6 Dec 2025 17:19:19 -0600 Subject: [PATCH 12/16] Memory Changes 3.2 (ryubing/ryujinx!234) See merge request ryubing/ryujinx!234 --- .../Memory/MemoryStreamManager.cs | 21 +- src/Ryujinx.Graphics.GAL/IPipeline.cs | 2 +- .../Commands/SetRenderTargetsCommand.cs | 14 +- .../Multithreading/ThreadedPipeline.cs | 6 +- .../Multithreading/ThreadedRenderer.cs | 2 +- src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs | 33 +-- .../Memory/BufferBackingState.cs | 6 +- .../Memory/BufferCache.cs | 88 ++------ .../Memory/BufferModifiedRangeList.cs | 167 ++++++++------- .../Memory/VirtualRangeCache.cs | 19 +- src/Ryujinx.Graphics.OpenGL/Pipeline.cs | 2 +- .../FramebufferParams.cs | 32 ++- .../MultiFenceHolder.cs | 8 +- src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 12 +- src/Ryujinx.Graphics.Vulkan/SyncManager.cs | 5 +- src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs | 5 +- .../HOS/Kernel/Ipc/KBufferDescriptorTable.cs | 9 + .../HOS/Kernel/Ipc/KClientSession.cs | 5 +- .../HOS/Kernel/Ipc/KServerSession.cs | 12 ++ .../HOS/Kernel/Ipc/KSessionRequest.cs | 16 +- .../HOS/Kernel/Threading/KAddressArbiter.cs | 148 +++++++------ .../HOS/Kernel/Threading/KThread.cs | 2 - src/Ryujinx.HLE/HOS/Services/IpcService.cs | 11 +- .../HOS/Services/Nv/INvDrvServices.cs | 85 ++++---- src/Ryujinx.HLE/HOS/Services/ServerBase.cs | 6 +- .../Sdk/OsTypes/Impl/MultiWaitImpl.cs | 29 ++- src/Ryujinx.Input/HLE/NpadController.cs | 2 - src/Ryujinx.Input/HLE/NpadManager.cs | 23 +- src/Ryujinx.Input/IKeyboard.cs | 12 +- .../Range/INonOverlappingRange.cs | 4 +- src/Ryujinx.Memory/Range/IRange.cs | 4 +- .../Range/NonOverlappingRangeList.cs | 198 +++++------------- src/Ryujinx.Memory/Range/RangeList.cs | 139 ++---------- src/Ryujinx.Memory/Range/RangeListBase.cs | 82 ++------ src/Ryujinx.Memory/Tracking/AbstractRegion.cs | 13 +- src/Ryujinx.Memory/Tracking/MemoryTracking.cs | 14 +- src/Ryujinx.Memory/Tracking/VirtualRegion.cs | 4 +- 37 files changed, 563 insertions(+), 677 deletions(-) diff --git a/src/Ryujinx.Common/Memory/MemoryStreamManager.cs b/src/Ryujinx.Common/Memory/MemoryStreamManager.cs index 834210e07..88f5956e9 100644 --- a/src/Ryujinx.Common/Memory/MemoryStreamManager.cs +++ b/src/Ryujinx.Common/Memory/MemoryStreamManager.cs @@ -7,6 +7,9 @@ namespace Ryujinx.Common.Memory { private static readonly RecyclableMemoryStreamManager _shared = new(); + private static readonly ObjectPool _streamPool = + new(() => new RecyclableMemoryStream(_shared, Guid.NewGuid(), null, 0)); + /// /// We don't expose the RecyclableMemoryStreamManager directly because version 2.x /// returns them as MemoryStream. This Shared class is here to a) offer only the GetStream() versions we use @@ -19,7 +22,12 @@ namespace Ryujinx.Common.Memory /// /// A RecyclableMemoryStream public static RecyclableMemoryStream GetStream() - => new(_shared); + { + RecyclableMemoryStream stream = _streamPool.Allocate(); + stream.SetLength(0); + + return stream; + } /// /// Retrieve a new MemoryStream object with the contents copied from the provided @@ -55,7 +63,8 @@ namespace Ryujinx.Common.Memory RecyclableMemoryStream stream = null; try { - stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length); + stream = _streamPool.Allocate(); + stream.SetLength(0); stream.Write(buffer); stream.Position = 0; return stream; @@ -83,7 +92,8 @@ namespace Ryujinx.Common.Memory RecyclableMemoryStream stream = null; try { - stream = new RecyclableMemoryStream(_shared, id, tag, count); + stream = _streamPool.Allocate(); + stream.SetLength(0); stream.Write(buffer, offset, count); stream.Position = 0; return stream; @@ -94,6 +104,11 @@ namespace Ryujinx.Common.Memory throw; } } + + public static void ReleaseStream(RecyclableMemoryStream stream) + { + _streamPool.Release(stream); + } } } } diff --git a/src/Ryujinx.Graphics.GAL/IPipeline.cs b/src/Ryujinx.Graphics.GAL/IPipeline.cs index b8409a573..da20da870 100644 --- a/src/Ryujinx.Graphics.GAL/IPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/IPipeline.cs @@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.GAL void SetRasterizerDiscard(bool discard); void SetRenderTargetColorMasks(ReadOnlySpan componentMask); - void SetRenderTargets(ITexture[] colors, ITexture depthStencil); + void SetRenderTargets(Span colors, ITexture depthStencil); void SetScissors(ReadOnlySpan> regions); diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs index ca7c8c8c2..2641ae528 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs @@ -1,5 +1,6 @@ using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System; using System.Buffers; namespace Ryujinx.Graphics.GAL.Multithreading.Commands @@ -8,11 +9,13 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands { public static readonly ArrayPool ArrayPool = ArrayPool.Create(512, 50); public readonly CommandType CommandType => CommandType.SetRenderTargets; + private int _colorsCount; private TableRef _colors; private TableRef _depthStencil; - public void Set(TableRef colors, TableRef depthStencil) + public void Set(int colorsCount, TableRef colors, TableRef depthStencil) { + _colorsCount = colorsCount; _colors = colors; _depthStencil = depthStencil; } @@ -20,16 +23,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands public static void Run(ref SetRenderTargetsCommand command, ThreadedRenderer threaded, IRenderer renderer) { ITexture[] colors = command._colors.Get(threaded); - ITexture[] colorsCopy = ArrayPool.Rent(colors.Length); + Span colorsSpan = colors.AsSpan(0, command._colorsCount); - for (int i = 0; i < colors.Length; i++) + for (int i = 0; i < colorsSpan.Length; i++) { - colorsCopy[i] = ((ThreadedTexture)colors[i])?.Base; + colorsSpan[i] = ((ThreadedTexture)colorsSpan[i])?.Base; } - renderer.Pipeline.SetRenderTargets(colorsCopy, command._depthStencil.GetAs(threaded)?.Base); + renderer.Pipeline.SetRenderTargets(colorsSpan, command._depthStencil.GetAs(threaded)?.Base); - ArrayPool.Return(colorsCopy); ArrayPool.Return(colors); } } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs index ea3fd1e11..6873574b7 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs @@ -267,12 +267,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading _renderer.QueueCommand(); } - public unsafe void SetRenderTargets(ITexture[] colors, ITexture depthStencil) + public unsafe void SetRenderTargets(Span colors, ITexture depthStencil) { ITexture[] colorsCopy = SetRenderTargetsCommand.ArrayPool.Rent(colors.Length); - colors.CopyTo(colorsCopy, 0); + colors.CopyTo(colorsCopy.AsSpan()); - _renderer.New()->Set(Ref(colorsCopy), Ref(depthStencil)); + _renderer.New()->Set(colors.Length, Ref(colorsCopy), Ref(depthStencil)); _renderer.QueueCommand(); } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs index 3c179da36..66ac31ab4 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading /// public class ThreadedRenderer : IRenderer { - private const int SpanPoolBytes = 4 * 1024 * 1024; + private const int SpanPoolBytes = 8 * 1024 * 1024; private const int MaxRefsPerCommand = 2; private const int QueueCount = 10000; diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs index 277a30689..f04576c2a 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// Buffer, used to store vertex and index data, uniform and storage buffers, and others. /// - class Buffer : INonOverlappingRange, ISyncActionHandler, IDisposable + class Buffer : INonOverlappingRange, ISyncActionHandler, IDisposable { private const ulong GranularBufferThreshold = 4096; @@ -41,6 +41,9 @@ namespace Ryujinx.Graphics.Gpu.Memory /// End address of the buffer in guest memory. /// public ulong EndAddress => Address + Size; + + public Buffer Next { get; set; } + public Buffer Previous { get; set; } /// /// Increments when the buffer is (partially) unmapped or disposed. @@ -87,6 +90,7 @@ namespace Ryujinx.Graphics.Gpu.Memory private readonly bool _useGranular; private bool _syncActionRegistered; + private bool _bufferInherited; private int _referenceCount = 1; @@ -113,7 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Memory ulong size, BufferStage stage, bool sparseCompatible, - RangeItem[] baseBuffers) + Buffer[] baseBuffers) { _context = context; _physicalMemory = physicalMemory; @@ -134,15 +138,15 @@ namespace Ryujinx.Graphics.Gpu.Memory if (baseBuffers.Length != 0) { baseHandles = new List(); - foreach (RangeItem item in baseBuffers) + foreach (Buffer item in baseBuffers) { - if (item.Value._useGranular) + if (item._useGranular) { - baseHandles.AddRange(item.Value._memoryTrackingGranular.Handles); + baseHandles.AddRange(item._memoryTrackingGranular.Handles); } else { - baseHandles.Add(item.Value._memoryTracking); + baseHandles.Add(item._memoryTracking); } } } @@ -247,14 +251,14 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Checks if a given range overlaps with the buffer. /// /// Start address of the range - /// Size in bytes of the range + /// End address of the range /// True if the range overlaps, false otherwise - public bool OverlapsWith(ulong address, ulong size) + public bool OverlapsWith(ulong address, ulong endAddress) { - return Address < address + size && address < EndAddress; + return Address < endAddress && address < EndAddress; } - public INonOverlappingRange Split(ulong splitAddress) + public INonOverlappingRange Split(ulong splitAddress) { throw new NotImplementedException(); } @@ -426,10 +430,13 @@ namespace Ryujinx.Graphics.Gpu.Memory { _syncActionRegistered = false; + if (_bufferInherited) + { + return true; + } + if (_useGranular) { - - _modifiedRanges?.GetRanges(Address, Size, _syncRangeAction); } else @@ -453,6 +460,8 @@ namespace Ryujinx.Graphics.Gpu.Memory /// The buffer to inherit from public void InheritModifiedRanges(Buffer from) { + from._bufferInherited = true; + if (from._modifiedRanges is { HasRanges: true }) { if (from._syncActionRegistered && !_syncActionRegistered) diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs index a81e7e98f..e674eb1d7 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs @@ -56,7 +56,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Parent buffer /// Initial buffer stage /// Buffers to inherit state from - public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, RangeItem[] baseBuffers) + public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, Buffer[] baseBuffers) { _size = (int)parent.Size; _systemMemoryType = context.Capabilities.MemoryType; @@ -102,9 +102,9 @@ namespace Ryujinx.Graphics.Gpu.Memory if (baseBuffers.Length != 0) { - foreach (RangeItem item in baseBuffers) + foreach (Buffer item in baseBuffers) { - CombineState(item.Value.BackingState); + CombineState(item.BackingState); } } } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs index 0d623ff95..83869ed02 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs @@ -79,16 +79,13 @@ namespace Ryujinx.Graphics.Gpu.Memory for (int index = 0; index < range.Count; index++) { MemoryRange subRange = range.GetSubRange(index); - - _buffers.Lock.EnterReadLock(); - Span> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size); + + ReadOnlySpan overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size); for (int i = 0; i < overlaps.Length; i++) { - overlaps[i].Value.Unmapped(subRange.Address, subRange.Size); + overlaps[i].Unmapped(subRange.Address, subRange.Size); } - - _buffers.Lock.ExitReadLock(); } } @@ -328,7 +325,7 @@ namespace Ryujinx.Graphics.Gpu.Memory ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask; ulong alignedSize = alignedEndAddress - alignedAddress; - Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize).Value; + Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize); BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false); alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize); @@ -395,7 +392,7 @@ namespace Ryujinx.Graphics.Gpu.Memory if (subRange.Address != MemoryManager.PteUnmapped) { - Buffer buffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value; + Buffer buffer = _buffers.FindOverlap(subRange.Address, subRange.Size); virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size); physicalBuffers.Add(buffer); @@ -487,10 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// The type of usage that created the buffer private void CreateBufferAligned(ulong address, ulong size, BufferStage stage) { - Buffer newBuffer = null; - - _buffers.Lock.EnterWriteLock(); - Span> overlaps = _buffers.FindOverlapsAsSpan(address, size); + ReadOnlySpan overlaps = _buffers.FindOverlapsAsSpan(address, size); if (overlaps.Length != 0) { @@ -521,7 +515,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { // Try to grow the buffer by 1.5x of its current size. // This improves performance in the cases where the buffer is resized often by small amounts. - ulong existingSize = overlaps[0].Value.Size; + ulong existingSize = overlaps[0].Size; ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask; size = Math.Max(size, growthSize); @@ -535,39 +529,22 @@ namespace Ryujinx.Graphics.Gpu.Memory for (int i = 0; i < overlaps.Length; i++) { - anySparseCompatible |= overlaps[i].Value.SparseCompatible; + anySparseCompatible |= overlaps[i].SparseCompatible; } - RangeItem[] overlapsArray = overlaps.ToArray(); + Buffer[] overlapsArray = overlaps.ToArray(); _buffers.RemoveRange(overlaps[0], overlaps[^1]); - _buffers.Lock.ExitWriteLock(); - ulong newSize = endAddress - address; - newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray); - } - else - { - _buffers.Lock.ExitWriteLock(); + _buffers.Add(CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray)); } } else { - _buffers.Lock.ExitWriteLock(); - // No overlap, just create a new buffer. - newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []); - } - - if (newBuffer is not null) - { - _buffers.Lock.EnterWriteLock(); - - _buffers.Add(newBuffer); - - _buffers.Lock.ExitWriteLock(); + _buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, [])); } } @@ -583,10 +560,8 @@ namespace Ryujinx.Graphics.Gpu.Memory private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment) { bool sparseAligned = alignment >= SparseBufferAlignmentSize; - Buffer newBuffer = null; - _buffers.Lock.EnterWriteLock(); - Span> overlaps = _buffers.FindOverlapsAsSpan(address, size); + ReadOnlySpan overlaps = _buffers.FindOverlapsAsSpan(address, size); if (overlaps.Length != 0) { @@ -598,7 +573,7 @@ namespace Ryujinx.Graphics.Gpu.Memory if (overlaps[0].Address > address || overlaps[0].EndAddress < endAddress || (overlaps[0].Address & (alignment - 1)) != 0 || - (!overlaps[0].Value.SparseCompatible && sparseAligned)) + (!overlaps[0].SparseCompatible && sparseAligned)) { // We need to make sure the new buffer is properly aligned. // However, after the range is aligned, it is possible that it @@ -622,35 +597,18 @@ namespace Ryujinx.Graphics.Gpu.Memory ulong newSize = endAddress - address; - RangeItem[] overlapsArray = overlaps.ToArray(); + Buffer[] overlapsArray = overlaps.ToArray(); _buffers.RemoveRange(overlaps[0], overlaps[^1]); - _buffers.Lock.ExitWriteLock(); - - newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray); - } - else - { - _buffers.Lock.ExitWriteLock(); + _buffers.Add(CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray)); } } else { - _buffers.Lock.ExitWriteLock(); - // No overlap, just create a new buffer. - newBuffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []); - } - - if (newBuffer is not null) - { - _buffers.Lock.EnterWriteLock(); - - _buffers.Add(newBuffer); - - _buffers.Lock.ExitWriteLock(); - } + _buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseAligned, [])); + } } /// @@ -663,13 +621,13 @@ namespace Ryujinx.Graphics.Gpu.Memory /// The type of usage that created the buffer /// Indicates if the buffer can be used in a sparse buffer mapping /// Buffers overlapping the range - private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, RangeItem[] overlaps) + private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, Buffer[] overlaps) { Buffer newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps); for (int index = 0; index < overlaps.Length; index++) { - Buffer buffer = overlaps[index].Value; + Buffer buffer = overlaps[index]; int dstOffset = (int)(buffer.Address - newBuffer.Address); @@ -897,7 +855,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { MemoryRange subRange = range.GetSubRange(i); - Buffer subBuffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value; + Buffer subBuffer = _buffers.FindOverlap(subRange.Address, subRange.Size); subBuffer.SynchronizeMemory(subRange.Address, subRange.Size); @@ -945,7 +903,7 @@ namespace Ryujinx.Graphics.Gpu.Memory if (size != 0) { - buffer = _buffers.FindOverlap(address, size).Value; + buffer = _buffers.FindOverlap(address, size); buffer.CopyFromDependantVirtualBuffers(); buffer.SynchronizeMemory(address, size); @@ -957,7 +915,7 @@ namespace Ryujinx.Graphics.Gpu.Memory } else { - buffer = _buffers.FindOverlapFast(address, 1).Value; + buffer = _buffers.FindOverlapFast(address, 1); } return buffer; @@ -995,7 +953,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { if (size != 0) { - Buffer buffer = _buffers.FindOverlap(address, size).Value; + Buffer buffer = _buffers.FindOverlap(address, size); if (copyBackVirtual) { diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs index 9c50eaf2f..aef04abc6 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// A range within a buffer that has been modified by the GPU. /// - class BufferModifiedRange : INonOverlappingRange + class BufferModifiedRange : INonOverlappingRange { /// /// Start address of the range in guest memory. @@ -24,6 +24,9 @@ namespace Ryujinx.Graphics.Gpu.Memory /// End address of the range in guest memory. /// public ulong EndAddress => Address + Size; + + public BufferModifiedRange Next { get; set; } + public BufferModifiedRange Previous { get; set; } /// /// The GPU sync number at the time of the last modification. @@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Checks if a given range overlaps with the modified range. /// /// Start address of the range - /// Size in bytes of the range + /// End address of the range /// True if the range overlaps, false otherwise - public bool OverlapsWith(ulong address, ulong size) + public bool OverlapsWith(ulong address, ulong endAddress) { - return Address < address + size && address < EndAddress; + return Address < endAddress && address < EndAddress; } - public INonOverlappingRange Split(ulong splitAddress) + public INonOverlappingRange Split(ulong splitAddress) { throw new NotImplementedException(); } @@ -119,11 +122,11 @@ namespace Ryujinx.Graphics.Gpu.Memory // Slices a given region using the modified regions in the list. Calls the action for the new slices. Lock.EnterReadLock(); - Span> overlaps = FindOverlapsAsSpan(address, size); + ReadOnlySpan overlaps = FindOverlapsAsSpan(address, size); for (int i = 0; i < overlaps.Length; i++) { - BufferModifiedRange overlap = overlaps[i].Value; + BufferModifiedRange overlap = overlaps[i]; if (overlap.Address > address) { @@ -157,7 +160,7 @@ namespace Ryujinx.Graphics.Gpu.Memory ulong syncNumber = _context.SyncNumber; // We may overlap with some existing modified regions. They must be cut into by the new entry. Lock.EnterWriteLock(); - (RangeItem first, RangeItem last) = FindOverlapsAsNodes(address, size); + (BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size); if (first is null) { @@ -170,34 +173,39 @@ namespace Ryujinx.Graphics.Gpu.Memory { if (first.Address == address && first.EndAddress == endAddress) { - first.Value.SyncNumber = syncNumber; - first.Value.Parent = this; + first.SyncNumber = syncNumber; + first.Parent = this; Lock.ExitWriteLock(); return; } if (first.Address < address) { - first.Value.Size = address - first.Address; - Update(first); - if (first.EndAddress > endAddress) { Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress, - first.Value.SyncNumber, first.Value.Parent)); + first.SyncNumber, first.Parent)); } + + first.Size = address - first.Address; } else { if (first.EndAddress > endAddress) { - first.Value.Size = first.EndAddress - endAddress; - first.Value.Address = endAddress; - Update(first); + first.Size = first.EndAddress - endAddress; + first.Address = endAddress; } else { - Remove(first.Value); + first.Address = address; + first.Size = size; + first.SyncNumber = syncNumber; + first.Parent = this; + + Lock.ExitWriteLock(); + + return; } } @@ -207,38 +215,39 @@ namespace Ryujinx.Graphics.Gpu.Memory return; } - BufferModifiedRange buffPre = null; - BufferModifiedRange buffPost = null; - bool extendsPost = false; - bool extendsPre = false; - if (first.Address < address) { - buffPre = new BufferModifiedRange(first.Address, address - first.Address, - first.Value.SyncNumber, first.Value.Parent); - extendsPre = true; + first.Size = address - first.Address; + first = first.Next; } if (last.EndAddress > endAddress) { - buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress, - last.Value.SyncNumber, last.Value.Parent); - extendsPost = true; + last.Size = last.EndAddress - endAddress; + last.Address = endAddress; + last = last.Previous; } - RemoveRange(first, last); - - if (extendsPre) + if (first.Address < last.Address) { - Add(buffPre); + RemoveRange(first.Next, last); + first.Address = address; + first.Size = size; + first.SyncNumber = syncNumber; + first.Parent = this; } - - if (extendsPost) + else if (first.Address == last.Address) { - Add(buffPost); + first.Address = address; + first.Size = size; + first.SyncNumber = syncNumber; + first.Parent = this; } - - Add(new BufferModifiedRange(address, size, syncNumber, this)); + else + { + Add(new BufferModifiedRange(address, size, syncNumber, this)); + } + Lock.ExitWriteLock(); } @@ -252,11 +261,11 @@ namespace Ryujinx.Graphics.Gpu.Memory public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action rangeAction) { Lock.EnterReadLock(); - Span> overlaps = FindOverlapsAsSpan(address, size); + ReadOnlySpan overlaps = FindOverlapsAsSpan(address, size); for (int i = 0; i < overlaps.Length; i++) { - BufferModifiedRange overlap = overlaps[i].Value; + BufferModifiedRange overlap = overlaps[i]; if (overlap.SyncNumber == syncNumber) { @@ -277,18 +286,18 @@ namespace Ryujinx.Graphics.Gpu.Memory { // We use the non-span method here because keeping the lock will cause a deadlock. Lock.EnterReadLock(); - RangeItem[] overlaps = FindOverlapsAsArray(address, size, out int length); + BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int length); Lock.ExitReadLock(); if (length != 0) { for (int i = 0; i < length; i++) { - BufferModifiedRange overlap = overlaps[i].Value; + BufferModifiedRange overlap = overlaps[i]; rangeAction(overlap.Address, overlap.Size); } - ArrayPool>.Shared.Return(overlaps); + ArrayPool.Shared.Return(overlaps); } } @@ -301,7 +310,7 @@ namespace Ryujinx.Graphics.Gpu.Memory public bool HasRange(ulong address, ulong size) { Lock.EnterReadLock(); - RangeItem first = FindOverlapFast(address, size); + BufferModifiedRange first = FindOverlapFast(address, size); bool result = first is not null; Lock.ExitReadLock(); return result; @@ -336,7 +345,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// The start address of the flush range /// The end address of the flush range private void RemoveRangesAndFlush( - RangeItem[] overlaps, + BufferModifiedRange[] overlaps, int rangeCount, long highestDiff, ulong currentSync, @@ -349,7 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Memory for (int i = 0; i < rangeCount; i++) { - BufferModifiedRange overlap = overlaps[i].Value; + BufferModifiedRange overlap = overlaps[i]; long diff = (long)(overlap.SyncNumber - currentSync); @@ -358,7 +367,14 @@ namespace Ryujinx.Graphics.Gpu.Memory ulong clampAddress = Math.Max(address, overlap.Address); ulong clampEnd = Math.Min(endAddress, overlap.EndAddress); - ClearPart(overlap, clampAddress, clampEnd); + if (i == 0 || i == rangeCount - 1) + { + ClearPart(overlap, clampAddress, clampEnd); + } + else + { + Remove(overlap); + } RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction); } @@ -398,7 +414,7 @@ namespace Ryujinx.Graphics.Gpu.Memory Lock.EnterWriteLock(); // We use the non-span method here because the array is partially modified by the code, which would invalidate a span. - RangeItem[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount); + BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount); if (rangeCount == 0) { @@ -414,7 +430,7 @@ namespace Ryujinx.Graphics.Gpu.Memory for (int i = 0; i < rangeCount; i++) { - BufferModifiedRange overlap = overlaps![i].Value; + BufferModifiedRange overlap = overlaps![i]; long diff = (long)(overlap.SyncNumber - currentSync); @@ -436,7 +452,7 @@ namespace Ryujinx.Graphics.Gpu.Memory RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress); - ArrayPool>.Shared.Return(overlaps!); + ArrayPool.Shared.Return(overlaps!); Lock.ExitWriteLock(); } @@ -452,7 +468,9 @@ namespace Ryujinx.Graphics.Gpu.Memory public void InheritRanges(BufferModifiedRangeList ranges, Action registerRangeAction) { ranges.Lock.EnterReadLock(); - BufferModifiedRange[] inheritRanges = ranges.ToArray(); + int rangesCount = ranges.Count; + BufferModifiedRange[] inheritRanges = ArrayPool.Shared.Rent(ranges.Count); + ranges.Items.AsSpan(0, ranges.Count).CopyTo(inheritRanges); ranges.Lock.ExitReadLock(); // Copy over the migration from the previous range list @@ -478,22 +496,26 @@ namespace Ryujinx.Graphics.Gpu.Memory ranges._migrationTarget = this; Lock.EnterWriteLock(); - - foreach (BufferModifiedRange range in inheritRanges) + + for (int i = 0; i < rangesCount; i++) { + BufferModifiedRange range = inheritRanges[i]; Add(range); } Lock.ExitWriteLock(); ulong currentSync = _context.SyncNumber; - foreach (BufferModifiedRange range in inheritRanges) + for (int i = 0; i < rangesCount; i++) { + BufferModifiedRange range = inheritRanges[i]; if (range.SyncNumber != currentSync) { registerRangeAction(range.Address, range.Size); } } + + ArrayPool.Shared.Return(inheritRanges); } /// @@ -534,18 +556,25 @@ namespace Ryujinx.Graphics.Gpu.Memory private void ClearPart(BufferModifiedRange overlap, ulong address, ulong endAddress) { - Remove(overlap); - // If the overlap extends outside of the clear range, make sure those parts still exist. if (overlap.Address < address) { - Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent)); + if (overlap.EndAddress > endAddress) + { + Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent)); + } + + overlap.Size = address - overlap.Address; } - - if (overlap.EndAddress > endAddress) + else if (overlap.EndAddress > endAddress) { - Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent)); + overlap.Size = overlap.EndAddress - endAddress; + overlap.Address = endAddress; + } + else + { + Remove(overlap); } } @@ -558,7 +587,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { ulong endAddress = address + size; Lock.EnterWriteLock(); - (RangeItem first, RangeItem last) = FindOverlapsAsNodes(address, size); + (BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size); if (first is null) { @@ -570,26 +599,24 @@ namespace Ryujinx.Graphics.Gpu.Memory { if (first.Address < address) { - first.Value.Size = address - first.Address; - Update(first); + first.Size = address - first.Address; if (first.EndAddress > endAddress) { Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress, - first.Value.SyncNumber, first.Value.Parent)); + first.SyncNumber, first.Parent)); } } else { if (first.EndAddress > endAddress) { - first.Value.Size = first.EndAddress - endAddress; - first.Value.Address = endAddress; - Update(first); + first.Size = first.EndAddress - endAddress; + first.Address = endAddress; } else { - Remove(first.Value); + Remove(first); } } @@ -605,14 +632,14 @@ namespace Ryujinx.Graphics.Gpu.Memory if (first.Address < address) { buffPre = new BufferModifiedRange(first.Address, address - first.Address, - first.Value.SyncNumber, first.Value.Parent); + first.SyncNumber, first.Parent); extendsPre = true; } if (last.EndAddress > endAddress) { buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress, - last.Value.SyncNumber, last.Value.Parent); + last.SyncNumber, last.Parent); extendsPost = true; } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs index 1d44ee65f..0339d8617 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs @@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// Represents a GPU virtual memory range. /// - private class VirtualRange : INonOverlappingRange + private class VirtualRange : INonOverlappingRange { /// /// GPU virtual address where the range starts. @@ -32,6 +32,9 @@ namespace Ryujinx.Graphics.Gpu.Memory /// public ulong EndAddress => Address + Size; + public VirtualRange Next { get; set; } + public VirtualRange Previous { get; set; } + /// /// Physical regions where the GPU virtual region is mapped. /// @@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Checks if a given range overlaps with the buffer. /// /// Start address of the range - /// Size in bytes of the range + /// End address of the range /// True if the range overlaps, false otherwise - public bool OverlapsWith(ulong address, ulong size) + public bool OverlapsWith(ulong address, ulong endAddress) { - return Address < address + size && address < EndAddress; + return Address < endAddress && address < EndAddress; } - public INonOverlappingRange Split(ulong splitAddress) + public INonOverlappingRange Split(ulong splitAddress) { throw new NotImplementedException(); } @@ -122,7 +125,7 @@ namespace Ryujinx.Graphics.Gpu.Memory ulong originalVa = gpuVa; _virtualRanges.Lock.EnterWriteLock(); - (RangeItem first, RangeItem last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size); + (VirtualRange first, VirtualRange last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size); if (first is not null) { @@ -147,8 +150,8 @@ namespace Ryujinx.Graphics.Gpu.Memory } else { - found = first.Value.Range.Count == 1 || IsSparseAligned(first.Value.Range); - range = first.Value.Range.Slice(gpuVa - first.Address, size); + found = first.Range.Count == 1 || IsSparseAligned(first.Range); + range = first.Range.Slice(gpuVa - first.Address, size); } } else diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs index c8ca02140..e58e6f2b9 100644 --- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -1166,7 +1166,7 @@ namespace Ryujinx.Graphics.OpenGL } } - public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) + public void SetRenderTargets(Span colors, ITexture depthStencil) { EnsureFramebuffer(); diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs index 919c45b9d..6d03fcd0d 100644 --- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs +++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs @@ -71,17 +71,31 @@ namespace Ryujinx.Graphics.Vulkan HasDepthStencil = isDepthStencil; } - public FramebufferParams(Device device, ITexture[] colors, ITexture depthStencil) + public FramebufferParams(Device device, ReadOnlySpan colors, ITexture depthStencil) { _device = device; - int colorsCount = colors.Count(IsValidTextureView); + int colorsCount = 0; + _colorsCanonical = new TextureView[colors.Length]; + + for (int i = 0; i < colors.Length; i++) + { + ITexture color = colors[i]; + if (color is TextureView { Valid: true } view) + { + colorsCount++; + _colorsCanonical[i] = view; + } + else + { + _colorsCanonical[i] = null; + } + } int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0); _attachments = new Auto[count]; _colors = new TextureView[colorsCount]; - _colorsCanonical = colors.Select(color => color is TextureView view && view.Valid ? view : null).ToArray(); AttachmentSamples = new uint[count]; AttachmentFormats = new VkFormat[count]; @@ -165,9 +179,17 @@ namespace Ryujinx.Graphics.Vulkan _totalCount = colors.Length; } - public FramebufferParams Update(ITexture[] colors, ITexture depthStencil) + public FramebufferParams Update(ReadOnlySpan colors, ITexture depthStencil) { - int colorsCount = colors.Count(IsValidTextureView); + int colorsCount = 0; + + foreach (ITexture color in colors) + { + if (IsValidTextureView(color)) + { + colorsCount++; + } + } int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0); diff --git a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs index 40ad7716d..b226ce1f3 100644 --- a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs @@ -1,7 +1,7 @@ +using Ryujinx.Common; using Ryujinx.Common.Memory; using Silk.NET.Vulkan; using System; -using System.Buffers; namespace Ryujinx.Graphics.Vulkan { @@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.Vulkan /// class MultiFenceHolder { + public static readonly ObjectPool FencePool = new(() => new FenceHolder[CommandBufferPool.MaxCommandBuffers]); + private const int BufferUsageTrackingGranularity = 4096; public FenceHolder[] Fences { get; } @@ -20,7 +22,7 @@ namespace Ryujinx.Graphics.Vulkan /// public MultiFenceHolder() { - Fences = ArrayPool.Shared.Rent(CommandBufferPool.MaxCommandBuffers); + Fences = FencePool.Allocate(); } /// @@ -29,7 +31,7 @@ namespace Ryujinx.Graphics.Vulkan /// Size of the buffer public MultiFenceHolder(int size) { - Fences = ArrayPool.Shared.Rent(CommandBufferPool.MaxCommandBuffers); + Fences = FencePool.Allocate(); _bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity); } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index f2f68378f..0172b5b56 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -1035,7 +1035,7 @@ namespace Ryujinx.Graphics.Vulkan } } - private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked) + private void SetRenderTargetsInternal(Span colors, ITexture depthStencil, bool filterWriteMasked) { CreateFramebuffer(colors, depthStencil, filterWriteMasked); CreateRenderPass(); @@ -1043,7 +1043,7 @@ namespace Ryujinx.Graphics.Vulkan SignalAttachmentChange(); } - public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) + public void SetRenderTargets(Span colors, ITexture depthStencil) { _framebufferUsingColorWriteMask = false; SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR); @@ -1389,7 +1389,7 @@ namespace Ryujinx.Graphics.Vulkan _currentPipelineHandle = 0; } - private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked) + private void CreateFramebuffer(Span colors, ITexture depthStencil, bool filterWriteMasked) { if (filterWriteMasked) { @@ -1399,7 +1399,7 @@ namespace Ryujinx.Graphics.Vulkan // Just try to remove duplicate attachments. // Save a copy of the array to rebind when mask changes. - void MaskOut() + void MaskOut(ReadOnlySpan colors) { if (!_framebufferUsingColorWriteMask) { @@ -1436,12 +1436,12 @@ namespace Ryujinx.Graphics.Vulkan if (vkBlend.ColorWriteMask == 0) { colors[i] = null; - MaskOut(); + MaskOut(colors); } else if (vkBlend2.ColorWriteMask == 0) { colors[j] = null; - MaskOut(); + MaskOut(colors); } } } diff --git a/src/Ryujinx.Graphics.Vulkan/SyncManager.cs b/src/Ryujinx.Graphics.Vulkan/SyncManager.cs index 149759906..15759b0de 100644 --- a/src/Ryujinx.Graphics.Vulkan/SyncManager.cs +++ b/src/Ryujinx.Graphics.Vulkan/SyncManager.cs @@ -1,6 +1,6 @@ using Ryujinx.Common.Logging; using Silk.NET.Vulkan; -using System.Buffers; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -193,7 +193,8 @@ namespace Ryujinx.Graphics.Vulkan { _firstHandle = first.ID + 1; _handles.RemoveAt(0); - ArrayPool.Shared.Return(first.Waitable.Fences); + Array.Clear(first.Waitable.Fences); + MultiFenceHolder.FencePool.Release(first.Waitable.Fences); first.Waitable = null; } } diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs index 7df2b778f..13a93db39 100644 --- a/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs +++ b/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs @@ -2,6 +2,7 @@ using Microsoft.IO; using Ryujinx.Common; using Ryujinx.Common.Memory; using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -37,7 +38,7 @@ namespace Ryujinx.HLE.HOS.Ipc public IpcMessage(ReadOnlySpan data, long cmdPtr) { - using RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data); + RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data); BinaryReader reader = new(ms); @@ -123,6 +124,8 @@ namespace Ryujinx.HLE.HOS.Ipc } ObjectIds = []; + + MemoryStreamManager.Shared.ReleaseStream(ms); } public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr) diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs index 373899b7b..d22cfb469 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs @@ -20,6 +20,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc _exchangeBufferDescriptors = new List(MaxInternalBuffersCount); } + public KBufferDescriptorTable Clear() + { + _sendBufferDescriptors.Clear(); + _receiveBufferDescriptors.Clear(); + _exchangeBufferDescriptors.Clear(); + + return this; + } + public Result AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state) { return Add(_sendBufferDescriptors, src, dst, size, state); diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs index 385f09020..d83e14ba3 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; @@ -32,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc { KThread currentThread = KernelStatic.GetCurrentThread(); - KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize); + KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize); KernelContext.CriticalSection.Enter(); @@ -55,7 +56,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc { KThread currentThread = KernelStatic.GetCurrentThread(); - KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent); + KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent); KernelContext.CriticalSection.Enter(); diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs index edc3d819e..f2c22c9f3 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs @@ -10,6 +10,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc { class KServerSession : KSynchronizationObject { + public readonly ObjectPool RequestPool = new(() => new KSessionRequest()); + private static readonly MemoryState[] _ipcMemoryStates = [ MemoryState.IpcBuffer3, @@ -274,6 +276,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc KernelContext.CriticalSection.Leave(); WakeClientThread(request, clientResult); + + RequestPool.Release(request); } if (clientHeader.ReceiveListType < 2 && @@ -627,6 +631,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc CloseAllHandles(clientMsg, serverHeader, clientProcess); FinishRequest(request, clientResult); + + RequestPool.Release(request); } if (clientHeader.ReceiveListType < 2 && @@ -865,6 +871,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc // Unmap buffers from server. FinishRequest(request, clientResult); + + RequestPool.Release(request); return serverResult; } @@ -1098,6 +1106,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc foreach (KSessionRequest request in IterateWithRemovalOfAllRequests()) { FinishRequest(request, KernelResult.PortRemoteClosed); + + RequestPool.Release(request); } } @@ -1117,6 +1127,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc { SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed); } + + RequestPool.Release(request); } WakeServerThreads(KernelResult.PortRemoteClosed); diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs index bc3eef71e..69a0d3a02 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs @@ -5,18 +5,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc { class KSessionRequest { - public KBufferDescriptorTable BufferDescriptorTable { get; } + public KBufferDescriptorTable BufferDescriptorTable { get; private set; } - public KThread ClientThread { get; } + public KThread ClientThread { get; private set; } public KProcess ServerProcess { get; set; } - public KWritableEvent AsyncEvent { get; } + public KWritableEvent AsyncEvent { get; private set; } - public ulong CustomCmdBuffAddr { get; } - public ulong CustomCmdBuffSize { get; } + public ulong CustomCmdBuffAddr { get; private set; } + public ulong CustomCmdBuffSize { get; private set; } - public KSessionRequest( + public KSessionRequest Set( KThread clientThread, ulong customCmdBuffAddr, ulong customCmdBuffSize, @@ -27,7 +27,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc CustomCmdBuffSize = customCmdBuffSize; AsyncEvent = asyncEvent; - BufferDescriptorTable = new KBufferDescriptorTable(); + BufferDescriptorTable = BufferDescriptorTable?.Clear() ?? new KBufferDescriptorTable(); + + return this; } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs index 278a9b2ff..3e13b917a 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs @@ -1,10 +1,8 @@ -using Ryujinx.Common; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.Horizon.Common; using System; using System.Collections.Generic; -using System.Linq; using System.Threading; namespace Ryujinx.HLE.HOS.Kernel.Threading @@ -12,12 +10,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading class KAddressArbiter { private const int HasListenersMask = 0x40000000; - private static readonly ObjectPool _threadArrayPool = new(() => []); private readonly KernelContext _context; - private readonly List _condVarThreads; - private readonly List _arbiterThreads; + private readonly Dictionary> _condVarThreads; + private readonly Dictionary> _arbiterThreads; + private readonly ByDynamicPriority _byDynamicPriority; public KAddressArbiter(KernelContext context) { @@ -25,6 +23,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading _condVarThreads = []; _arbiterThreads = []; + _byDynamicPriority = new ByDynamicPriority(); } public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle) @@ -140,9 +139,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading currentThread.MutexAddress = mutexAddress; currentThread.ThreadHandleForUserMutex = threadHandle; - currentThread.CondVarAddress = condVarAddress; - _condVarThreads.Add(currentThread); + if (_condVarThreads.TryGetValue(condVarAddress, out List threads)) + { + int i = 0; + + if (threads.Count > 0) + { + i = threads.BinarySearch(currentThread, _byDynamicPriority); + if (i < 0) i = ~i; + } + + threads.Insert(i, currentThread); + } + else + { + _condVarThreads.Add(condVarAddress, [currentThread]); + } if (timeout != 0) { @@ -165,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading currentThread.MutexOwner?.RemoveMutexWaiter(currentThread); - _condVarThreads.Remove(currentThread); + _condVarThreads[condVarAddress].Remove(currentThread); _context.CriticalSection.Leave(); @@ -200,13 +213,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { _context.CriticalSection.Enter(); - static bool SignalProcessWideKeyPredicate(KThread thread, ulong address) + int validThreads = 0; + _condVarThreads.TryGetValue(address, out List threads); + + if (threads is not null && threads.Count > 0) { - return thread.CondVarAddress == address; + validThreads = WakeThreads(threads, count, TryAcquireMutex); } - - int validThreads = WakeThreads(_condVarThreads, count, TryAcquireMutex, SignalProcessWideKeyPredicate, address); - + if (validThreads == 0) { KernelTransfer.KernelToUser(address, 0); @@ -315,9 +329,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading currentThread.MutexAddress = address; currentThread.WaitingInArbitration = true; + + if (_arbiterThreads.TryGetValue(address, out List threads)) + { + int i = 0; - _arbiterThreads.Add(currentThread); - + if (threads.Count > 0) + { + i = threads.BinarySearch(currentThread, _byDynamicPriority); + if (i < 0) i = ~i; + } + + threads.Insert(i, currentThread); + } + else + { + _arbiterThreads.Add(address, [currentThread]); + } + currentThread.Reschedule(ThreadSchedState.Paused); if (timeout > 0) @@ -336,7 +365,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading if (currentThread.WaitingInArbitration) { - _arbiterThreads.Remove(currentThread); + _arbiterThreads[address].Remove(currentThread); currentThread.WaitingInArbitration = false; } @@ -392,9 +421,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading currentThread.MutexAddress = address; currentThread.WaitingInArbitration = true; + + if (_arbiterThreads.TryGetValue(address, out List threads)) + { + int i = 0; - _arbiterThreads.Add(currentThread); - + if (threads.Count > 0) + { + i = threads.BinarySearch(currentThread, _byDynamicPriority); + if (i < 0) i = ~i; + } + + threads.Insert(i, currentThread); + } + else + { + _arbiterThreads.Add(address, [currentThread]); + } + currentThread.Reschedule(ThreadSchedState.Paused); if (timeout > 0) @@ -413,7 +457,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading if (currentThread.WaitingInArbitration) { - _arbiterThreads.Remove(currentThread); + _arbiterThreads[address].Remove(currentThread); currentThread.WaitingInArbitration = false; } @@ -485,16 +529,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading // The value is decremented if the number of threads waiting is less // or equal to the Count of threads to be signaled, or Count is zero // or negative. It is incremented if there are no threads waiting. - int waitingCount = 0; - - foreach (KThread thread in _arbiterThreads) - { - if (thread.MutexAddress == address && - ++waitingCount >= count) - { - break; - } - } + int waitingCount = _arbiterThreads[address].Count; if (waitingCount > 0) { @@ -561,55 +596,38 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading thread.WaitingInArbitration = false; } - static bool ArbiterThreadPredecate(KThread thread, ulong address) - { - return thread.MutexAddress == address; - } + _arbiterThreads.TryGetValue(address, out List threads); - WakeThreads(_arbiterThreads, count, RemoveArbiterThread, ArbiterThreadPredecate, address); + if (threads is not null && threads.Count > 0) + { + WakeThreads(threads, count, RemoveArbiterThread); + } } private static int WakeThreads( List threads, int count, - Action removeCallback, - Func predicate, - ulong address = 0) + Action removeCallback) { - KThread[] candidates = _threadArrayPool.Allocate(); - if (candidates.Length < threads.Count) - { - Array.Resize(ref candidates, threads.Count); - } - - int validCount = 0; - - for (int i = 0; i < threads.Count; i++) - { - if (predicate(threads[i], address)) - { - candidates[validCount++] = threads[i]; - } - } - - Span candidatesSpan = candidates.AsSpan(..validCount); - - candidatesSpan.Sort((x, y) => (x.DynamicPriority.CompareTo(y.DynamicPriority))); + int validCount = count > 0 ? Math.Min(count, threads.Count) : threads.Count; - if (count > 0) - { - candidatesSpan = candidatesSpan[..Math.Min(count, candidatesSpan.Length)]; - } - - foreach (KThread thread in candidatesSpan) + for (int i = 0; i < validCount; i++) { + KThread thread = threads[i]; removeCallback(thread); - threads.Remove(thread); } - _threadArrayPool.Release(candidates); - + threads.RemoveRange(0, validCount); + return validCount; } + + private class ByDynamicPriority : IComparer + { + public int Compare(KThread x, KThread y) + { + return x!.DynamicPriority.CompareTo(y!.DynamicPriority); + } + } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index 64cd4d595..aaa4ccd99 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -61,8 +61,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public KSynchronizationObject SignaledObj { get; set; } - public ulong CondVarAddress { get; set; } - private ulong _entrypoint; private ThreadStart _customThreadStart; private bool _forcedUnschedulable; diff --git a/src/Ryujinx.HLE/HOS/Services/IpcService.cs b/src/Ryujinx.HLE/HOS/Services/IpcService.cs index 4c354ebc6..c7dee64fb 100644 --- a/src/Ryujinx.HLE/HOS/Services/IpcService.cs +++ b/src/Ryujinx.HLE/HOS/Services/IpcService.cs @@ -23,6 +23,9 @@ namespace Ryujinx.HLE.HOS.Services private int _selfId; private bool _isDomain; + // cache array so we don't recreate it all the time + private object[] _parameters = [null]; + public IpcService(ServerBase server = null, bool registerTipc = false) { Stopwatch sw = Stopwatch.StartNew(); @@ -146,7 +149,9 @@ namespace Ryujinx.HLE.HOS.Services { Logger.Trace?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}"); - result = (ResultCode)processRequest.Invoke(service, [context]); + _parameters[0] = context; + + result = (ResultCode)processRequest.Invoke(service, _parameters); } else { @@ -196,7 +201,9 @@ namespace Ryujinx.HLE.HOS.Services { Logger.Debug?.Print(LogClass.KernelIpc, $"{GetType().Name}: {processRequest.Name}"); - result = (ResultCode)processRequest.Invoke(this, [context]); + _parameters[0] = context; + + result = (ResultCode)processRequest.Invoke(this, _parameters); } else { diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs index 598c7e6e2..ed14b3e15 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs @@ -59,6 +59,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv // TODO: This should call set:sys::GetDebugModeFlag private readonly bool _debugModeEnabled = false; + + private byte[] _ioctl2Buffer = []; + private byte[] _ioctlArgumentBuffer = []; + private byte[] _ioctl3Buffer = []; public INvDrvServices(ServiceCtx context) : base(context.Device.System.NvDrvServer) { @@ -128,27 +132,38 @@ namespace Ryujinx.HLE.HOS.Services.Nv if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments)) { - arguments = new byte[inputDataSize]; + if (_ioctlArgumentBuffer.Length < (int)inputDataSize) + { + Array.Resize(ref _ioctlArgumentBuffer, (int)inputDataSize); + } + + arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize); + context.Memory.Read(inputDataPosition, arguments); } - else - { - arguments = arguments.ToArray(); - } } else if (isWrite) { - byte[] outputData = new byte[outputDataSize]; - - arguments = new Span(outputData); + if (_ioctlArgumentBuffer.Length < (int)outputDataSize) + { + Array.Resize(ref _ioctlArgumentBuffer, (int)outputDataSize); + } + + arguments = _ioctlArgumentBuffer.AsSpan(0, (int)outputDataSize); } else { - byte[] temp = new byte[inputDataSize]; + if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments)) + { + if (_ioctlArgumentBuffer.Length < (int)inputDataSize) + { + Array.Resize(ref _ioctlArgumentBuffer, (int)inputDataSize); + } + + arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize); - context.Memory.Read(inputDataPosition, temp); - - arguments = new Span(temp); + context.Memory.Read(inputDataPosition, arguments); + } } return NvResult.Success; @@ -270,7 +285,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) { - context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments); } } } @@ -474,13 +489,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments); - byte[] inlineInBuffer = null; - if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span inlineInBufferSpan)) { - inlineInBuffer = _byteArrayPool.Rent((int)inlineInBufferSize); - inlineInBufferSpan = inlineInBuffer; - context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan[..(int)inlineInBufferSize]); + if (_ioctl2Buffer.Length < (int)inlineInBufferSize) + { + Array.Resize(ref _ioctl2Buffer, (int)inlineInBufferSize); + } + + inlineInBufferSpan = _ioctl2Buffer.AsSpan(0, (int)inlineInBufferSize); + context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan); } if (errorCode == NvResult.Success) @@ -489,7 +506,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv if (errorCode == NvResult.Success) { - NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan[..(int)inlineInBufferSize]); + NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan); if (internalResult == NvInternalResult.NotImplemented) { @@ -500,15 +517,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) { - context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments); } } } - - if (inlineInBuffer is not null) - { - _byteArrayPool.Return(inlineInBuffer); - } } context.ResponseData.Write((uint)errorCode); @@ -531,13 +543,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments); - byte[] inlineOutBuffer = null; - if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span inlineOutBufferSpan)) { - inlineOutBuffer = _byteArrayPool.Rent((int)inlineOutBufferSize); - inlineOutBufferSpan = inlineOutBuffer; - context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize]); + if (_ioctl3Buffer.Length < (int)inlineOutBufferSize) + { + Array.Resize(ref _ioctl3Buffer, (int)inlineOutBufferSize); + } + + inlineOutBufferSpan = _ioctl3Buffer.AsSpan(0, (int)inlineOutBufferSize); + context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan); } if (errorCode == NvResult.Success) @@ -546,7 +560,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv if (errorCode == NvResult.Success) { - NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan[..(int)inlineOutBufferSize]); + NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan); if (internalResult == NvInternalResult.NotImplemented) { @@ -557,16 +571,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) { - context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); - context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize].ToArray()); + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments); + context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan); } } } - - if (inlineOutBuffer is not null) - { - _byteArrayPool.Return(inlineOutBuffer); - } } context.ResponseData.Write((uint)errorCode); diff --git a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs index dcc01bf38..a54dc637e 100644 --- a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs +++ b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs @@ -454,8 +454,9 @@ namespace Ryujinx.HLE.HOS.Services response.RawData = _responseDataStream.ToArray(); - using RecyclableMemoryStream responseStream = response.GetStreamTipc(); + RecyclableMemoryStream responseStream = response.GetStreamTipc(); _selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence()); + MemoryStreamManager.Shared.ReleaseStream(responseStream); } else { @@ -464,8 +465,9 @@ namespace Ryujinx.HLE.HOS.Services if (!isTipcCommunication) { - using RecyclableMemoryStream responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48)); + RecyclableMemoryStream responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48)); _selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence()); + MemoryStreamManager.Shared.ReleaseStream(responseStream); } return shouldReply; diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs index d723e5322..412337b6b 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs @@ -19,6 +19,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl private int _waitingThreadHandle; private MultiWaitHolderBase _signaledHolder; + + ObjectPool _objectHandlePool = new(() => new int[64]); + ObjectPool _objectPool = new(() => new MultiWaitHolderBase[64]); public long CurrentTime { get; private set; } @@ -76,11 +79,15 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl private MultiWaitHolderBase WaitAnyHandleImpl(bool infinite, long timeout) { - Span objectHandles = new int[64]; + int[] objectHandles = _objectHandlePool.Allocate(); + Span objectHandlesSpan = objectHandles; + objectHandlesSpan.Clear(); - Span objects = new MultiWaitHolderBase[64]; + MultiWaitHolderBase[] objects = _objectPool.Allocate(); + Span objectsSpan = objects; + objectsSpan.Clear(); - int count = FillObjectsArray(objectHandles, objects); + int count = FillObjectsArray(objectHandlesSpan, objectsSpan); long endTime = infinite ? long.MaxValue : PerformanceCounter.ElapsedMilliseconds * 1000000; @@ -98,7 +105,7 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl } else { - index = WaitSynchronization(objectHandles[..count], minTimeout); + index = WaitSynchronization(objectHandlesSpan[..count], minTimeout); DebugUtil.Assert(index != WaitInvalid); } @@ -116,12 +123,18 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl { _signaledHolder = minTimeoutObject; + _objectHandlePool.Release(objectHandles); + _objectPool.Release(objects); + return _signaledHolder; } } } else { + _objectHandlePool.Release(objectHandles); + _objectPool.Release(objects); + return null; } @@ -131,6 +144,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl { if (_signaledHolder != null) { + _objectHandlePool.Release(objectHandles); + _objectPool.Release(objects); + return _signaledHolder; } } @@ -139,8 +155,11 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl default: lock (_lock) { - _signaledHolder = objects[index]; + _signaledHolder = objectsSpan[index]; + _objectHandlePool.Release(objectHandles); + _objectPool.Release(objects); + return _signaledHolder; } } diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index dd8907a4b..84f9e89ab 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -532,8 +532,6 @@ namespace Ryujinx.Input.HLE hidKeyboard.Modifier |= value << entry.Target; } - - ArrayPool.Shared.Return(keyboardState.KeysState); return hidKeyboard; diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs index 866504128..f2936aa72 100644 --- a/src/Ryujinx.Input/HLE/NpadManager.cs +++ b/src/Ryujinx.Input/HLE/NpadManager.cs @@ -20,7 +20,6 @@ namespace Ryujinx.Input.HLE { public class NpadManager : IDisposable { - private static readonly ObjectPool> _hleMotionStatesPool = new (() => new List(NpadDevices.MaxControllers)); private readonly CemuHookClient _cemuHookClient; private readonly Lock _lock = new(); @@ -40,6 +39,9 @@ namespace Ryujinx.Input.HLE private bool _enableKeyboard; private bool _enableMouse; private Switch _device; + + private readonly List _hleInputStates = []; + private readonly List _hleMotionStates = new(NpadDevices.MaxControllers); public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver) { @@ -217,8 +219,8 @@ namespace Ryujinx.Input.HLE { lock (_lock) { - List hleInputStates = []; - List hleMotionStates = _hleMotionStatesPool.Allocate(); + _hleInputStates.Clear(); + _hleMotionStates.Clear(); KeyboardInput? hleKeyboardInput = null; @@ -260,14 +262,14 @@ namespace Ryujinx.Input.HLE inputState.PlayerId = playerIndex; motionState.Item1.PlayerId = playerIndex; - hleInputStates.Add(inputState); - hleMotionStates.Add(motionState.Item1); + _hleInputStates.Add(inputState); + _hleMotionStates.Add(motionState.Item1); if (isJoyconPair && !motionState.Item2.Equals(default)) { motionState.Item2.PlayerId = playerIndex; - hleMotionStates.Add(motionState.Item2); + _hleMotionStates.Add(motionState.Item2); } } @@ -276,8 +278,8 @@ namespace Ryujinx.Input.HLE hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver); } - _device.Hid.Npads.Update(hleInputStates); - _device.Hid.Npads.UpdateSixAxis(hleMotionStates); + _device.Hid.Npads.Update(_hleInputStates); + _device.Hid.Npads.UpdateSixAxis(_hleMotionStates); if (hleKeyboardInput.HasValue) { @@ -328,10 +330,7 @@ namespace Ryujinx.Input.HLE _device.Hid.Mouse.Update(0, 0); } - _device.TamperMachine.UpdateInput(hleInputStates); - - hleMotionStates.Clear(); - _hleMotionStatesPool.Release(hleMotionStates); + _device.TamperMachine.UpdateInput(_hleInputStates); } } diff --git a/src/Ryujinx.Input/IKeyboard.cs b/src/Ryujinx.Input/IKeyboard.cs index 7fecaaa5d..c51d5aea3 100644 --- a/src/Ryujinx.Input/IKeyboard.cs +++ b/src/Ryujinx.Input/IKeyboard.cs @@ -8,6 +8,8 @@ namespace Ryujinx.Input /// public interface IKeyboard : IGamepad { + private static bool[] _keyState; + /// /// Check if a given key is pressed on the keyboard. /// @@ -29,15 +31,17 @@ namespace Ryujinx.Input [MethodImpl(MethodImplOptions.AggressiveInlining)] static KeyboardStateSnapshot GetStateSnapshot(IKeyboard keyboard) { + if (_keyState is null) + { + _keyState = new bool[(int)Key.Count]; + } - bool[] keysState = ArrayPool.Shared.Rent((int)Key.Count); - for (Key key = 0; key < Key.Count; key++) { - keysState[(int)key] = keyboard.IsPressed(key); + _keyState[(int)key] = keyboard.IsPressed(key); } - return new KeyboardStateSnapshot(keysState); + return new KeyboardStateSnapshot(_keyState); } } } diff --git a/src/Ryujinx.Memory/Range/INonOverlappingRange.cs b/src/Ryujinx.Memory/Range/INonOverlappingRange.cs index c6a0197d4..09311a830 100644 --- a/src/Ryujinx.Memory/Range/INonOverlappingRange.cs +++ b/src/Ryujinx.Memory/Range/INonOverlappingRange.cs @@ -3,7 +3,7 @@ namespace Ryujinx.Memory.Range /// /// Range of memory that can be split in two. /// - public interface INonOverlappingRange : IRange + public interface INonOverlappingRange : IRangeListRange where T : class, IRangeListRange { /// /// Split this region into two, around the specified address. @@ -11,6 +11,6 @@ namespace Ryujinx.Memory.Range /// /// Address to split the region around /// The second part of the split region, with start address at the given split. - public INonOverlappingRange Split(ulong splitAddress); + public INonOverlappingRange Split(ulong splitAddress); } } diff --git a/src/Ryujinx.Memory/Range/IRange.cs b/src/Ryujinx.Memory/Range/IRange.cs index c85e21d1d..c8f50a921 100644 --- a/src/Ryujinx.Memory/Range/IRange.cs +++ b/src/Ryujinx.Memory/Range/IRange.cs @@ -24,8 +24,8 @@ namespace Ryujinx.Memory.Range /// Check if this range overlaps with another. /// /// Base address - /// Size of the range + /// EndAddress of the range /// True if overlapping, false otherwise - bool OverlapsWith(ulong address, ulong size); + bool OverlapsWith(ulong address, ulong endAddress); } } diff --git a/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs b/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs index 7560d2ae7..1ef51644c 100644 --- a/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs +++ b/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs @@ -11,7 +11,7 @@ namespace Ryujinx.Memory.Range /// A range list that assumes ranges are non-overlapping, with list items that can be split in two to avoid overlaps. /// /// Type of the range. - public unsafe class NonOverlappingRangeList : RangeListBase where T : class, INonOverlappingRange + public class NonOverlappingRangeList : RangeListBase where T : class, INonOverlappingRange { public readonly ReaderWriterLockSlim Lock = new(); @@ -32,83 +32,18 @@ namespace Ryujinx.Memory.Range /// The item to be added public override void Add(T item) { + Debug.Assert(item.Address != item.EndAddress); + int index = BinarySearch(item.Address); if (index < 0) { index = ~index; } - - RangeItem rangeItem = _rangeItemPool.Allocate().Set(item); - - Insert(index, rangeItem); - } - - /// - /// Updates an item's end address on the list. Address must be the same. - /// - /// The item to be updated - /// True if the item was located and updated, false otherwise - protected override bool Update(T item) - { - int index = BinarySearch(item.Address); - - if (index >= 0 && Items[index].Value.Equals(item)) - { - RangeItem rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next }; - - if (index > 0) - { - Items[index - 1].Next = rangeItem; - } - - if (index < Count - 1) - { - Items[index + 1].Previous = rangeItem; - } - - Items[index] = rangeItem; - - return true; - } - - return false; - } - - /// - /// Updates an item's end address on the list. Address must be the same. - /// - /// The RangeItem to be updated - /// True if the item was located and updated, false otherwise - protected override bool Update(RangeItem item) - { - int index = BinarySearch(item.Address); - - RangeItem rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next }; - - if (index > 0) - { - Items[index - 1].Next = rangeItem; - } - - if (index < Count - 1) - { - Items[index + 1].Previous = rangeItem; - } - - Items[index] = rangeItem; - - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Insert(int index, RangeItem item) - { - Debug.Assert(item.Address != item.EndAddress); if (Count + 1 > Items.Length) { - Array.Resize(ref Items, Items.Length + BackingGrowthSize); + Array.Resize(ref Items, (int)(Items.Length * 1.5)); } if (index >= Count) @@ -145,8 +80,6 @@ namespace Ryujinx.Memory.Range [MethodImpl(MethodImplOptions.AggressiveInlining)] private void RemoveAt(int index) { - _rangeItemPool.Release(Items[index]); - if (index < Count - 1) { Items[index + 1].Previous = index > 0 ? Items[index - 1] : null; @@ -173,7 +106,7 @@ namespace Ryujinx.Memory.Range { int index = BinarySearch(item.Address); - if (index >= 0 && Items[index].Value.Equals(item)) + if (index >= 0 && Items[index] == item) { RemoveAt(index); @@ -188,7 +121,7 @@ namespace Ryujinx.Memory.Range /// /// The first item in the range of items to be removed /// The last item in the range of items to be removed - public override void RemoveRange(RangeItem startItem, RangeItem endItem) + public override void RemoveRange(T startItem, T endItem) { if (startItem is null) { @@ -197,7 +130,7 @@ namespace Ryujinx.Memory.Range if (startItem == endItem) { - Remove(startItem.Value); + Remove(startItem); return; } @@ -229,42 +162,45 @@ namespace Ryujinx.Memory.Range /// Size of the range public void RemoveRange(ulong address, ulong size) { - int startIndex = BinarySearchLeftEdge(address, address + size); + (int startIndex, int endIndex) = BinarySearchEdges(address, address + size); if (startIndex < 0) { return; } - int endIndex = startIndex; - - while (Items[endIndex] is not null && Items[endIndex].Address < address + size) + if (startIndex == endIndex - 1) { - if (endIndex == Count - 1) - { - break; - } - - endIndex++; + RemoveAt(startIndex); + return; } - if (endIndex < Count - 1) + RemoveRangeInternal(startIndex, endIndex); + } + + /// + /// Removes a range of items from the item list + /// + /// Start index of the range + /// End index of the range (exclusive) + private void RemoveRangeInternal(int index, int endIndex) + { + if (endIndex < Count) { - Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null; + Items[endIndex].Previous = index > 0 ? Items[index - 1] : null; } - if (startIndex > 0) + if (index > 0) { - Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null; + Items[index - 1].Next = endIndex < Count ? Items[endIndex] : null; } - - if (endIndex < Count - 1) + if (endIndex < Count) { - Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1); + Array.Copy(Items, endIndex, Items, index, Count - endIndex); } - Count -= endIndex - startIndex + 1; + Count -= endIndex - index; } /// @@ -296,8 +232,8 @@ namespace Ryujinx.Memory.Range // So we need to return both the split 0-1 and 1-2 ranges. Lock.EnterWriteLock(); - (RangeItem first, RangeItem last) = FindOverlapsAsNodes(address, size); - list = new List(); + (T first, T last) = FindOverlapsAsNodes(address, size); + list = []; if (first is null) { @@ -311,42 +247,41 @@ namespace Ryujinx.Memory.Range ulong lastAddress = address; ulong endAddress = address + size; - RangeItem current = first; + T current = first; while (last is not null && current is not null && current.Address < endAddress) { - T region = current.Value; - if (first == last && region.Address == address && region.Size == size) + if (first == last && current.Address == address && current.Size == size) { // Exact match, no splitting required. - list.Add(region); + list.Add(current); Lock.ExitWriteLock(); return; } - if (lastAddress < region.Address) + if (lastAddress < current.Address) { // There is a gap between this region and the last. We need to fill it. - T fillRegion = factory(lastAddress, region.Address - lastAddress); + T fillRegion = factory(lastAddress, current.Address - lastAddress); list.Add(fillRegion); Add(fillRegion); } - if (region.Address < address) + if (current.Address < address) { // Split the region around our base address and take the high half. - region = Split(region, address); + current = Split(current, address); } - if (region.EndAddress > address + size) + if (current.EndAddress > address + size) { // Split the region around our end address and take the low half. - Split(region, address + size); + Split(current, address + size); } - list.Add(region); - lastAddress = region.EndAddress; + list.Add(current); + lastAddress = current.EndAddress; current = current.Next; } @@ -374,7 +309,6 @@ namespace Ryujinx.Memory.Range private T Split(T region, ulong splitAddress) { T newRegion = (T)region.Split(splitAddress); - Update(region); Add(newRegion); return newRegion; } @@ -386,16 +320,11 @@ namespace Ryujinx.Memory.Range /// Size in bytes of the range /// The leftmost overlapping item, or null if none is found [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override RangeItem FindOverlap(ulong address, ulong size) + public override T FindOverlap(ulong address, ulong size) { int index = BinarySearchLeftEdge(address, address + size); - if (index < 0) - { - return null; - } - - return Items[index]; + return index < 0 ? null : Items[index]; } /// @@ -405,16 +334,11 @@ namespace Ryujinx.Memory.Range /// Size in bytes of the range /// The overlapping item, or null if none is found [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override RangeItem FindOverlapFast(ulong address, ulong size) + public override T FindOverlapFast(ulong address, ulong size) { int index = BinarySearch(address, address + size); - if (index < 0) - { - return null; - } - - return Items[index]; + return index < 0 ? null : Items[index]; } /// @@ -424,23 +348,18 @@ namespace Ryujinx.Memory.Range /// Size in bytes of the range /// The first and last overlapping items, or null if none are found [MethodImpl(MethodImplOptions.AggressiveInlining)] - public (RangeItem, RangeItem) FindOverlapsAsNodes(ulong address, ulong size) + public (T, T) FindOverlapsAsNodes(ulong address, ulong size) { (int index, int endIndex) = BinarySearchEdges(address, address + size); - if (index < 0) - { - return (null, null); - } - - return (Items[index], Items[endIndex - 1]); + return index < 0 ? (null, null) : (Items[index], Items[endIndex - 1]); } - public RangeItem[] FindOverlapsAsArray(ulong address, ulong size, out int length) + public T[] FindOverlapsAsArray(ulong address, ulong size, out int length) { (int index, int endIndex) = BinarySearchEdges(address, address + size); - RangeItem[] result; + T[] result; if (index < 0) { @@ -449,29 +368,20 @@ namespace Ryujinx.Memory.Range } else { - result = ArrayPool>.Shared.Rent(endIndex - index); + result = ArrayPool.Shared.Rent(endIndex - index); length = endIndex - index; - Array.Copy(Items, index, result, 0, endIndex - index); + Items.AsSpan(index, endIndex - index).CopyTo(result); } return result; } - public Span> FindOverlapsAsSpan(ulong address, ulong size) + public ReadOnlySpan FindOverlapsAsSpan(ulong address, ulong size) { (int index, int endIndex) = BinarySearchEdges(address, address + size); - Span> result; - - if (index < 0) - { - result = []; - } - else - { - result = Items.AsSpan().Slice(index, endIndex - index); - } + ReadOnlySpan result = index < 0 ? [] : Items.AsSpan(index, endIndex - index); return result; } @@ -480,7 +390,7 @@ namespace Ryujinx.Memory.Range { for (int i = 0; i < Count; i++) { - yield return Items[i].Value; + yield return Items[i]; } } } diff --git a/src/Ryujinx.Memory/Range/RangeList.cs b/src/Ryujinx.Memory/Range/RangeList.cs index 63025f1e8..e7ea55a94 100644 --- a/src/Ryujinx.Memory/Range/RangeList.cs +++ b/src/Ryujinx.Memory/Range/RangeList.cs @@ -14,14 +14,14 @@ namespace Ryujinx.Memory.Range /// startIndex is inclusive. /// endIndex is exclusive. /// - public readonly struct OverlapResult where T : IRange + public readonly struct OverlapResult where T : class, IRangeListRange { public readonly int StartIndex = -1; public readonly int EndIndex = -1; - public readonly RangeItem QuickResult; + public readonly T QuickResult; public int Count => EndIndex - StartIndex; - public OverlapResult(int startIndex, int endIndex, RangeItem quickResult = null) + public OverlapResult(int startIndex, int endIndex, T quickResult = null) { this.StartIndex = startIndex; this.EndIndex = endIndex; @@ -33,7 +33,7 @@ namespace Ryujinx.Memory.Range /// Sorted list of ranges that supports binary search. /// /// Type of the range. - public class RangeList : RangeListBase where T : IRange + public class RangeList : RangeListBase where T : class, IRangeListRange { public readonly ReaderWriterLockSlim Lock = new(); @@ -61,104 +61,6 @@ namespace Ryujinx.Memory.Range index = ~index; } - Insert(index, new RangeItem(item)); - } - - /// - /// Updates an item's end address on the list. Address must be the same. - /// - /// The item to be updated - /// True if the item was located and updated, false otherwise - protected override bool Update(T item) - { - int index = BinarySearch(item.Address); - - if (index >= 0) - { - while (index < Count) - { - if (Items[index].Value.Equals(item)) - { - RangeItem rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next }; - - if (index > 0) - { - Items[index - 1].Next = rangeItem; - } - - if (index < Count - 1) - { - Items[index + 1].Previous = rangeItem; - } - - Items[index] = rangeItem; - - return true; - } - - if (Items[index].Address > item.Address) - { - break; - } - - index++; - } - } - - return false; - } - - /// - /// Updates an item's end address on the list. Address must be the same. - /// - /// The RangeItem to be updated - /// True if the item was located and updated, false otherwise - protected override bool Update(RangeItem item) - { - int index = BinarySearch(item.Address); - - if (index >= 0) - { - while (index < Count) - { - if (Items[index].Equals(item)) - { - RangeItem rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next }; - - if (index > 0) - { - Items[index - 1].Next = rangeItem; - } - - if (index < Count - 1) - { - Items[index + 1].Previous = rangeItem; - } - - Items[index] = rangeItem; - - return true; - } - - if (Items[index].Address > item.Address) - { - break; - } - - index++; - } - } - - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Insert(int index, RangeItem item) - { - Debug.Assert(item.Address != item.EndAddress); - - Debug.Assert(item.Address % 32 == 0); - if (Count + 1 > Items.Length) { Array.Resize(ref Items, Items.Length + BackingGrowthSize); @@ -220,7 +122,7 @@ namespace Ryujinx.Memory.Range /// The first item in the range of items to be removed /// The last item in the range of items to be removed [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void RemoveRange(RangeItem startItem, RangeItem endItem) + public override void RemoveRange(T startItem, T endItem) { if (startItem is null) { @@ -229,30 +131,29 @@ namespace Ryujinx.Memory.Range if (startItem == endItem) { - Remove(startItem.Value); + Remove(startItem); return; } - int startIndex = BinarySearch(startItem.Address); - int endIndex = BinarySearch(endItem.Address); + (int index, int endIndex) = BinarySearchEdges(startItem.Address, endItem.EndAddress); - if (endIndex < Count - 1) + if (endIndex < Count) { - Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null; + Items[endIndex].Previous = index > 0 ? Items[index - 1] : null; } - if (startIndex > 0) + if (index > 0) { - Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null; + Items[index - 1].Next = endIndex < Count ? Items[endIndex] : null; } - if (endIndex < Count - 1) + if (endIndex < Count) { - Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1); + Array.Copy(Items, endIndex, Items, index, Count - endIndex); } - Count -= endIndex - startIndex + 1; + Count -= endIndex - index; } /// @@ -268,7 +169,7 @@ namespace Ryujinx.Memory.Range { while (index < Count) { - if (Items[index].Value.Equals(item)) + if (Items[index] == item) { RemoveAt(index); @@ -298,7 +199,7 @@ namespace Ryujinx.Memory.Range /// Size in bytes of the range /// The overlapping item, or the default value for the type if none found [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override RangeItem FindOverlap(ulong address, ulong size) + public override T FindOverlap(ulong address, ulong size) { int index = BinarySearchLeftEdge(address, address + size); @@ -321,7 +222,7 @@ namespace Ryujinx.Memory.Range /// Size in bytes of the range /// The overlapping item, or the default value for the type if none found [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override RangeItem FindOverlapFast(ulong address, ulong size) + public override T FindOverlapFast(ulong address, ulong size) { int index = BinarySearch(address, address + size); @@ -340,7 +241,7 @@ namespace Ryujinx.Memory.Range /// Size in bytes of the range /// Output array where matches will be written. It is automatically resized to fit the results /// Range information of overlapping items found - private OverlapResult FindOverlaps(ulong address, ulong size, ref RangeItem[] output) + private OverlapResult FindOverlaps(ulong address, ulong size, ref T[] output) { int outputCount = 0; @@ -353,7 +254,7 @@ namespace Ryujinx.Memory.Range for (int i = startIndex; i < Count; i++) { - ref RangeItem item = ref Items[i]; + T item = Items[i]; if (item.Address >= endAddress) { @@ -398,7 +299,7 @@ namespace Ryujinx.Memory.Range { for (int i = 0; i < Count; i++) { - yield return Items[i].Value; + yield return Items[i]; } } } diff --git a/src/Ryujinx.Memory/Range/RangeListBase.cs b/src/Ryujinx.Memory/Range/RangeListBase.cs index 01fe1b0dc..9f0284253 100644 --- a/src/Ryujinx.Memory/Range/RangeListBase.cs +++ b/src/Ryujinx.Memory/Range/RangeListBase.cs @@ -1,56 +1,22 @@ using Ryujinx.Common; +using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Ryujinx.Memory.Range { - public class RangeItem where TValue : IRange + public interface IRangeListRange : IRange where TValue : class, IRangeListRange { - public RangeItem Next; - public RangeItem Previous; - - public ulong Address; - public ulong EndAddress; - - public TValue Value; - - public RangeItem() - { - - } - - public RangeItem(TValue value) - { - Address = value.Address; - EndAddress = value.Address + value.Size; - Value = value; - } - - public RangeItem Set(TValue value) - { - Next = null; - Previous = null; - Address = value.Address; - EndAddress = value.Address + value.Size; - Value = value; - - return this; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool OverlapsWith(ulong address, ulong endAddress) - { - return Address < endAddress && address < EndAddress; - } + public TValue Next { get; set; } + public TValue Previous { get; set; } } - public unsafe abstract class RangeListBase : IEnumerable where T : IRange + public unsafe abstract class RangeListBase : IEnumerable where T : class, IRangeListRange { - protected static readonly ObjectPool> _rangeItemPool = new(() => new RangeItem()); private const int BackingInitialSize = 1024; - protected RangeItem[] Items; + protected T[] Items; protected readonly int BackingGrowthSize; public int Count { get; protected set; } @@ -62,32 +28,18 @@ namespace Ryujinx.Memory.Range protected RangeListBase(int backingInitialSize = BackingInitialSize) { BackingGrowthSize = backingInitialSize; - Items = new RangeItem[backingInitialSize]; + Items = new T[backingInitialSize]; } public abstract void Add(T item); - - /// - /// Updates an item's end address on the list. Address must be the same. - /// - /// The item to be updated - /// True if the item was located and updated, false otherwise - protected abstract bool Update(T item); - - /// - /// Updates an item's end address on the list. Address must be the same. - /// - /// The RangeItem to be updated - /// True if the item was located and updated, false otherwise - protected abstract bool Update(RangeItem item); public abstract bool Remove(T item); - public abstract void RemoveRange(RangeItem startItem, RangeItem endItem); + public abstract void RemoveRange(T startItem, T endItem); - public abstract RangeItem FindOverlap(ulong address, ulong size); + public abstract T FindOverlap(ulong address, ulong size); - public abstract RangeItem FindOverlapFast(ulong address, ulong size); + public abstract T FindOverlapFast(ulong address, ulong size); /// /// Performs binary search on the internal list of items. @@ -106,7 +58,7 @@ namespace Ryujinx.Memory.Range int middle = left + (range >> 1); - ref RangeItem item = ref Items[middle]; + T item = Items[middle]; if (item.Address == address) { @@ -144,7 +96,7 @@ namespace Ryujinx.Memory.Range int middle = left + (range >> 1); - ref RangeItem item = ref Items[middle]; + T item = Items[middle]; if (item.OverlapsWith(address, endAddress)) { @@ -185,7 +137,7 @@ namespace Ryujinx.Memory.Range int middle = left + (range >> 1); - ref RangeItem item = ref Items[middle]; + T item = Items[middle]; bool match = item.OverlapsWith(address, endAddress); @@ -237,7 +189,7 @@ namespace Ryujinx.Memory.Range int middle = right - (range >> 1); - ref RangeItem item = ref Items[middle]; + T item = Items[middle]; bool match = item.OverlapsWith(address, endAddress); @@ -282,7 +234,7 @@ namespace Ryujinx.Memory.Range if (Count == 1) { - ref RangeItem item = ref Items[0]; + T item = Items[0]; if (item.OverlapsWith(address, endAddress)) { @@ -312,7 +264,7 @@ namespace Ryujinx.Memory.Range int middle = left + (range >> 1); - ref RangeItem item = ref Items[middle]; + T item = Items[middle]; bool match = item.OverlapsWith(address, endAddress); @@ -369,7 +321,7 @@ namespace Ryujinx.Memory.Range int middle = right - (range >> 1); - ref RangeItem item = ref Items[middle]; + T item = Items[middle]; bool match = item.OverlapsWith(address, endAddress); diff --git a/src/Ryujinx.Memory/Tracking/AbstractRegion.cs b/src/Ryujinx.Memory/Tracking/AbstractRegion.cs index 7226fe954..09703a253 100644 --- a/src/Ryujinx.Memory/Tracking/AbstractRegion.cs +++ b/src/Ryujinx.Memory/Tracking/AbstractRegion.cs @@ -5,7 +5,7 @@ namespace Ryujinx.Memory.Tracking /// /// A region of memory. /// - abstract class AbstractRegion : INonOverlappingRange + abstract class AbstractRegion : INonOverlappingRange where T : class, INonOverlappingRange { /// /// Base address. @@ -21,6 +21,9 @@ namespace Ryujinx.Memory.Tracking /// End address. /// public ulong EndAddress => Address + Size; + + public T Next { get; set; } + public T Previous { get; set; } /// /// Create a new region. @@ -37,11 +40,11 @@ namespace Ryujinx.Memory.Tracking /// Check if this range overlaps with another. /// /// Base address - /// Size of the range + /// End address /// True if overlapping, false otherwise - public bool OverlapsWith(ulong address, ulong size) + public bool OverlapsWith(ulong address, ulong endAddress) { - return Address < address + size && address < EndAddress; + return Address < endAddress && address < EndAddress; } /// @@ -68,6 +71,6 @@ namespace Ryujinx.Memory.Tracking /// /// Address to split the region around /// The second part of the split region, with start address at the given split. - public abstract INonOverlappingRange Split(ulong splitAddress); + public abstract INonOverlappingRange Split(ulong splitAddress); } } diff --git a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs index b4b06da8c..0160791e8 100644 --- a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs +++ b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs @@ -81,10 +81,10 @@ namespace Ryujinx.Memory.Tracking { NonOverlappingRangeList regions = type == 0 ? _virtualRegions : _guestVirtualRegions; regions.Lock.EnterReadLock(); - Span> overlaps = regions.FindOverlapsAsSpan(va, size); + ReadOnlySpan overlaps = regions.FindOverlapsAsSpan(va, size); for (int i = 0; i < overlaps.Length; i++) { - VirtualRegion region = overlaps[i].Value; + VirtualRegion region = overlaps[i]; // If the region has been fully remapped, signal that it has been mapped again. bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size); @@ -117,11 +117,11 @@ namespace Ryujinx.Memory.Tracking { NonOverlappingRangeList regions = type == 0 ? _virtualRegions : _guestVirtualRegions; regions.Lock.EnterReadLock(); - Span> overlaps = regions.FindOverlapsAsSpan(va, size); + ReadOnlySpan overlaps = regions.FindOverlapsAsSpan(va, size); for (int i = 0; i < overlaps.Length; i++) { - overlaps[i].Value.SignalMappingChanged(false); + overlaps[i].SignalMappingChanged(false); } regions.Lock.ExitReadLock(); } @@ -301,7 +301,7 @@ namespace Ryujinx.Memory.Tracking // We use the non-span method here because keeping the lock will cause a deadlock. regions.Lock.EnterReadLock(); - RangeItem[] overlaps = regions.FindOverlapsAsArray(address, size, out int length); + VirtualRegion[] overlaps = regions.FindOverlapsAsArray(address, size, out int length); regions.Lock.ExitReadLock(); if (length == 0 && !precise) @@ -327,7 +327,7 @@ namespace Ryujinx.Memory.Tracking for (int i = 0; i < length; i++) { - VirtualRegion region = overlaps[i].Value; + VirtualRegion region = overlaps[i]; if (precise) { @@ -341,7 +341,7 @@ namespace Ryujinx.Memory.Tracking if (length != 0) { - ArrayPool>.Shared.Return(overlaps); + ArrayPool.Shared.Return(overlaps); } } } diff --git a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs index b86631db2..e95754c7a 100644 --- a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs +++ b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs @@ -6,7 +6,7 @@ namespace Ryujinx.Memory.Tracking /// /// A region of virtual memory. /// - class VirtualRegion : AbstractRegion + class VirtualRegion : AbstractRegion { public List Handles = []; @@ -137,7 +137,7 @@ namespace Ryujinx.Memory.Tracking } } - public override INonOverlappingRange Split(ulong splitAddress) + public override INonOverlappingRange Split(ulong splitAddress) { VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, Guest, _lastPermission); Size = splitAddress - Address; From 3a593b60845d894ece9e86b967a07e1390949fff Mon Sep 17 00:00:00 2001 From: LotP <22-lotp@users.noreply.git.ryujinx.app> Date: Sat, 6 Dec 2025 20:16:43 -0600 Subject: [PATCH 13/16] Fix kaddressarbiter crash (ryubing/ryujinx!235) See merge request ryubing/ryujinx!235 --- .gitignore | 1 + src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0c46c50c0..6f887e638 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,7 @@ DocProject/Help/html # Click-Once directory publish/ +RyubingMaintainerTools/ # Publish Web Output *.Publish.xml diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs index 3e13b917a..c9ac86fc9 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs @@ -529,7 +529,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading // The value is decremented if the number of threads waiting is less // or equal to the Count of threads to be signaled, or Count is zero // or negative. It is incremented if there are no threads waiting. - int waitingCount = _arbiterThreads[address].Count; + int waitingCount = 0; + + if (_arbiterThreads.TryGetValue(address, out List threads)) + { + waitingCount = threads.Count; + } + if (waitingCount > 0) { From 4bd46c50be85c0f906528b112dadf599e20bcc81 Mon Sep 17 00:00:00 2001 From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app> Date: Thu, 23 Oct 2025 23:39:39 +0800 Subject: [PATCH 14/16] Create minidump at ProcessUnhandledException --- src/Ryujinx.HLE/HOS/Horizon.cs | 5 +++++ src/Ryujinx/Program.cs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index 83aaa1f4d..895d0c2bf 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -510,6 +510,11 @@ namespace Ryujinx.HLE.HOS } } + public string DebugGetApplicationProcessMinidump() + { + return DebugGetApplicationProcess()?.Debugger?.GetMinidump(); + } + internal KProcess DebugGetApplicationProcess() { lock (KernelContext.Processes) diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index d77e79756..90fc568d5 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -382,6 +382,11 @@ namespace Ryujinx.Ava exceptions.Add(initialException); } + if (isTerminating) + { + TryWriteApplicationMinidump(); + } + foreach (Exception e in exceptions) { string message = $"Unhandled exception caught: {e}"; @@ -400,6 +405,31 @@ namespace Ryujinx.Ava } } + private static void TryWriteApplicationMinidump() + { + try + { + if (HLE.Switch.Shared is not { } device) + { + return; + } + + var minidump = device?.System?.DebugGetApplicationProcessMinidump(); + + if (minidump == null) + { + Logger.Warning?.Print(LogClass.Application, "Failed to create minidump"); + return; + } + + Logger.Info?.Print(LogClass.Application, minidump); + } + catch (Exception e) + { + Logger.Error?.Print(LogClass.Application, $"Failed to create minidump: {e.Message}"); + } + } + internal static void Exit() { DiscordIntegrationModule.Exit(); From c65fed8af7cb0cf12217a4432f2671985adbb866 Mon Sep 17 00:00:00 2001 From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app> Date: Thu, 13 Nov 2025 17:30:22 +0800 Subject: [PATCH 15/16] Print a message first in case it crashes again during minidump creation --- src/Ryujinx/Program.cs | 46 ++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index 90fc568d5..68a871f51 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -384,7 +384,26 @@ namespace Ryujinx.Ava if (isTerminating) { - TryWriteApplicationMinidump(); + try + { + // Print a short message first just in case it crashes again during minidump creation (should not happen) + Logger.Error?.Print(LogClass.Application, $"Unhandled exception caught: {initialException.GetType().Name}. Creating guest program minidump..."); + + var minidump = HLE.Switch.Shared?.System?.DebugGetApplicationProcessMinidump(); + + if (minidump == null) + { + Logger.Warning?.Print(LogClass.Application, "Failed to create minidump"); + } + else + { + Logger.Info?.Print(LogClass.Application, minidump); + } + } + catch (Exception e) + { + Logger.Error?.Print(LogClass.Application, $"Failed to create minidump: {e.Message}"); + } } foreach (Exception e in exceptions) @@ -405,31 +424,6 @@ namespace Ryujinx.Ava } } - private static void TryWriteApplicationMinidump() - { - try - { - if (HLE.Switch.Shared is not { } device) - { - return; - } - - var minidump = device?.System?.DebugGetApplicationProcessMinidump(); - - if (minidump == null) - { - Logger.Warning?.Print(LogClass.Application, "Failed to create minidump"); - return; - } - - Logger.Info?.Print(LogClass.Application, minidump); - } - catch (Exception e) - { - Logger.Error?.Print(LogClass.Application, $"Failed to create minidump: {e.Message}"); - } - } - internal static void Exit() { DiscordIntegrationModule.Exit(); From e1e1fe40dfd4197bc79c7f609c92544c12923697 Mon Sep 17 00:00:00 2001 From: Coxxs <58-coxxs@users.noreply.git.ryujinx.app> Date: Sat, 15 Nov 2025 03:46:25 +0800 Subject: [PATCH 16/16] Check if Switch is running before creating minidump --- src/Ryujinx/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index 68a871f51..653fb44c0 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -382,14 +382,14 @@ namespace Ryujinx.Ava exceptions.Add(initialException); } - if (isTerminating) + if (isTerminating && HLE.Switch.Shared is { } device) { try { // Print a short message first just in case it crashes again during minidump creation (should not happen) Logger.Error?.Print(LogClass.Application, $"Unhandled exception caught: {initialException.GetType().Name}. Creating guest program minidump..."); - var minidump = HLE.Switch.Shared?.System?.DebugGetApplicationProcessMinidump(); + var minidump = device.System?.DebugGetApplicationProcessMinidump(); if (minidump == null) {