/ src / Ryujinx.Headless.SDL2 / Program.cs
Program.cs
  1  using CommandLine;
  2  using LibHac.Tools.FsSystem;
  3  using Ryujinx.Audio.Backends.SDL2;
  4  using Ryujinx.Common;
  5  using Ryujinx.Common.Configuration;
  6  using Ryujinx.Common.Configuration.Hid;
  7  using Ryujinx.Common.Configuration.Hid.Controller;
  8  using Ryujinx.Common.Configuration.Hid.Controller.Motion;
  9  using Ryujinx.Common.Configuration.Hid.Keyboard;
 10  using Ryujinx.Common.GraphicsDriver;
 11  using Ryujinx.Common.Logging;
 12  using Ryujinx.Common.Logging.Targets;
 13  using Ryujinx.Common.SystemInterop;
 14  using Ryujinx.Common.Utilities;
 15  using Ryujinx.Cpu;
 16  using Ryujinx.Graphics.GAL;
 17  using Ryujinx.Graphics.GAL.Multithreading;
 18  using Ryujinx.Graphics.Gpu;
 19  using Ryujinx.Graphics.Gpu.Shader;
 20  using Ryujinx.Graphics.OpenGL;
 21  using Ryujinx.Graphics.Vulkan;
 22  using Ryujinx.Graphics.Vulkan.MoltenVK;
 23  using Ryujinx.Headless.SDL2.OpenGL;
 24  using Ryujinx.Headless.SDL2.Vulkan;
 25  using Ryujinx.HLE;
 26  using Ryujinx.HLE.FileSystem;
 27  using Ryujinx.HLE.HOS;
 28  using Ryujinx.HLE.HOS.Services.Account.Acc;
 29  using Ryujinx.Input;
 30  using Ryujinx.Input.HLE;
 31  using Ryujinx.Input.SDL2;
 32  using Ryujinx.SDL2.Common;
 33  using Silk.NET.Vulkan;
 34  using System;
 35  using System.Collections.Generic;
 36  using System.IO;
 37  using System.Text.Json;
 38  using System.Threading;
 39  using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
 40  using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
 41  using Key = Ryujinx.Common.Configuration.Hid.Key;
 42  
 43  namespace Ryujinx.Headless.SDL2
 44  {
 45      class Program
 46      {
 47          public static string Version { get; private set; }
 48  
 49          private static VirtualFileSystem _virtualFileSystem;
 50          private static ContentManager _contentManager;
 51          private static AccountManager _accountManager;
 52          private static LibHacHorizonManager _libHacHorizonManager;
 53          private static UserChannelPersistence _userChannelPersistence;
 54          private static InputManager _inputManager;
 55          private static Switch _emulationContext;
 56          private static WindowBase _window;
 57          private static WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
 58          private static List<InputConfig> _inputConfiguration;
 59          private static bool _enableKeyboard;
 60          private static bool _enableMouse;
 61  
 62          private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
 63  
 64          static void Main(string[] args)
 65          {
 66              Version = ReleaseInformation.Version;
 67  
 68              // Make process DPI aware for proper window sizing on high-res screens.
 69              ForceDpiAware.Windows();
 70  
 71              Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
 72  
 73              if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
 74              {
 75                  AutoResetEvent invoked = new(false);
 76  
 77                  // MacOS must perform SDL polls from the main thread.
 78                  SDL2Driver.MainThreadDispatcher = action =>
 79                  {
 80                      invoked.Reset();
 81  
 82                      WindowBase.QueueMainThreadAction(() =>
 83                      {
 84                          action();
 85  
 86                          invoked.Set();
 87                      });
 88  
 89                      invoked.WaitOne();
 90                  };
 91              }
 92  
 93              if (OperatingSystem.IsMacOS())
 94              {
 95                  MVKInitialization.InitializeResolver();
 96              }
 97  
 98              Parser.Default.ParseArguments<Options>(args)
 99              .WithParsed(Load)
100              .WithNotParsed(errors => errors.Output());
101          }
102  
103          private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
104          {
105              if (inputId == null)
106              {
107                  if (index == PlayerIndex.Player1)
108                  {
109                      Logger.Info?.Print(LogClass.Application, $"{index} not configured, defaulting to default keyboard.");
110  
111                      // Default to keyboard
112                      inputId = "0";
113                  }
114                  else
115                  {
116                      Logger.Info?.Print(LogClass.Application, $"{index} not configured");
117  
118                      return null;
119                  }
120              }
121  
122              IGamepad gamepad;
123  
124              bool isKeyboard = true;
125  
126              gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId);
127  
128              if (gamepad == null)
129              {
130                  gamepad = _inputManager.GamepadDriver.GetGamepad(inputId);
131                  isKeyboard = false;
132  
133                  if (gamepad == null)
134                  {
135                      Logger.Error?.Print(LogClass.Application, $"{index} gamepad not found (\"{inputId}\")");
136  
137                      return null;
138                  }
139              }
140  
141              string gamepadName = gamepad.Name;
142  
143              gamepad.Dispose();
144  
145              InputConfig config;
146  
147              if (inputProfileName == null || inputProfileName.Equals("default"))
148              {
149                  if (isKeyboard)
150                  {
151                      config = new StandardKeyboardInputConfig
152                      {
153                          Version = InputConfig.CurrentVersion,
154                          Backend = InputBackendType.WindowKeyboard,
155                          Id = null,
156                          ControllerType = ControllerType.JoyconPair,
157                          LeftJoycon = new LeftJoyconCommonConfig<Key>
158                          {
159                              DpadUp = Key.Up,
160                              DpadDown = Key.Down,
161                              DpadLeft = Key.Left,
162                              DpadRight = Key.Right,
163                              ButtonMinus = Key.Minus,
164                              ButtonL = Key.E,
165                              ButtonZl = Key.Q,
166                              ButtonSl = Key.Unbound,
167                              ButtonSr = Key.Unbound,
168                          },
169  
170                          LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
171                          {
172                              StickUp = Key.W,
173                              StickDown = Key.S,
174                              StickLeft = Key.A,
175                              StickRight = Key.D,
176                              StickButton = Key.F,
177                          },
178  
179                          RightJoycon = new RightJoyconCommonConfig<Key>
180                          {
181                              ButtonA = Key.Z,
182                              ButtonB = Key.X,
183                              ButtonX = Key.C,
184                              ButtonY = Key.V,
185                              ButtonPlus = Key.Plus,
186                              ButtonR = Key.U,
187                              ButtonZr = Key.O,
188                              ButtonSl = Key.Unbound,
189                              ButtonSr = Key.Unbound,
190                          },
191  
192                          RightJoyconStick = new JoyconConfigKeyboardStick<Key>
193                          {
194                              StickUp = Key.I,
195                              StickDown = Key.K,
196                              StickLeft = Key.J,
197                              StickRight = Key.L,
198                              StickButton = Key.H,
199                          },
200                      };
201                  }
202                  else
203                  {
204                      bool isNintendoStyle = gamepadName.Contains("Nintendo");
205  
206                      config = new StandardControllerInputConfig
207                      {
208                          Version = InputConfig.CurrentVersion,
209                          Backend = InputBackendType.GamepadSDL2,
210                          Id = null,
211                          ControllerType = ControllerType.JoyconPair,
212                          DeadzoneLeft = 0.1f,
213                          DeadzoneRight = 0.1f,
214                          RangeLeft = 1.0f,
215                          RangeRight = 1.0f,
216                          TriggerThreshold = 0.5f,
217                          LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
218                          {
219                              DpadUp = ConfigGamepadInputId.DpadUp,
220                              DpadDown = ConfigGamepadInputId.DpadDown,
221                              DpadLeft = ConfigGamepadInputId.DpadLeft,
222                              DpadRight = ConfigGamepadInputId.DpadRight,
223                              ButtonMinus = ConfigGamepadInputId.Minus,
224                              ButtonL = ConfigGamepadInputId.LeftShoulder,
225                              ButtonZl = ConfigGamepadInputId.LeftTrigger,
226                              ButtonSl = ConfigGamepadInputId.Unbound,
227                              ButtonSr = ConfigGamepadInputId.Unbound,
228                          },
229  
230                          LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
231                          {
232                              Joystick = ConfigStickInputId.Left,
233                              StickButton = ConfigGamepadInputId.LeftStick,
234                              InvertStickX = false,
235                              InvertStickY = false,
236                              Rotate90CW = false,
237                          },
238  
239                          RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
240                          {
241                              ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
242                              ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
243                              ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
244                              ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
245                              ButtonPlus = ConfigGamepadInputId.Plus,
246                              ButtonR = ConfigGamepadInputId.RightShoulder,
247                              ButtonZr = ConfigGamepadInputId.RightTrigger,
248                              ButtonSl = ConfigGamepadInputId.Unbound,
249                              ButtonSr = ConfigGamepadInputId.Unbound,
250                          },
251  
252                          RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
253                          {
254                              Joystick = ConfigStickInputId.Right,
255                              StickButton = ConfigGamepadInputId.RightStick,
256                              InvertStickX = false,
257                              InvertStickY = false,
258                              Rotate90CW = false,
259                          },
260  
261                          Motion = new StandardMotionConfigController
262                          {
263                              MotionBackend = MotionInputBackendType.GamepadDriver,
264                              EnableMotion = true,
265                              Sensitivity = 100,
266                              GyroDeadzone = 1,
267                          },
268                          Rumble = new RumbleConfigController
269                          {
270                              StrongRumble = 1f,
271                              WeakRumble = 1f,
272                              EnableRumble = false,
273                          },
274                      };
275                  }
276              }
277              else
278              {
279                  string profileBasePath;
280  
281                  if (isKeyboard)
282                  {
283                      profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "keyboard");
284                  }
285                  else
286                  {
287                      profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "controller");
288                  }
289  
290                  string path = Path.Combine(profileBasePath, inputProfileName + ".json");
291  
292                  if (!File.Exists(path))
293                  {
294                      Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" not found for \"{inputId}\"");
295  
296                      return null;
297                  }
298  
299                  try
300                  {
301                      config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig);
302                  }
303                  catch (JsonException)
304                  {
305                      Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" parsing failed for \"{inputId}\"");
306  
307                      return null;
308                  }
309              }
310  
311              config.Id = inputId;
312              config.PlayerIndex = index;
313  
314              string inputTypeName = isKeyboard ? "Keyboard" : "Gamepad";
315  
316              Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} configured with {inputTypeName} \"{config.Id}\"");
317  
318              // If both stick ranges are 0 (usually indicative of an outdated profile load) then both sticks will be set to 1.0.
319              if (config is StandardControllerInputConfig controllerConfig)
320              {
321                  if (controllerConfig.RangeLeft <= 0.0f && controllerConfig.RangeRight <= 0.0f)
322                  {
323                      controllerConfig.RangeLeft = 1.0f;
324                      controllerConfig.RangeRight = 1.0f;
325  
326                      Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration");
327                  }
328              }
329  
330              return config;
331          }
332  
333          static void Load(Options option)
334          {
335              AppDataManager.Initialize(option.BaseDataDir);
336  
337              _virtualFileSystem = VirtualFileSystem.CreateInstance();
338              _libHacHorizonManager = new LibHacHorizonManager();
339  
340              _libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
341              _libHacHorizonManager.InitializeArpServer();
342              _libHacHorizonManager.InitializeBcatServer();
343              _libHacHorizonManager.InitializeSystemClients();
344  
345              _contentManager = new ContentManager(_virtualFileSystem);
346              _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
347              _userChannelPersistence = new UserChannelPersistence();
348  
349              _inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
350  
351              GraphicsConfig.EnableShaderCache = true;
352  
353              if (OperatingSystem.IsMacOS())
354              {
355                  if (option.GraphicsBackend == GraphicsBackend.OpenGl)
356                  {
357                      option.GraphicsBackend = GraphicsBackend.Vulkan;
358                      Logger.Warning?.Print(LogClass.Application, "OpenGL is not supported on macOS, switching to Vulkan!");
359                  }
360              }
361  
362              IGamepad gamepad;
363  
364              if (option.ListInputIds)
365              {
366                  Logger.Info?.Print(LogClass.Application, "Input Ids:");
367  
368                  foreach (string id in _inputManager.KeyboardDriver.GamepadsIds)
369                  {
370                      gamepad = _inputManager.KeyboardDriver.GetGamepad(id);
371  
372                      Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
373  
374                      gamepad.Dispose();
375                  }
376  
377                  foreach (string id in _inputManager.GamepadDriver.GamepadsIds)
378                  {
379                      gamepad = _inputManager.GamepadDriver.GetGamepad(id);
380  
381                      Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
382  
383                      gamepad.Dispose();
384                  }
385  
386                  return;
387              }
388  
389              if (option.InputPath == null)
390              {
391                  Logger.Error?.Print(LogClass.Application, "Please provide a file to load");
392  
393                  return;
394              }
395  
396              _inputConfiguration = new List<InputConfig>();
397              _enableKeyboard = option.EnableKeyboard;
398              _enableMouse = option.EnableMouse;
399  
400              static void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
401              {
402                  InputConfig inputConfig = HandlePlayerConfiguration(inputProfileName, inputId, index);
403  
404                  if (inputConfig != null)
405                  {
406                      _inputConfiguration.Add(inputConfig);
407                  }
408              }
409  
410              LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1);
411              LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2);
412              LoadPlayerConfiguration(option.InputProfile3Name, option.InputId3, PlayerIndex.Player3);
413              LoadPlayerConfiguration(option.InputProfile4Name, option.InputId4, PlayerIndex.Player4);
414              LoadPlayerConfiguration(option.InputProfile5Name, option.InputId5, PlayerIndex.Player5);
415              LoadPlayerConfiguration(option.InputProfile6Name, option.InputId6, PlayerIndex.Player6);
416              LoadPlayerConfiguration(option.InputProfile7Name, option.InputId7, PlayerIndex.Player7);
417              LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8);
418              LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld);
419  
420              if (_inputConfiguration.Count == 0)
421              {
422                  return;
423              }
424  
425              // Setup logging level
426              Logger.SetEnable(LogLevel.Debug, option.LoggingEnableDebug);
427              Logger.SetEnable(LogLevel.Stub, !option.LoggingDisableStub);
428              Logger.SetEnable(LogLevel.Info, !option.LoggingDisableInfo);
429              Logger.SetEnable(LogLevel.Warning, !option.LoggingDisableWarning);
430              Logger.SetEnable(LogLevel.Error, option.LoggingEnableError);
431              Logger.SetEnable(LogLevel.Trace, option.LoggingEnableTrace);
432              Logger.SetEnable(LogLevel.Guest, !option.LoggingDisableGuest);
433              Logger.SetEnable(LogLevel.AccessLog, option.LoggingEnableFsAccessLog);
434  
435              if (!option.DisableFileLog)
436              {
437                  string logDir = AppDataManager.LogsDirPath;
438                  FileStream logFile = null;
439  
440                  if (!string.IsNullOrEmpty(logDir))
441                  {
442                      logFile = FileLogTarget.PrepareLogFile(logDir);
443                  }
444  
445                  if (logFile != null)
446                  {
447                      Logger.AddTarget(new AsyncLogTargetWrapper(
448                          new FileLogTarget("file", logFile),
449                          1000,
450                          AsyncLogTargetOverflowAction.Block
451                      ));
452                  }
453                  else
454                  {
455                      Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the Logs directory, Application Data, or the Ryujinx directory is writable.");
456                  }
457              }
458  
459              // Setup graphics configuration
460              GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;
461              GraphicsConfig.EnableTextureRecompression = option.EnableTextureRecompression;
462              GraphicsConfig.ResScale = option.ResScale;
463              GraphicsConfig.MaxAnisotropy = option.MaxAnisotropy;
464              GraphicsConfig.ShadersDumpPath = option.GraphicsShadersDumpPath;
465              GraphicsConfig.EnableMacroHLE = !option.DisableMacroHLE;
466  
467              DriverUtilities.InitDriverConfig(option.BackendThreading == BackendThreading.Off);
468  
469              while (true)
470              {
471                  LoadApplication(option);
472  
473                  if (_userChannelPersistence.PreviousIndex == -1 || !_userChannelPersistence.ShouldRestart)
474                  {
475                      break;
476                  }
477  
478                  _userChannelPersistence.ShouldRestart = false;
479              }
480  
481              _inputManager.Dispose();
482          }
483  
484          private static void SetupProgressHandler()
485          {
486              if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
487              {
488                  _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
489                  _emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
490              }
491  
492              _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
493              _emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler;
494          }
495  
496          private static void ProgressHandler<T>(T state, int current, int total) where T : Enum
497          {
498              string label = state switch
499              {
500                  LoadState => $"PTC : {current}/{total}",
501                  ShaderCacheState => $"Shaders : {current}/{total}",
502                  _ => throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}"),
503              };
504  
505              Logger.Info?.Print(LogClass.Application, label);
506          }
507  
508          private static WindowBase CreateWindow(Options options)
509          {
510              return options.GraphicsBackend == GraphicsBackend.Vulkan
511                  ? new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode)
512                  : new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode);
513          }
514  
515          private static IRenderer CreateRenderer(Options options, WindowBase window)
516          {
517              if (options.GraphicsBackend == GraphicsBackend.Vulkan && window is VulkanWindow vulkanWindow)
518              {
519                  string preferredGpuId = string.Empty;
520                  Vk api = Vk.GetApi();
521  
522                  if (!string.IsNullOrEmpty(options.PreferredGPUVendor))
523                  {
524                      string preferredGpuVendor = options.PreferredGPUVendor.ToLowerInvariant();
525                      var devices = VulkanRenderer.GetPhysicalDevices(api);
526  
527                      foreach (var device in devices)
528                      {
529                          if (device.Vendor.ToLowerInvariant() == preferredGpuVendor)
530                          {
531                              preferredGpuId = device.Id;
532                              break;
533                          }
534                      }
535                  }
536  
537                  return new VulkanRenderer(
538                      api,
539                      (instance, vk) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))),
540                      vulkanWindow.GetRequiredInstanceExtensions,
541                      preferredGpuId);
542              }
543  
544              return new OpenGLRenderer();
545          }
546  
547          private static Switch InitializeEmulationContext(WindowBase window, IRenderer renderer, Options options)
548          {
549              BackendThreading threadingMode = options.BackendThreading;
550  
551              bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
552  
553              if (threadedGAL)
554              {
555                  renderer = new ThreadedRenderer(renderer);
556              }
557  
558              HLEConfiguration configuration = new(_virtualFileSystem,
559                  _libHacHorizonManager,
560                  _contentManager,
561                  _accountManager,
562                  _userChannelPersistence,
563                  renderer,
564                  new SDL2HardwareDeviceDriver(),
565                  options.ExpandRAM ? MemoryConfiguration.MemoryConfiguration8GiB : MemoryConfiguration.MemoryConfiguration4GiB,
566                  window,
567                  options.SystemLanguage,
568                  options.SystemRegion,
569                  !options.DisableVSync,
570                  !options.DisableDockedMode,
571                  !options.DisablePTC,
572                  options.EnableInternetAccess,
573                  !options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
574                  options.FsGlobalAccessLogMode,
575                  options.SystemTimeOffset,
576                  options.SystemTimeZone,
577                  options.MemoryManagerMode,
578                  options.IgnoreMissingServices,
579                  options.AspectRatio,
580                  options.AudioVolume,
581                  options.UseHypervisor ?? true,
582                  options.MultiplayerLanInterfaceId,
583                  Common.Configuration.Multiplayer.MultiplayerMode.Disabled);
584  
585              return new Switch(configuration);
586          }
587  
588          private static void ExecutionEntrypoint()
589          {
590              if (OperatingSystem.IsWindows())
591              {
592                  _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1);
593              }
594  
595              DisplaySleep.Prevent();
596  
597              _window.Initialize(_emulationContext, _inputConfiguration, _enableKeyboard, _enableMouse);
598  
599              _window.Execute();
600  
601              _emulationContext.Dispose();
602              _window.Dispose();
603  
604              if (OperatingSystem.IsWindows())
605              {
606                  _windowsMultimediaTimerResolution?.Dispose();
607                  _windowsMultimediaTimerResolution = null;
608              }
609          }
610  
611          private static bool LoadApplication(Options options)
612          {
613              string path = options.InputPath;
614  
615              Logger.RestartTime();
616  
617              WindowBase window = CreateWindow(options);
618              IRenderer renderer = CreateRenderer(options, window);
619  
620              _window = window;
621  
622              _window.IsFullscreen = options.IsFullscreen;
623              _window.DisplayId = options.DisplayId;
624              _window.IsExclusiveFullscreen = options.IsExclusiveFullscreen;
625              _window.ExclusiveFullscreenWidth = options.ExclusiveFullscreenWidth;
626              _window.ExclusiveFullscreenHeight = options.ExclusiveFullscreenHeight;
627              _window.AntiAliasing = options.AntiAliasing;
628              _window.ScalingFilter = options.ScalingFilter;
629              _window.ScalingFilterLevel = options.ScalingFilterLevel;
630  
631              _emulationContext = InitializeEmulationContext(window, renderer, options);
632  
633              SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
634  
635              Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}");
636  
637              if (Directory.Exists(path))
638              {
639                  string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
640  
641                  if (romFsFiles.Length == 0)
642                  {
643                      romFsFiles = Directory.GetFiles(path, "*.romfs");
644                  }
645  
646                  if (romFsFiles.Length > 0)
647                  {
648                      Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
649  
650                      if (!_emulationContext.LoadCart(path, romFsFiles[0]))
651                      {
652                          _emulationContext.Dispose();
653  
654                          return false;
655                      }
656                  }
657                  else
658                  {
659                      Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
660  
661                      if (!_emulationContext.LoadCart(path))
662                      {
663                          _emulationContext.Dispose();
664  
665                          return false;
666                      }
667                  }
668              }
669              else if (File.Exists(path))
670              {
671                  switch (Path.GetExtension(path).ToLowerInvariant())
672                  {
673                      case ".xci":
674                          Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
675  
676                          if (!_emulationContext.LoadXci(path))
677                          {
678                              _emulationContext.Dispose();
679  
680                              return false;
681                          }
682                          break;
683                      case ".nca":
684                          Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
685  
686                          if (!_emulationContext.LoadNca(path))
687                          {
688                              _emulationContext.Dispose();
689  
690                              return false;
691                          }
692                          break;
693                      case ".nsp":
694                      case ".pfs0":
695                          Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
696  
697                          if (!_emulationContext.LoadNsp(path))
698                          {
699                              _emulationContext.Dispose();
700  
701                              return false;
702                          }
703                          break;
704                      default:
705                          Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
706                          try
707                          {
708                              if (!_emulationContext.LoadProgram(path))
709                              {
710                                  _emulationContext.Dispose();
711  
712                                  return false;
713                              }
714                          }
715                          catch (ArgumentOutOfRangeException)
716                          {
717                              Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
718  
719                              _emulationContext.Dispose();
720  
721                              return false;
722                          }
723                          break;
724                  }
725              }
726              else
727              {
728                  Logger.Warning?.Print(LogClass.Application, $"Couldn't load '{options.InputPath}'. Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
729  
730                  _emulationContext.Dispose();
731  
732                  return false;
733              }
734  
735              SetupProgressHandler();
736              ExecutionEntrypoint();
737  
738              return true;
739          }
740      }
741  }