aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Headless.SDL2/Program.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Headless.SDL2/Program.cs')
-rw-r--r--src/Ryujinx.Headless.SDL2/Program.cs704
1 files changed, 704 insertions, 0 deletions
diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs
new file mode 100644
index 00000000..b0bdb97f
--- /dev/null
+++ b/src/Ryujinx.Headless.SDL2/Program.cs
@@ -0,0 +1,704 @@
+using ARMeilleure.Translation;
+using CommandLine;
+using LibHac.Tools.FsSystem;
+using Ryujinx.Audio.Backends.SDL2;
+using Ryujinx.Common;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Configuration.Hid.Controller;
+using Ryujinx.Common.Configuration.Hid.Controller.Motion;
+using Ryujinx.Common.Configuration.Hid.Keyboard;
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.SystemInterop;
+using Ryujinx.Common.Utilities;
+using Ryujinx.Cpu;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Multithreading;
+using Ryujinx.Graphics.Gpu;
+using Ryujinx.Graphics.Gpu.Shader;
+using Ryujinx.Graphics.OpenGL;
+using Ryujinx.Graphics.Vulkan;
+using Ryujinx.Headless.SDL2.OpenGL;
+using Ryujinx.Headless.SDL2.Vulkan;
+using Ryujinx.HLE;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS;
+using Ryujinx.HLE.HOS.Services.Account.Acc;
+using Ryujinx.Input;
+using Ryujinx.Input.HLE;
+using Ryujinx.Input.SDL2;
+using Silk.NET.Vulkan;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.Json;
+using System.Threading;
+using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
+using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
+using Key = Ryujinx.Common.Configuration.Hid.Key;
+
+namespace Ryujinx.Headless.SDL2
+{
+ class Program
+ {
+ public static string Version { get; private set; }
+
+ private static VirtualFileSystem _virtualFileSystem;
+ private static ContentManager _contentManager;
+ private static AccountManager _accountManager;
+ private static LibHacHorizonManager _libHacHorizonManager;
+ private static UserChannelPersistence _userChannelPersistence;
+ private static InputManager _inputManager;
+ private static Switch _emulationContext;
+ private static WindowBase _window;
+ private static WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
+ private static List<InputConfig> _inputConfiguration;
+ private static bool _enableKeyboard;
+ private static bool _enableMouse;
+
+ private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
+
+ static void Main(string[] args)
+ {
+ Version = ReleaseInformation.GetVersion();
+
+ Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
+
+ if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
+ {
+ AutoResetEvent invoked = new AutoResetEvent(false);
+
+ // MacOS must perform SDL polls from the main thread.
+ Ryujinx.SDL2.Common.SDL2Driver.MainThreadDispatcher = (Action action) =>
+ {
+ invoked.Reset();
+
+ WindowBase.QueueMainThreadAction(() =>
+ {
+ action();
+
+ invoked.Set();
+ });
+
+ invoked.WaitOne();
+ };
+ }
+
+ Parser.Default.ParseArguments<Options>(args)
+ .WithParsed(Load)
+ .WithNotParsed(errors => errors.Output());
+ }
+
+ private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
+ {
+ if (inputId == null)
+ {
+ if (index == PlayerIndex.Player1)
+ {
+ Logger.Info?.Print(LogClass.Application, $"{index} not configured, defaulting to default keyboard.");
+
+ // Default to keyboard
+ inputId = "0";
+ }
+ else
+ {
+ Logger.Info?.Print(LogClass.Application, $"{index} not configured");
+
+ return null;
+ }
+ }
+
+ IGamepad gamepad;
+
+ bool isKeyboard = true;
+
+ gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId);
+
+ if (gamepad == null)
+ {
+ gamepad = _inputManager.GamepadDriver.GetGamepad(inputId);
+ isKeyboard = false;
+
+ if (gamepad == null)
+ {
+ Logger.Error?.Print(LogClass.Application, $"{index} gamepad not found (\"{inputId}\")");
+
+ return null;
+ }
+ }
+
+ string gamepadName = gamepad.Name;
+
+ gamepad.Dispose();
+
+ InputConfig config;
+
+ if (inputProfileName == null || inputProfileName.Equals("default"))
+ {
+ if (isKeyboard)
+ {
+ config = new StandardKeyboardInputConfig
+ {
+ Version = InputConfig.CurrentVersion,
+ Backend = InputBackendType.WindowKeyboard,
+ Id = null,
+ ControllerType = ControllerType.JoyconPair,
+ LeftJoycon = new LeftJoyconCommonConfig<Key>
+ {
+ DpadUp = Key.Up,
+ DpadDown = Key.Down,
+ DpadLeft = Key.Left,
+ DpadRight = Key.Right,
+ ButtonMinus = Key.Minus,
+ ButtonL = Key.E,
+ ButtonZl = Key.Q,
+ ButtonSl = Key.Unbound,
+ ButtonSr = Key.Unbound
+ },
+
+ LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
+ {
+ StickUp = Key.W,
+ StickDown = Key.S,
+ StickLeft = Key.A,
+ StickRight = Key.D,
+ StickButton = Key.F,
+ },
+
+ RightJoycon = new RightJoyconCommonConfig<Key>
+ {
+ ButtonA = Key.Z,
+ ButtonB = Key.X,
+ ButtonX = Key.C,
+ ButtonY = Key.V,
+ ButtonPlus = Key.Plus,
+ ButtonR = Key.U,
+ ButtonZr = Key.O,
+ ButtonSl = Key.Unbound,
+ ButtonSr = Key.Unbound
+ },
+
+ RightJoyconStick = new JoyconConfigKeyboardStick<Key>
+ {
+ StickUp = Key.I,
+ StickDown = Key.K,
+ StickLeft = Key.J,
+ StickRight = Key.L,
+ StickButton = Key.H,
+ }
+ };
+ }
+ else
+ {
+ bool isNintendoStyle = gamepadName.Contains("Nintendo");
+
+ config = new StandardControllerInputConfig
+ {
+ Version = InputConfig.CurrentVersion,
+ Backend = InputBackendType.GamepadSDL2,
+ Id = null,
+ ControllerType = ControllerType.JoyconPair,
+ DeadzoneLeft = 0.1f,
+ DeadzoneRight = 0.1f,
+ RangeLeft = 1.0f,
+ RangeRight = 1.0f,
+ TriggerThreshold = 0.5f,
+ LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
+ {
+ DpadUp = ConfigGamepadInputId.DpadUp,
+ DpadDown = ConfigGamepadInputId.DpadDown,
+ DpadLeft = ConfigGamepadInputId.DpadLeft,
+ DpadRight = ConfigGamepadInputId.DpadRight,
+ ButtonMinus = ConfigGamepadInputId.Minus,
+ ButtonL = ConfigGamepadInputId.LeftShoulder,
+ ButtonZl = ConfigGamepadInputId.LeftTrigger,
+ ButtonSl = ConfigGamepadInputId.Unbound,
+ ButtonSr = ConfigGamepadInputId.Unbound,
+ },
+
+ LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
+ {
+ Joystick = ConfigStickInputId.Left,
+ StickButton = ConfigGamepadInputId.LeftStick,
+ InvertStickX = false,
+ InvertStickY = false,
+ Rotate90CW = false,
+ },
+
+ RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
+ {
+ ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
+ ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
+ ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
+ ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
+ ButtonPlus = ConfigGamepadInputId.Plus,
+ ButtonR = ConfigGamepadInputId.RightShoulder,
+ ButtonZr = ConfigGamepadInputId.RightTrigger,
+ ButtonSl = ConfigGamepadInputId.Unbound,
+ ButtonSr = ConfigGamepadInputId.Unbound,
+ },
+
+ RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
+ {
+ Joystick = ConfigStickInputId.Right,
+ StickButton = ConfigGamepadInputId.RightStick,
+ InvertStickX = false,
+ InvertStickY = false,
+ Rotate90CW = false,
+ },
+
+ Motion = new StandardMotionConfigController
+ {
+ MotionBackend = MotionInputBackendType.GamepadDriver,
+ EnableMotion = true,
+ Sensitivity = 100,
+ GyroDeadzone = 1,
+ },
+ Rumble = new RumbleConfigController
+ {
+ StrongRumble = 1f,
+ WeakRumble = 1f,
+ EnableRumble = false
+ }
+ };
+ }
+ }
+ else
+ {
+ string profileBasePath;
+
+ if (isKeyboard)
+ {
+ profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "keyboard");
+ }
+ else
+ {
+ profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "controller");
+ }
+
+ string path = Path.Combine(profileBasePath, inputProfileName + ".json");
+
+ if (!File.Exists(path))
+ {
+ Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" not found for \"{inputId}\"");
+
+ return null;
+ }
+
+ try
+ {
+ config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig);
+ }
+ catch (JsonException)
+ {
+ Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" parsing failed for \"{inputId}\"");
+
+ return null;
+ }
+ }
+
+ config.Id = inputId;
+ config.PlayerIndex = index;
+
+ string inputTypeName = isKeyboard ? "Keyboard" : "Gamepad";
+
+ Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} configured with {inputTypeName} \"{config.Id}\"");
+
+ // If both stick ranges are 0 (usually indicative of an outdated profile load) then both sticks will be set to 1.0.
+ if (config is StandardControllerInputConfig controllerConfig)
+ {
+ if (controllerConfig.RangeLeft <= 0.0f && controllerConfig.RangeRight <= 0.0f)
+ {
+ controllerConfig.RangeLeft = 1.0f;
+ controllerConfig.RangeRight = 1.0f;
+
+ Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration");
+ }
+ }
+
+ return config;
+ }
+
+ static void Load(Options option)
+ {
+ AppDataManager.Initialize(option.BaseDataDir);
+
+ _virtualFileSystem = VirtualFileSystem.CreateInstance();
+ _libHacHorizonManager = new LibHacHorizonManager();
+
+ _libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
+ _libHacHorizonManager.InitializeArpServer();
+ _libHacHorizonManager.InitializeBcatServer();
+ _libHacHorizonManager.InitializeSystemClients();
+
+ _contentManager = new ContentManager(_virtualFileSystem);
+ _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
+ _userChannelPersistence = new UserChannelPersistence();
+
+ _inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
+
+ GraphicsConfig.EnableShaderCache = true;
+
+ IGamepad gamepad;
+
+ if (option.ListInputIds)
+ {
+ Logger.Info?.Print(LogClass.Application, "Input Ids:");
+
+ foreach (string id in _inputManager.KeyboardDriver.GamepadsIds)
+ {
+ gamepad = _inputManager.KeyboardDriver.GetGamepad(id);
+
+ Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
+
+ gamepad.Dispose();
+ }
+
+ foreach (string id in _inputManager.GamepadDriver.GamepadsIds)
+ {
+ gamepad = _inputManager.GamepadDriver.GetGamepad(id);
+
+ Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
+
+ gamepad.Dispose();
+ }
+
+ return;
+ }
+
+ if (option.InputPath == null)
+ {
+ Logger.Error?.Print(LogClass.Application, "Please provide a file to load");
+
+ return;
+ }
+
+ _inputConfiguration = new List<InputConfig>();
+ _enableKeyboard = option.EnableKeyboard;
+ _enableMouse = option.EnableMouse;
+
+ void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
+ {
+ InputConfig inputConfig = HandlePlayerConfiguration(inputProfileName, inputId, index);
+
+ if (inputConfig != null)
+ {
+ _inputConfiguration.Add(inputConfig);
+ }
+ }
+
+ LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1);
+ LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2);
+ LoadPlayerConfiguration(option.InputProfile3Name, option.InputId3, PlayerIndex.Player3);
+ LoadPlayerConfiguration(option.InputProfile4Name, option.InputId4, PlayerIndex.Player4);
+ LoadPlayerConfiguration(option.InputProfile5Name, option.InputId5, PlayerIndex.Player5);
+ LoadPlayerConfiguration(option.InputProfile6Name, option.InputId6, PlayerIndex.Player6);
+ LoadPlayerConfiguration(option.InputProfile7Name, option.InputId7, PlayerIndex.Player7);
+ LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8);
+ LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld);
+
+ if (_inputConfiguration.Count == 0)
+ {
+ return;
+ }
+
+ // Setup logging level
+ Logger.SetEnable(LogLevel.Debug, option.LoggingEnableDebug);
+ Logger.SetEnable(LogLevel.Stub, !option.LoggingDisableStub);
+ Logger.SetEnable(LogLevel.Info, !option.LoggingDisableInfo);
+ Logger.SetEnable(LogLevel.Warning, !option.LoggingDisableWarning);
+ Logger.SetEnable(LogLevel.Error, option.LoggingEnableError);
+ Logger.SetEnable(LogLevel.Trace, option.LoggingEnableTrace);
+ Logger.SetEnable(LogLevel.Guest, !option.LoggingDisableGuest);
+ Logger.SetEnable(LogLevel.AccessLog, option.LoggingEnableFsAccessLog);
+
+ if (!option.DisableFileLog)
+ {
+ Logger.AddTarget(new AsyncLogTargetWrapper(
+ new FileLogTarget(ReleaseInformation.GetBaseApplicationDirectory(), "file"),
+ 1000,
+ AsyncLogTargetOverflowAction.Block
+ ));
+ }
+
+ // Setup graphics configuration
+ GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;
+ GraphicsConfig.EnableTextureRecompression = option.EnableTextureRecompression;
+ GraphicsConfig.ResScale = option.ResScale;
+ GraphicsConfig.MaxAnisotropy = option.MaxAnisotropy;
+ GraphicsConfig.ShadersDumpPath = option.GraphicsShadersDumpPath;
+ GraphicsConfig.EnableMacroHLE = !option.DisableMacroHLE;
+
+ while (true)
+ {
+ LoadApplication(option);
+
+ if (_userChannelPersistence.PreviousIndex == -1 || !_userChannelPersistence.ShouldRestart)
+ {
+ break;
+ }
+
+ _userChannelPersistence.ShouldRestart = false;
+ }
+
+ _inputManager.Dispose();
+ }
+
+ private static void SetupProgressHandler()
+ {
+ if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
+ {
+ _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
+ _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
+ }
+
+ _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
+ _emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler;
+ }
+
+ private static void ProgressHandler<T>(T state, int current, int total) where T : Enum
+ {
+ string label;
+
+ switch (state)
+ {
+ case LoadState ptcState:
+ label = $"PTC : {current}/{total}";
+ break;
+ case ShaderCacheState shaderCacheState:
+ label = $"Shaders : {current}/{total}";
+ break;
+ default:
+ throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}");
+ }
+
+ Logger.Info?.Print(LogClass.Application, label);
+ }
+
+ private static WindowBase CreateWindow(Options options)
+ {
+ return options.GraphicsBackend == GraphicsBackend.Vulkan
+ ? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursor)
+ : new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursor);
+ }
+
+ private static IRenderer CreateRenderer(Options options, WindowBase window)
+ {
+ if (options.GraphicsBackend == GraphicsBackend.Vulkan && window is VulkanWindow vulkanWindow)
+ {
+ string preferredGpuId = string.Empty;
+
+ if (!string.IsNullOrEmpty(options.PreferredGpuVendor))
+ {
+ string preferredGpuVendor = options.PreferredGpuVendor.ToLowerInvariant();
+ var devices = VulkanRenderer.GetPhysicalDevices();
+
+ foreach (var device in devices)
+ {
+ if (device.Vendor.ToLowerInvariant() == preferredGpuVendor)
+ {
+ preferredGpuId = device.Id;
+ break;
+ }
+ }
+ }
+
+ return new VulkanRenderer(
+ (instance, vk) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))),
+ vulkanWindow.GetRequiredInstanceExtensions,
+ preferredGpuId);
+ }
+ else
+ {
+ return new OpenGLRenderer();
+ }
+ }
+
+ private static Switch InitializeEmulationContext(WindowBase window, IRenderer renderer, Options options)
+ {
+ BackendThreading threadingMode = options.BackendThreading;
+
+ bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
+
+ if (threadedGAL)
+ {
+ renderer = new ThreadedRenderer(renderer);
+ }
+
+ HLEConfiguration configuration = new HLEConfiguration(_virtualFileSystem,
+ _libHacHorizonManager,
+ _contentManager,
+ _accountManager,
+ _userChannelPersistence,
+ renderer,
+ new SDL2HardwareDeviceDriver(),
+ options.ExpandRam ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB,
+ window,
+ options.SystemLanguage,
+ options.SystemRegion,
+ !options.DisableVsync,
+ !options.DisableDockedMode,
+ !options.DisablePtc,
+ options.EnableInternetAccess,
+ !options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
+ options.FsGlobalAccessLogMode,
+ options.SystemTimeOffset,
+ options.SystemTimeZone,
+ options.MemoryManagerMode,
+ options.IgnoreMissingServices,
+ options.AspectRatio,
+ options.AudioVolume,
+ options.UseHypervisor,
+ options.MultiplayerLanInterfaceId);
+
+ return new Switch(configuration);
+ }
+
+ private static void ExecutionEntrypoint()
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1);
+ }
+
+ DisplaySleep.Prevent();
+
+ _window.Initialize(_emulationContext, _inputConfiguration, _enableKeyboard, _enableMouse);
+
+ _window.Execute();
+
+ _emulationContext.Dispose();
+ _window.Dispose();
+
+ if (OperatingSystem.IsWindows())
+ {
+ _windowsMultimediaTimerResolution?.Dispose();
+ _windowsMultimediaTimerResolution = null;
+ }
+ }
+
+ private static bool LoadApplication(Options options)
+ {
+ string path = options.InputPath;
+
+ Logger.RestartTime();
+
+ WindowBase window = CreateWindow(options);
+ IRenderer renderer = CreateRenderer(options, window);
+
+ _window = window;
+
+ _emulationContext = InitializeEmulationContext(window, renderer, options);
+
+ SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
+
+ Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}");
+
+ if (Directory.Exists(path))
+ {
+ string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
+
+ if (romFsFiles.Length == 0)
+ {
+ romFsFiles = Directory.GetFiles(path, "*.romfs");
+ }
+
+ if (romFsFiles.Length > 0)
+ {
+ Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
+
+ if (!_emulationContext.LoadCart(path, romFsFiles[0]))
+ {
+ _emulationContext.Dispose();
+
+ return false;
+ }
+ }
+ else
+ {
+ Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
+
+ if (!_emulationContext.LoadCart(path))
+ {
+ _emulationContext.Dispose();
+
+ return false;
+ }
+ }
+ }
+ else if (File.Exists(path))
+ {
+ switch (Path.GetExtension(path).ToLowerInvariant())
+ {
+ case ".xci":
+ Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
+
+ if (!_emulationContext.LoadXci(path))
+ {
+ _emulationContext.Dispose();
+
+ return false;
+ }
+ break;
+ case ".nca":
+ Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
+
+ if (!_emulationContext.LoadNca(path))
+ {
+ _emulationContext.Dispose();
+
+ return false;
+ }
+ break;
+ case ".nsp":
+ case ".pfs0":
+ Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
+
+ if (!_emulationContext.LoadNsp(path))
+ {
+ _emulationContext.Dispose();
+
+ return false;
+ }
+ break;
+ default:
+ Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
+ try
+ {
+ if (!_emulationContext.LoadProgram(path))
+ {
+ _emulationContext.Dispose();
+
+ return false;
+ }
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
+
+ _emulationContext.Dispose();
+
+ return false;
+ }
+ break;
+ }
+ }
+ else
+ {
+ Logger.Warning?.Print(LogClass.Application, $"Couldn't load '{options.InputPath}'. Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
+
+ _emulationContext.Dispose();
+
+ return false;
+ }
+
+ SetupProgressHandler();
+
+ Translator.IsReadyForTranslation.Reset();
+
+ ExecutionEntrypoint();
+
+ return true;
+ }
+ }
+}