MainWindow.cs 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328
  1. using ARMeilleure.Translation.PTC;
  2. using Gtk;
  3. using LibHac.Common;
  4. using LibHac.Ns;
  5. using Ryujinx.Audio;
  6. using Ryujinx.Common.Configuration;
  7. using Ryujinx.Common.Logging;
  8. using Ryujinx.Common.System;
  9. using Ryujinx.Configuration;
  10. using Ryujinx.Configuration.System;
  11. using Ryujinx.Debugger.Profiler;
  12. using Ryujinx.Graphics.GAL;
  13. using Ryujinx.Graphics.OpenGL;
  14. using Ryujinx.HLE.FileSystem;
  15. using Ryujinx.HLE.FileSystem.Content;
  16. using Ryujinx.HLE.HOS.Services.Hid;
  17. using System;
  18. using System.Diagnostics;
  19. using System.IO;
  20. using System.Linq;
  21. using System.Reflection;
  22. using System.Threading;
  23. using System.Threading.Tasks;
  24. using GUI = Gtk.Builder.ObjectAttribute;
  25. namespace Ryujinx.Ui
  26. {
  27. public class MainWindow : Window
  28. {
  29. private static VirtualFileSystem _virtualFileSystem;
  30. private static ContentManager _contentManager;
  31. private static WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
  32. private static HLE.Switch _emulationContext;
  33. private static GlRenderer _glWidget;
  34. private static GtkHostUiHandler _uiHandler;
  35. private static AutoResetEvent _deviceExitStatus = new AutoResetEvent(false);
  36. private static ListStore _tableStore;
  37. private static bool _updatingGameTable;
  38. private static bool _gameLoaded;
  39. private static bool _ending;
  40. #pragma warning disable CS0169
  41. private static bool _debuggerOpened;
  42. private static Debugger.Debugger _debugger;
  43. #pragma warning restore CS0169
  44. #pragma warning disable CS0169, CS0649, IDE0044
  45. [GUI] MenuBar _menuBar;
  46. [GUI] Box _footerBox;
  47. [GUI] Box _statusBar;
  48. [GUI] MenuItem _stopEmulation;
  49. [GUI] MenuItem _fullScreen;
  50. [GUI] CheckMenuItem _favToggle;
  51. [GUI] MenuItem _firmwareInstallDirectory;
  52. [GUI] MenuItem _firmwareInstallFile;
  53. [GUI] Label _hostStatus;
  54. [GUI] MenuItem _openDebugger;
  55. [GUI] CheckMenuItem _iconToggle;
  56. [GUI] CheckMenuItem _developerToggle;
  57. [GUI] CheckMenuItem _appToggle;
  58. [GUI] CheckMenuItem _timePlayedToggle;
  59. [GUI] CheckMenuItem _versionToggle;
  60. [GUI] CheckMenuItem _lastPlayedToggle;
  61. [GUI] CheckMenuItem _fileExtToggle;
  62. [GUI] CheckMenuItem _pathToggle;
  63. [GUI] CheckMenuItem _fileSizeToggle;
  64. [GUI] Label _dockedMode;
  65. [GUI] Label _gameStatus;
  66. [GUI] TreeView _gameTable;
  67. [GUI] TreeSelection _gameTableSelection;
  68. [GUI] ScrolledWindow _gameTableWindow;
  69. [GUI] Label _gpuName;
  70. [GUI] Label _progressLabel;
  71. [GUI] Label _firmwareVersionLabel;
  72. [GUI] LevelBar _progressBar;
  73. [GUI] Box _viewBox;
  74. [GUI] Label _vSyncStatus;
  75. [GUI] Box _listStatusBox;
  76. #pragma warning restore CS0649, IDE0044, CS0169
  77. public MainWindow() : this(new Builder("Ryujinx.Ui.MainWindow.glade")) { }
  78. private MainWindow(Builder builder) : base(builder.GetObject("_mainWin").Handle)
  79. {
  80. builder.Autoconnect(this);
  81. int monitorWidth = Display.PrimaryMonitor.Geometry.Width * Display.PrimaryMonitor.ScaleFactor;
  82. int monitorHeight = Display.PrimaryMonitor.Geometry.Height * Display.PrimaryMonitor.ScaleFactor;
  83. this.DefaultWidth = monitorWidth < 1280 ? monitorWidth : 1280;
  84. this.DefaultHeight = monitorHeight < 760 ? monitorHeight : 760;
  85. this.WindowStateEvent += MainWindow_WindowStateEvent;
  86. this.DeleteEvent += Window_Close;
  87. _fullScreen.Activated += FullScreen_Toggled;
  88. this.Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png");
  89. this.Title = $"Ryujinx {Program.Version}";
  90. ApplicationLibrary.ApplicationAdded += Application_Added;
  91. ApplicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
  92. GlRenderer.StatusUpdatedEvent += Update_StatusBar;
  93. _gameTable.ButtonReleaseEvent += Row_Clicked;
  94. // First we check that a migration isn't needed. (because VirtualFileSystem will create the new directory otherwise)
  95. bool continueWithStartup = Migration.PromptIfMigrationNeededForStartup(this, out bool migrationNeeded);
  96. if (!continueWithStartup)
  97. {
  98. End(null);
  99. }
  100. _virtualFileSystem = VirtualFileSystem.CreateInstance();
  101. _contentManager = new ContentManager(_virtualFileSystem);
  102. if (migrationNeeded)
  103. {
  104. bool migrationSuccessful = Migration.DoMigrationForStartup(this, _virtualFileSystem);
  105. if (!migrationSuccessful)
  106. {
  107. End(null);
  108. }
  109. }
  110. // Make sure that everything is loaded.
  111. _virtualFileSystem.Reload();
  112. ApplyTheme();
  113. _stopEmulation.Sensitive = false;
  114. if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) _favToggle.Active = true;
  115. if (ConfigurationState.Instance.Ui.GuiColumns.IconColumn) _iconToggle.Active = true;
  116. if (ConfigurationState.Instance.Ui.GuiColumns.AppColumn) _appToggle.Active = true;
  117. if (ConfigurationState.Instance.Ui.GuiColumns.DevColumn) _developerToggle.Active = true;
  118. if (ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) _versionToggle.Active = true;
  119. if (ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) _timePlayedToggle.Active = true;
  120. if (ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) _lastPlayedToggle.Active = true;
  121. if (ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) _fileExtToggle.Active = true;
  122. if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) _fileSizeToggle.Active = true;
  123. if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn) _pathToggle.Active = true;
  124. #if USE_DEBUGGING
  125. _debugger = new Debugger.Debugger();
  126. _openDebugger.Activated += _openDebugger_Opened;
  127. #else
  128. _openDebugger.Hide();
  129. #endif
  130. _gameTable.Model = _tableStore = new ListStore(
  131. typeof(bool),
  132. typeof(Gdk.Pixbuf),
  133. typeof(string),
  134. typeof(string),
  135. typeof(string),
  136. typeof(string),
  137. typeof(string),
  138. typeof(string),
  139. typeof(string),
  140. typeof(string),
  141. typeof(BlitStruct<ApplicationControlProperty>));
  142. _tableStore.SetSortFunc(5, TimePlayedSort);
  143. _tableStore.SetSortFunc(6, LastPlayedSort);
  144. _tableStore.SetSortFunc(8, FileSizeSort);
  145. int columnId = ConfigurationState.Instance.Ui.ColumnSort.SortColumnId;
  146. bool ascending = ConfigurationState.Instance.Ui.ColumnSort.SortAscending;
  147. _tableStore.SetSortColumnId(columnId, ascending ? SortType.Ascending : SortType.Descending);
  148. _gameTable.EnableSearch = true;
  149. _gameTable.SearchColumn = 2;
  150. UpdateColumns();
  151. UpdateGameTable();
  152. ConfigurationState.Instance.Ui.GameDirs.Event += (sender, args) =>
  153. {
  154. if (args.OldValue != args.NewValue)
  155. {
  156. UpdateGameTable();
  157. }
  158. };
  159. Task.Run(RefreshFirmwareLabel);
  160. _statusBar.Hide();
  161. _uiHandler = new GtkHostUiHandler(this);
  162. }
  163. private void MainWindow_WindowStateEvent(object o, WindowStateEventArgs args)
  164. {
  165. _fullScreen.Label = args.Event.NewWindowState.HasFlag(Gdk.WindowState.Fullscreen) ? "Exit Fullscreen" : "Enter Fullscreen";
  166. }
  167. #if USE_DEBUGGING
  168. private void _openDebugger_Opened(object sender, EventArgs e)
  169. {
  170. if (_debuggerOpened)
  171. {
  172. return;
  173. }
  174. Window debugWindow = new Window("Debugger");
  175. debugWindow.SetSizeRequest(1280, 640);
  176. debugWindow.Child = _debugger.Widget;
  177. debugWindow.DeleteEvent += DebugWindow_DeleteEvent;
  178. debugWindow.ShowAll();
  179. _debugger.Enable();
  180. _debuggerOpened = true;
  181. }
  182. private void DebugWindow_DeleteEvent(object o, DeleteEventArgs args)
  183. {
  184. _debuggerOpened = false;
  185. _debugger.Disable();
  186. (_debugger.Widget.Parent as Window)?.Remove(_debugger.Widget);
  187. }
  188. #endif
  189. internal static void ApplyTheme()
  190. {
  191. if (!ConfigurationState.Instance.Ui.EnableCustomTheme)
  192. {
  193. return;
  194. }
  195. if (File.Exists(ConfigurationState.Instance.Ui.CustomThemePath) && (System.IO.Path.GetExtension(ConfigurationState.Instance.Ui.CustomThemePath) == ".css"))
  196. {
  197. CssProvider cssProvider = new CssProvider();
  198. cssProvider.LoadFromPath(ConfigurationState.Instance.Ui.CustomThemePath);
  199. StyleContext.AddProviderForScreen(Gdk.Screen.Default, cssProvider, 800);
  200. }
  201. else
  202. {
  203. Logger.Warning?.Print(LogClass.Application, $"The \"custom_theme_path\" section in \"Config.json\" contains an invalid path: \"{ConfigurationState.Instance.Ui.CustomThemePath}\".");
  204. }
  205. }
  206. private void UpdateColumns()
  207. {
  208. foreach (TreeViewColumn column in _gameTable.Columns)
  209. {
  210. _gameTable.RemoveColumn(column);
  211. }
  212. CellRendererToggle favToggle = new CellRendererToggle();
  213. favToggle.Toggled += FavToggle_Toggled;
  214. if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) _gameTable.AppendColumn("Fav", favToggle, "active", 0);
  215. if (ConfigurationState.Instance.Ui.GuiColumns.IconColumn) _gameTable.AppendColumn("Icon", new CellRendererPixbuf(), "pixbuf", 1);
  216. if (ConfigurationState.Instance.Ui.GuiColumns.AppColumn) _gameTable.AppendColumn("Application", new CellRendererText(), "text", 2);
  217. if (ConfigurationState.Instance.Ui.GuiColumns.DevColumn) _gameTable.AppendColumn("Developer", new CellRendererText(), "text", 3);
  218. if (ConfigurationState.Instance.Ui.GuiColumns.VersionColumn) _gameTable.AppendColumn("Version", new CellRendererText(), "text", 4);
  219. if (ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn) _gameTable.AppendColumn("Time Played", new CellRendererText(), "text", 5);
  220. if (ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn) _gameTable.AppendColumn("Last Played", new CellRendererText(), "text", 6);
  221. if (ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn) _gameTable.AppendColumn("File Ext", new CellRendererText(), "text", 7);
  222. if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) _gameTable.AppendColumn("File Size", new CellRendererText(), "text", 8);
  223. if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn) _gameTable.AppendColumn("Path", new CellRendererText(), "text", 9);
  224. foreach (TreeViewColumn column in _gameTable.Columns)
  225. {
  226. switch (column.Title)
  227. {
  228. case "Fav":
  229. column.SortColumnId = 0;
  230. column.Clicked += Column_Clicked;
  231. break;
  232. case "Application":
  233. column.SortColumnId = 2;
  234. column.Clicked += Column_Clicked;
  235. break;
  236. case "Developer":
  237. column.SortColumnId = 3;
  238. column.Clicked += Column_Clicked;
  239. break;
  240. case "Version":
  241. column.SortColumnId = 4;
  242. column.Clicked += Column_Clicked;
  243. break;
  244. case "Time Played":
  245. column.SortColumnId = 5;
  246. column.Clicked += Column_Clicked;
  247. break;
  248. case "Last Played":
  249. column.SortColumnId = 6;
  250. column.Clicked += Column_Clicked;
  251. break;
  252. case "File Ext":
  253. column.SortColumnId = 7;
  254. column.Clicked += Column_Clicked;
  255. break;
  256. case "File Size":
  257. column.SortColumnId = 8;
  258. column.Clicked += Column_Clicked;
  259. break;
  260. case "Path":
  261. column.SortColumnId = 9;
  262. column.Clicked += Column_Clicked;
  263. break;
  264. }
  265. }
  266. }
  267. private HLE.Switch InitializeSwitchInstance()
  268. {
  269. _virtualFileSystem.Reload();
  270. HLE.Switch instance = new HLE.Switch(_virtualFileSystem, _contentManager, InitializeRenderer(), InitializeAudioEngine())
  271. {
  272. UiHandler = _uiHandler
  273. };
  274. instance.Initialize();
  275. return instance;
  276. }
  277. internal static void UpdateGameTable()
  278. {
  279. if (_updatingGameTable || _gameLoaded)
  280. {
  281. return;
  282. }
  283. _updatingGameTable = true;
  284. _tableStore.Clear();
  285. Thread applicationLibraryThread = new Thread(() =>
  286. {
  287. ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs,
  288. _virtualFileSystem, ConfigurationState.Instance.System.Language);
  289. _updatingGameTable = false;
  290. });
  291. applicationLibraryThread.Name = "GUI.ApplicationLibraryThread";
  292. applicationLibraryThread.IsBackground = true;
  293. applicationLibraryThread.Start();
  294. }
  295. internal void LoadApplication(string path)
  296. {
  297. if (_gameLoaded)
  298. {
  299. GtkDialog.CreateInfoDialog("Ryujinx", "A game has already been loaded", "Please close it first and try again.");
  300. }
  301. else
  302. {
  303. if (ConfigurationState.Instance.Logger.EnableDebug.Value)
  304. {
  305. MessageDialog debugWarningDialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null)
  306. {
  307. Title = "Ryujinx - Warning",
  308. Text = "You have debug logging enabled, which is designed to be used by developers only.",
  309. SecondaryText = "For optimal performance, it's recommended to disable debug logging. Would you like to disable debug logging now?"
  310. };
  311. if (debugWarningDialog.Run() == (int)ResponseType.Yes)
  312. {
  313. ConfigurationState.Instance.Logger.EnableDebug.Value = false;
  314. SaveConfig();
  315. }
  316. debugWarningDialog.Dispose();
  317. }
  318. if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value))
  319. {
  320. MessageDialog shadersDumpWarningDialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null)
  321. {
  322. Title = "Ryujinx - Warning",
  323. Text = "You have shader dumping enabled, which is designed to be used by developers only.",
  324. SecondaryText = "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?"
  325. };
  326. if (shadersDumpWarningDialog.Run() == (int)ResponseType.Yes)
  327. {
  328. ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = "";
  329. SaveConfig();
  330. }
  331. shadersDumpWarningDialog.Dispose();
  332. }
  333. Logger.RestartTime();
  334. HLE.Switch device = InitializeSwitchInstance();
  335. UpdateGraphicsConfig();
  336. Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {_contentManager.GetCurrentFirmwareVersion()?.VersionString}");
  337. if (Directory.Exists(path))
  338. {
  339. string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
  340. if (romFsFiles.Length == 0)
  341. {
  342. romFsFiles = Directory.GetFiles(path, "*.romfs");
  343. }
  344. if (romFsFiles.Length > 0)
  345. {
  346. Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
  347. device.LoadCart(path, romFsFiles[0]);
  348. }
  349. else
  350. {
  351. Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
  352. device.LoadCart(path);
  353. }
  354. }
  355. else if (File.Exists(path))
  356. {
  357. switch (System.IO.Path.GetExtension(path).ToLowerInvariant())
  358. {
  359. case ".xci":
  360. Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
  361. device.LoadXci(path);
  362. break;
  363. case ".nca":
  364. Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
  365. device.LoadNca(path);
  366. break;
  367. case ".nsp":
  368. case ".pfs0":
  369. Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
  370. device.LoadNsp(path);
  371. break;
  372. default:
  373. Logger.Info?.Print(LogClass.Application, "Loading as homebrew.");
  374. try
  375. {
  376. device.LoadProgram(path);
  377. }
  378. catch (ArgumentOutOfRangeException)
  379. {
  380. Logger.Error?.Print(LogClass.Application, "The file which you have specified is unsupported by Ryujinx.");
  381. }
  382. break;
  383. }
  384. }
  385. else
  386. {
  387. Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
  388. device.Dispose();
  389. return;
  390. }
  391. _emulationContext = device;
  392. _deviceExitStatus.Reset();
  393. #if MACOS_BUILD
  394. CreateGameWindow(device);
  395. #else
  396. Thread windowThread = new Thread(() =>
  397. {
  398. CreateGameWindow(device);
  399. })
  400. {
  401. Name = "GUI.WindowThread"
  402. };
  403. windowThread.Start();
  404. #endif
  405. _gameLoaded = true;
  406. _stopEmulation.Sensitive = true;
  407. _firmwareInstallFile.Sensitive = false;
  408. _firmwareInstallDirectory.Sensitive = false;
  409. DiscordIntegrationModule.SwitchToPlayingState(device.Application.TitleIdText, device.Application.TitleName);
  410. ApplicationLibrary.LoadAndSaveMetaData(device.Application.TitleIdText, appMetadata =>
  411. {
  412. appMetadata.LastPlayed = DateTime.UtcNow.ToString();
  413. });
  414. }
  415. }
  416. private void CreateGameWindow(HLE.Switch device)
  417. {
  418. if (Environment.OSVersion.Platform == PlatformID.Win32NT)
  419. {
  420. _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1);
  421. }
  422. _glWidget = new GlRenderer(_emulationContext, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
  423. Application.Invoke(delegate
  424. {
  425. _viewBox.Remove(_gameTableWindow);
  426. _glWidget.Expand = true;
  427. _viewBox.Child = _glWidget;
  428. _glWidget.ShowAll();
  429. EditFooterForGameRender();
  430. if (this.Window.State.HasFlag(Gdk.WindowState.Fullscreen))
  431. {
  432. ToggleExtraWidgets(false);
  433. }
  434. });
  435. _glWidget.WaitEvent.WaitOne();
  436. _glWidget.Start();
  437. Ptc.Close();
  438. PtcProfiler.Stop();
  439. device.Dispose();
  440. _deviceExitStatus.Set();
  441. // NOTE: Everything that is here will not be executed when you close the UI.
  442. Application.Invoke(delegate
  443. {
  444. if (this.Window.State.HasFlag(Gdk.WindowState.Fullscreen))
  445. {
  446. ToggleExtraWidgets(true);
  447. }
  448. _viewBox.Remove(_glWidget);
  449. _glWidget.Exit();
  450. if(_glWidget.Window != this.Window && _glWidget.Window != null)
  451. {
  452. _glWidget.Window.Dispose();
  453. }
  454. _glWidget.Dispose();
  455. _windowsMultimediaTimerResolution?.Dispose();
  456. _windowsMultimediaTimerResolution = null;
  457. _viewBox.Add(_gameTableWindow);
  458. _gameTableWindow.Expand = true;
  459. this.Window.Title = $"Ryujinx {Program.Version}";
  460. _emulationContext = null;
  461. _gameLoaded = false;
  462. _glWidget = null;
  463. DiscordIntegrationModule.SwitchToMainMenu();
  464. RecreateFooterForMenu();
  465. UpdateColumns();
  466. UpdateGameTable();
  467. Task.Run(RefreshFirmwareLabel);
  468. _stopEmulation.Sensitive = false;
  469. _firmwareInstallFile.Sensitive = true;
  470. _firmwareInstallDirectory.Sensitive = true;
  471. });
  472. }
  473. private void RecreateFooterForMenu()
  474. {
  475. _listStatusBox.Show();
  476. _statusBar.Hide();
  477. }
  478. private void EditFooterForGameRender()
  479. {
  480. _listStatusBox.Hide();
  481. _statusBar.Show();
  482. }
  483. public void ToggleExtraWidgets(bool show)
  484. {
  485. if (_glWidget != null)
  486. {
  487. if (show)
  488. {
  489. _menuBar.ShowAll();
  490. _footerBox.Show();
  491. _statusBar.Show();
  492. }
  493. else
  494. {
  495. _menuBar.Hide();
  496. _footerBox.Hide();
  497. }
  498. }
  499. }
  500. private static void UpdateGameMetadata(string titleId)
  501. {
  502. if (_gameLoaded)
  503. {
  504. ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
  505. {
  506. DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
  507. double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
  508. appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
  509. });
  510. }
  511. }
  512. public static void UpdateGraphicsConfig()
  513. {
  514. int resScale = ConfigurationState.Instance.Graphics.ResScale;
  515. float resScaleCustom = ConfigurationState.Instance.Graphics.ResScaleCustom;
  516. Graphics.Gpu.GraphicsConfig.ResScale = (resScale == -1) ? resScaleCustom : resScale;
  517. Graphics.Gpu.GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
  518. Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
  519. }
  520. public static void SaveConfig()
  521. {
  522. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  523. }
  524. private void End(HLE.Switch device)
  525. {
  526. #if USE_DEBUGGING
  527. _debugger.Dispose();
  528. #endif
  529. if (_ending)
  530. {
  531. return;
  532. }
  533. _ending = true;
  534. if (device != null)
  535. {
  536. UpdateGameMetadata(device.Application.TitleIdText);
  537. if (_glWidget != null)
  538. {
  539. // We tell the widget that we are exiting
  540. _glWidget.Exit();
  541. // Wait for the other thread to dispose the HLE context before exiting.
  542. _deviceExitStatus.WaitOne();
  543. }
  544. }
  545. Dispose();
  546. Profile.FinishProfiling();
  547. DiscordIntegrationModule.Exit();
  548. Ptc.Dispose();
  549. PtcProfiler.Dispose();
  550. Logger.Shutdown();
  551. Application.Quit();
  552. }
  553. private static IRenderer InitializeRenderer()
  554. {
  555. return new Renderer();
  556. }
  557. private static IAalOutput InitializeAudioEngine()
  558. {
  559. if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo)
  560. {
  561. if (SoundIoAudioOut.IsSupported)
  562. {
  563. return new SoundIoAudioOut();
  564. }
  565. else
  566. {
  567. Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out.");
  568. }
  569. }
  570. else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.OpenAl)
  571. {
  572. if (OpenALAudioOut.IsSupported)
  573. {
  574. return new OpenALAudioOut();
  575. }
  576. else
  577. {
  578. Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SoundIO.");
  579. if (SoundIoAudioOut.IsSupported)
  580. {
  581. Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration.");
  582. ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo;
  583. SaveConfig();
  584. return new SoundIoAudioOut();
  585. }
  586. else
  587. {
  588. Logger.Warning?.Print(LogClass.Audio, "SoundIO is not supported, falling back to dummy audio out.");
  589. }
  590. }
  591. }
  592. return new DummyAudioOut();
  593. }
  594. //Events
  595. private void Application_Added(object sender, ApplicationAddedEventArgs args)
  596. {
  597. Application.Invoke(delegate
  598. {
  599. _tableStore.AppendValues(
  600. args.AppData.Favorite,
  601. new Gdk.Pixbuf(args.AppData.Icon, 75, 75),
  602. $"{args.AppData.TitleName}\n{args.AppData.TitleId.ToUpper()}",
  603. args.AppData.Developer,
  604. args.AppData.Version,
  605. args.AppData.TimePlayed,
  606. args.AppData.LastPlayed,
  607. args.AppData.FileExtension,
  608. args.AppData.FileSize,
  609. args.AppData.Path,
  610. args.AppData.ControlHolder);
  611. });
  612. }
  613. private void ApplicationCount_Updated(object sender, ApplicationCountUpdatedEventArgs args)
  614. {
  615. Application.Invoke(delegate
  616. {
  617. _progressLabel.Text = $"{args.NumAppsLoaded}/{args.NumAppsFound} Games Loaded";
  618. float barValue = 0;
  619. if (args.NumAppsFound != 0)
  620. {
  621. barValue = (float)args.NumAppsLoaded / args.NumAppsFound;
  622. }
  623. _progressBar.Value = barValue;
  624. if (args.NumAppsLoaded == args.NumAppsFound) // Reset the vertical scrollbar to the top when titles finish loading
  625. {
  626. _gameTableWindow.Vadjustment.Value = 0;
  627. }
  628. });
  629. }
  630. private void Update_StatusBar(object sender, StatusUpdatedEventArgs args)
  631. {
  632. Application.Invoke(delegate
  633. {
  634. _hostStatus.Text = args.HostStatus;
  635. _gameStatus.Text = args.GameStatus;
  636. _gpuName.Text = args.GpuName;
  637. _dockedMode.Text = args.DockedMode;
  638. if (args.VSyncEnabled)
  639. {
  640. _vSyncStatus.Attributes = new Pango.AttrList();
  641. _vSyncStatus.Attributes.Insert(new Pango.AttrForeground(11822, 60138, 51657));
  642. }
  643. else
  644. {
  645. _vSyncStatus.Attributes = new Pango.AttrList();
  646. _vSyncStatus.Attributes.Insert(new Pango.AttrForeground(ushort.MaxValue, 17733, 21588));
  647. }
  648. });
  649. }
  650. private void FavToggle_Toggled(object sender, ToggledArgs args)
  651. {
  652. _tableStore.GetIter(out TreeIter treeIter, new TreePath(args.Path));
  653. string titleId = _tableStore.GetValue(treeIter, 2).ToString().Split("\n")[1].ToLower();
  654. bool newToggleValue = !(bool)_tableStore.GetValue(treeIter, 0);
  655. _tableStore.SetValue(treeIter, 0, newToggleValue);
  656. ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
  657. {
  658. appMetadata.Favorite = newToggleValue;
  659. });
  660. }
  661. private void Column_Clicked(object sender, EventArgs args)
  662. {
  663. TreeViewColumn column = (TreeViewColumn)sender;
  664. ConfigurationState.Instance.Ui.ColumnSort.SortColumnId.Value = column.SortColumnId;
  665. ConfigurationState.Instance.Ui.ColumnSort.SortAscending.Value = column.SortOrder == SortType.Ascending;
  666. SaveConfig();
  667. }
  668. private void Row_Activated(object sender, RowActivatedArgs args)
  669. {
  670. _gameTableSelection.GetSelected(out TreeIter treeIter);
  671. string path = (string)_tableStore.GetValue(treeIter, 9);
  672. LoadApplication(path);
  673. }
  674. private void Row_Clicked(object sender, ButtonReleaseEventArgs args)
  675. {
  676. if (args.Event.Button != 3) return;
  677. _gameTableSelection.GetSelected(out TreeIter treeIter);
  678. if (treeIter.UserData == IntPtr.Zero) return;
  679. BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10);
  680. GameTableContextMenu contextMenu = new GameTableContextMenu(_tableStore, controlData, treeIter, _virtualFileSystem);
  681. contextMenu.ShowAll();
  682. contextMenu.PopupAtPointer(null);
  683. }
  684. private void Load_Application_File(object sender, EventArgs args)
  685. {
  686. FileChooserDialog fileChooser = new FileChooserDialog("Choose the file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept);
  687. fileChooser.Filter = new FileFilter();
  688. fileChooser.Filter.AddPattern("*.nsp" );
  689. fileChooser.Filter.AddPattern("*.pfs0");
  690. fileChooser.Filter.AddPattern("*.xci" );
  691. fileChooser.Filter.AddPattern("*.nca" );
  692. fileChooser.Filter.AddPattern("*.nro" );
  693. fileChooser.Filter.AddPattern("*.nso" );
  694. if (fileChooser.Run() == (int)ResponseType.Accept)
  695. {
  696. LoadApplication(fileChooser.Filename);
  697. }
  698. fileChooser.Dispose();
  699. }
  700. private void Load_Application_Folder(object sender, EventArgs args)
  701. {
  702. FileChooserDialog fileChooser = new FileChooserDialog("Choose the folder to open", this, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept);
  703. if (fileChooser.Run() == (int)ResponseType.Accept)
  704. {
  705. LoadApplication(fileChooser.Filename);
  706. }
  707. fileChooser.Dispose();
  708. }
  709. private void Open_Ryu_Folder(object sender, EventArgs args)
  710. {
  711. Process.Start(new ProcessStartInfo()
  712. {
  713. FileName = AppDataManager.BaseDirPath,
  714. UseShellExecute = true,
  715. Verb = "open"
  716. });
  717. }
  718. private void Exit_Pressed(object sender, EventArgs args)
  719. {
  720. End(_emulationContext);
  721. }
  722. private void Window_Close(object sender, DeleteEventArgs args)
  723. {
  724. End(_emulationContext);
  725. }
  726. private void StopEmulation_Pressed(object sender, EventArgs args)
  727. {
  728. _glWidget?.Exit();
  729. }
  730. private void Installer_File_Pressed(object o, EventArgs args)
  731. {
  732. FileChooserDialog fileChooser = new FileChooserDialog("Choose the firmware file to open",
  733. this,
  734. FileChooserAction.Open,
  735. "Cancel",
  736. ResponseType.Cancel,
  737. "Open",
  738. ResponseType.Accept);
  739. fileChooser.Filter = new FileFilter();
  740. fileChooser.Filter.AddPattern("*.zip");
  741. fileChooser.Filter.AddPattern("*.xci");
  742. HandleInstallerDialog(fileChooser);
  743. }
  744. private void Installer_Directory_Pressed(object o, EventArgs args)
  745. {
  746. FileChooserDialog directoryChooser = new FileChooserDialog("Choose the firmware directory to open",
  747. this,
  748. FileChooserAction.SelectFolder,
  749. "Cancel",
  750. ResponseType.Cancel,
  751. "Open",
  752. ResponseType.Accept);
  753. HandleInstallerDialog(directoryChooser);
  754. }
  755. private void RefreshFirmwareLabel()
  756. {
  757. SystemVersion currentFirmware = _contentManager.GetCurrentFirmwareVersion();
  758. GLib.Idle.Add(new GLib.IdleHandler(() =>
  759. {
  760. _firmwareVersionLabel.Text = currentFirmware != null ? currentFirmware.VersionString : "0.0.0";
  761. return false;
  762. }));
  763. }
  764. private void HandleInstallerDialog(FileChooserDialog fileChooser)
  765. {
  766. if (fileChooser.Run() == (int)ResponseType.Accept)
  767. {
  768. MessageDialog dialog = null;
  769. try
  770. {
  771. string filename = fileChooser.Filename;
  772. fileChooser.Dispose();
  773. SystemVersion firmwareVersion = _contentManager.VerifyFirmwarePackage(filename);
  774. if (firmwareVersion == null)
  775. {
  776. dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, false, "");
  777. dialog.Text = "Firmware not found.";
  778. dialog.SecondaryText = $"A valid system firmware was not found in {filename}.";
  779. Logger.Error?.Print(LogClass.Application, $"A valid system firmware was not found in {filename}.");
  780. dialog.Run();
  781. dialog.Hide();
  782. dialog.Dispose();
  783. return;
  784. }
  785. SystemVersion currentVersion = _contentManager.GetCurrentFirmwareVersion();
  786. string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed.";
  787. if (currentVersion != null)
  788. {
  789. dialogMessage += $"This will replace the current system version {currentVersion.VersionString}. ";
  790. }
  791. dialogMessage += "Do you want to continue?";
  792. dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, false, "");
  793. dialog.Text = $"Install Firmware {firmwareVersion.VersionString}";
  794. dialog.SecondaryText = dialogMessage;
  795. int response = dialog.Run();
  796. dialog.Dispose();
  797. dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.None, false, "");
  798. dialog.Text = $"Install Firmware {firmwareVersion.VersionString}";
  799. dialog.SecondaryText = "Installing firmware...";
  800. if (response == (int)ResponseType.Yes)
  801. {
  802. Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}");
  803. Thread thread = new Thread(() =>
  804. {
  805. GLib.Idle.Add(new GLib.IdleHandler(() =>
  806. {
  807. dialog.Run();
  808. return false;
  809. }));
  810. try
  811. {
  812. _contentManager.InstallFirmware(filename);
  813. GLib.Idle.Add(new GLib.IdleHandler(() =>
  814. {
  815. dialog.Dispose();
  816. dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, false, "");
  817. dialog.Text = $"Install Firmware {firmwareVersion.VersionString}";
  818. dialog.SecondaryText = $"System version {firmwareVersion.VersionString} successfully installed.";
  819. Logger.Info?.Print(LogClass.Application, $"System version {firmwareVersion.VersionString} successfully installed.");
  820. dialog.Run();
  821. dialog.Dispose();
  822. return false;
  823. }));
  824. }
  825. catch (Exception ex)
  826. {
  827. GLib.Idle.Add(new GLib.IdleHandler(() =>
  828. {
  829. dialog.Dispose();
  830. dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, false, "");
  831. dialog.Text = $"Install Firmware {firmwareVersion.VersionString} Failed.";
  832. dialog.SecondaryText = $"An error occured while installing system version {firmwareVersion.VersionString}." +
  833. " Please check logs for more info.";
  834. Logger.Error?.Print(LogClass.Application, ex.Message);
  835. dialog.Run();
  836. dialog.Dispose();
  837. return false;
  838. }));
  839. }
  840. finally
  841. {
  842. RefreshFirmwareLabel();
  843. }
  844. });
  845. thread.Name = "GUI.FirmwareInstallerThread";
  846. thread.Start();
  847. }
  848. else
  849. {
  850. dialog.Dispose();
  851. }
  852. }
  853. catch (Exception ex)
  854. {
  855. if (dialog != null)
  856. {
  857. dialog.Dispose();
  858. }
  859. dialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Info, ButtonsType.Ok, false, "");
  860. dialog.Text = "Parsing Firmware Failed.";
  861. dialog.SecondaryText = "An error occured while parsing firmware. Please check the logs for more info.";
  862. Logger.Error?.Print(LogClass.Application, ex.Message);
  863. dialog.Run();
  864. dialog.Dispose();
  865. }
  866. }
  867. else
  868. {
  869. fileChooser.Dispose();
  870. }
  871. }
  872. private void FullScreen_Toggled(object o, EventArgs args)
  873. {
  874. bool fullScreenToggled = this.Window.State.HasFlag(Gdk.WindowState.Fullscreen);
  875. if (!fullScreenToggled)
  876. {
  877. Fullscreen();
  878. ToggleExtraWidgets(false);
  879. }
  880. else
  881. {
  882. Unfullscreen();
  883. ToggleExtraWidgets(true);
  884. }
  885. }
  886. private void Settings_Pressed(object sender, EventArgs args)
  887. {
  888. SettingsWindow settingsWin = new SettingsWindow(_virtualFileSystem, _contentManager);
  889. settingsWin.Show();
  890. }
  891. private void Update_Pressed(object sender, EventArgs args)
  892. {
  893. string ryuUpdater = System.IO.Path.Combine(AppDataManager.BaseDirPath, "RyuUpdater.exe");
  894. try
  895. {
  896. Process.Start(new ProcessStartInfo(ryuUpdater, "/U") { UseShellExecute = true });
  897. }
  898. catch(System.ComponentModel.Win32Exception)
  899. {
  900. GtkDialog.CreateErrorDialog("Update canceled by user or updater was not found");
  901. }
  902. }
  903. private void About_Pressed(object sender, EventArgs args)
  904. {
  905. AboutWindow aboutWin = new AboutWindow();
  906. aboutWin.Show();
  907. }
  908. private void Fav_Toggled(object sender, EventArgs args)
  909. {
  910. ConfigurationState.Instance.Ui.GuiColumns.FavColumn.Value = _favToggle.Active;
  911. SaveConfig();
  912. UpdateColumns();
  913. }
  914. private void Icon_Toggled(object sender, EventArgs args)
  915. {
  916. ConfigurationState.Instance.Ui.GuiColumns.IconColumn.Value = _iconToggle.Active;
  917. SaveConfig();
  918. UpdateColumns();
  919. }
  920. private void Title_Toggled(object sender, EventArgs args)
  921. {
  922. ConfigurationState.Instance.Ui.GuiColumns.AppColumn.Value = _appToggle.Active;
  923. SaveConfig();
  924. UpdateColumns();
  925. }
  926. private void Developer_Toggled(object sender, EventArgs args)
  927. {
  928. ConfigurationState.Instance.Ui.GuiColumns.DevColumn.Value = _developerToggle.Active;
  929. SaveConfig();
  930. UpdateColumns();
  931. }
  932. private void Version_Toggled(object sender, EventArgs args)
  933. {
  934. ConfigurationState.Instance.Ui.GuiColumns.VersionColumn.Value = _versionToggle.Active;
  935. SaveConfig();
  936. UpdateColumns();
  937. }
  938. private void TimePlayed_Toggled(object sender, EventArgs args)
  939. {
  940. ConfigurationState.Instance.Ui.GuiColumns.TimePlayedColumn.Value = _timePlayedToggle.Active;
  941. SaveConfig();
  942. UpdateColumns();
  943. }
  944. private void LastPlayed_Toggled(object sender, EventArgs args)
  945. {
  946. ConfigurationState.Instance.Ui.GuiColumns.LastPlayedColumn.Value = _lastPlayedToggle.Active;
  947. SaveConfig();
  948. UpdateColumns();
  949. }
  950. private void FileExt_Toggled(object sender, EventArgs args)
  951. {
  952. ConfigurationState.Instance.Ui.GuiColumns.FileExtColumn.Value = _fileExtToggle.Active;
  953. SaveConfig();
  954. UpdateColumns();
  955. }
  956. private void FileSize_Toggled(object sender, EventArgs args)
  957. {
  958. ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn.Value = _fileSizeToggle.Active;
  959. SaveConfig();
  960. UpdateColumns();
  961. }
  962. private void Path_Toggled(object sender, EventArgs args)
  963. {
  964. ConfigurationState.Instance.Ui.GuiColumns.PathColumn.Value = _pathToggle.Active;
  965. SaveConfig();
  966. UpdateColumns();
  967. }
  968. private void RefreshList_Pressed(object sender, ButtonReleaseEventArgs args)
  969. {
  970. UpdateGameTable();
  971. }
  972. private static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b)
  973. {
  974. string aValue = model.GetValue(a, 5).ToString();
  975. string bValue = model.GetValue(b, 5).ToString();
  976. if (aValue.Length > 4 && aValue.Substring(aValue.Length - 4) == "mins")
  977. {
  978. aValue = (float.Parse(aValue.Substring(0, aValue.Length - 5)) * 60).ToString();
  979. }
  980. else if (aValue.Length > 3 && aValue.Substring(aValue.Length - 3) == "hrs")
  981. {
  982. aValue = (float.Parse(aValue.Substring(0, aValue.Length - 4)) * 3600).ToString();
  983. }
  984. else if (aValue.Length > 4 && aValue.Substring(aValue.Length - 4) == "days")
  985. {
  986. aValue = (float.Parse(aValue.Substring(0, aValue.Length - 5)) * 86400).ToString();
  987. }
  988. else
  989. {
  990. aValue = aValue.Substring(0, aValue.Length - 1);
  991. }
  992. if (bValue.Length > 4 && bValue.Substring(bValue.Length - 4) == "mins")
  993. {
  994. bValue = (float.Parse(bValue.Substring(0, bValue.Length - 5)) * 60).ToString();
  995. }
  996. else if (bValue.Length > 3 && bValue.Substring(bValue.Length - 3) == "hrs")
  997. {
  998. bValue = (float.Parse(bValue.Substring(0, bValue.Length - 4)) * 3600).ToString();
  999. }
  1000. else if (bValue.Length > 4 && bValue.Substring(bValue.Length - 4) == "days")
  1001. {
  1002. bValue = (float.Parse(bValue.Substring(0, bValue.Length - 5)) * 86400).ToString();
  1003. }
  1004. else
  1005. {
  1006. bValue = bValue.Substring(0, bValue.Length - 1);
  1007. }
  1008. if (float.Parse(aValue) > float.Parse(bValue))
  1009. {
  1010. return -1;
  1011. }
  1012. else if (float.Parse(bValue) > float.Parse(aValue))
  1013. {
  1014. return 1;
  1015. }
  1016. else
  1017. {
  1018. return 0;
  1019. }
  1020. }
  1021. private static int LastPlayedSort(ITreeModel model, TreeIter a, TreeIter b)
  1022. {
  1023. string aValue = model.GetValue(a, 6).ToString();
  1024. string bValue = model.GetValue(b, 6).ToString();
  1025. if (aValue == "Never")
  1026. {
  1027. aValue = DateTime.UnixEpoch.ToString();
  1028. }
  1029. if (bValue == "Never")
  1030. {
  1031. bValue = DateTime.UnixEpoch.ToString();
  1032. }
  1033. return DateTime.Compare(DateTime.Parse(bValue), DateTime.Parse(aValue));
  1034. }
  1035. private static int FileSizeSort(ITreeModel model, TreeIter a, TreeIter b)
  1036. {
  1037. string aValue = model.GetValue(a, 8).ToString();
  1038. string bValue = model.GetValue(b, 8).ToString();
  1039. if (aValue.Substring(aValue.Length - 2) == "GB")
  1040. {
  1041. aValue = (float.Parse(aValue[0..^2]) * 1024).ToString();
  1042. }
  1043. else
  1044. {
  1045. aValue = aValue[0..^2];
  1046. }
  1047. if (bValue.Substring(bValue.Length - 2) == "GB")
  1048. {
  1049. bValue = (float.Parse(bValue[0..^2]) * 1024).ToString();
  1050. }
  1051. else
  1052. {
  1053. bValue = bValue[0..^2];
  1054. }
  1055. if (float.Parse(aValue) > float.Parse(bValue))
  1056. {
  1057. return -1;
  1058. }
  1059. else if (float.Parse(bValue) > float.Parse(aValue))
  1060. {
  1061. return 1;
  1062. }
  1063. else
  1064. {
  1065. return 0;
  1066. }
  1067. }
  1068. }
  1069. }