Program.cs 30 KB

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