/ src / Ryujinx / Program.cs
Program.cs
  1  using Avalonia;
  2  using Avalonia.Threading;
  3  using Ryujinx.Ava.UI.Helpers;
  4  using Ryujinx.Ava.UI.Windows;
  5  using Ryujinx.Common;
  6  using Ryujinx.Common.Configuration;
  7  using Ryujinx.Common.GraphicsDriver;
  8  using Ryujinx.Common.Logging;
  9  using Ryujinx.Common.SystemInterop;
 10  using Ryujinx.Graphics.Vulkan.MoltenVK;
 11  using Ryujinx.Modules;
 12  using Ryujinx.SDL2.Common;
 13  using Ryujinx.UI.Common;
 14  using Ryujinx.UI.Common.Configuration;
 15  using Ryujinx.UI.Common.Helper;
 16  using Ryujinx.UI.Common.SystemInfo;
 17  using System;
 18  using System.IO;
 19  using System.Runtime.InteropServices;
 20  using System.Threading.Tasks;
 21  
 22  namespace Ryujinx.Ava
 23  {
 24      internal partial class Program
 25      {
 26          public static double WindowScaleFactor { get; set; }
 27          public static double DesktopScaleFactor { get; set; } = 1.0;
 28          public static string Version { get; private set; }
 29          public static string ConfigurationPath { get; private set; }
 30          public static bool PreviewerDetached { get; private set; }
 31          public static bool UseHardwareAcceleration { get; private set; }
 32  
 33          [LibraryImport("user32.dll", SetLastError = true)]
 34          public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
 35  
 36          private const uint MbIconwarning = 0x30;
 37  
 38          public static void Main(string[] args)
 39          {
 40              Version = ReleaseInformation.Version;
 41  
 42              if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
 43              {
 44                  _ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning);
 45              }
 46  
 47              PreviewerDetached = true;
 48  
 49              Initialize(args);
 50  
 51              LoggerAdapter.Register();
 52  
 53              BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
 54          }
 55  
 56          public static AppBuilder BuildAvaloniaApp()
 57          {
 58              return AppBuilder.Configure<App>()
 59                  .UsePlatformDetect()
 60                  .With(new X11PlatformOptions
 61                  {
 62                      EnableMultiTouch = true,
 63                      EnableIme = true,
 64                      EnableInputFocusProxy = Environment.GetEnvironmentVariable("XDG_CURRENT_DESKTOP") == "gamescope",
 65                      RenderingMode = UseHardwareAcceleration ?
 66                          new[] { X11RenderingMode.Glx, X11RenderingMode.Software } :
 67                          new[] { X11RenderingMode.Software },
 68                  })
 69                  .With(new Win32PlatformOptions
 70                  {
 71                      WinUICompositionBackdropCornerRadius = 8.0f,
 72                      RenderingMode = UseHardwareAcceleration ?
 73                          new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software } :
 74                          new[] { Win32RenderingMode.Software },
 75                  })
 76                  .UseSkia();
 77          }
 78  
 79          private static void Initialize(string[] args)
 80          {
 81              // Parse arguments
 82              CommandLineState.ParseArguments(args);
 83  
 84              if (OperatingSystem.IsMacOS())
 85              {
 86                  MVKInitialization.InitializeResolver();
 87              }
 88  
 89              // Delete backup files after updating.
 90              Task.Run(Updater.CleanupUpdate);
 91  
 92              Console.Title = $"Ryujinx Console {Version}";
 93  
 94              // Hook unhandled exception and process exit events.
 95              AppDomain.CurrentDomain.UnhandledException += (sender, e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
 96              AppDomain.CurrentDomain.ProcessExit += (sender, e) => Exit();
 97  
 98              // Setup base data directory.
 99              AppDataManager.Initialize(CommandLineState.BaseDirPathArg);
100  
101              // Initialize the configuration.
102              ConfigurationState.Initialize();
103  
104              // Initialize the logger system.
105              LoggerModule.Initialize();
106  
107              // Initialize Discord integration.
108              DiscordIntegrationModule.Initialize();
109  
110              // Initialize SDL2 driver
111              SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
112  
113              ReloadConfig();
114  
115              WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
116  
117              // Logging system information.
118              PrintSystemInfo();
119  
120              // Enable OGL multithreading on the driver, and some other flags.
121              DriverUtilities.InitDriverConfig(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
122  
123              // Check if keys exists.
124              if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
125              {
126                  if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
127                  {
128                      MainWindow.ShowKeyErrorOnLoad = true;
129                  }
130              }
131  
132              if (CommandLineState.LaunchPathArg != null)
133              {
134                  MainWindow.DeferLoadApplication(CommandLineState.LaunchPathArg, CommandLineState.LaunchApplicationId, CommandLineState.StartFullscreenArg);
135              }
136          }
137  
138          public static void ReloadConfig()
139          {
140              string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
141              string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
142  
143              // Now load the configuration as the other subsystems are now registered
144              if (File.Exists(localConfigurationPath))
145              {
146                  ConfigurationPath = localConfigurationPath;
147              }
148              else if (File.Exists(appDataConfigurationPath))
149              {
150                  ConfigurationPath = appDataConfigurationPath;
151              }
152  
153              if (ConfigurationPath == null)
154              {
155                  // No configuration, we load the default values and save it to disk
156                  ConfigurationPath = appDataConfigurationPath;
157                  Logger.Notice.Print(LogClass.Application, $"No configuration file found. Saving default configuration to: {ConfigurationPath}");
158  
159                  ConfigurationState.Instance.LoadDefault();
160                  ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath);
161              }
162              else
163              {
164                  Logger.Notice.Print(LogClass.Application, $"Loading configuration from: {ConfigurationPath}");
165  
166                  if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat))
167                  {
168                      ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath);
169                  }
170                  else
171                  {
172                      Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location: {ConfigurationPath}");
173  
174                      ConfigurationState.Instance.LoadDefault();
175                  }
176              }
177  
178              UseHardwareAcceleration = ConfigurationState.Instance.EnableHardwareAcceleration.Value;
179  
180              // Check if graphics backend was overridden
181              if (CommandLineState.OverrideGraphicsBackend != null)
182              {
183                  if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl")
184                  {
185                      ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl;
186                  }
187                  else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan")
188                  {
189                      ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan;
190                  }
191              }
192  
193              // Check if docked mode was overriden.
194              if (CommandLineState.OverrideDockedMode.HasValue)
195              {
196                  ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
197              }
198  
199              // Check if HideCursor was overridden.
200              if (CommandLineState.OverrideHideCursor is not null)
201              {
202                  ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch
203                  {
204                      "never" => HideCursorMode.Never,
205                      "onidle" => HideCursorMode.OnIdle,
206                      "always" => HideCursorMode.Always,
207                      _ => ConfigurationState.Instance.HideCursor.Value,
208                  };
209              }
210  
211              // Check if hardware-acceleration was overridden.
212              if (CommandLineState.OverrideHardwareAcceleration != null)
213              {
214                  UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value;
215              }
216          }
217  
218          private static void PrintSystemInfo()
219          {
220              Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
221              SystemInfo.Gather().Print();
222  
223              Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}");
224  
225              if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom)
226              {
227                  Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}");
228              }
229              else
230              {
231                  Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}");
232              }
233          }
234  
235          private static void ProcessUnhandledException(Exception ex, bool isTerminating)
236          {
237              string message = $"Unhandled exception caught: {ex}";
238  
239              Logger.Error?.PrintMsg(LogClass.Application, message);
240  
241              if (Logger.Error == null)
242              {
243                  Logger.Notice.PrintMsg(LogClass.Application, message);
244              }
245  
246              if (isTerminating)
247              {
248                  Exit();
249              }
250          }
251  
252          public static void Exit()
253          {
254              DiscordIntegrationModule.Exit();
255  
256              Logger.Shutdown();
257          }
258      }
259  }