Compare commits

..

20 Commits

Author SHA1 Message Date
Shyanne
abb3b5c370 Merge branch 'master' into more-launch-checks 2026-03-14 16:00:50 -04:00
Coxxs
9cae62096a HLE: Implement CreateContextForSystem (ryubing/ryujinx!285)
See merge request ryubing/ryujinx!285
2026-03-14 13:57:49 -05:00
BowedCascade
648b609ebb Add restart emulation command (ryubing/ryujinx!276)
See merge request ryubing/ryujinx!276
2026-03-14 13:56:20 -05:00
BowedCascade
5ae86fc493 Fix keys file overwrite on installation and method name typo (ryubing/ryujinx!268)
See merge request ryubing/ryujinx!268
2026-03-14 13:52:58 -05:00
Shyanne
7a3afa1deb Merge branch 'master' into more-launch-checks 2026-03-14 12:11:25 -04:00
Shyanne
38b7d26fdd Merge branch 'master' into more-launch-checks 2026-03-14 12:07:49 -04:00
Shyanne
c3f7e1b47b Removed DBusFilePicker
never should have been included tbh, i forgot to remove it lol
2026-01-28 00:27:53 -05:00
Shyanne
ed69f28113 Merge branch 'master' into more-launch-checks 2026-01-28 00:25:21 -05:00
Shyanne
6d6ef44d28 Update macOSNativeInterop.cs 2026-01-05 16:15:31 -05:00
Shyanne
9f363d63c2 Update Program.cs 2026-01-05 15:58:55 -05:00
Shyanne
50df9bf10d Merge branch 'more-launch-checks' of https://git.ryujinx.app/sh0inx/ryujinx into more-launch-checks 2026-01-05 15:54:32 -05:00
Shyanne
068c654d95 added null check for onedrive
separated out native interop to respective classes/methods
2026-01-05 15:50:37 -05:00
Shyanne
df7b940bb9 fixed the boxes fr this time 2026-01-03 12:55:10 -06:00
Shyanne
6cd6c1ae18 added some debugging and fix for fs 2026-01-03 12:55:10 -06:00
Shyanne
f49029803a Added links to flatpak check 2026-01-03 12:55:10 -06:00
Shyanne
2bf306ea58 UI: Included more launch checks
Windows:
- Check for OneDrive install path.

macOS:
- Check for root (needs testing).

Linux:
- Check for root.
- Check for flatpak.
2026-01-03 12:55:10 -06:00
Shyanne
e92b4fdd05 fixed the boxes fr this time 2026-01-03 13:50:27 -05:00
Shyanne
8b7c228cf7 added some debugging and fix for fs 2026-01-02 15:54:42 -05:00
Shyanne
89487d67c4 Added links to flatpak check 2026-01-01 15:10:53 -05:00
Shyanne
9f3394ad30 UI: Included more launch checks
Windows:
- Check for OneDrive install path.

macOS:
- Check for root (needs testing).

Linux:
- Check for root.
- Check for flatpak.
2026-01-01 09:18:43 -05:00
10 changed files with 240 additions and 22 deletions

View File

@@ -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 @@
}
}
]
}
}

View File

@@ -488,6 +488,8 @@ namespace Ryujinx.HLE.FileSystem
if (keyPaths.Length is 0)
throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
List<string> 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)

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
public ISslService(ServiceCtx context) { }
[CommandCmif(0)]
// CreateContext(nn::ssl::sf::SslVersion, u64, pid) -> object<nn::ssl::sf::ISslContext>
// CreateContext(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object<nn::ssl::sf::ISslContext>
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<nn::ssl::sf::ISslContextForSystem>
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;
}

View File

@@ -54,17 +54,39 @@ namespace Ryujinx.Ava
{
if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041))
{
_ = Win32NativeInterop.MessageBoxA(nint.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 20H1 and newer.\n", $"Ryujinx {Version}", MbIconwarning);
Logger.Error?.PrintMsg(LogClass.Application, "Ryujinx is not intended to be run on an outdated version of Windows. Exiting...");
_ = Win32NativeInterop.MessageBoxA(nint.Zero,
"You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 20H1 and newer.\n",
$"Ryujinx {Version}", MbIconwarning);
return 0;
}
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
string onedriveFiles = Environment.GetEnvironmentVariable("Onedrive");
string onedriveConsumerFiles = Environment.GetEnvironmentVariable("OnedriveConsumer");
string onedriveCommercialFiles = Environment.GetEnvironmentVariable("OnedriveCommercial");
// Apparently not everyone has OneDrive shoved onto their system.
if ((onedriveFiles is not null && Environment.CurrentDirectory.StartsWithIgnoreCase(onedriveFiles))
|| (onedriveConsumerFiles is not null && Environment.CurrentDirectory.StartsWithIgnoreCase(onedriveConsumerFiles))
|| (onedriveCommercialFiles is not null && Environment.CurrentDirectory.StartsWithIgnoreCase(onedriveCommercialFiles)))
{
Logger.Error?.PrintMsg(LogClass.Application, "Ryujinx is not intended to be run from a OneDrive folder. Exiting...");
_ = Win32NativeInterop.MessageBoxA(nint.Zero,
"Ryujinx is not intended to be run from a OneDrive folder. Please move it out and relaunch.",
$"Ryujinx {Version}", MbIconwarning);
return 0;
}
string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
string 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);
Logger.Error?.PrintMsg(LogClass.Application, "Ryujinx is not intended to be run from the Program Files folder. Exiting...");
_ = 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;
}
@@ -74,10 +96,54 @@ namespace Ryujinx.Ava
// ...but this reads like it checks if the current is in/has the Windows admin role? lol
if (new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator))
{
_ = Win32NativeInterop.MessageBoxA(nint.Zero, "Ryujinx is not intended to be run as administrator.", $"Ryujinx {Version}", MbIconwarning);
Logger.Error?.PrintMsg(LogClass.Application, "Ryujinx is not intended to be run as administrator. Exiting...");
_ = Win32NativeInterop.MessageBoxA(nint.Zero, "Ryujinx is not intended to be run as administrator.",
$"Ryujinx {Version}", MbIconwarning);
return 0;
}
}
else // Unix
{
// sudo check
[DllImport("libc")]
static extern uint geteuid();
bool root = geteuid().Equals(0);
if (OperatingSystem.IsMacOS())
{
if (root)
{
Logger.Error?.PrintMsg(LogClass.Application, "Ryujinx is not intended to be run as administrator. Exiting...");
macOSNativeInterop.SimpleMessageBox($"Ryujinx {Version}",
"Ryujinx is not intended to be run as administrator.", "Ok");
return 0;
}
}
if (OperatingSystem.IsLinux())
{
if (root)
{
Logger.Error?.PrintMsg(LogClass.Application, "Ryujinx is not intended to be run as administrator. Exiting...");
LinuxSDLInterop.SimpleMessageBox($"Ryujinx {Version}", "Ryujinx is not intended to be run as administrator.");
return 0;
}
string container = Environment.GetEnvironmentVariable("container");
if (container is not null && container.EqualsIgnoreCase("flatpak"))
{
Logger.Warning?.PrintMsg(LogClass.Application, "This is very likely an unofficial build, Ryujinx does NOT have a flatpak!");
Logger.Info?.PrintMsg(LogClass.Application, "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
Logger.Info?.PrintMsg(LogClass.Application, "Please visit https://ryujinx.app/ for our official builds.");
Logger.Info?.PrintMsg(LogClass.Application,
" AppImage >> https://update.ryujinx.app/download/query?os=linuxappimage&arch=x64&rc=stable");
Logger.Info?.PrintMsg(LogClass.Application,
" Tarball >> https://update.ryujinx.app/download/query?os=linux&arch=x64&rc=stable");
Logger.Info?.PrintMsg(LogClass.Application, "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
}
}
}
bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui");
bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps");
@@ -310,7 +376,7 @@ namespace Ryujinx.Ava
"never" => HideCursorMode.Never,
"onidle" => HideCursorMode.OnIdle,
"always" => HideCursorMode.Always,
_ => ConfigurationState.Instance.HideCursor,
_ => ConfigurationState.Instance.HideCursor
};
// Check if memoryManagerMode was overridden.

View File

@@ -0,0 +1,30 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Ava.UI.Helpers
{
public class LinuxSDLInterop
{
// TODO: add a parameter for prompt style
// TODO: look into adding text for the button
// TODO: check success of prompt box
public static int SimpleMessageBox(string caption, string text)
{
const string sdl = "SDL2";
[DllImport(sdl)]
static extern int SDL_Init(uint flags);
[DllImport(sdl, CallingConvention = CallingConvention.Cdecl)]
static extern int SDL_ShowSimpleMessageBox(uint flags, string title, string message, IntPtr window);
[DllImport(sdl)]
static extern void SDL_Quit();
SDL_Init(0);
SDL_ShowSimpleMessageBox(32 /* 32 = warning style */, caption, text, IntPtr.Zero);
SDL_Quit();
return 0;
}
}
}

View File

@@ -0,0 +1,62 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Ava.UI.Helpers
{
public class macOSNativeInterop
{
// TODO: add a parameter for prompt style
// TODO: check success of prompt box
public static int SimpleMessageBox(string caption, string text, string button)
{
// Grab what we need to make the message box.
const string ObjCRuntime = "/usr/lib/libobjc.A.dylib";
const string FoundationFramework = "/System/Library/Frameworks/Foundation.framework/Foundation";
const string AppKitFramework = "/System/Library/Frameworks/AppKit.framework/AppKit";
[DllImport(ObjCRuntime, EntryPoint = "sel_registerName")]
static extern IntPtr GetSelector(string name);
[DllImport(ObjCRuntime, EntryPoint = "objc_getClass")]
static extern IntPtr GetClass(string name);
[DllImport(FoundationFramework, EntryPoint = "objc_msgSend")]
static extern IntPtr SendMessage(IntPtr target, IntPtr selector);
[DllImport(FoundationFramework, EntryPoint = "objc_msgSend")]
static extern IntPtr SendMessageWithParameter(IntPtr target, IntPtr selector, IntPtr param);
[DllImport(ObjCRuntime)]
static extern IntPtr dlopen(string path, int mode);
dlopen(AppKitFramework, 0x1); // have to invoke AppKit so that NSAlert doesn't return a null pointer
IntPtr NSStringClass = GetClass("NSString");
IntPtr Selector = GetSelector("stringWithUTF8String:");
IntPtr SharedApp = SendMessage(GetClass("NSApplication"), GetSelector("sharedApplication"));
IntPtr NSAlert = SendMessage(GetClass("NSAlert"), GetSelector("alloc"));
IntPtr AlertInstance = SendMessage(NSAlert, GetSelector("init"));
// Create caption, text, and button text.
IntPtr boxCaption = SendMessageWithParameter(NSStringClass, Selector, Marshal.StringToHGlobalAnsi(caption));
IntPtr boxText = SendMessageWithParameter(NSStringClass, Selector, Marshal.StringToHGlobalAnsi(text));
IntPtr boxButton = SendMessageWithParameter(NSStringClass, Selector, Marshal.StringToHGlobalAnsi(button));
// Set up the window.
SendMessageWithParameter(SharedApp, GetSelector("setActivationPolicy:"), IntPtr.Zero); // Give it a window.
SendMessageWithParameter(SharedApp, GetSelector("activateIgnoringOtherApps:"), (IntPtr) 1); // Force it to the front.
// Set up the message box.
SendMessageWithParameter(AlertInstance, GetSelector("setAlertStyle:"), IntPtr.Zero); // Set style to warning.
SendMessageWithParameter(AlertInstance, GetSelector("setMessageText:"), boxCaption);
SendMessageWithParameter(AlertInstance, GetSelector("setInformativeText:"), boxText);
SendMessageWithParameter(AlertInstance, GetSelector("addButtonWithTitle:"), boxButton);
// Send prompt to user, then clean up.
SendMessage(AlertInstance, GetSelector("runModal"));
SendMessage(AlertInstance, GetSelector("release"));
return 0;
}
}
}

View File

@@ -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)

View File

@@ -167,6 +167,12 @@
Icon="{ext:Icon fa-solid fa-stop}"
InputGesture="Escape"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Name="RestartEmulationMenuItem"
Header="{ext:Locale MenuBarOptionsRestartEmulation}"
Icon="{ext:Icon fa-solid fa-rotate-right}"
InputGesture="Ctrl + R"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem Command="{Binding SimulateWakeUpMessage}" Header="{ext:Locale MenuBarOptionsSimulateWakeUpMessage}" Icon="{ext:Icon fa-solid fa-sun}" />
<Separator />
<MenuItem

View File

@@ -43,6 +43,7 @@ namespace Ryujinx.Ava.UI.Views.Main
PauseEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Pause());
ResumeEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Resume());
StopEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.ShowExitPrompt().OrCompleted());
RestartEmulationMenuItem.Command = Commands.Create(() => ViewModel.RestartEmulation());
CheatManagerMenuItem.Command = Commands.CreateSilentFail(OpenCheatManagerForCurrentApp);
InstallFileTypesMenuItem.Command = Commands.Create(InstallFileTypes);
UninstallFileTypesMenuItem.Command = Commands.Create(UninstallFileTypes);

View File

@@ -41,6 +41,7 @@
<KeyBinding Gesture="Escape" Command="{Binding ExitCurrentState}" />
<KeyBinding Gesture="Ctrl+A" Command="{Binding OpenAmiiboWindow}" />
<KeyBinding Gesture="Ctrl+B" Command="{Binding OpenBinFile}" />
<KeyBinding Gesture="Ctrl+R" Command="{Binding RestartEmulation}" />
<KeyBinding Gesture="Ctrl+Shift+R" Command="{Binding ReloadRenderDocApi}" />
<KeyBinding Gesture="Ctrl+Shift+C" Command="{Binding ToggleCapture}" />
</Window.KeyBindings>