diff --git a/Directory.Packages.props b/Directory.Packages.props
index fba3792db..9e3e9c97d 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -3,13 +3,13 @@
true
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -22,7 +22,7 @@
-
+
diff --git a/assets/Locales/Root.json b/assets/Locales/Root.json
index c427ea78b..ac9aa09a4 100644
--- a/assets/Locales/Root.json
+++ b/assets/Locales/Root.json
@@ -575,6 +575,31 @@
"zh_TW": "停止模擬"
}
},
+ {
+ "ID": "MenuBarOptionsRestartEmulation",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Restart Emulation",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
{
"ID": "MenuBarOptionsSettings",
"Translations": {
@@ -24876,4 +24901,4 @@
}
}
]
-}
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Common/Utilities/OsUtils.cs b/src/Ryujinx.Common/Utilities/OsUtils.cs
index 29c6e187c..79fe8b722 100644
--- a/src/Ryujinx.Common/Utilities/OsUtils.cs
+++ b/src/Ryujinx.Common/Utilities/OsUtils.cs
@@ -22,10 +22,11 @@ namespace Ryujinx.Common.Utilities
}
// "dumpable" attribute of the calling process
+ private const int PR_GET_DUMPABLE = 3;
private const int PR_SET_DUMPABLE = 4;
- [DllImport("libc", SetLastError = true)]
- private static extern int prctl(int option, int arg2);
+ [LibraryImport("libc", SetLastError = true)]
+ private static partial int prctl(int option, int arg2);
public static void SetCoreDumpable(bool dumpable)
{
@@ -36,5 +37,13 @@ namespace Ryujinx.Common.Utilities
Debug.Assert(result == 0);
}
}
+
+ // Use the below line to display dumpable status in the console:
+ // Console.WriteLine($"{OsUtils.IsCoreDumpable()}");
+ public static bool IsCoreDumpable()
+ {
+ int result = prctl(PR_GET_DUMPABLE, 0);
+ return result == 1;
+ }
}
}
diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs
index d0fe0f1a7..d6eedd32f 100644
--- a/src/Ryujinx.HLE/FileSystem/ContentManager.cs
+++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs
@@ -488,6 +488,8 @@ namespace Ryujinx.HLE.FileSystem
if (keyPaths.Length is 0)
throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
+ List failedFiles = new();
+
foreach (string filePath in keyPaths)
{
try
@@ -497,17 +499,20 @@ namespace Ryujinx.HLE.FileSystem
catch (Exception e)
{
Logger.Error?.Print(LogClass.Application, e.Message);
+ failedFiles.Add(Path.GetFileName(filePath));
continue;
}
string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath));
- if (File.Exists(destPath))
- File.Delete(destPath);
-
File.Copy(filePath, destPath, true);
}
+ if (failedFiles.Count > 0)
+ {
+ throw new InvalidOperationException($"Failed to install the following key files: {string.Join(", ", failedFiles)}");
+ }
+
return;
}
@@ -518,8 +523,6 @@ namespace Ryujinx.HLE.FileSystem
FileInfo info = new(keysSource);
- using FileStream file = File.OpenRead(keysSource);
-
if (info.Extension is not ".keys")
throw new InvalidFirmwarePackageException("Input file extension is not .keys");
@@ -534,10 +537,6 @@ namespace Ryujinx.HLE.FileSystem
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);
}
@@ -1059,7 +1058,7 @@ namespace Ryujinx.HLE.FileSystem
}
}
- public static bool AreKeysAlredyPresent(string pathToCheck)
+ public static bool AreKeysAlreadyPresent(string pathToCheck)
{
string[] fileNames = ["prod.keys", "title.keys", "console.keys", "dev.keys"];
foreach (string file in fileNames)
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs
index b9235b033..990b2e288 100644
--- a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs
@@ -56,6 +56,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
_activeCount = 0;
JoyHold = NpadJoyHoldType.Vertical;
+ SixAxisActive = false;
}
internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
@@ -580,6 +581,24 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return needUpdateRight;
}
+
+ public bool isAtRest(int playerNumber)
+ {
+
+ ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[playerNumber].InternalState;
+ ref SixAxisSensorState storage = ref GetSixAxisSensorLifo(ref currentNpad, false).GetCurrentEntryRef();
+
+ float acceleration = Math.Abs(storage.Acceleration.X)
+ + Math.Abs(storage.Acceleration.Y)
+ + Math.Abs(storage.Acceleration.Z);
+
+ float angularVelocity = Math.Abs(storage.AngularVelocity.X)
+ + Math.Abs(storage.AngularVelocity.Y)
+ + Math.Abs(storage.AngularVelocity.Z);
+
+ // TODO: check against config deadzone and add sensitivity setting
+ return ((acceleration <= 1.0F) && (angularVelocity <= 1.0F));
+ }
private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
{
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
index 28db75663..08e2a2d68 100644
--- a/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
@@ -602,19 +602,33 @@ namespace Ryujinx.HLE.HOS.Services.Hid
}
[CommandCmif(82)]
- // IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest
+ // IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAtRest
public ResultCode IsSixAxisSensorAtRest(ServiceCtx context)
{
int sixAxisSensorHandle = context.RequestData.ReadInt32();
+
+ // 4 byte struct w/ 4-byte alignment
+
+ // uint typeValue = (uint) sixAxisSensorHandle; // 0x0 0x4 TypeValue
+ // uint npadStyleIndex = (uint) sixAxisSensorHandle & 0xff; // 0x0 0x1 NpadStyleIndex
+ int playerNumber = (sixAxisSensorHandle << 8) & 0xff; // 0x1 0x1 PlayerNumber
+ // uint deviceIdx= ((uint) sixAxisSensorHandle << 16) & 0xff; // 0x2 0x1 DeviceIdx
+ // uint unknown = ((uint) sixAxisSensorHandle << 24) & 0xff;
+
+ // 32bit sign extension padding -> if = 0, + offset, else - offset
+
+ // npadStyleIndex = ((npadStyleIndex & 0x8000) == 0) ? npadStyleIndex | 0xFFFF0000 : npadStyleIndex & 0xFFFF0000;
+ // playerNumber = ((playerNumber & 0x8000) == 0) ? playerNumber | 0xFFFF0000 : playerNumber & 0xFFFF0000;
+ // deviceIdx = ((deviceIdx & 0x8000) == 0) ? deviceIdx | 0xFFFF0000 : deviceIdx & 0xFFFF0000;
+ // unknown = ((unknown & 0x8000) == 0) ? unknown | 0xFFFF0000 : unknown & 0xFFFF0000;
+
context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64();
-
- bool isAtRest = true;
-
- context.ResponseData.Write(isAtRest);
-
- Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, isAtRest });
-
+
+ // TODO: link to context.Device.Hid.Npads.SixAxisActive when properly implemented
+ // We currently do not support stopping or starting SixAxisTracking.
+
+ context.ResponseData.Write(context.Device.Hid.Npads.isAtRest(playerNumber));
return ResultCode.Success;
}
@@ -629,7 +643,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor);
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor });
-
+
return ResultCode.Success;
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs
index 8f38f293f..3d8fb5d51 100644
--- a/src/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs
@@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
public ISslService(ServiceCtx context) { }
[CommandCmif(0)]
- // CreateContext(nn::ssl::sf::SslVersion, u64, pid) -> object
+ // CreateContext(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object
public ResultCode CreateContext(ServiceCtx context)
{
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
@@ -126,14 +126,18 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
}
[CommandCmif(100)]
- // CreateContextForSystem(u64 pid, nn::ssl::sf::SslVersion, u64)
+ // CreateContextForSystem(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object
public ResultCode CreateContextForSystem(ServiceCtx context)
{
- ulong pid = context.RequestData.ReadUInt64();
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
+#pragma warning disable IDE0059 // Remove unnecessary value assignment
ulong pidPlaceholder = context.RequestData.ReadUInt64();
+#pragma warning restore IDE0059
- Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { pid, sslVersion, pidPlaceholder });
+ // Note: We use ISslContext here instead of ISslContextForSystem class because Ryujinx implements both in one class.
+ MakeObject(context, new ISslContext(context.Request.HandleDesc.PId, sslVersion));
+
+ Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { sslVersion });
return ResultCode.Success;
}
diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs
index 8d03f81da..d1b85c6b0 100644
--- a/src/Ryujinx/Program.cs
+++ b/src/Ryujinx/Program.cs
@@ -42,6 +42,7 @@ namespace Ryujinx.Ava
public static bool PreviewerDetached { get; private set; }
public static bool UseHardwareAcceleration { get; private set; }
public static string BackendThreadingArg { get; private set; }
+ public static bool CoreDumpArg { get; private set; }
private const uint MbIconwarning = 0x30;
@@ -81,6 +82,8 @@ namespace Ryujinx.Ava
bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui");
bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps");
+ CoreDumpArg = coreDumpArg;
+
// 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.
diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj
index 715460274..489b2c313 100644
--- a/src/Ryujinx/Ryujinx.csproj
+++ b/src/Ryujinx/Ryujinx.csproj
@@ -49,7 +49,7 @@
-
+
diff --git a/src/Ryujinx/UI/RyujinxApp.axaml.cs b/src/Ryujinx/UI/RyujinxApp.axaml.cs
index c778f27fb..22b98dea7 100644
--- a/src/Ryujinx/UI/RyujinxApp.axaml.cs
+++ b/src/Ryujinx/UI/RyujinxApp.axaml.cs
@@ -5,6 +5,7 @@ using Avalonia.Markup.Xaml;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
+using FluentAvalonia.Core;
using FluentAvalonia.UI.Windowing;
using Gommon;
using Ryujinx.Ava.Common.Locale;
@@ -53,6 +54,9 @@ namespace Ryujinx.Ava
{
Name = FormatTitle();
+ // Disable menu animations
+ FAUISettings.SetAnimationsEnabledAtAppLevel(false);
+
AvaloniaXamlLoader.Load(this);
if (OperatingSystem.IsMacOS())
diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
index 48e18a12e..ae84a15a2 100644
--- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs
@@ -174,6 +174,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private string _screenshotKey = "F8";
private float _volume;
private ApplicationData _currentApplicationData;
+ private bool _pendingRestart;
private readonly AutoResetEvent _rendererWaitEvent;
private int _customVSyncInterval;
private int _customVSyncIntervalPercentageProxy;
@@ -1062,7 +1063,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string dialogMessage =
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage);
- if (ContentManager.AreKeysAlredyPresent(systemDirectory))
+ if (ContentManager.AreKeysAlreadyPresent(systemDirectory))
{
dialogMessage +=
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys
@@ -1250,6 +1251,14 @@ namespace Ryujinx.Ava.UI.ViewModels
await LoadApplication(_currentApplicationData);
}
+ else if (_pendingRestart)
+ {
+ _pendingRestart = false;
+
+ Logger.Info?.Print(LogClass.Application, $"Restarting emulation for '{_currentApplicationData.Name}'");
+
+ await LoadApplication(_currentApplicationData);
+ }
else
{
// Otherwise, clear state.
@@ -1258,6 +1267,21 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
+ public void RestartEmulation()
+ {
+ if (AppHost is null || _currentApplicationData is null)
+ {
+ Logger.Warning?.Print(LogClass.Application, "RestartEmulation called but no application is running.");
+
+ return;
+ }
+
+ Logger.Info?.Print(LogClass.Application, $"Restart requested for '{_currentApplicationData.Name}'");
+
+ _pendingRestart = true;
+ AppHost.Stop();
+ }
+
private void Update_StatusBar(object sender, StatusUpdatedEventArgs args)
{
if (ShowMenuAndStatusBar && !ShowLoadProgress)
diff --git a/src/Ryujinx/UI/Views/Input/LedInputView.axaml b/src/Ryujinx/UI/Views/Input/LedInputView.axaml
index c6319f424..327f27718 100644
--- a/src/Ryujinx/UI/Views/Input/LedInputView.axaml
+++ b/src/Ryujinx/UI/Views/Input/LedInputView.axaml
@@ -47,18 +47,13 @@
-
-
+
diff --git a/src/Ryujinx/UI/Views/Input/LedInputView.axaml.cs b/src/Ryujinx/UI/Views/Input/LedInputView.axaml.cs
index dab553f82..d0407beb3 100644
--- a/src/Ryujinx/UI/Views/Input/LedInputView.axaml.cs
+++ b/src/Ryujinx/UI/Views/Input/LedInputView.axaml.cs
@@ -1,4 +1,5 @@
using Avalonia;
+using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
@@ -30,19 +31,17 @@ namespace Ryujinx.UI.Views.Input
InitializeComponent();
}
- private void ColorPickerButton_OnColorChanged(ColorPickerButton sender, ColorButtonColorChangedEventArgs args)
+ private void ColorPicker_OnColorChanged(object sender, ColorChangedEventArgs args)
{
- if (!args.NewColor.HasValue)
- return;
if (!ViewModel.EnableLedChanging)
return;
if (ViewModel.TurnOffLed)
return;
- ViewModel.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32());
+ ViewModel.ParentModel.SelectedGamepad.SetLed(args.NewColor.ToUInt32());
}
- private void ColorPickerButton_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
+ private void ColorPicker_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (!ViewModel.EnableLedChanging)
return;
diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml
index d5a59c181..5e41017d8 100755
--- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml
+++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml
@@ -167,6 +167,12 @@
Icon="{ext:Icon fa-solid fa-stop}"
InputGesture="Escape"
IsEnabled="{Binding IsGameRunning}" />
+